pgsql-v9.3-row-level-security.rw.v7.patch

application/octet-stream

Filename: pgsql-v9.3-row-level-security.rw.v7.patch
Type: application/octet-stream
Part: 0
Message: Re: [v9.3] Row-Level Security

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
Series: patch v9
File+
doc/src/sgml/catalogs.sgml 59 0
doc/src/sgml/ref/alter_table.sgml 39 0
doc/src/sgml/user-manag.sgml 168 0
src/backend/catalog/dependency.c 23 0
src/backend/catalog/heap.c 1 0
src/backend/catalog/Makefile 2 2
src/backend/catalog/pg_rowlevelsec.c 288 0
src/backend/commands/copy.c 88 3
src/backend/commands/explain.c 6 2
src/backend/commands/tablecmds.c 27 0
src/backend/nodes/nodeFuncs.c 9 3
src/backend/optimizer/plan/planner.c 18 1
src/backend/optimizer/prep/preptlist.c 47 5
src/backend/optimizer/prep/prepunion.c 82 2
src/backend/optimizer/util/Makefile 1 1
src/backend/optimizer/util/rowlevelsec.c 685 0
src/backend/parser/gram.y 16 0
src/backend/parser/parse_agg.c 6 0
src/backend/parser/parse_expr.c 3 0
src/backend/rewrite/rewriteHandler.c 16 0
src/backend/utils/adt/ri_triggers.c 12 1
src/backend/utils/cache/plancache.c 32 0
src/backend/utils/cache/relcache.c 16 1
src/bin/pg_dump/pg_dump.c 66 10
src/bin/pg_dump/pg_dump.h 1 0
src/include/catalog/dependency.h 1 0
src/include/catalog/indexing.h 3 0
src/include/catalog/pg_class.h 11 9
src/include/catalog/pg_rowlevelsec.h 59 0
src/include/commands/copy.h 1 1
src/include/miscadmin.h 1 0
src/include/nodes/nodeFuncs.h 1 0
src/include/nodes/parsenodes.h 11 1
src/include/nodes/plannodes.h 2 0
src/include/nodes/relation.h 2 0
src/include/optimizer/rowlevelsec.h 25 0
src/include/parser/parse_node.h 2 1
src/include/rewrite/rewriteHandler.h 1 0
src/include/utils/plancache.h 2 0
src/include/utils/rel.h 2 0
src/test/regress/expected/rowlevelsec.out 954 0
src/test/regress/expected/sanity_check.out 2 1
src/test/regress/parallel_schedule 1 1
src/test/regress/serial_schedule 1 0
src/test/regress/sql/rowlevelsec.sql 295 0
 doc/src/sgml/catalogs.sgml                 |   59 ++
 doc/src/sgml/ref/alter_table.sgml          |   39 ++
 doc/src/sgml/user-manag.sgml               |  168 +++++
 src/backend/catalog/Makefile               |    4 +-
 src/backend/catalog/dependency.c           |   23 +
 src/backend/catalog/heap.c                 |    1 +
 src/backend/catalog/pg_rowlevelsec.c       |  288 +++++++++
 src/backend/commands/copy.c                |   91 ++-
 src/backend/commands/explain.c             |    8 +-
 src/backend/commands/tablecmds.c           |   27 +
 src/backend/nodes/nodeFuncs.c              |   12 +-
 src/backend/optimizer/plan/planner.c       |   19 +-
 src/backend/optimizer/prep/preptlist.c     |   52 +-
 src/backend/optimizer/prep/prepunion.c     |   84 ++-
 src/backend/optimizer/util/Makefile        |    2 +-
 src/backend/optimizer/util/rowlevelsec.c   |  685 ++++++++++++++++++++
 src/backend/parser/gram.y                  |   16 +
 src/backend/parser/parse_agg.c             |    6 +
 src/backend/parser/parse_expr.c            |    3 +
 src/backend/rewrite/rewriteHandler.c       |   16 +
 src/backend/utils/adt/ri_triggers.c        |   13 +-
 src/backend/utils/cache/plancache.c        |   32 +
 src/backend/utils/cache/relcache.c         |   17 +-
 src/bin/pg_dump/pg_dump.c                  |   76 ++-
 src/bin/pg_dump/pg_dump.h                  |    1 +
 src/include/catalog/dependency.h           |    1 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_class.h             |   20 +-
 src/include/catalog/pg_rowlevelsec.h       |   59 ++
 src/include/commands/copy.h                |    2 +-
 src/include/miscadmin.h                    |    1 +
 src/include/nodes/nodeFuncs.h              |    1 +
 src/include/nodes/parsenodes.h             |   12 +-
 src/include/nodes/plannodes.h              |    2 +
 src/include/nodes/relation.h               |    2 +
 src/include/optimizer/rowlevelsec.h        |   25 +
 src/include/parser/parse_node.h            |    3 +-
 src/include/rewrite/rewriteHandler.h       |    1 +
 src/include/utils/plancache.h              |    2 +
 src/include/utils/rel.h                    |    2 +
 src/test/regress/expected/rowlevelsec.out  |  954 ++++++++++++++++++++++++++++
 src/test/regress/expected/sanity_check.out |    3 +-
 src/test/regress/parallel_schedule         |    2 +-
 src/test/regress/serial_schedule           |    1 +
 src/test/regress/sql/rowlevelsec.sql       |  295 +++++++++
 45 files changed, 3088 insertions(+), 45 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index f999190..55898a5 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -234,6 +234,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link></entry>
+      <entry>row-level security policy of relation</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
       <entry>security labels on database objects</entry>
      </row>
@@ -1807,6 +1812,16 @@
      </row>
 
      <row>
+      <entry><structfield>relhasrowlevelsec</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       True if table has row-level security policy; see
+       <link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link> catalog
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>relhassubclass</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
@@ -4906,6 +4921,50 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-rowlevelsec">
+  <title><structname>pg_rowlevelsec</structname></title>
+
+  <indexterm zone="catalog-pg-rowlevelsec">
+   <primary>pg_rowlevelsec</primary>
+  </indexterm>
+  <para>
+   The catalog <structname>pg_rowlevelsec</structname> expression tree of
+   row-level security policy to be performed on a particular relation.
+  </para>
+  <table>
+   <title><structname>pg_rowlevelsec</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>rlsrelid</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 this row-level security is for</entry>
+     </row>
+     <row>
+      <entry><structfield>rlsqual</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry></entry>
+      <entry>An expression tree to be performed as rowl-level security policy</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+  <note>
+   <para>
+    <literal>pg_class.relhasrowlevelsec</literal>
+    must be true if a table has row-level security policy in this catalog.
+   </para>
+  </note>
+ </sect1>
 
  <sect1 id="catalog-pg-seclabel">
   <title><structname>pg_seclabel</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 356419e..4f2cd37 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -68,6 +68,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)
+    RESET ROW LEVEL SECURITY
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -567,6 +569,29 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This form set row-level security policy of the table.
+      Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+      as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+      clause, although mechanism guarantees to evaluate this condition earlier
+      than any other user given condition.
+      See also <xref linkend="ROW-LEVEL-SECURITY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESET ROW LEVEL SECURITY</literal></term>
+    <listitem>
+     <para>
+      This form reset row-level security policy of the table, if exists.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>RENAME</literal></term>
     <listitem>
      <para>
@@ -808,6 +833,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">condition</replaceable></term>
+      <listitem>
+       <para>
+        An expression that returns a value of type boolean. Expect for a case
+        when queries are executed with superuser privilege, only rows for which
+        this expression returns true will be fetched, updated or deleted.
+        This expression can reference columns of the relation being configured.
+        Sub-queries can be contained within expression tree, unless referenced
+        relation recursively references the same relation.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 177ac7a..dab5b1e 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -439,4 +439,172 @@ DROP ROLE <replaceable>name</replaceable>;
   </para>
  </sect1>
 
+ <sect1 id="row-level-security">
+  <title>Row-level Security</title>
+  <para>
+   <productname>PostgreSQL</productname> v9.3 or later provides
+   row-level security feature, like several commercial database
+   management system. It allows table owner to assign a particular
+   condition that performs as a security policy of the table; only
+   rows that satisfies the condition should be visible, except for
+   a case when superuser runs queries.
+  </para>
+  <para>
+   Row-level security policy can be set using
+   <literal>SET ROW LEVEL SECURITY</literal> command of
+   <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+    form that returns a value of type boolean. This expression can
+    contain references to columns of the relation, so it enables
+    to construct arbitrary rule to make access control decision
+    based on contents of each rows.
+  </para>
+  <para>
+   For example, the following <literal>customer</literal> table
+   has <literal>uname</literal> field to store user name, and
+   it assume we don't want to expose any properties of other
+   customers.
+   The following command set <literal>current_user = uname</literal>
+   as row-level security policy on the <literal>customer</literal>
+   table.
+<screen>
+postgres=> ALTER TABLE customer SET ROW LEVEL SECURITY (current_user = uname);
+ALTER TABLE
+</screen>
+   <xref linkend="SQL-EXPLAIN"> command shows how row-level
+   security policy works on the supplied query.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                 QUERY PLAN
+--------------------------------------------
+ Subquery Scan on customer
+   Filter: f_leak(customer.upasswd)
+   ->  Seq Scan on customer customer_1
+         Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+   This query execution plan means the preconfigured row-level
+   security policy is implicitly added, and scan plan on the
+   target relation being wrapped up with a sub-query.
+   It ensures user given qualifiers, including functions with
+   side effects, are never executed earlier than the row-level
+   security policy regardless of its cost, except for the cases
+   when these were fully leakproof.
+   This design helps to tackle the scenario described in
+   <xref linkend="RULES-PRIVILEGES">; that introduces the order
+   to evaluate qualifiers is significant to keep confidentiality
+   of invisible rows.
+  </para>
+
+  <para>
+   On the other hand, this design allows superusers to bypass
+   checks with row-level security.
+   It ensures <application>pg_dump</application> can obtain
+   a complete set of database backup, and avoid to execute
+   Trojan horse trap, being injected as a row-level security
+   policy of user-defined table, with privileges of superuser.
+  </para>
+
+  <para>
+   In case of queries on inherited tables, row-level security
+   policy of the parent relation is not applied to child
+   relations. Scope of the row-level security policy is limited
+   to the relation on which it is set.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+                QUERY PLAN
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.y)
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((x % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: f_leak(y)
+         ->  Subquery Scan on t3
+               Filter: f_leak(t3.y)
+               ->  Seq Scan on t3 t3_1
+                     Filter: ((x % 2) = 1)
+(12 rows)
+</screen>
+    In the above example, <literal>t1</literal> has inherited
+    child table <literal>t2</literal> and <literal>t3</literal>,
+    and row-level security policy is set on only <literal>t1</literal>,
+    and <literal>t3</literal>, not <literal>t2</literal>.
+
+    The row-level security policy of <literal>t1</literal>,
+    <literal>x</literal> must be even-number, is appended only
+    <literal>t1</literal>, neither <literal>t2</literal> nor
+    <literal>t3</literal>. On the contrary, <literal>t3</literal>
+    has different row-level security policy; <literal>x</literal>
+    must be odd-number.
+  </para>
+
+  <para>
+   Row-level security feature also works to queries for writer-
+   operations; such as <xref linkend="SQL-UPDATE"> or
+   <xref linkend="SQL-DELETE"> commands.
+   It prevents to modify rows that does not satisfy the configured
+   row-level security policy.
+   The below query tries to update e-mail address of the
+   <literal>customer</> table, and the row-level security makes
+   sure any rows that don't match with <literal>current_user</>.
+  </para>
+<screen>
+postgres=> EXPLAIN (costs off) UPDATE customer SET email = 'alice@example.com';
+                    QUERY PLAN
+--------------------------------------------------
+ Update on customer
+   ->  Subquery Scan on customer
+         ->  Seq Scan on customer customer_1
+               Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+  <para>
+   On the other hands, please note that row-level security is
+   not applied on <xref linkend="SQL-INSERT"> command, because
+   of no sense.
+   Any row-level security shall be applied on the timing when
+   rows are fetched from the tables, prior to evaluation of
+   user given <literal>WHERE</> clause to prevent unexpected
+   information leaks using non-leakproof functions.
+   All the rows to be inserted are. at least, visible for
+   current session users. therefore, it makes no sense to
+   check something from viewpoint of row-level security.
+  </para>
+  <para>
+   If you want to apply some constraints on rows to be inserted
+   or updated, we recommend to set up <literal>CHECK</>
+   constraints or before row triggers, rather than row-level
+   security features here.
+   Please also check the <xref linkend="rules-privileges">
+   section to understand leaky-view scenario bahalf on
+   the row-level security feature.
+  </para>
+
+  <para>
+   Unlike other commercial database systems, we don't have any
+   plan to allow individual row-level security policy for each
+   command type. Even if we want to perform with difference
+   policy between <xref linkend="SQL-SELECT"> and
+   <xref linkend="SQL-DELETE">, <literal>RETURNING</> clause
+   can leak the rows to be invisible using
+   <xref linkend="SQL-DELETE"> command.
+  </para>
+
+  <para>
+   Even though it is not a specific matter in row-level security,
+   please be careful if you plan to use <literal>current_user</>
+   in row-level security policy.
+   <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH">
+   allows to switch current user identifier during execution
+   of the query. Thus, it can fetch the first 100 rows with
+   privilege of <literal>alice</>, then remaining rows with
+   privilege of <literal>bob</>. If and when query execution plan
+   contains some kind of materialization and row-level security
+   policy contains <literal>current_user</>, the fetched tuples
+   in <literal>bob</>'s screen might be evaluated according to
+   the privilege of <literal>alice</>.
+  </para>
+ </sect1>
 </chapter>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index df6da1f..965aa38 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -14,7 +14,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        objectaddress.o pg_aggregate.o pg_collation.o pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_rowlevelsec.o pg_type.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -38,7 +38,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_rowlevelsec.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/dependency.c b/src/backend/catalog/dependency.c
index b9cfee2..2142c36 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -47,6 +47,7 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_ts_config.h"
@@ -1240,6 +1241,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveExtensionById(object->objectId);
 			break;
 
+		case OCLASS_ROWLEVELSEC:
+			RemoveRowLevelSecurityById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2291,6 +2296,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
+
+		case RowLevelSecurityRelationId:
+			return OCLASS_ROWLEVELSEC;
 	}
 
 	/* shouldn't get here */
@@ -2940,6 +2948,21 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_ROWLEVELSEC:
+			{
+				char	   *relname;
+
+				relname = get_rel_name(object->objectId);
+				if (!relname)
+					elog(ERROR, "cache lookup failed for relation %u",
+						 object->objectId);
+				appendStringInfo(&buffer,
+								 _("row-level security of %s"), relname);
+
+
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 8818b68..cc56143 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -776,6 +776,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_relhasrowlevelsec - 1] = BoolGetDatum(rd_rel->relhasrowlevelsec);
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	if (relacl != (Datum) 0)
diff --git a/src/backend/catalog/pg_rowlevelsec.c b/src/backend/catalog/pg_rowlevelsec.c
new file mode 100644
index 0000000..f0e49a6
--- /dev/null
+++ b/src/backend/catalog/pg_rowlevelsec.c
@@ -0,0 +1,288 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_rowlevelsec.c
+ *    routines to support manipulation of the pg_rowlevelsec relation
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_rowlevelsec.h"
+#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+/*
+ * Load row-level security policy from the catalog, and keep it on
+ * the relation cache.
+ */
+void
+RelationBuildRowLevelSecurity(Relation relation)
+{
+	Relation	rlsrel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	rlsrel = heap_open(RowLevelSecurityRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+
+	tuple = systable_getnext(sscan);
+	if (HeapTupleIsValid(tuple))
+	{
+		RowLevelSecDesc	*rlsdesc;
+		MemoryContext	rlscxt;
+		MemoryContext	oldcxt;
+		Datum	datum;
+		bool	isnull;
+		char   *temp;
+
+		/*
+		 * Make the private memory context to store RowLevelSecDesc that
+		 * includes expression tree also.
+		 */
+		rlscxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_MINSIZE,
+									   ALLOCSET_SMALL_INITSIZE,
+									   ALLOCSET_SMALL_MAXSIZE);
+		PG_TRY();
+		{
+			datum = heap_getattr(tuple, Anum_pg_rowlevelsec_rlsqual,
+								 RelationGetDescr(rlsrel), &isnull);
+			Assert(!isnull);
+			temp = TextDatumGetCString(datum);
+
+			oldcxt = MemoryContextSwitchTo(rlscxt);
+
+			rlsdesc = palloc0(sizeof(RowLevelSecDesc));
+			rlsdesc->rlscxt = rlscxt;
+			rlsdesc->rlsqual = (Expr *) stringToNode(temp);
+			Assert(exprType((Node *)rlsdesc->rlsqual) == BOOLOID);
+
+			rlsdesc->rlshassublinks
+				= contain_subplans((Node *)rlsdesc->rlsqual);
+
+			MemoryContextSwitchTo(oldcxt);
+
+			pfree(temp);
+		}
+		PG_CATCH();
+		{
+			MemoryContextDelete(rlscxt);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+
+		relation->rlsdesc = rlsdesc;
+	}
+	else
+	{
+		relation->rlsdesc = NULL;
+	}
+	systable_endscan(sscan);
+	heap_close(rlsrel, AccessShareLock);
+}
+
+/*
+ * Parse the supplied row-level security policy, and insert/update a row
+ * of pg_rowlevelsec catalog.
+ */
+static void
+InsertOrUpdatePolicyRow(Relation relation, Node *clause)
+{
+	Oid				relationId = RelationGetRelid(relation);
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	Node		   *qual;
+	Relation		rlsrel;
+	ScanKeyData		skey;
+	SysScanDesc		sscan;
+	HeapTuple		oldtup;
+	HeapTuple		newtup;
+	Datum			values[Natts_pg_rowlevelsec];
+	bool			isnull[Natts_pg_rowlevelsec];
+	bool			replaces[Natts_pg_rowlevelsec];
+	ObjectAddress	target;
+	ObjectAddress	myself;
+
+	/* Parse the supplied clause */
+	pstate = make_parsestate(NULL);
+
+	rte = addRangeTableEntryForRelation(pstate, relation,
+										NULL, false, false);
+	addRTEtoQuery(pstate, rte, false, true, true);
+
+	qual = transformWhereClause(pstate, copyObject(clause),
+								EXPR_KIND_ROW_LEVEL_SEC,
+								"ROW LEVEL SECURITY");
+	/* zero-clear */
+	memset(values,   0, sizeof(values));
+	memset(replaces, 0, sizeof(replaces));
+	memset(isnull,   0, sizeof(isnull));
+
+	/* Update or Insert an entry to pg_rowlevelsec catalog  */
+	rlsrel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+	oldtup = systable_getnext(sscan);
+	if (HeapTupleIsValid(oldtup))
+	{
+		replaces[Anum_pg_rowlevelsec_rlsqual - 1] = true;
+		values[Anum_pg_rowlevelsec_rlsqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+
+		newtup = heap_modify_tuple(oldtup,
+								   RelationGetDescr(rlsrel),
+								   values, isnull, replaces);
+		simple_heap_update(rlsrel, &newtup->t_self, newtup);
+
+		deleteDependencyRecordsFor(RowLevelSecurityRelationId,
+								   relationId, false);
+	}
+	else
+	{
+		values[Anum_pg_rowlevelsec_rlsrelid - 1]
+			= ObjectIdGetDatum(relationId);
+		values[Anum_pg_rowlevelsec_rlsqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+		newtup = heap_form_tuple(RelationGetDescr(rlsrel),
+								 values, isnull);
+		simple_heap_insert(rlsrel, newtup);
+	}
+	CatalogUpdateIndexes(rlsrel, newtup);
+
+	heap_freetuple(newtup);
+
+	/* records dependencies of RLS-policy and relation/columns */
+	target.classId = RelationRelationId;
+	target.objectId = relationId;
+	target.objectSubId = 0;
+
+	myself.classId = RowLevelSecurityRelationId;
+	myself.objectId = relationId;
+	myself.objectSubId = 0;
+
+	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+						   DEPENDENCY_NORMAL);
+	free_parsestate(pstate);
+
+	systable_endscan(sscan);
+	heap_close(rlsrel, RowExclusiveLock);
+}
+
+/*
+ * Remove row-level security policy row of pg_rowlevelsec
+ */
+static void
+DeletePolicyRow(Relation relation)
+{
+	if (relation->rlsdesc)
+	{
+		ObjectAddress	address;
+
+		address.classId = RowLevelSecurityRelationId;
+		address.objectId = RelationGetRelid(relation);
+		address.objectSubId = 0;
+
+		performDeletion(&address, DROP_RESTRICT, 0);
+	}
+	else
+	{
+		/* Nothing to do here */
+		elog(INFO, "relation %s has no row-level security policy, skipped",
+			 RelationGetRelationName(relation));
+	}
+}
+
+/*
+ * Guts of row-level security policy deletion.
+ */
+void
+RemoveRowLevelSecurityById(Oid relationId)
+{
+	Relation	rlsrel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	rlsrel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relationId));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+	while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+	{
+		simple_heap_delete(rlsrel, &tuple->t_self);
+	}
+	systable_endscan(sscan);
+	heap_close(rlsrel, RowExclusiveLock);
+}
+
+/*
+ * ALTER TABLE <name> SET ROW LEVEL SECURITY (...) OR
+ *                    RESET ROW LEVEL SECURITY
+ */
+void
+SetRowLevelSecurity(Relation relation, Node *clause)
+{
+	Oid			relid = RelationGetRelid(relation);
+	Relation	class_rel;
+	HeapTuple	tuple;
+	Form_pg_class	class_form;
+
+	class_rel = heap_open(RelationRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", relid);
+
+	class_form = (Form_pg_class) GETSTRUCT(tuple);
+	if (clause != NULL)
+	{
+		InsertOrUpdatePolicyRow(relation, clause);
+		class_form->relhasrowlevelsec = true;
+	}
+	else
+	{
+		DeletePolicyRow(relation);
+		class_form->relhasrowlevelsec = false;
+	}
+	simple_heap_update(class_rel, &tuple->t_self, tuple);
+	CatalogUpdateIndexes(class_rel, tuple);
+
+	heap_close(class_rel, RowExclusiveLock);
+	heap_freetuple(tuple);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 10c89c7..7ffe4a3 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -24,6 +24,7 @@
 #include "access/htup_details.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
+#include "catalog/heap.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
@@ -34,15 +35,19 @@
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/rowlevelsec.h"
 #include "parser/parse_relation.h"
+#include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
@@ -742,7 +747,7 @@ CopyLoadRawBuf(CopyState cstate)
  * the table or the specifically requested columns.
  */
 uint64
-DoCopy(const CopyStmt *stmt, const char *queryString)
+DoCopy(CopyStmt *stmt, const char *queryString)
 {
 	CopyState	cstate;
 	bool		is_from = stmt->is_from;
@@ -772,14 +777,26 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
 		rel = heap_openrv(stmt->relation,
 						  (is_from ? RowExclusiveLock : AccessShareLock));
 
+		tupDesc = RelationGetDescr(rel);
+		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
+
+		/*
+		 * We have to run regular query, if the target relation has
+ 		 * row-level security policy
+		 */
+		if (copy_row_level_security(stmt, rel, attnums))
+		{
+			heap_close(rel, NoLock);	/* close with keeping lock */
+			rel = NULL;
+		}
+		else
+		{
 		rte = makeNode(RangeTblEntry);
 		rte->rtekind = RTE_RELATION;
 		rte->relid = RelationGetRelid(rel);
 		rte->relkind = rel->rd_rel->relkind;
 		rte->requiredPerms = required_access;
 
-		tupDesc = RelationGetDescr(rel);
-		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
 		foreach(cur, attnums)
 		{
 			int			attno = lfirst_int(cur) -
@@ -791,6 +808,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
 				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
 		}
 		ExecCheckRTPerms(list_make1(rte), true);
+		}
 	}
 	else
 	{
@@ -1139,6 +1157,53 @@ ProcessCopyOptions(CopyState cstate,
 }
 
 /*
+ * Adjust Query tree constructed with row-level security feature.
+ * If WITH OIDS option was supplied, it adds Var node to reference
+ * object-id system column.
+ */
+static void
+fixup_oid_of_rls_query(Query *query)
+{
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	Var			   *subvar;
+	ListCell	   *cell;
+	Form_pg_attribute	attform
+		= SystemAttributeDefinition(ObjectIdAttributeNumber, true);
+
+	subrte = rt_fetch((Index) 1, query->rtable);
+	Assert(subrte->rtekind == RTE_RELATION);
+
+	if (!SearchSysCacheExists2(ATTNUM,
+							   ObjectIdGetDatum(subrte->relid),
+							   Int16GetDatum(attform->attnum)))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("table \"%s\" does not have OIDs",
+						get_rel_name(subrte->relid))));
+
+	subvar = makeVar((Index) 1,
+					 attform->attnum,
+					 attform->atttypid,
+					 attform->atttypmod,
+					 attform->attcollation,
+					 0);
+	subtle = makeTargetEntry((Expr *) subvar,
+							 0,
+							 pstrdup(NameStr(attform->attname)),
+							 false);
+
+	query->targetList = list_concat(list_make1(subtle),
+									query->targetList);
+	/* adjust resno of TargetEntry */
+	foreach (cell, query->targetList)
+	{
+		subtle = lfirst(cell);
+		subtle->resno++;
+	}
+}
+
+/*
  * Common setup routines used by BeginCopyFrom and BeginCopyTo.
  *
  * Iff <binary>, unload or reload in the binary format, as opposed to the
@@ -1210,6 +1275,25 @@ BeginCopy(bool is_from,
 		Assert(!is_from);
 		cstate->rel = NULL;
 
+		/*
+		 * In case when regular COPY TO was replaced because of row-level
+		 * security, "raw_query" node have already analyzed / rewritten
+		 * query tree.
+		 */
+		if (IsA(raw_query, Query))
+		{
+			query = (Query *) raw_query;
+
+			Assert(query->querySource == QSRC_ROW_LEVEL_SECURITY);
+			if (cstate->oids)
+			{
+				fixup_oid_of_rls_query(query);
+				cstate->oids = false;
+			}
+			attnamelist = NIL;
+		}
+		else
+		{
 		/* Don't allow COPY w/ OIDs from a select */
 		if (cstate->oids)
 			ereport(ERROR,
@@ -1234,6 +1318,7 @@ BeginCopy(bool is_from,
 			elog(ERROR, "unexpected rewrite result");
 
 		query = (Query *) linitial(rewritten);
+		}
 
 		/* The grammar allows SELECT INTO, but we don't support that */
 		if (query->utilityStmt != NULL &&
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 33252a8..24756d0 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1964,8 +1964,12 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
 		case T_TidScan:
 		case T_ForeignScan:
 		case T_ModifyTable:
-			/* Assert it's on a real relation */
-			Assert(rte->rtekind == RTE_RELATION);
+			/*
+			 * Assert it's on either a real relation, or a sub-query of
+			 * row-level security being originated from a real relation.
+			 */
+			Assert((rte->rtekind == RTE_RELATION ||
+					rte->rtekind == RTE_SUBQUERY) && OidIsValid(rte->relid));
 			objectname = get_rel_name(rte->relid);
 			if (es->verbose)
 				namespace = get_namespace_name(get_rel_namespace(rte->relid));
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f88bf79..3896522 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -2749,6 +2750,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetRowLevelSecurity:
+			case AT_ResetRowLevelSecurity:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3112,6 +3115,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_SetRowLevelSecurity:
+		case AT_ResetRowLevelSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3391,6 +3396,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowLevelSecurity:
+			SetRowLevelSecurity(rel, (Node *) cmd->def);
+			break;
+		case AT_ResetRowLevelSecurity:
+			SetRowLevelSecurity(rel, NULL);
+			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
@@ -7552,6 +7563,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				Assert(defaultexpr);
 				break;
 
+			case OCLASS_ROWLEVELSEC:
+				/*
+				 * A row-level security policy can depend on a column in case
+				 * when the policy clause references a particular column.
+				 * Due to same reason why TRIGGER ... WHEN does not support
+				 * to change column's type being referenced in clause, row-
+				 * level security policy also does not support it.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type of a column used in a row-level security policy"),
+						 errdetail("%s depends on column \"%s\"",
+								   getObjectDescription(&foundObject),
+								   colName)));
+				break;
+
 			case OCLASS_PROC:
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c52f4ed..8e04c78 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1869,8 +1869,11 @@ query_tree_walker(Query *query,
 
 	if (walker((Node *) query->targetList, context))
 		return true;
-	if (walker((Node *) query->returningList, context))
-		return true;
+	if (!(flags & QTW_IGNORE_RETURNING))
+	{
+		if (walker((Node *) query->returningList, context))
+			return true;
+	}
 	if (walker((Node *) query->jointree, context))
 		return true;
 	if (walker(query->setOperations, context))
@@ -2583,7 +2586,10 @@ query_tree_mutator(Query *query,
 	}
 
 	MUTATE(query->targetList, query->targetList, List *);
-	MUTATE(query->returningList, query->returningList, List *);
+	if (!(flags & QTW_IGNORE_RETURNING))
+		MUTATE(query->returningList, query->returningList, List *);
+	else
+		query->returningList = copyObject(query->returningList);
 	MUTATE(query->jointree, query->jointree, FromExpr *);
 	MUTATE(query->setOperations, query->setOperations, Node *);
 	MUTATE(query->havingQual, query->havingQual, Node *);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b61005f..7a4ba39 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -33,6 +33,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
+#include "optimizer/rowlevelsec.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
@@ -167,6 +168,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPHId = 0;
 	glob->lastRowMarkId = 0;
 	glob->transientPlan = false;
+	glob->planUserId = InvalidOid;
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -244,6 +246,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
 	result->nParamExec = glob->nParamExec;
+	result->planUserId = glob->planUserId;
 
 	return result;
 }
@@ -393,6 +396,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	expand_inherited_tables(root);
 
 	/*
+	 * Apply row-level security policy of the relation being referenced,
+	 * if configured with either of built-in or extension's features.
+	 * RangeTblEntry of the relation with row-level security policy shall
+	 * be replaced with a RLS sub-query that has simple scan on the table
+	 * with security policy qualifiers.
+	 *
+	 * This routine assumes PlannerInfo is already handled with
+	 * expand_inherited_tables, thus, AppendRelInfo or PlanRowMark have
+	 * valid information.
+	 */
+	apply_row_level_security(root);
+
+	/*
 	 * Set hasHavingQual to remember if HAVING clause is present.  Needed
 	 * because preprocess_expression will reduce a constant-true condition to
 	 * an empty qual list ... but "HAVING TRUE" is not a semantic no-op.
@@ -842,7 +858,8 @@ inheritance_planner(PlannerInfo *root)
 			{
 				RangeTblEntry *rte = (RangeTblEntry *) lfirst(lr);
 
-				if (rte->rtekind == RTE_SUBQUERY)
+				if (rte->rtekind == RTE_SUBQUERY &&
+					rte->subquery->querySource != QSRC_ROW_LEVEL_SECURITY)
 				{
 					Index		newrti;
 
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 1af4e7f..9bf7566 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -37,7 +37,44 @@
 
 static List *expand_targetlist(List *tlist, int command_type,
 				  Index result_relation, List *range_table);
+/*
+ * lookup_varattno
+ *
+ * This routine returns an attribute number to reference a particular
+ * attribute. In case when the target relation is really relation,
+ * we can reference arbitrary attribute (including system column)
+ * without any translations. However, we have to translate varattno
+ * of Vat that references sub-queries being originated from regular
+ * relations with row-level security policy.
+ */
+static AttrNumber
+lookup_varattno(AttrNumber attno, Index rt_index, List *rtables)
+{
+	RangeTblEntry  *rte = rt_fetch(rt_index, rtables);
 
+	if (rte->rtekind == RTE_SUBQUERY &&
+		rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+	{
+		ListCell   *cell;
+
+		foreach (cell, rte->subquery->targetList)
+		{
+			TargetEntry *tle = lfirst(cell);
+			Var			*var;
+
+			if (IsA(tle->expr, Const))
+				continue;
+
+			var = (Var *) tle->expr;
+			Assert(IsA(var, Var));
+
+			if (var->varattno == attno)
+				return tle->resno;
+		}
+		elog(ERROR, "invalid attno %d on the pseudo targetList", attno);
+	}
+	return attno;
+}
 
 /*
  * preprocess_targetlist
@@ -62,7 +99,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 	{
 		RangeTblEntry *rte = rt_fetch(result_relation, range_table);
 
-		if (rte->subquery != NULL || rte->relid == InvalidOid)
+		if (rte->subquery != NULL &&
+			rte->subquery->querySource != QSRC_ROW_LEVEL_SECURITY)
 			elog(ERROR, "subquery cannot be result relation");
 	}
 
@@ -95,7 +133,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 		{
 			/* It's a regular table, so fetch its TID */
 			var = makeVar(rc->rti,
-						  SelfItemPointerAttributeNumber,
+						  lookup_varattno(SelfItemPointerAttributeNumber,
+										  rc->rti, range_table),
 						  TIDOID,
 						  -1,
 						  InvalidOid,
@@ -111,7 +150,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			if (rc->isParent)
 			{
 				var = makeVar(rc->rti,
-							  TableOidAttributeNumber,
+							  lookup_varattno(TableOidAttributeNumber,
+											  rc->rti, range_table),
 							  OIDOID,
 							  -1,
 							  InvalidOid,
@@ -129,7 +169,7 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			/* Not a table, so we need the whole row as a junk var */
 			var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
 								  rc->rti,
-								  0,
+								  lookup_varattno(0, rc->rti, range_table),
 								  false);
 			snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
 			tle = makeTargetEntry((Expr *) var,
@@ -298,7 +338,9 @@ expand_targetlist(List *tlist, int command_type,
 					if (!att_tup->attisdropped)
 					{
 						new_expr = (Node *) makeVar(result_relation,
-													attrno,
+													lookup_varattno(attrno,
+														result_relation,
+														range_table),
 													atttype,
 													atttypmod,
 													attcollation,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b91e9f4..5b60c16 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -55,6 +55,7 @@ typedef struct
 {
 	PlannerInfo *root;
 	AppendRelInfo *appinfo;
+	bool		in_returning;
 } adjust_appendrel_attrs_context;
 
 static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
@@ -1594,6 +1595,7 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 
 	context.root = root;
 	context.appinfo = appinfo;
+	context.in_returning = false;
 
 	/*
 	 * Must be prepared to start with a Query or a bare expression tree.
@@ -1605,7 +1607,25 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 		newnode = query_tree_mutator((Query *) node,
 									 adjust_appendrel_attrs_mutator,
 									 (void *) &context,
-									 QTW_IGNORE_RC_SUBQUERIES);
+									 QTW_IGNORE_RC_SUBQUERIES |
+									 QTW_IGNORE_RETURNING);
+		/*
+		 * XXX - Returning clause should be handled in a special way.
+		 * In case when result relation of UPDATE / DELETE has row-level
+		 * security policy, its RangeTblEntry was replace by a sub-query,
+		 * thus, references to system-column need to be adjusted to point
+		 * pseudo-column behalf on the target system column.
+		 * However, Var nodes in returning clause are exception, because
+		 * its attribute number is evaluated towards the written image of
+		 * the tuple being updated or deleted, not virtual tuple of the
+		 * sub-query.
+		 */
+		context.in_returning = true;
+		newnode->returningList =
+			(List *) expression_tree_mutator((Node *) newnode->returningList,
+											 adjust_appendrel_attrs_mutator,
+											 (void *) &context);
+
 		if (newnode->resultRelation == appinfo->parent_relid)
 		{
 			newnode->resultRelation = appinfo->child_relid;
@@ -1624,6 +1644,49 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 }
 
 static Node *
+fixup_var_on_rls_subquery(RangeTblEntry *rte, Var *var)
+{
+	ListCell   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY);
+	/*
+	 * In case when row-level security policy is applied on the referenced
+	 * table, its RangeTblEntry (RTE_RELATION) is replaced with sub-query
+	 * to filter out unprivileged rows of underlying relation.
+	 * Even though reference to this sub-query should perform as if ones
+	 * to real relations, system column has to be cared in special way
+	 * due to the nature of sub-query.
+	 * Target-entries that reference system columns should be added on
+	 * rowlevelsec.c, so all we need to do here is looking up underlying
+	 * target-list that can reference underlying system column, and fix-
+	 * up varattno of the referencing Var node with resno of TargetEntry.
+	 */
+	foreach (cell, rte->subquery->targetList)
+	{
+		TargetEntry *subtle = lfirst(cell);
+
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *) subtle->expr;
+			Var	   *newnode;
+
+			if (subvar->varattno == var->varattno)
+			{
+				newnode = copyObject(var);
+				newnode->varattno = subtle->resno;
+				return (Node *)newnode;
+			}
+		}
+		else
+			Assert(IsA(subtle->expr, Const));
+	}
+	elog(ERROR, "could not find pseudo column of %d in relation %s",
+		 var->varattno, get_rel_name(rte->relid));
+	return NULL;
+}
+
+static Node *
 adjust_appendrel_attrs_mutator(Node *node,
 							   adjust_appendrel_attrs_context *context)
 {
@@ -1664,6 +1727,14 @@ adjust_appendrel_attrs_mutator(Node *node,
 				 */
 				if (OidIsValid(appinfo->child_reltype))
 				{
+					Query          *parse = context->root->parse;
+					RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+												   parse->rtable);
+					if (!context->in_returning &&
+						rte->rtekind == RTE_SUBQUERY &&
+						rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+						var = (Var *)fixup_var_on_rls_subquery(rte, var);
+
 					Assert(var->vartype == appinfo->parent_reltype);
 					if (appinfo->parent_reltype != appinfo->child_reltype)
 					{
@@ -1708,7 +1779,16 @@ adjust_appendrel_attrs_mutator(Node *node,
 					return (Node *) rowexpr;
 				}
 			}
-			/* system attributes don't need any other translation */
+			else
+			{
+				Query		   *parse = context->root->parse;
+				RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+											   parse->rtable);
+				if (!context->in_returning &&
+					rte->rtekind == RTE_SUBQUERY &&
+					rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+					return fixup_var_on_rls_subquery(rte, var);
+			}
 		}
 		return (Node *) var;
 	}
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index 3b2d16b..3430689 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -13,6 +13,6 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
-       relnode.o restrictinfo.o tlist.o var.o
+       relnode.o restrictinfo.o tlist.o var.o rowlevelsec.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowlevelsec.c b/src/backend/optimizer/util/rowlevelsec.c
new file mode 100644
index 0000000..1721bb5
--- /dev/null
+++ b/src/backend/optimizer/util/rowlevelsec.c
@@ -0,0 +1,685 @@
+/*
+ * optimizer/util/rowlvsec.c
+ *    Row-level security support routines
+ *
+ * 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_rowlevelsec.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/plannodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/prep.h"
+#include "optimizer/rowlevelsec.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/* flags to pull row-level security policy */
+#define RLS_FLAG_HAS_SUBLINKS			0x0001
+
+/* hook to allow extensions to apply their own security policy */
+rowlevel_security_hook_type	rowlevel_security_hook = NULL;
+
+/*
+ * make_pseudo_column
+ *
+ * It makes TargetEntry that references underlying attribute. It may be
+ * Const node of dummy NULL, not Var node, if it is already dropped.
+ */
+static TargetEntry *
+make_pseudo_column(RangeTblEntry *subrte, AttrNumber attnum)
+{
+	Expr   *expr;
+	char   *resname;
+
+	Assert(subrte->rtekind == RTE_RELATION && OidIsValid(subrte->relid));
+	if (attnum == InvalidAttrNumber)
+	{
+		expr = (Expr *) makeWholeRowVar(subrte, (Index) 1, 0, false);
+		resname = get_rel_name(subrte->relid);
+	}
+	else
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(subrte->relid),
+								Int16GetDatum(attnum));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+				 attnum, subrte->relid);
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		if (attform->attisdropped)
+		{
+			char	namebuf[NAMEDATALEN];
+
+			/* Insert NULL just for a placeholder of dropped column */
+			expr = (Expr *) makeConst(INT4OID,
+									  -1,
+									  InvalidOid,
+									  sizeof(int32),
+									  (Datum) 0,
+									  true,		/* isnull */
+									  true);	/* byval */
+			sprintf(namebuf, "dummy-%d", (int)attform->attnum);
+			resname = pstrdup(namebuf);
+		}
+		else
+		{
+			expr = (Expr *) makeVar((Index) 1,
+									attform->attnum,
+									attform->atttypid,
+									attform->atttypmod,
+									attform->attcollation,
+									0);
+			resname = pstrdup(NameStr(attform->attname));
+		}
+		ReleaseSysCache(tuple);
+	}
+	return makeTargetEntry(expr, -1, resname, false);
+}
+
+/*
+ * append_pseudo_system_column
+ *
+ * It returns attribute number of pseudo-column relevant to the supplied
+ * Var-node referencing the RLS sub-query. If required attribute is not
+ * in target-list, it also adds a new pseudo-column.
+ */
+static AttrNumber
+append_pseudo_system_column(RangeTblEntry *rte, Var *var)
+{
+	Query		   *subqry = rte->subquery;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	ListCell	   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY);
+
+	foreach (cell, subqry->targetList)
+	{
+		subtle = lfirst(cell);
+
+		/*
+		 * If referenced system column is already attached on the target-
+		 * list of RLS sub-query, nothing to do here.
+		 */
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *)subtle->expr;
+
+			if (var->varattno == subvar->varattno)
+			{
+				if (subtle->resjunk)
+					subtle->resjunk = false;
+				return subtle->resno;
+			}
+		}
+	}
+
+	/*
+	 * Here is no target-list for the referenced system column, so append
+	 * a new pseudo column on demand
+	 */
+	subrte = rt_fetch((Index) 1, subqry->rtable);
+	subtle = make_pseudo_column(subrte, var->varattno);
+	subtle->resno = list_length(subqry->targetList) + 1;
+
+	subqry->targetList = lappend(subqry->targetList, subtle);
+	rte->eref->colnames = lappend(rte->eref->colnames,
+								  makeString(pstrdup(subtle->resname)));
+	return subtle->resno;
+}
+
+/*
+ * fixup_varattno
+ *
+ * It recursively fixes up references to RLS sub-query, and adds pseudo-
+ * columns of underlying system columns, if necessary.
+ */
+typedef struct {
+	PlannerInfo	*root;
+	int		varlevelsup;
+	bool	is_returning;
+} fixup_varattno_context;
+
+static bool
+fixup_varattno_walker(Node *node, fixup_varattno_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var			   *var = (Var *) node;
+		RangeTblEntry  *rte;
+		ListCell	   *cell;
+
+		/* Var node does not reference Query node currently we focus on */
+		if (var->varlevelsup != context->varlevelsup)
+			return false;
+
+		/*
+		 * All the regular columns should already have its own pseudo
+		 * column on expansion of RTE. Its resno of TargetEntry is
+		 * identical with underlying attribute, so never need to fix-up
+		 * varattno of Var node that references the sub-query.
+		 */
+		if (var->varattno > InvalidAttrNumber)
+			return false;
+
+		rte = rt_fetch(var->varno, context->root->parse->rtable);
+		if (!context->is_returning &&
+			rte->rtekind == RTE_SUBQUERY &&
+			rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+		{
+			/*
+			 * If this Var node is system-column or whole-row reference
+			 * on RLS sub-queries, its varattno has to be adjusted to
+			 * reference correct pseudo column. Pseudo column entries
+			 * of them are not constructed at expansion time, we append
+			 * it on demand.
+			 */
+			var->varattno = append_pseudo_system_column(rte, var);
+		}
+		else if (rte->rtekind == RTE_RELATION && rte->inh)
+		{
+			/*
+			 * Also, if this Var node is system-columns or whole-row
+			 * reference on the parent relation of inheritance tree that
+			 * includes RLS sub-queries, even though the parent relation
+			 * itself was not expanded, its pseudo-column entries have to
+			 * be added on the underlying child relations.
+			 * However, no need to fix up varattno of Var node, because
+			 * it shall be handled in prep/prepunion.c.
+			 */
+			foreach (cell, context->root->append_rel_list)
+			{
+				AppendRelInfo *appinfo = lfirst(cell);
+
+				if (appinfo->parent_relid != var->varno)
+					continue;
+
+				rte = rt_fetch(appinfo->child_relid,
+							   context->root->parse->rtable);
+				if (rte->rtekind == RTE_SUBQUERY &&
+					rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+					append_pseudo_system_column(rte, var);
+			}
+		}
+		return false;
+	}
+	else if (IsA(node, Query))
+	{
+		bool	result;
+
+		context->varlevelsup++;
+		result = query_tree_walker((Query *) node,
+								   fixup_varattno_walker,
+								   (void *) context, 0);
+		context->varlevelsup--;
+
+		return result;
+	}
+	return expression_tree_walker(node,
+								  fixup_varattno_walker,
+								  (void *) context);
+}
+
+/*
+ * check_infinite_recursion
+ *
+ * It is a wrong row-level security configuration, if we try to expand
+ * the relation inside of RLS sub-query originated from same relation!
+ */
+static void
+check_infinite_recursion(PlannerInfo *root, Oid relid)
+{
+	PlannerInfo	   *parent = root->parent_root;
+
+	if (parent && parent->parse->querySource == QSRC_ROW_LEVEL_SECURITY)
+	{
+		RangeTblEntry  *rte = rt_fetch(1, parent->parse->rtable);
+
+		Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+		if (relid == rte->relid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("infinite recursion detected for relation \"%s\"",
+							get_rel_name(relid))));
+		check_infinite_recursion(parent, relid);
+	}
+}
+
+/*
+ * fixup_plan_rowmark
+ *
+ * Push down the given PlanRowMark into RLS sub-query.
+ */
+static void
+fixup_plan_rowmark(RangeTblEntry *rte, PlanRowMark *rowmark)
+{
+	Query		   *subqry = rte->subquery;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+
+	Assert(!rowmark->isParent);
+
+	if (rowmark->markType == ROW_MARK_EXCLUSIVE ||
+		rowmark->markType == ROW_MARK_SHARE)
+	{
+		RowMarkClause  *rclause = makeNode(RowMarkClause);
+
+		rclause->rti = (Index) 1;
+		if (rowmark->markType == ROW_MARK_EXCLUSIVE)
+			rclause->forUpdate = true;
+		else
+			rclause->forUpdate = false;
+		rclause->noWait = rowmark->noWait;
+		rclause->pushedDown = true;
+
+		subqry->rowMarks = lappend(subqry->rowMarks, rclause);
+	}
+	rowmark->markType = ROW_MARK_REFERENCE;
+
+	/*
+	 * Add 'ctid' and 'tableoid' pseudo columns to be required for
+	 * row-level locks
+	 */
+	subrte = rt_fetch(1, subqry->rtable);
+
+	subtle = make_pseudo_column(subrte, SelfItemPointerAttributeNumber);
+	subtle->resno = list_length(subqry->targetList) + 1;
+	subqry->targetList = lappend(subqry->targetList, subtle);
+	rte->eref->colnames = lappend(rte->eref->colnames,
+		makeString(pstrdup(subtle->resname)));
+
+	subtle = make_pseudo_column(subrte, TableOidAttributeNumber);
+	subtle->resno = list_length(subqry->targetList) + 1;
+	subqry->targetList = lappend(subqry->targetList, subtle);
+	rte->eref->colnames = lappend(rte->eref->colnames,
+		makeString(pstrdup(subtle->resname)));
+}
+
+/*
+ * expand_rtentry_rls
+ *
+ * It replaces the supplied RangeTblEntry (should be RTE_RELATION) by RLS
+ * sub-query with configured row-level security policy.
+ * This sub-query should have pseudo-column relevant to the regular columns,
+ * but no pseudo-columns for system-column or whole-row reference without
+ * references to them.
+ */
+static void
+expand_rtentry_rls(PlannerInfo *root, Index rtindex, Expr *qual, int flags)
+{
+	Query		   *parse = root->parse;
+	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *subrtr;
+	TargetEntry	   *subtle;
+	HeapTuple		tuple;
+	AttrNumber		nattrs;
+	AttrNumber		attnum;
+	List		   *targetList = NIL;
+	List		   *colNameList = NIL;
+	PlanRowMark	   *rowmark;
+
+	/* check recursion to prevent infinite loop */
+	check_infinite_recursion(root, rte->relid);
+
+	/* Expand views inside SubLink node */
+	if (flags & RLS_FLAG_HAS_SUBLINKS)
+		QueryRewriteExpr((Node *)qual, list_make1_oid(rte->relid));
+
+	/*
+	 * Construction of sub-query
+	 */
+	subqry = (Query *) makeNode(Query);
+	subqry->commandType = CMD_SELECT;
+	subqry->querySource = QSRC_ROW_LEVEL_SECURITY;
+
+	subrte = copyObject(rte);
+	subqry->rtable = list_make1(subrte);
+
+	subrtr = makeNode(RangeTblRef);
+	subrtr->rtindex = 1;
+	subqry->jointree = makeFromExpr(list_make1(subrtr), (Node *) qual);
+	if (flags & RLS_FLAG_HAS_SUBLINKS)
+		subqry->hasSubLinks = true;
+
+	/*
+	 * Construct pseudo columns as TargetEntry of sub-query that
+	 * references a particular regular attribute of the underlying
+	 * relation.
+	 */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(rte->relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", rte->relid);
+	nattrs = ((Form_pg_class) GETSTRUCT(tuple))->relnatts;
+	ReleaseSysCache(tuple);
+
+	for (attnum = 1; attnum <= nattrs; attnum++)
+	{
+		subtle = make_pseudo_column(subrte, attnum);
+		subtle->resno = list_length(targetList) + 1;
+		Assert(subtle->resno == attnum);
+
+		targetList = lappend(targetList, subtle);
+		colNameList = lappend(colNameList,
+							  makeString(pstrdup(subtle->resname)));
+	}
+	subqry->targetList = targetList;
+
+	/* Replace the original RengeTblEntry by sub-query */
+	/* XXX - relid has to be kept */
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = subqry;
+	rte->security_barrier = true;
+
+	/* no permission checks on subquery itself */
+	rte->requiredPerms = 0;
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->modifiedCols = NULL;
+
+	rte->eref = makeAlias(get_rel_name(rte->relid), colNameList);
+
+	/*
+	 * Push-down of PlanRowMark if needed
+	 */
+	rowmark = get_plan_rowmark(root->rowMarks, rtindex);
+	if (rowmark)
+		fixup_plan_rowmark(rte, rowmark);
+}
+
+/*
+ * pull_row_level_security
+ *
+ * It pulls the configured row-level security policy of both built-in
+ * and extensions. If any, it returns expression tree.
+ */
+static Expr *
+pull_row_level_security(Relation relation, int *p_flags)
+{
+	Expr   *quals = NULL;
+	int		flags = 0;
+
+	/*
+	 * Pull the row-level security policy configured with built-in
+	 * features, if unprivileged users. Please note that superuser
+	 * can bypass it.
+	 */
+	if (relation->rlsdesc && !superuser())
+	{
+		RowLevelSecDesc *rlsdesc = relation->rlsdesc;
+
+		quals = copyObject(rlsdesc->rlsqual);
+		if (rlsdesc->rlshassublinks)
+			flags |= RLS_FLAG_HAS_SUBLINKS;
+	}
+
+	/*
+	 * Also, ask extensions whether they want to apply their own
+	 * row-level security policy. If both built-in and extension
+	 * has their own policy, it shall be merged.
+	 */
+	if (rowlevel_security_hook)
+	{
+		List   *temp;
+
+		temp = (*rowlevel_security_hook)(relation);
+		if (temp != NIL)
+		{
+			if ((flags & RLS_FLAG_HAS_SUBLINKS) == 0 &&
+				contain_subplans((Node *) temp))
+				flags |= RLS_FLAG_HAS_SUBLINKS;
+
+			if (quals != NULL)
+				temp = lappend(temp, quals);
+
+			if (list_length(temp) == 1)
+				quals = (Expr *)list_head(temp);
+			else if (list_length(temp) > 1)
+				quals = makeBoolExpr(AND_EXPR, temp, -1);
+		}
+	}
+	*p_flags = flags;
+	return quals;
+}
+
+/*
+ * copy_row_level_security
+ *
+ * It construct a RLS sub-query instead of raw COPY TO statement,
+ * if target relation has a row-level security policy
+ */
+bool
+copy_row_level_security(CopyStmt *stmt, Relation rel, List *attnums)
+{
+	Expr		  *quals;
+	int			   flags;
+	Query		  *parse;
+	RangeTblEntry  *rte;
+	RangeTblRef	   *rtr;
+	TargetEntry	   *tle;
+	Var			   *var;
+	ListCell	   *cell;
+
+	if (stmt->is_from)
+		return false;
+
+	quals = pull_row_level_security(rel, &flags);
+	if (!quals)
+		return false;
+
+	parse = (Query *) makeNode(Query);
+	parse->commandType = CMD_SELECT;
+	parse->querySource = QSRC_ROW_LEVEL_SECURITY;
+
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(rel);
+	rte->relkind = RelationGetForm(rel)->relkind;
+
+	foreach (cell, attnums)
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+		AttrNumber	attno = lfirst_int(cell);
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(RelationGetRelid(rel)),
+								Int16GetDatum(attno));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %s",
+				 attno, RelationGetRelationName(rel));
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		var = makeVar((Index) 1,
+					  attform->attnum,
+					  attform->atttypid,
+					  attform->atttypmod,
+					  attform->attcollation,
+					  0);
+		tle = makeTargetEntry((Expr *) var,
+							  list_length(parse->targetList) + 1,
+							  pstrdup(NameStr(attform->attname)),
+							  false);
+		parse->targetList = lappend(parse->targetList, tle);
+
+		ReleaseSysCache(tuple);
+
+		rte->selectedCols = bms_add_member(rte->selectedCols,
+								attno - FirstLowInvalidHeapAttributeNumber);
+	}
+	rte->inFromCl = true;
+	rte->requiredPerms = ACL_SELECT;
+
+	rtr = makeNode(RangeTblRef);
+	rtr->rtindex = 1;
+
+	parse->jointree = makeFromExpr(list_make1(rtr), (Node *) quals);
+	parse->rtable = list_make1(rte);
+	if (flags & RLS_FLAG_HAS_SUBLINKS)
+		parse->hasSubLinks = true;
+
+	stmt->query = (Node *) parse;
+
+	return true;
+}
+
+/*
+ * apply_row_level_security
+ *
+ * Entrypoint to apply configured row-level security policy of the relation.
+ *
+ * In case when the supplied query references relations with row-level
+ * security policy, its RangeTblEntry shall be replaced by a RLS sub-query
+ * that has simple scan on the referenced table with policy qualifiers.
+ * Of course, security-barrier shall be set on the sub-query to prevent
+ * unexpected push-down of functions without leakproof flag.
+ *
+ * For example, when table t1 has a security policy "(x % 2 = 0)", the
+ * following query:
+ *   SELECT * FROM t1 WHERE f_leak(y)
+ * performs as if
+ *   SELECT * FROM (
+ *     SELECT x, y FROM t1 WHERE (x % 2 = 0)
+ *   ) AS t1 WHERE f_leak(y)
+ * would be given. Because the sub-query has security barrier flag, 
+ * configured security policy qualifier is always executed prior to
+ * user given functions.
+ */
+void
+apply_row_level_security(PlannerInfo *root)
+{
+	Query	   *parse = root->parse;
+	Oid			curr_userid;
+	int			curr_seccxt;
+	Index		rtindex;
+	bool		has_rowlevel_security = false;
+
+	/*
+	 * Mode checks. In case when SECURITY_ROW_LEVEL_DISABLED is set,
+	 * no row-level security policy should be applied regardless
+	 * whether it is built-in or extension.
+	 */
+	GetUserIdAndSecContext(&curr_userid, &curr_seccxt);
+	if (curr_seccxt & SECURITY_ROW_LEVEL_DISABLED)
+		return;
+
+	for (rtindex = 1; rtindex <= list_length(parse->rtable); rtindex++)
+	{
+		RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+		Relation	rel;
+		Expr	   *quals;
+		int			flags;
+
+		/* only relation can have row-level security policy */
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		/*
+		 * Parent relation of inheritance tree is just a placeholder here.
+		 * So, no need to apply row-level security.
+		 */
+		if (rte->inh)
+			continue;
+
+		/*
+		 * It does not make sense to check row-level security policy on
+		 * the target relation of INSERT command.
+		 */
+		if (parse->commandType == CMD_INSERT &&
+			parse->resultRelation == rtindex)
+			continue;
+
+		/*
+		 * It does not make sense to apply row-level security policy on
+		 * the relation we already handled.
+		 * Note that the underlying relation never have inh==true.
+		 */
+		if (parse->querySource == QSRC_ROW_LEVEL_SECURITY &&
+			rtindex == 1)
+			continue;
+
+		/*
+		 * OK, it is a reference to "real" relation. Let's try to apply
+		 * row-level security policy being configured, if any.
+		 */
+		rel = heap_open(rte->relid, NoLock);
+
+		quals = pull_row_level_security(rel, &flags);
+		if (quals)
+		{
+			expand_rtentry_rls(root, rtindex, quals, flags);
+			has_rowlevel_security = true;
+		}
+		heap_close(rel, NoLock);
+	}
+
+	if (has_rowlevel_security)
+	{
+		PlannerGlobal  *glob = root->glob;
+		PlanInvalItem  *pi_item;
+		fixup_varattno_context context;
+
+		/*
+		 * XXX - Constructed Plan with row-level security policy depends
+		 * on properties of current used (database superuser can bypass
+		 * configured RLS policy), thus, it has to be invalidated when
+		 * its assumption was changed.
+		 */
+		if (!OidIsValid(glob->planUserId))
+		{
+			/* Plan invalidation on session user-id */
+			glob->planUserId = GetUserId();
+
+			/* Plan invalidation on catalog updates of pg_authid */
+			pi_item = makeNode(PlanInvalItem);
+			pi_item->cacheId = AUTHOID;
+			pi_item->hashValue =
+				GetSysCacheHashValue1(AUTHOID,
+									  ObjectIdGetDatum(glob->planUserId));
+			glob->invalItems = lappend(glob->invalItems, pi_item);
+		}
+		else
+			Assert(glob->planUserId == GetUserId());
+
+		/*
+		 * XXX - varattno of Var node that references the RangeTblEntry
+		 * being replaced by RLS sub-query has to be adjusted for proper
+		 * reference to the underlying pseudo-column of the relation.
+		 */
+		context.root = root;
+		context.varlevelsup = 0;
+		context.is_returning = false;
+		query_tree_walker(parse,
+						  fixup_varattno_walker,
+						  (void *) &context,
+						  QTW_IGNORE_RETURNING);
+		context.is_returning = true;
+		expression_tree_walker((Node *)parse->returningList,
+							   fixup_varattno_walker,
+							   (void *) &context);
+	}
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e4ff76e..a3d445d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2127,6 +2127,22 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ROW LEVEL SECURITY (expression) */
+			| SET ROW LEVEL SECURITY '(' a_expr ')'
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetRowLevelSecurity;
+					n->def = (Node *) $6;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> RESET ROW LEVEL SECURITY */
+			| RESET ROW LEVEL SECURITY
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ResetRowLevelSecurity;
+					n->def = NULL;
+					$$ = (Node *)n;
+				}
 			| alter_generic_options
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b75b2d9..9494889 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -269,6 +269,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("aggregate functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			err = _("aggregate functions are not allowed in row-level security");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -537,6 +540,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			err = _("window functions are not allowed in row-level security");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index e9267c5..94c25d1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1443,6 +1443,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_OFFSET:
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
+		case EXPR_KIND_ROW_LEVEL_SEC:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -2609,6 +2610,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			return "ROW LEVEL SECURITY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b785c26..12e7dd7 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2250,3 +2250,19 @@ QueryRewrite(Query *parsetree)
 
 	return results;
 }
+
+/*
+ * QueryRewriteExpr
+ *
+ * This routine provides an entry point of query rewriter towards
+ * a certain expression tree with SubLink node; being added after
+ * the top level query rewrite.
+ * It primarily intends to expand views appeared in the qualifiers
+ * appended with row-level security which needs to modify query
+ * tree at head of the planner stage.
+ */
+void
+QueryRewriteExpr(Node *node, List *activeRIRs)
+{
+	fireRIRonSubLink(node, activeRIRs);
+}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 97e68b1..e54f92f 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2999,6 +2999,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
+	int			temp_sec_context;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -3078,8 +3079,18 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 
 	/* Switch to proper UID to perform check as */
 	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+
+	/*
+	 * Row-level security should be disabled in case when 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 (source_is_pk)
+		temp_sec_context |= SECURITY_ROW_LEVEL_DISABLED;
+
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
-						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+						   temp_sec_context);
 
 	/* Finally we can run the query. */
 	spi_result = SPI_execute_snapshot(qplan,
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 8c0391f..36a8750 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -51,6 +51,7 @@
 #include "catalog/namespace.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/planmain.h"
 #include "optimizer/prep.h"
@@ -665,6 +666,16 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		AcquireExecutorLocks(plan->stmt_list, true);
 
 		/*
+		 * If plan was constructed with assumption of a particular user-id,
+		 * and it is different from the current one, the cached-plan shall
+		 * be invalidated to construct suitable query plan.
+		 */
+		if (plan->is_valid &&
+			OidIsValid(plan->planUserId) &&
+			plan->planUserId == GetUserId())
+			plan->is_valid = false;
+
+		/*
 		 * If plan was transient, check to see if TransactionXmin has
 		 * advanced, and if so invalidate it.
 		 */
@@ -716,6 +727,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 {
 	CachedPlan *plan;
 	List	   *plist;
+	ListCell   *cell;
+	Oid			planUserId = InvalidOid;
 	bool		snapshot_set;
 	bool		spi_pushed;
 	MemoryContext plan_context;
@@ -794,6 +807,24 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	PopOverrideSearchPath();
 
 	/*
+	 * Check whether the generated plan assumes a particular user-id, or not.
+	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+	 * it should be kept and used to validation check of the cached plan
+	 * under the "current" user-id.
+	 */
+	foreach (cell, plist)
+	{
+		PlannedStmt	*pstmt = lfirst(cell);
+
+		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+		{
+			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+
+			planUserId = pstmt->planUserId;
+		}
+	}
+
+	/*
 	 * Make a dedicated memory context for the CachedPlan and its subsidiary
 	 * data.  It's probably not going to be large, but just in case, use the
 	 * default maxsize parameter.  It's transient for the moment.
@@ -828,6 +859,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->context = plan_context;
 	plan->is_saved = false;
 	plan->is_valid = true;
+	plan->planUserId = planUserId;
 
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 8c9ebe0..28ba831 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -896,6 +897,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->trigdesc = NULL;
 
+	if (relation->rd_rel->relhasrowlevelsec)
+		RelationBuildRowLevelSecurity(relation);
+	else
+		relation->rlsdesc = NULL;
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -1785,6 +1791,8 @@ RelationDestroyRelation(Relation relation)
 		MemoryContextDelete(relation->rd_indexcxt);
 	if (relation->rd_rulescxt)
 		MemoryContextDelete(relation->rd_rulescxt);
+	if (relation->rlsdesc)
+		MemoryContextDelete(relation->rlsdesc->rlscxt);
 	pfree(relation);
 }
 
@@ -3024,7 +3032,13 @@ RelationCacheInitializePhase3(void)
 				relation->rd_rel->relhastriggers = false;
 			restart = true;
 		}
-
+		if (relation->rd_rel->relhasrowlevelsec && relation->rlsdesc == NULL)
+		{
+			RelationBuildRowLevelSecurity(relation);
+			if (relation->rlsdesc == NULL)
+				relation->rd_rel->relhasrowlevelsec = false;
+			restart = true;
+		}
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4162,6 +4176,7 @@ load_relcache_init_file(bool shared)
 		rel->rd_rules = NULL;
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
+		rel->rlsdesc = NULL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 82330cb..694fa95 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3908,6 +3908,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloptions;
 	int			i_toastreloptions;
 	int			i_reloftype;
+	int			i_rlsqual;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -3932,7 +3933,45 @@ getTables(Archive *fout, int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (fout->remoteVersion >= 90100)
+	if (fout->remoteVersion >= 90300)
+	{
+		/*
+		 * 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.relfrozenxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "c.relpersistence, "
+						  "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(c.reloptions, ', ') AS reloptions, "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "pg_catalog.pg_get_expr(rls.rlsqual, rls.rlsrelid) AS rlsqual "
+						  "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) "
+					   "LEFT JOIN pg_rowlevelsec rls ON (c.oid = rls.rlsrelid) "
+						  "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c') "
+						  "ORDER BY c.oid",
+						  username_subquery,
+						  RELKIND_SEQUENCE,
+						  RELKIND_RELATION, RELKIND_SEQUENCE,
+						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+						  RELKIND_FOREIGN_TABLE);
+	}
+	else if (fout->remoteVersion >= 90100)
 	{
 		/*
 		 * Left join to pick up dependency info linking sequences to their
@@ -3952,7 +3991,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL as rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -3988,7 +4028,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4023,7 +4064,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4058,7 +4100,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4094,7 +4137,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4129,7 +4173,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4160,7 +4205,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class "
 						  "WHERE relkind IN ('%c', '%c', '%c') "
 						  "ORDER BY oid",
@@ -4186,7 +4232,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class "
 						  "WHERE relkind IN ('%c', '%c', '%c') "
 						  "ORDER BY oid",
@@ -4222,7 +4269,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "WHERE relkind IN ('%c', '%c') "
 						  "ORDER BY oid",
@@ -4270,6 +4318,7 @@ getTables(Archive *fout, int *numTables)
 	i_reloptions = PQfnumber(res, "reloptions");
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
+	i_rlsqual = PQfnumber(res, "rlsqual");
 
 	if (lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -4312,6 +4361,10 @@ getTables(Archive *fout, int *numTables)
 			tblinfo[i].reloftype = NULL;
 		else
 			tblinfo[i].reloftype = pg_strdup(PQgetvalue(res, i, i_reloftype));
+		if (PQgetisnull(res, i, i_rlsqual))
+			tblinfo[i].rlsqual = NULL;
+		else
+			tblinfo[i].rlsqual = pg_strdup(PQgetvalue(res, i, i_rlsqual));
 		tblinfo[i].ncheck = atoi(PQgetvalue(res, i, i_relchecks));
 		if (PQgetisnull(res, i, i_owning_tab))
 		{
@@ -12847,6 +12900,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			}
 		}
 	}
+	if (tbinfo->rlsqual)
+		appendPQExpBuffer(q, "ALTER TABLE ONLY %s SET ROW LEVEL SECURITY %s;\n",
+						  fmtId(tbinfo->dobj.name), tbinfo->rlsqual);
 
 	if (binary_upgrade)
 		binary_upgrade_extension_member(q, &tbinfo->dobj, labelq->data);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2100d43..57bd58b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -257,6 +257,7 @@ typedef struct _tableInfo
 	uint32		toast_frozenxid;	/* for restore toast frozen xid */
 	int			ncheck;			/* # of CHECK expressions */
 	char	   *reloftype;		/* underlying type for typed table */
+	char	   *rlsqual;		/* row-level security policy */
 	/* these two are set only if table is a sequence owned by a column: */
 	Oid			owning_tab;		/* OID of table owning sequence */
 	int			owning_col;		/* attr # of column owning sequence */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8499768..49baa0e 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_ROWLEVELSEC,			/* pg_rowlevelsec */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 238fe58..a3b07e2 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -311,6 +311,9 @@ 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_rowlevelsec_relid_index, 3839, on pg_rowlevelsec using btree(rlsrelid oid_ops));
+#define RowLevelSecurityIndexId				3839
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index f83ce80..5b6e38b 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		relhaspkey;		/* has (or has had) PRIMARY KEY index */
 	bool		relhasrules;	/* has (or has had) any rules */
 	bool		relhastriggers; /* has (or has had) any TRIGGERs */
+	bool		relhasrowlevelsec;	/* has (or has had) row-level security */
 	bool		relhassubclass; /* has (or has had) derived classes */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 
@@ -91,7 +92,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					27
+#define Natts_pg_class					28
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -115,10 +116,11 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhaspkey		21
 #define Anum_pg_class_relhasrules		22
 #define Anum_pg_class_relhastriggers	23
-#define Anum_pg_class_relhassubclass	24
-#define Anum_pg_class_relfrozenxid		25
-#define Anum_pg_class_relacl			26
-#define Anum_pg_class_reloptions		27
+#define Anum_pg_class_relhasrowlevelsec	24
+#define Anum_pg_class_relhassubclass	25
+#define Anum_pg_class_relfrozenxid		26
+#define Anum_pg_class_relacl			27
+#define Anum_pg_class_reloptions		28
 
 /* ----------------
  *		initial contents of pg_class
@@ -130,13 +132,13 @@ typedef FormData_pg_class *Form_pg_class;
  */
 
 /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 28 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
 
 
diff --git a/src/include/catalog/pg_rowlevelsec.h b/src/include/catalog/pg_rowlevelsec.h
new file mode 100644
index 0000000..41fcb21
--- /dev/null
+++ b/src/include/catalog/pg_rowlevelsec.h
@@ -0,0 +1,59 @@
+/*
+ * pg_rowlevelsec.h
+ *   definition of the system catalog for row-level security policy
+ *   (pg_rowlevelsec)
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ */
+#ifndef PG_ROWLEVELSEC_H
+#define PG_ROWLEVELSEC_H
+
+#include "catalog/genbki.h"
+#include "nodes/primnodes.h"
+#include "utils/memutils.h"
+#include "utils/relcache.h"
+
+/* ----------------
+ *		pg_rowlevelsec definition. cpp turns this into
+ *		typedef struct FormData_pg_rowlevelsec
+ * ----------------
+ */
+#define RowLevelSecurityRelationId	3838
+
+CATALOG(pg_rowlevelsec,3838) BKI_WITHOUT_OIDS
+{
+	Oid				rlsrelid;
+#ifdef CATALOG_VARLEN
+	pg_node_tree	rlsqual;
+#endif
+} FormData_pg_rowlevelsec;
+
+/* ----------------
+ *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+ *		the format of pg_rowlevelsec relation.
+ * ----------------
+ */
+typedef FormData_pg_rowlevelsec *Form_pg_rowlevelsec;
+
+/* ----------------
+ * 		compiler constants for pg_rowlevelsec
+ * ----------------
+ */
+#define Natts_pg_rowlevelsec				2
+#define Anum_pg_rowlevelsec_rlsrelid		1
+#define Anum_pg_rowlevelsec_rlsqual			2
+
+typedef struct
+{
+	MemoryContext	rlscxt;
+	Expr		   *rlsqual;
+	bool			rlshassublinks;
+} RowLevelSecDesc;
+
+extern void	RelationBuildRowLevelSecurity(Relation relation);
+extern void	SetRowLevelSecurity(Relation relation, Node *clause);
+extern void	RemoveRowLevelSecurityById(Oid relationId);
+
+#endif  /* PG_ROWLEVELSEC_H */
diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h
index 8680ac3..1c49dfa 100644
--- a/src/include/commands/copy.h
+++ b/src/include/commands/copy.h
@@ -21,7 +21,7 @@
 /* CopyStateData is private in commands/copy.c */
 typedef struct CopyStateData *CopyState;
 
-extern uint64 DoCopy(const CopyStmt *stmt, const char *queryString);
+extern uint64 DoCopy(CopyStmt *stmt, const char *queryString);
 
 extern void ProcessCopyOptions(CopyState cstate, bool is_from, List *options);
 extern CopyState BeginCopyFrom(Relation rel, const char *filename,
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 3ea3493..8d7a9ad 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -277,6 +277,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/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index e609e4b..a1d98e4 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -24,6 +24,7 @@
 #define QTW_IGNORE_RANGE_TABLE		0x08		/* skip rangetable entirely */
 #define QTW_EXAMINE_RTES			0x10		/* examine RTEs */
 #define QTW_DONT_COPY_QUERY			0x20		/* do not copy top Query */
+#define QTW_IGNORE_RETURNING		0x40		/* skip returning clause */
 
 
 extern Oid	exprType(const Node *expr);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8834499..a771727 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -31,7 +31,8 @@ typedef enum QuerySource
 	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
-	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
+	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
+	QSRC_ROW_LEVEL_SECURITY,	/* added by row-level security */
 } QuerySource;
 
 /* Sort ordering options for ORDER BY and CREATE INDEX */
@@ -700,6 +701,13 @@ typedef struct RangeTblEntry
 
 	/*
 	 * Fields valid for a plain relation RTE (else zero):
+	 *
+	 * XXX - Query optimizer may modify and replace RangeTblEntry on
+	 * a particular relation by sub-query, but should perform as result
+	 * relation of the query. In this case, relid field is used to track
+	 * which relation is the sub-query originated.
+	 * Right now, only row-level security feature uses this field to track
+	 * the relation-id of sub-query being originated.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
@@ -1233,6 +1241,8 @@ typedef enum AlterTableType
 	AT_DropInherit,				/* NO INHERIT parent */
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
+	AT_SetRowLevelSecurity,		/* SET ROW LEVEL SECURITY (...) */
+	AT_ResetRowLevelSecurity,	/* RESET ROW LEVEL SECURITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fb9a863..6b3ea3d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,8 @@ typedef struct PlannedStmt
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
+
+	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
 } 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 0a1f8d5..b195dd7 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -98,6 +98,8 @@ typedef struct PlannerGlobal
 	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+
+	Oid			planUserId;		/* User-Id to be assumed on this plan */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/optimizer/rowlevelsec.h b/src/include/optimizer/rowlevelsec.h
new file mode 100644
index 0000000..c8e0019
--- /dev/null
+++ b/src/include/optimizer/rowlevelsec.h
@@ -0,0 +1,25 @@
+/* -------------------------------------------------------------------------
+ *
+ * rowlevelsec.h
+ *    prototypes for optimizer/rowlevelsec.c
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef ROWLEVELSEC_H
+#define ROWLEVELSEC_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/relation.h"
+#include "utils/rel.h"
+
+typedef List *(*rowlevel_security_hook_type)(Relation relation);
+extern PGDLLIMPORT rowlevel_security_hook_type rowlevel_security_hook;
+
+extern bool	copy_row_level_security(CopyStmt *stmt,
+									Relation relation, List *attnums);
+extern void	 apply_row_level_security(PlannerInfo *root);
+
+#endif	/* ROWLEVELSEC_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index aa9c648..3a1e4ea 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -62,7 +62,8 @@ typedef enum ParseExprKind
 	EXPR_KIND_INDEX_PREDICATE,		/* index predicate */
 	EXPR_KIND_ALTER_COL_TRANSFORM,	/* transform expr in ALTER COLUMN TYPE */
 	EXPR_KIND_EXECUTE_PARAMETER,	/* parameter value in EXECUTE */
-	EXPR_KIND_TRIGGER_WHEN			/* WHEN condition in CREATE TRIGGER */
+	EXPR_KIND_TRIGGER_WHEN,			/* WHEN condition in CREATE TRIGGER */
+	EXPR_KIND_ROW_LEVEL_SEC,		/* policy of ROW LEVEL SECURITY */
 } ParseExprKind;
 
 
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 50625d4..d470cad 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -18,6 +18,7 @@
 #include "nodes/parsenodes.h"
 
 extern List *QueryRewrite(Query *parsetree);
+extern void	QueryRewriteExpr(Node *node, List *activeRIRs);
 extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
 extern Node *build_column_default(Relation rel, int attrno);
 
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 413e846..5f89028 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -115,6 +115,8 @@ typedef struct CachedPlan
 								 * bare utility statements) */
 	bool		is_saved;		/* is CachedPlan in a long-lived context? */
 	bool		is_valid;		/* is the stmt_list currently valid? */
+	Oid			planUserId;		/* is user-id that is assumed on this cached
+								   plan, or InvalidOid if portable for anybody */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
 	int			generation;		/* parent's generation number for this plan */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 4669d8a..dce8463 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -18,6 +18,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_index.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "fmgr.h"
 #include "nodes/bitmapset.h"
 #include "rewrite/prs2lock.h"
@@ -109,6 +110,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 */
+	RowLevelSecDesc	*rlsdesc;	/* Row-level security info, or NULL */
 
 	/*
 	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
diff --git a/src/test/regress/expected/rowlevelsec.out b/src/test/regress/expected/rowlevelsec.out
new file mode 100644
index 0000000..854acb0
--- /dev/null
+++ b/src/test/regress/expected/rowlevelsec.out
@@ -0,0 +1,954 @@
+--
+-- 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 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 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
+CREATE TABLE uaccount (
+    pguser      name primary key,
+    seclv       int
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+SET SESSION AUTHORIZATION rls_regress_user0;
+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');
+-- user's security level must higher than or equal to document's one
+ALTER TABLE document SET ROW LEVEL SECURITY
+    (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));
+-- viewpoint from rls_regress_user1
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   4 |      1 | rls_regress_user1 | my first manga        | manga
+(4 rows)
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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 |   2 |      2 | rls_regress_user1 | my second novel       | novel
+  11 |   1 |      1 | rls_regress_user1 | my first novel        | novel
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  22 |   3 |      2 | rls_regress_user1 | my science fiction    | science fiction
+  33 |   7 |      2 | rls_regress_user2 | great technology book | technology
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   5 |      2 | rls_regress_user1 | my second manga       | manga
+  44 |   4 |      1 | rls_regress_user1 | my first 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 TABLE document SET ROW LEVEL SECURITY (true);     -- fail
+ERROR:  must be owner of relation document
+ALTER TABLE document RESET ROW LEVEL SECURITY;          -- fail
+ERROR:  must be owner of relation document
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW LEVEL SECURITY (dauthor = current_user);
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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;
+ALTER TABLE category SET ROW LEVEL SECURITY
+    (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);
+-- 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;    -- failed
+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'); -- failed
+ERROR:  insert or update on table "document" violates foreign key constraint "document_cid_fkey"
+DETAIL:  Key (cid)=(33) is not present in table "category".
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+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
+(8 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;
+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);
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);	-- be even number
+ALTER TABLE t2 SET ROW LEVEL SECURITY (a % 2 = 1);	-- be odd number
+SELECT * FROM t1;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(9 rows)
+
+SELECT * FROM t1 WHERE f_leak(b);
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ddd
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => cde
+NOTICE:  f_leak => xxx
+NOTICE:  f_leak => yyy
+NOTICE:  f_leak => zzz
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b);
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.b)
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               Filter: f_leak(t2.b)
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+               Filter: f_leak(b)
+(12 rows)
+
+-- reference to system column
+SELECT oid, * FROM t1;
+ oid | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | ddd
+ 201 | 1 | abc
+ 203 | 3 | cde
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(9 rows)
+
+-- reference to whole-row reference
+SELECT *,t1 FROM t1;
+ a |  b  |   t1    
+---+-----+---------
+ 2 | bbb | (2,bbb)
+ 4 | ddd | (4,ddd)
+ 1 | abc | (1,abc)
+ 3 | cde | (3,cde)
+ 1 | xxx | (1,xxx)
+ 2 | yyy | (2,yyy)
+ 3 | zzz | (3,zzz)
+(7 rows)
+
+EXPLAIN (costs off) SELECT *,t1 FROM t1;
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(9 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                      QUERY PLAN                       
+-------------------------------------------------------
+ LockRows
+   ->  Result
+         ->  Append
+               ->  Subquery Scan on t1
+                     ->  LockRows
+                           ->  Seq Scan on t1 t1_1
+                                 Filter: ((a % 2) = 0)
+               ->  Subquery Scan on t2
+                     ->  LockRows
+                           ->  Seq Scan on t2 t2_1
+                                 Filter: ((a % 2) = 1)
+               ->  Seq Scan on t3
+(12 rows)
+
+SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ddd
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => cde
+NOTICE:  f_leak => xxx
+NOTICE:  f_leak => yyy
+NOTICE:  f_leak => zzz
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+                      QUERY PLAN                       
+-------------------------------------------------------
+ LockRows
+   ->  Result
+         ->  Append
+               ->  Subquery Scan on t1
+                     Filter: f_leak(t1.b)
+                     ->  LockRows
+                           ->  Seq Scan on t1 t1_1
+                                 Filter: ((a % 2) = 0)
+               ->  Subquery Scan on t2
+                     Filter: f_leak(t2.b)
+                     ->  LockRows
+                           ->  Seq Scan on t2 t2_1
+                                 Filter: ((a % 2) = 1)
+               ->  Seq Scan on t3
+                     Filter: f_leak(b)
+(15 rows)
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+2	bbb
+4	ddd
+COPY t1 TO stdout WITH OIDS;
+102	2	bbb
+104	4	ddd
+COPY t2(c,b) TO stdout WITH OIDS;
+201	1.1	abc
+203	3.3	cde
+COPY (SELECT * FROM t1) TO stdout;
+2	bbb
+4	ddd
+1	abc
+3	cde
+1	xxx
+2	yyy
+3	zzz
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ERROR:  table "document" does not have OIDs
+--
+-- recursive RLS and VIEWs in policy
+--
+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);
+CREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%';
+ALTER TABLE s1 SET ROW LEVEL SECURITY
+   (a in (select x from s2 where y like '%2f%'));
+ALTER TABLE s2 SET ROW LEVEL SECURITY
+   (x in (select a from s1 where b like '%22%'));
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ERROR:  infinite recursion detected for relation "s1"
+ALTER TABLE s2 SET ROW LEVEL SECURITY (x % 2 = 0);
+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 SELECT * FROM only s1 WHERE f_leak(b);
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Subquery Scan on s1  (cost=28.55..61.67 rows=205 width=36)
+   Filter: f_leak(s1.b)
+   ->  Hash Join  (cost=28.55..55.52 rows=615 width=36)
+         Hash Cond: (s1_1.a = s2.x)
+         ->  Seq Scan on s1 s1_1  (cost=0.00..22.30 rows=1230 width=36)
+         ->  Hash  (cost=28.54..28.54 rows=1 width=4)
+               ->  HashAggregate  (cost=28.53..28.54 rows=1 width=4)
+                     ->  Subquery Scan on s2  (cost=0.00..28.52 rows=1 width=4)
+                           Filter: (s2.y ~~ '%2f%'::text)
+                           ->  Seq Scan on s2 s2_1  (cost=0.00..28.45 rows=6 width=36)
+                                 Filter: ((x % 2) = 0)
+(11 rows)
+
+ALTER TABLE s1 SET ROW LEVEL SECURITY
+   (a in (select x from v2));		-- using VIEW in RLS policy
+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
+                     ->  Subquery Scan on s2
+                           Filter: (s2.y ~~ '%af%'::text)
+                           ->  Seq Scan on s2 s2_1
+                                 Filter: ((x % 2) = 0)
+(11 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)
+
+ALTER TABLE s2 SET ROW LEVEL SECURITY
+   (x in (select a from s1 where b like '%d2%'));
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion via view)
+ERROR:  infinite recursion detected 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
+ 1 | abc
+ 1 | xxx
+ 2 | yyy
+(4 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a <= 2) AND ((a % 2) = 0))
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a <= 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a <= 2)
+(10 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+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            
+---------------------------------
+ Result
+   ->  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)
+(8 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           
+--------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: (a <= 2)
+         ->  Seq Scan on t2
+               Filter: (a <= 2)
+         ->  Seq Scan on t3
+               Filter: (a <= 2)
+(8 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           
+-------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: (a = 2)
+         ->  Seq Scan on t2
+               Filter: (a = 2)
+         ->  Seq Scan on t3
+               Filter: (a = 2)
+(8 rows)
+
+-- also, case when privilege switch from superuser
+SET SESSION AUTHORIZATION rls_regress_user0;
+EXECUTE p2(2);
+ a |  b  
+---+-----
+ 2 | bbb
+ 2 | yyy
+(2 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a = 2) AND ((a % 2) = 0))
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a = 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a = 2)
+(10 rows)
+
+--
+-- UPDATE / DELETE and Row-level security
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+EXPLAIN (costs off) UPDATE t1 SET b = b || b WHERE f_leak(b);
+             QUERY PLAN              
+-------------------------------------
+ Update on t1
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         Filter: f_leak(t2.b)
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+         Filter: f_leak(b)
+(11 rows)
+
+UPDATE t1 SET b = b || b WHERE f_leak(b);
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ddd
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => cde
+NOTICE:  f_leak => xxx
+NOTICE:  f_leak => yyy
+NOTICE:  f_leak => zzz
+EXPLAIN (costs off) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);
+             QUERY PLAN              
+-------------------------------------
+ Update on t1
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               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 => abcabc
+NOTICE:  f_leak => cdecde
+NOTICE:  f_leak => xxxxxx
+NOTICE:  f_leak => yyyyyy
+NOTICE:  f_leak => zzzzzz
+ a |      b      
+---+-------------
+ 2 | bbbbbb_updt
+ 4 | dddddd_updt
+ 1 | abcabc
+ 3 | cdecde
+ 1 | xxxxxx
+ 2 | yyyyyy
+ 3 | zzzzzz
+(7 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 => abcabc
+NOTICE:  f_leak => cdecde
+NOTICE:  f_leak => xxxxxx
+NOTICE:  f_leak => yyyyyy
+NOTICE:  f_leak => zzzzzz
+ oid | a |      b      |       t1        
+-----+---+-------------+-----------------
+ 102 | 2 | bbbbbb_updt | (2,bbbbbb_updt)
+ 104 | 4 | dddddd_updt | (4,dddddd_updt)
+ 201 | 1 | abcabc      | (1,abcabc)
+ 203 | 3 | cdecde      | (3,cdecde)
+ 301 | 1 | xxxxxx      | (1,xxxxxx)
+ 302 | 2 | yyyyyy      | (2,yyyyyy)
+ 303 | 3 | zzzzzz      | (3,zzzzzz)
+(7 rows)
+
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1;
+ a |      b      
+---+-------------
+ 1 | aaa
+ 3 | ccc
+ 2 | bbbbbb_updt
+ 4 | dddddd_updt
+ 2 | bcd
+ 4 | def
+ 1 | abcabc
+ 3 | cdecde
+ 1 | xxxxxx
+ 2 | yyyyyy
+ 3 | zzzzzz
+(11 rows)
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+EXPLAIN (costs off) DELETE FROM only t1 WHERE f_leak(b);
+             QUERY PLAN              
+-------------------------------------
+ Delete on t1
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+(5 rows)
+
+EXPLAIN (costs off) DELETE FROM t1 WHERE f_leak(b);
+             QUERY PLAN              
+-------------------------------------
+ Delete on t1
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         Filter: f_leak(t2.b)
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+         Filter: f_leak(b)
+(11 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 => abcabc
+NOTICE:  f_leak => cdecde
+NOTICE:  f_leak => xxxxxx
+NOTICE:  f_leak => yyyyyy
+NOTICE:  f_leak => zzzzzz
+ oid | a |   b    |     t1     
+-----+---+--------+------------
+ 201 | 1 | abcabc | (1,abcabc)
+ 203 | 3 | cdecde | (3,cdecde)
+ 301 | 1 | xxxxxx | (1,xxxxxx)
+ 302 | 2 | yyyyyy | (2,yyyyyy)
+ 303 | 3 | zzzzzz | (3,zzzzzz)
+(5 rows)
+
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1;
+ a |  b  
+---+-----
+ 1 | aaa
+ 3 | ccc
+ 2 | bcd
+ 4 | def
+(4 rows)
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rls_regress_schema CASCADE;
+NOTICE:  drop cascades to 10 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 t1
+drop cascades to table t2
+drop cascades to table t3
+drop cascades to table s1
+drop cascades to table s2
+drop cascades to view v2
+DROP USER rls_regress_user0;
+DROP USER rls_regress_user1;
+DROP USER rls_regress_user2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 3f04442..ffcd8d3 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ SELECT relname, relhasindex
  pg_proc                 | t
  pg_range                | t
  pg_rewrite              | t
+ pg_rowlevelsec          | t
  pg_seclabel             | t
  pg_shdepend             | t
  pg_shdescription        | t
@@ -166,7 +167,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(155 rows)
+(156 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 663bf8a..4c4552f 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
+test: privileges rowlevelsec security_label collate
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index be789e3..eea4c3e 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -92,6 +92,7 @@ test: delete
 test: namespace
 test: prepared_xacts
 test: privileges
+test: rowlevelsec
 test: security_label
 test: collate
 test: misc
diff --git a/src/test/regress/sql/rowlevelsec.sql b/src/test/regress/sql/rowlevelsec.sql
new file mode 100644
index 0000000..a996b8c
--- /dev/null
+++ b/src/test/regress/sql/rowlevelsec.sql
@@ -0,0 +1,295 @@
+--
+-- 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 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 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
+CREATE TABLE uaccount (
+    pguser      name primary key,
+    seclv       int
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+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');
+
+-- user's security level must higher than or equal to document's one
+ALTER TABLE document SET ROW LEVEL SECURITY
+    (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));
+
+-- viewpoint from rls_regress_user1
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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 TABLE document SET ROW LEVEL SECURITY (true);     -- fail
+ALTER TABLE document RESET ROW LEVEL SECURITY;          -- fail
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW LEVEL SECURITY (dauthor = current_user);
+
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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;
+ALTER TABLE category SET ROW LEVEL SECURITY
+    (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);
+
+-- 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;    -- failed
+
+-- 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'); -- failed
+
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+SELECT * FROM document;
+SELECT * FROM category;
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+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
+\.
+
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);	-- be even number
+ALTER TABLE t2 SET ROW LEVEL SECURITY (a % 2 = 1);	-- be odd number
+
+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 * 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;
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+COPY t1 TO stdout WITH OIDS;
+COPY t2(c,b) TO stdout WITH OIDS;
+COPY (SELECT * FROM t1) TO stdout;
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+
+--
+-- recursive RLS and VIEWs in policy
+--
+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);
+CREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%';
+
+ALTER TABLE s1 SET ROW LEVEL SECURITY
+   (a in (select x from s2 where y like '%2f%'));
+
+ALTER TABLE s2 SET ROW LEVEL SECURITY
+   (x in (select a from s1 where b like '%22%'));
+
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+
+ALTER TABLE s2 SET ROW LEVEL SECURITY (x % 2 = 0);
+
+SELECT * FROM s1 WHERE f_leak(b);	-- OK
+EXPLAIN SELECT * FROM only s1 WHERE f_leak(b);
+
+ALTER TABLE s1 SET ROW LEVEL SECURITY
+   (a in (select x from v2));		-- using VIEW in RLS policy
+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%';
+
+ALTER TABLE s2 SET ROW LEVEL SECURITY
+   (x in (select a from s1 where b like '%d2%'));
+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;
+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_user0;
+EXECUTE p2(2);
+EXPLAIN (costs off) EXECUTE p2(2);
+
+--
+-- UPDATE / DELETE and Row-level security
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+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;
+SELECT * FROM t1;
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+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;
+
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1;
+
+--
+-- 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;