pgsql-v9.3-row-level-security.ro.v3.patch

application/octet-stream

Filename: pgsql-v9.3-row-level-security.ro.v3.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 38 0
doc/src/sgml/user-manag.sgml 123 0
src/backend/access/transam/xact.c 12 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 241 0
src/backend/commands/tablecmds.c 64 0
src/backend/optimizer/plan/planner.c 16 0
src/backend/optimizer/util/Makefile 1 1
src/backend/optimizer/util/rowlevelsec.c 678 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/utils/adt/ri_triggers.c 14 0
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 60 0
src/include/nodes/parsenodes.h 4 1
src/include/nodes/plannodes.h 2 0
src/include/nodes/relation.h 2 0
src/include/optimizer/rowlevelsec.h 31 0
src/include/parser/parse_node.h 2 1
src/include/utils/plancache.h 2 0
src/include/utils/rel.h 2 0
src/test/regress/expected/rowlevelsec.out 753 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 237 0
 doc/src/sgml/catalogs.sgml                 |  59 +++
 doc/src/sgml/ref/alter_table.sgml          |  38 ++
 doc/src/sgml/user-manag.sgml               | 123 +++++
 src/backend/access/transam/xact.c          |  12 +
 src/backend/catalog/Makefile               |   4 +-
 src/backend/catalog/dependency.c           |  23 +
 src/backend/catalog/heap.c                 |   1 +
 src/backend/catalog/pg_rowlevelsec.c       | 241 +++++++++
 src/backend/commands/tablecmds.c           |  64 +++
 src/backend/optimizer/plan/planner.c       |  16 +
 src/backend/optimizer/util/Makefile        |   2 +-
 src/backend/optimizer/util/rowlevelsec.c   | 678 ++++++++++++++++++++++++++
 src/backend/parser/gram.y                  |  16 +
 src/backend/parser/parse_agg.c             |   6 +
 src/backend/parser/parse_expr.c            |   3 +
 src/backend/utils/adt/ri_triggers.c        |  14 +
 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       |  60 +++
 src/include/nodes/parsenodes.h             |   5 +-
 src/include/nodes/plannodes.h              |   2 +
 src/include/nodes/relation.h               |   2 +
 src/include/optimizer/rowlevelsec.h        |  31 ++
 src/include/parser/parse_node.h            |   3 +-
 src/include/utils/plancache.h              |   2 +
 src/include/utils/rel.h                    |   2 +
 src/test/regress/expected/rowlevelsec.out  | 753 +++++++++++++++++++++++++++++
 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       | 237 +++++++++
 36 files changed, 2526 insertions(+), 27 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3bcd82c..5a5233e 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 b764814..f3f2d91 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>
@@ -806,6 +831,19 @@ 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,
+        however, unavailable to include sub-query right now.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 177ac7a..c283e07 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -439,4 +439,127 @@ 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
+         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
+                     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
+                     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>
+   Right now, row-level security feature has several limitation,
+   although these shall be improved in the future version.
+
+   Row-level security policy is not applied to
+   <command>UPDATE</command> or <command>DELETE</command>
+   commands in this revision, even though it should be
+   supported soon.
+
+   Row-level security policy is not applied to rows to be inserted
+   and newer revision of updated rows, thus, it requires to
+   define before-row-insert or before-row-update trigger to check
+   whether the row's contents satisfies the policy individually.
+
+   Although it is not a specific matter in row-level security,
+   <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH">
+   allows to switch current user identifier during execution
+   of the query. Thus, it may cause unpredicated behavior
+   if and when <literal>current_user</literal> is used as
+   a part of row-level security policy.
+  </para>
+ </sect1>
 </chapter>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index e7a6606..851f9db 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -35,6 +35,7 @@
 #include "executor/spi.h"
 #include "libpq/be-fsstubs.h"
 #include "miscadmin.h"
+#include "optimizer/rowlevelsec.h"
 #include "pgstat.h"
 #include "replication/walsender.h"
 #include "replication/syncrep.h"
@@ -144,6 +145,7 @@ typedef struct TransactionStateData
 	int			maxChildXids;	/* allocated size of childXids[] */
 	Oid			prevUser;		/* previous CurrentUserId setting */
 	int			prevSecContext; /* previous SecurityRestrictionContext */
+	RowLevelSecMode	prevRowLevelSecMode;	/* previous RLS-mode setting */
 	bool		prevXactReadOnly;		/* entry-time xact r/o state */
 	bool		startedInRecovery;		/* did we start in recovery? */
 	struct TransactionStateData *parent;		/* back link to parent */
@@ -173,6 +175,7 @@ static TransactionStateData TopTransactionStateData = {
 	0,							/* allocated size of childXids[] */
 	InvalidOid,					/* previous CurrentUserId setting */
 	0,							/* previous SecurityRestrictionContext */
+	RowLevelSecModeEnabled,		/* previous RowLevelSecMode setting */
 	false,						/* entry-time xact r/o state */
 	false,						/* startedInRecovery */
 	NULL						/* link to parent state block */
@@ -1764,6 +1767,8 @@ StartTransaction(void)
 	/* SecurityRestrictionContext should never be set outside a transaction */
 	Assert(s->prevSecContext == 0);
 
+	s->prevRowLevelSecMode = getRowLevelSecurityMode();
+
 	/*
 	 * initialize other subsystems for new transaction
 	 */
@@ -2295,6 +2300,9 @@ AbortTransaction(void)
 	 */
 	SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
 
+	/* Also, mode setting of row-level security should be restored. */
+	setRowLevelSecurityMode(s->prevRowLevelSecMode);
+
 	/*
 	 * do abort processing
 	 */
@@ -4188,6 +4196,9 @@ AbortSubTransaction(void)
 	 */
 	SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
 
+	/* Reset row-level security mode */
+	setRowLevelSecurityMode(s->prevRowLevelSecMode);
+
 	/*
 	 * We can skip all this stuff if the subxact failed before creating a
 	 * ResourceOwner...
@@ -4327,6 +4338,7 @@ PushTransaction(void)
 	s->state = TRANS_DEFAULT;
 	s->blockState = TBLOCK_SUBBEGIN;
 	GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
+	s->prevRowLevelSecMode = getRowLevelSecurityMode();
 	s->prevXactReadOnly = XactReadOnly;
 
 	CurrentTransactionState = s;
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 87d6f02..f0736b1 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"
@@ -1229,6 +1230,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);
@@ -2280,6 +2285,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
+
+		case RowLevelSecurityRelationId:
+			return OCLASS_ROWLEVELSEC;
 	}
 
 	/* shouldn't get here */
@@ -2929,6 +2937,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 c80df41..60de76d 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..e554f20
--- /dev/null
+++ b/src/backend/catalog/pg_rowlevelsec.c
@@ -0,0 +1,241 @@
+/* -------------------------------------------------------------------------
+ *
+ * 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"
+
+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);
+}
+
+void
+SetRowLevelSecurity(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 Indert 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);
+}
+
+void
+ResetRowLevelSecurity(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);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 359d478..83ca86b 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"
@@ -384,6 +385,7 @@ static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
 static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
 static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
+static void ATExecSetRowLevelSecurity(Relation relation, Node *clause);
 static void ATExecGenericOptions(Relation rel, List *options);
 
 static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
@@ -2746,6 +2748,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;
@@ -3108,6 +3112,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 */
@@ -3383,6 +3389,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowLevelSecurity:
+			ATExecSetRowLevelSecurity(rel, (Node *) cmd->def);
+			break;
+		case AT_ResetRowLevelSecurity:
+			ATExecSetRowLevelSecurity(rel, NULL);
+			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
@@ -7533,6 +7545,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:
@@ -9631,6 +9659,42 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode)
 }
 
 /*
+ * ALTER TABLE <name> SET ROW LEVEL SECURITY (...) OR
+ *                    RESET ROW LEVEL SECURITY
+ */
+static void
+ATExecSetRowLevelSecurity(Relation relation, Node *clause)
+{
+	Oid			relid = RelationGetRelid(relation);
+	Relation	class_rel;
+	HeapTuple	tuple;
+	Form_pg_class	class_form;
+
+	if (clause != NULL)
+		SetRowLevelSecurity(relation, clause);
+	else
+		ResetRowLevelSecurity(relation);
+
+	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)
+		class_form->relhasrowlevelsec = true;
+	else
+		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);
+}
+
+/*
  * ALTER FOREIGN TABLE <name> OPTIONS (...)
  */
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4284eed..7e6349c 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,13 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPHId = 0;
 	glob->lastRowMarkId = 0;
 	glob->transientPlan = false;
+	glob->planUserId = InvalidOid;
+	/*
+	 * XXX - a valid user-id shall be set on planUserId later, if constructed
+	 * plan assumes being executed under privilege of a particular user-id.
+	 * Elsewhere, keep InvalidOid; that means the constructed plan is portable
+	 * for any users.
+	 */
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -244,6 +252,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
 	result->nParamExec = list_length(glob->paramlist);
+	result->planUserId = glob->planUserId;
 
 	return result;
 }
@@ -350,6 +359,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 		flatten_simple_union_all(root);
 
 	/*
+	 * Apply row-level security policy of appeared tables, if configured.
+	 * Note that it must be processed after all the simple subqueries or
+	 * union all queries being flatten, but prior to preprocess_rowmarks().
+	 */
+	applyRowLevelSecurity(root);
+
+	/*
 	 * Detect whether any rangetable entries are RTE_JOIN kind; if not, we can
 	 * avoid the expense of doing flatten_join_alias_vars().  Also check for
 	 * outer joins --- if none, we can skip reduce_outer_joins().  And check
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..3a01b0c
--- /dev/null
+++ b/src/backend/optimizer/util/rowlevelsec.c
@@ -0,0 +1,678 @@
+/*
+ * 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 "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;
+
+/* current performing mode of row-level security policy */
+static RowLevelSecMode	rowlevel_security_mode = RowLevelSecModeEnabled;
+
+/*
+ * getRowLevelSecurityMode / setRowLevelSecurityMode
+ *
+ * These functions allow to get or set current performing mode of row-
+ * level security feature. It enables to disable this feature temporarily
+ * for some cases in which row-level security prevent correct behavior
+ * such as foreign-key checks to prohibit update of PKs being referenced
+ * by others.
+ * The caller must ensure the saved previous mode shall be restored, but
+ * no need to care about cases when an error would be raised.
+ */
+RowLevelSecMode
+getRowLevelSecurityMode(void)
+{
+	return rowlevel_security_mode;
+}
+
+void
+setRowLevelSecurityMode(RowLevelSecMode new_mode)
+{
+	rowlevel_security_mode = new_mode;
+}
+
+/*
+ * pull_rowlevel_security_policy
+ *
+ * This routine tries to pull expression node of row-level security policy
+ * on the target relation, and its children if configured.
+ * If one or more relation has a security policy at least, this function
+ * returns true, or false elsewhere.
+ */
+static bool
+pull_rowlevel_security_policy(PlannerInfo *root,
+							  RangeTblEntry *rte,
+							  Index rtindex,
+							  List **rls_relids,
+							  List **rls_quals,
+							  List **rls_flags)
+{
+	Relation		rel;
+	LOCKMODE		lockmode;
+	List		   *relid_list = NIL;
+	List		   *qual_list = NIL;
+	List		   *flag_list = NIL;
+	ListCell	   *cell;
+	bool			result = false;
+
+	Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+	if (!rte->inh)
+	{
+		lockmode = NoLock;
+		relid_list = list_make1_oid(rte->relid);
+	}
+	else
+	{
+		/*
+		 * In case when the target relation may have inheritances,
+		 * we need to suggest an appropriate lock mode because it
+		 * is the first time to reference these tables in a series
+		 * of processes. For more details, see the comments in
+		 * expand_inherited_tables.
+		 * Also note that it does not guarantee the locks on child
+		 * tables being already acquired at expand_inherited_tables,
+		 * because row-level security routines can be bypassed if
+		 * RowLevelSecModeDisabled.
+		 */
+		if (rtindex == root->parse->resultRelation)
+			lockmode = RowExclusiveLock;
+		else
+		{
+			foreach (cell, root->parse->rowMarks)
+			{
+				Assert(IsA(lfirst(cell), RowMarkClause));
+				if (((RowMarkClause *) lfirst(cell))->rti == rtindex)
+					break;
+			}
+			if (cell)
+				lockmode = RowShareLock;
+			else
+				lockmode = AccessShareLock;
+		}
+		relid_list = find_all_inheritors(rte->relid, lockmode, NULL);
+	}
+
+	/*
+	 * Try to fetch row-level security policy of the target relation
+	 * or its children.
+	 */
+	foreach (cell, relid_list)
+	{
+		Expr   *qual = NULL;
+		int		flags = 0;
+
+		rel = heap_open(lfirst_oid(cell), lockmode);
+
+		/*
+		 * Pull out row-level security policy configured with built-in
+		 * features, if unprivileged users. Please note that superuser
+		 * can bypass it.
+		 */
+		if (rel->rlsdesc && !superuser())
+		{
+			RowLevelSecDesc	*rlsdesc = rel->rlsdesc;
+
+			qual = 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   *qual_list;
+
+			qual_list = (*rowlevel_security_hook)(root, rel);
+			if (qual_list != NIL)
+			{
+				if ((flags & RLS_FLAG_HAS_SUBLINKS) == 0 &&
+					contain_subplans((Node *)qual_list))
+					flags |= RLS_FLAG_HAS_SUBLINKS;
+
+				if (qual != NULL)
+					qual_list = lappend(qual_list, qual);
+
+				if (list_length(qual_list) == 1)
+					qual = (Expr *)list_head(qual_list);
+				else
+					qual = makeBoolExpr(AND_EXPR, qual_list, -1);
+			}
+		}
+
+		qual_list = lappend(qual_list, qual);
+		if (qual)
+			result = true;
+		flag_list = lappend_int(flag_list, flags);
+
+		heap_close(rel, NoLock);  /* close the relation, but keep locks */
+	}
+
+	/*
+	 * Inform the caller list of relation Oid, qualifier of row-level
+	 * security policy and its flag, if one or more target relations
+	 * have its row-level security policy. Elsewhere, release it.
+	 */
+	if (result)
+	{
+		*rls_relids = relid_list;
+		*rls_quals = qual_list;
+		*rls_flags = flag_list;
+	}
+	else
+	{
+		list_free(relid_list);
+		list_free(qual_list);
+		list_free(flag_list);
+	}
+	return result;
+}
+
+/*
+ * fixup_varattnos
+ *
+ * It fixes up varattno of Var node that referenced the relation with
+ * RLS policy, thus replaced to a sub-query. Here is no guarantee the
+ * varattno matches with TargetEntry's resno of the sub-query, so needs
+ * to adjust them.
+ */
+typedef struct {
+	PlannerInfo *root;
+	int		varlevelsup;
+} fixup_var_context;
+
+static bool
+fixup_var_references_walker(Node *node, fixup_var_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var            *var = (Var *)node;
+		RangeTblEntry  *rte;
+
+		/*
+		 * Does this Var node reference the Query node currently we focused
+		 * on. If not, we simply ignore it.
+		 */
+		if (var->varlevelsup != context->varlevelsup)
+			return false;
+
+		rte = rt_fetch(var->varno, context->root->parse->rtable);
+		if (!rte)
+			elog(ERROR, "invalid varno %d", var->varno);
+
+		if (rte->rtekind == RTE_SUBQUERY &&
+			rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+		{
+			List       *targetList = rte->subquery->targetList;
+			ListCell   *cell;
+
+			foreach (cell, targetList)
+			{
+				TargetEntry    *subtle = lfirst(cell);
+
+				if ((IsA(subtle->expr, ConvertRowtypeExpr) &&
+					 var->varattno == InvalidAttrNumber) ||
+					(IsA(subtle->expr, Var) &&
+					 var->varattno == ((Var *)(subtle->expr))->varattno))
+				{
+					var->varattno = subtle->resno;
+					return false;
+				}
+			}
+			elog(ERROR, "invalid varattno %d", var->varattno);
+		}
+		return false;
+	}
+	else if (IsA(node, Query))
+	{
+		bool    result;
+
+		context->varlevelsup++;
+		result = query_tree_walker((Query *) node,
+								   fixup_var_references_walker,
+								   (void *) context, 0);
+		context->varlevelsup--;
+
+		return result;
+	}
+	return expression_tree_walker(node,
+								  fixup_var_references_walker,
+								  (void *) context);
+}
+
+static void
+fixup_varattnos(PlannerInfo *root)
+{
+	fixup_var_context   context;
+
+	/*
+	 * Fixup Var->varattno that references the sub-queries originated from
+	 * regular relations with RLS policy.
+	 */
+	context.root = root;
+	context.varlevelsup = 0;
+
+	query_tree_walker(root->parse,
+					  fixup_var_references_walker,
+					  (void *) &context, 0);
+}
+
+/*
+ * make_pseudo_column
+ *
+ * make a TargetEntry object which references a particular column of
+ * the underlying table.
+ */
+static TargetEntry *
+make_pseudo_column(Oid relid_head, Oid relid, AttrNumber attnum)
+{
+	Form_pg_attribute attform;
+	HeapTuple	tuple;
+	Var		   *var;
+	char	   *resname;
+
+	if (attnum == InvalidAttrNumber)
+	{
+		ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr);
+
+		r->arg = (Expr *) makeVar((Index) 1,
+								  InvalidAttrNumber,
+								  get_rel_type_id(relid),
+								  -1,
+								  InvalidOid,
+								  0);
+		r->resulttype = get_rel_type_id(relid_head);
+		r->convertformat = COERCE_IMPLICIT_CAST;
+		r->location = -1;
+
+		return makeTargetEntry((Expr *) r, -1, get_rel_name(relid), false);
+	}
+
+	tuple = SearchSysCache2(ATTNUM,
+							ObjectIdGetDatum(relid),
+							Int16GetDatum(attnum));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+			 attnum, relid);
+	attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+	var = makeVar((Index) 1,
+				  attform->attnum,
+				  attform->atttypid,
+				  attform->atttypmod,
+				  InvalidOid,
+				  0);
+	resname = pstrdup(NameStr(attform->attname));
+
+	ReleaseSysCache(tuple);
+
+	return makeTargetEntry((Expr *)var, -1, resname, false);
+}
+
+/*
+ * make_pseudo_subquery
+ *
+ * This routine makes a sub-query that references the target relation
+ * with given row-level security policy. This sub-query shall have
+ * security_barrier attribute to prevent unexpected push-down.
+ */
+static Query *
+make_pseudo_subquery(Oid relid_head,
+					 Oid relid,
+					 Node *qual,
+					 int flags,
+					 List *targetList,
+					 RangeTblEntry *rte,
+					 RowMarkClause *rclause)
+{
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *subrtr;
+	List		   *colnameList = NIL;
+	ListCell	   *cell;
+
+	subqry = makeNode(Query);
+	subqry->commandType = CMD_SELECT;
+	subqry->querySource = QSRC_ROW_LEVEL_SECURITY;
+
+	subrte = makeNode(RangeTblEntry);
+	subrte->rtekind = RTE_RELATION;
+	subrte->relid = relid;
+	subrte->relkind = get_rel_relkind(relid);
+	subrte->inFromCl = true;
+	subrte->requiredPerms = rte->requiredPerms;
+	subrte->selectedCols = NULL;
+	subrte->modifiedCols = NULL;
+
+	foreach (cell, targetList)
+	{
+		TargetEntry	   *oldtle = lfirst(cell);
+		TargetEntry	   *subtle;
+		AttrNumber		attnum;
+
+		if (IsA(oldtle->expr, ConvertRowtypeExpr))
+			attnum = InvalidAttrNumber;
+		else
+		{
+			Assert(IsA(oldtle->expr, Var));
+
+			attnum = get_attnum(relid, oldtle->resname);
+			if (attnum == InvalidAttrNumber)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+					errmsg("column \"%s\" of relation \"%s\" does not exist",
+								oldtle->resname, get_rel_name(relid))));
+		}
+		subtle = make_pseudo_column(relid_head, relid, attnum);
+		subtle->resno = oldtle->resno;
+		subqry->targetList = lappend(subqry->targetList, subtle);
+
+		colnameList = lappend(colnameList,
+							  makeString(pstrdup(subtle->resname)));
+		attnum -= FirstLowInvalidHeapAttributeNumber;
+		subrte->selectedCols = bms_add_member(rte->selectedCols, attnum);
+	}
+	subrte->eref = makeAlias(get_rel_name(relid), colnameList);
+
+	if (flags & RLS_FLAG_HAS_SUBLINKS)
+		subqry->hasSubLinks = true;
+
+	subqry->rtable = list_make1(subrte);
+
+	subrtr = makeNode(RangeTblRef);
+	subrtr->rtindex = 1;
+	subqry->jointree = makeFromExpr(list_make1(subrtr), qual);
+
+	if (rclause)
+	{
+		RowMarkClause  *rclause_sub;
+
+		rclause_sub = copyObject(rclause);
+		rclause_sub->rti = 1;
+
+		subqry->rowMarks = list_make1(rclause);
+		subqry->hasForUpdate = true;
+	}
+	return subqry;
+}
+
+/*
+ * expand_rtentry_with_policy
+ *
+ * This routine expand reference to the given RangeTblEntry by a sub-query
+ * which simply references the target relation with the qualifier of row-
+ * level security policy.
+ */
+static void
+expand_rtentry_with_policy(PlannerInfo *root,
+						   RangeTblEntry *rte,
+						   Index rtindex,
+						   List *rls_relids,
+						   List *rls_quals,
+						   List *rls_flags)
+{
+	Query		   *parse = root->parse;
+	Oid				relid_head;
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	List		   *targetList;
+	List		   *colnameList;
+	Bitmapset	   *attr_used;
+	AttrNumber		attnum;
+	RowMarkClause  *rclause;
+	ListCell	   *cell1;
+	ListCell	   *cell2;
+	ListCell	   *cell3;
+	ListCell	   *lc;
+
+	Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+	Assert(list_length(rls_relids) == list_length(rls_quals));
+	Assert(list_length(rls_relids) == list_length(rls_flags));
+	Assert(list_length(rls_relids) > 0);
+
+	/*
+	 * Construct a target-entry list
+	 */
+	targetList = NIL;
+	colnameList = NIL;
+	relid_head = linitial_oid(rls_relids);
+	attr_used = bms_union(rte->selectedCols,
+						  rte->modifiedCols);
+	while ((attnum = bms_first_member(attr_used)) >= 0)
+	{
+		attnum += FirstLowInvalidHeapAttributeNumber;
+
+		subtle = make_pseudo_column(relid_head, relid_head, attnum);
+		subtle->resno = list_length(targetList) + 1;
+
+		targetList = lappend(targetList, subtle);
+		colnameList = lappend(colnameList,
+							  makeString(pstrdup(subtle->resname)));
+	}
+	bms_free(attr_used);
+
+	/*
+	 * Push-down row-level lock of the target relation, since sub-query
+	 * does not support FOR SHARE/FOR UPDATE locks being assigned.
+	 */
+	rclause = NULL;
+	foreach (lc, parse->rowMarks)
+	{
+		if (((RowMarkClause *) lfirst(lc))->rti == rtindex)
+		{
+			rclause = lfirst(lc);
+			parse->rowMarks = list_delete(parse->rowMarks, rclause);
+			break;
+		}
+	}
+
+	/*
+	 * Construct sub-query structures
+	 */
+	forthree (cell1, rls_relids, cell2, rls_quals, cell3, rls_flags)
+	{
+		Oid			relid = lfirst_oid(cell1);
+		Node	   *qual = lfirst(cell2);
+		int			flags = lfirst_int(cell3);
+
+		subqry = make_pseudo_subquery(relid_head, relid, qual, flags,
+									  targetList, rte, rclause);
+		if (cell1 == list_head(rls_relids))
+		{
+			Assert(rte->relid == relid);
+			Assert(rte->inh == true || list_length(rls_relids) == 1);
+
+			rte->relid = InvalidOid;
+			rte->rtekind = RTE_SUBQUERY;
+			rte->subquery = subqry;
+			rte->security_barrier = true;
+
+			/* no permission checks are needed to subquery itself */
+			rte->requiredPerms = 0;
+			rte->checkAsUser = InvalidOid;
+			rte->selectedCols = NULL;
+			rte->modifiedCols = NULL;
+
+			rte->alias = NULL;
+			rte->eref = makeAlias(get_rel_name(relid),
+								  copyObject(colnameList));
+			if (list_length(rls_relids) == 1)
+				rte->inh = false;
+		}
+
+		/*
+		 * RTE's for child relations and AppendRelInfo in case when
+		 * the target relation has its children.
+		 */
+		if (list_length(rls_relids) > 1)
+		{
+			AppendRelInfo  *apinfo;
+			AttrNumber		child_rtindex;
+			ListCell	   *l;
+
+			/*
+			 * XXX - Set up RangeTblEntry for the child relation.
+			 * Note that it does not need to have security_barrier
+			 * attribute, if no row-level security policy is
+			 * configured on.
+			 */
+			subrte = makeNode(RangeTblEntry);
+			subrte->rtekind = RTE_SUBQUERY;
+			subrte->subquery = subqry;
+			if (qual)
+				subrte->security_barrier = true;
+			subrte->alias = NULL;
+			subrte->eref = makeAlias(get_rel_name(relid),
+									 copyObject(colnameList));
+			parse->rtable = lappend(parse->rtable, subrte);
+			child_rtindex = list_length(parse->rtable);
+
+			/*
+			 * reference to inherited children performs as if simple
+			 * UNION ALL operation, so add AppendRelInfo here.
+			 */
+			apinfo = makeNode(AppendRelInfo);
+			apinfo->parent_relid = rtindex;
+			apinfo->child_relid = child_rtindex;
+			foreach (l, targetList)
+			{
+				Var	   *trans_var
+					= makeVarFromTargetEntry(child_rtindex, lfirst(l));
+				apinfo->translated_vars
+					= lappend(apinfo->translated_vars, trans_var);
+			}
+			root->append_rel_list = lappend(root->append_rel_list, apinfo);
+		}
+	}
+}
+
+/*
+ * applyRowLevelSecurity
+ *
+ * It tries to apply row-level security policy of the relation.
+ * If and when a particular policy is configured on the referenced
+ * relation, it shall be replaced by a sub-query with security-barrier flag;
+ * that references the relation with row-level security policy.
+ * In the result, all users can see is rows of the relation that satisfies
+ * the condition supplied as security policy.
+ */
+void
+applyRowLevelSecurity(PlannerInfo *root)
+{
+	Query	   *parse = root->parse;
+	ListCell   *cell;
+	Index		rtindex;
+	bool		has_rowlevel_sec = false;
+
+	/* mode checks */
+	if (rowlevel_security_mode == RowLevelSecModeDisabled)
+		return;
+
+	/*
+	 * No need to apply row-level security on sub-query being originated
+	 * from regular relation with RLS policy any more.
+	 */
+	if (parse->querySource == QSRC_ROW_LEVEL_SECURITY)
+		return;
+
+	rtindex = 0;
+	foreach (cell, parse->rtable)
+	{
+		RangeTblEntry  *rte = lfirst(cell);
+		List		   *rls_relids;
+		List		   *rls_quals;
+		List		   *rls_flags;
+
+		rtindex++;
+
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		/*
+		 * XXX - In this revision, we have no support for UPDATE / DELETE
+		 * statement, so simply skip it.
+		 */
+		if (rtindex == parse->resultRelation)
+			continue;
+
+		/*
+		 * In case when a row-level security policy was configured on
+		 * the table referenced by this RangeTblEntry or its children,
+		 * it shall be rewritten to sub-query with this policy.
+		 */
+		if (pull_rowlevel_security_policy(root, rte, rtindex,
+										  &rls_relids, &rls_quals, &rls_flags))
+		{
+			expand_rtentry_with_policy(root, rte, rtindex,
+									   rls_relids, rls_quals, rls_flags);
+			has_rowlevel_sec = true;
+		}
+	}
+
+	/*
+	 * Post case handling if one or more relation was replaced to sub-query.
+	 */
+	if (has_rowlevel_sec)
+	{
+		PlanInvalItem  *pi_item;
+
+		/*
+		 * plan should be invalidated if and when userid was changed
+		 * on the executor stage, from planner stage.
+		 */
+		Assert(!OidIsValid(root->glob->planUserId) ||
+			   root->glob->planUserId == GetUserId());
+		root->glob->planUserId = GetUserId();
+
+		pi_item = makeNode(PlanInvalItem);
+		pi_item->cacheId = AUTHOID;
+		pi_item->hashValue
+			= GetSysCacheHashValue1(AUTHOID,
+									ObjectIdGetDatum(root->glob->planUserId));
+		root->glob->invalItems = lappend(root->glob->invalItems, pi_item);
+
+		/*
+		 * Since the relation with RLS policy was replaced by a sub-query,
+		 * thus resource number to reference a particular column can be
+		 * also moditifed. If we applied RLS policy on one or more relations,
+		 * varattno of Var node that has referenced the rewritten relation
+		 * needs to be fixed up.
+		 */
+		fixup_varattnos(root);
+	}
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5894cb0..0a86ce8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2068,6 +2068,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 d1d835b..87423d8 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/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 97e68b1..19a2255 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -43,6 +43,7 @@
 #include "parser/parse_coerce.h"
 #include "parser/parse_relation.h"
 #include "miscadmin.h"
+#include "optimizer/rowlevelsec.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -2999,6 +3000,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
+	RowLevelSecMode	save_rls_mode;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -3081,6 +3083,15 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
 						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
 
+	/*
+	 * Disabled row-level security in case when foreign-key relation is
+	 * queried to check existence of tupls that references the tuple to
+	 * be modified on the primary-key side.
+	 */
+	save_rls_mode = getRowLevelSecurityMode();
+	if (source_is_pk)
+		setRowLevelSecurityMode(RowLevelSecModeDisabled);
+
 	/* Finally we can run the query. */
 	spi_result = SPI_execute_snapshot(qplan,
 									  vals, nulls,
@@ -3090,6 +3101,9 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	/* Restore UID and security context */
 	SetUserIdAndSecContext(save_userid, save_sec_context);
 
+	/* Restore row-level security performing mode */
+	setRowLevelSecurityMode(save_rls_mode);
+
 	/* Check result */
 	if (spi_result < 0)
 		elog(ERROR, "SPI_execute_snapshot returned %d", spi_result);
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 0cdd30d..f37586e 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);
 
@@ -4174,6 +4188,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 cdbed20..e781dae 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3867,6 +3867,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");
@@ -3891,7 +3892,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
@@ -3911,7 +3950,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 "
@@ -3947,7 +3987,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 "
@@ -3982,7 +4023,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 "
@@ -4017,7 +4059,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 "
@@ -4053,7 +4096,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 "
@@ -4088,7 +4132,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 "
@@ -4119,7 +4164,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",
@@ -4145,7 +4191,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",
@@ -4181,7 +4228,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",
@@ -4229,6 +4277,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)
 	{
@@ -4271,6 +4320,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))
 		{
@@ -12803,6 +12856,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 2aa2060..5485101 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -256,6 +256,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..5a64d1b
--- /dev/null
+++ b/src/include/catalog/pg_rowlevelsec.h
@@ -0,0 +1,60 @@
+/*
+ * 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	ResetRowLevelSecurity(Relation relation);
+extern void	RemoveRowLevelSecurityById(Oid relationId);
+
+#endif  /* PG_ROWLEVELSEC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 19178b5..2f9de9f 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 */
@@ -1231,6 +1232,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 af9425a..73c6ff1 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..9afe00a
--- /dev/null
+++ b/src/include/optimizer/rowlevelsec.h
@@ -0,0 +1,31 @@
+/* -------------------------------------------------------------------------
+ *
+ * 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/relation.h"
+#include "utils/rel.h"
+
+typedef List *(*rowlevel_security_hook_type)(PlannerInfo *root,
+											 Relation relation);
+extern PGDLLIMPORT rowlevel_security_hook_type rowlevel_security_hook;
+
+typedef enum {
+	RowLevelSecModeEnabled,
+	RowLevelSecModeDisabled,
+} RowLevelSecMode;
+
+extern RowLevelSecMode getRowLevelSecurityMode(void);
+extern void setRowLevelSecurityMode(RowLevelSecMode new_mode);
+
+extern void	applyRowLevelSecurity(PlannerInfo *root);
+
+#endif	/* ROWLEVELSEC_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3bb35f..d13ef32 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/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..5673e7a
--- /dev/null
+++ b/src/test/regress/expected/rowlevelsec.out
@@ -0,0 +1,753 @@
+--
+-- 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;
+CREATE TABLE account (
+	   pguser       name primary key,
+	   slevel		int,
+	   scategory	bit(4)
+);
+GRANT SELECT,REFERENCES ON TABLE account TO public;
+INSERT INTO account VALUES
+  ('rls_regress_user1', 1, B'0011'),
+  ('rls_regress_user2', 2, B'0110'),
+  ('rls_regress_user3', 0, B'0101');
+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;
+-- Creation of Test Data
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE document (
+       did           int primary key,
+       dlevel        int,
+       dcategory     bit(4),
+	   dtitle        text
+);
+GRANT ALL ON document TO public;
+INSERT INTO document VALUES
+  ( 10, 0, B'0000', 'this document is unclassified, category(----)'),
+  ( 20, 0, B'0001', 'this document is unclassified, category(---A)'),
+  ( 30, 0, B'0010', 'this document is unclassified, category(--B-)'),
+  ( 40, 0, B'0011', 'this document is unclassified, category(--BA)'),
+  ( 50, 0, B'0100', 'this document is unclassified, category(-C--)'),
+  ( 60, 0, B'0101', 'this document is unclassified, category(-C-A)'),
+  ( 70, 0, B'0110', 'this document is unclassified, category(-CB-)'),
+  ( 80, 0, B'0111', 'this document is unclassified, category(-CBA)'),
+  ( 90, 1, B'0000', 'this document is classified, category(----)'),
+  (100, 1, B'0001', 'this document is classified, category(---A)'),
+  (110, 1, B'0010', 'this document is classified, category(--B-)'),
+  (120, 1, B'0011', 'this document is classified, category(--BA)'),
+  (130, 1, B'0100', 'this document is classified, category(-C--)'),
+  (140, 1, B'0101', 'this document is classified, category(-C-A)'),
+  (150, 1, B'0110', 'this document is classified, category(-CB-)'),
+  (160, 1, B'0111', 'this document is classified, category(-CBA)'),
+  (170, 2, B'0000', 'this document is secret, category(----)'),
+  (180, 2, B'0001', 'this document is secret, category(---A)'),
+  (190, 2, B'0010', 'this document is secret, category(--B-)'),
+  (200, 2, B'0011', 'this document is secret, category(--BA)'),
+  (210, 2, B'0100', 'this document is secret, category(-C--)'),
+  (220, 2, B'0101', 'this document is secret, category(-C-A)'),
+  (230, 2, B'0110', 'this document is secret, category(-CB-)'),
+  (240, 2, B'0111', 'this document is secret, category(-CBA)');
+CREATE TABLE browse (
+  pguser       name references account(pguser),
+  did          int  references document(did),
+  ymd          date
+);
+GRANT ALL ON browse TO public;
+INSERT INTO browse VALUES
+  ('rls_regress_user1',  20, '2012-07-01'),
+  ('rls_regress_user1',  40, '2012-07-02'),
+  ('rls_regress_user1', 110, '2012-07-03'),
+  ('rls_regress_user2',  30, '2012-07-04'),
+  ('rls_regress_user2',  50, '2012-07-05'),
+  ('rls_regress_user2',  90, '2012-07-06'),
+  ('rls_regress_user2', 130, '2012-07-07'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 190, '2012-07-09'),
+  ('rls_regress_user2', 210, '2012-07-10'),
+  ('rls_regress_user3',  10, '2012-07-11'),
+  ('rls_regress_user3',  50, '2012-07-12');
+-- user's sensitivity level must higher than document's level
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dlevel <= (SELECT slevel FROM account WHERE pguser = current_user));
+ALTER TABLE browse SET ROW LEVEL SECURITY
+  (pguser = current_user);
+-- viewpoint from rls_regress_user1
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-C-A)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is unclassified, category(-CBA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-C-A)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(-CBA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  60 |      0 | 0101      | this document is unclassified, category(-C-A)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  80 |      0 | 0111      | this document is unclassified, category(-CBA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 140 |      1 | 0101      | this document is classified, category(-C-A)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 160 |      1 | 0111      | this document is classified, category(-CBA)
+(16 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user1,20,07-01-2012)
+NOTICE:  f_leak => (rls_regress_user1,40,07-02-2012)
+NOTICE:  f_leak => (rls_regress_user1,110,07-03-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+(3 rows)
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-C-A)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is unclassified, category(-CBA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-C-A)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(-CBA)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(---A)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(--BA)
+NOTICE:  f_leak => this document is secret, category(-C--)
+NOTICE:  f_leak => this document is secret, category(-C-A)
+NOTICE:  f_leak => this document is secret, category(-CB-)
+NOTICE:  f_leak => this document is secret, category(-CBA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  60 |      0 | 0101      | this document is unclassified, category(-C-A)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  80 |      0 | 0111      | this document is unclassified, category(-CBA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 140 |      1 | 0101      | this document is classified, category(-C-A)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 160 |      1 | 0111      | this document is classified, category(-CBA)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 180 |      2 | 0001      | this document is secret, category(---A)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 200 |      2 | 0011      | this document is secret, category(--BA)
+ 210 |      2 | 0100      | this document is secret, category(-C--)
+ 220 |      2 | 0101      | this document is secret, category(-C-A)
+ 230 |      2 | 0110      | this document is secret, category(-CB-)
+ 240 |      2 | 0111      | this document is secret, category(-CBA)
+(24 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user2,30,07-04-2012)
+NOTICE:  f_leak => (rls_regress_user2,50,07-05-2012)
+NOTICE:  f_leak => (rls_regress_user2,90,07-06-2012)
+NOTICE:  f_leak => (rls_regress_user2,130,07-07-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,190,07-09-2012)
+NOTICE:  f_leak => (rls_regress_user2,210,07-10-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  30 |      0 | 0010      | this document is unclassified, category(--B-) | rls_regress_user2 | 07-04-2012
+  50 |      0 | 0100      | this document is unclassified, category(-C--) | rls_regress_user2 | 07-05-2012
+  90 |      1 | 0000      | this document is classified, category(----)   | rls_regress_user2 | 07-06-2012
+ 130 |      1 | 0100      | this document is classified, category(-C--)   | rls_regress_user2 | 07-07-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 190 |      2 | 0010      | this document is secret, category(--B-)       | rls_regress_user2 | 07-09-2012
+ 210 |      2 | 0100      | this document is secret, category(-C--)       | rls_regress_user2 | 07-10-2012
+(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
+         Filter: (dlevel <= $0)
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account
+                 Index Cond: (pguser = "current_user"())
+(7 rows)
+
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Hash Join  (cost=30.34..57.95 rows=2 width=117)
+   Hash Cond: (rls_regress_schema.document.did = browse.did)
+   ->  Seq Scan on document  (cost=8.27..31.15 rows=343 width=49)
+         Filter: (dlevel <= $0)
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account  (cost=0.00..8.27 rows=1 width=4)
+                 Index Cond: (pguser = "current_user"())
+   ->  Hash  (cost=22.06..22.06 rows=1 width=72)
+         ->  Subquery Scan on browse  (cost=0.00..22.06 rows=1 width=72)
+               Filter: f_leak((browse.browse)::text)
+               ->  Seq Scan on browse  (cost=0.00..22.00 rows=4 width=168)
+                     Filter: (pguser = "current_user"())
+(12 rows)
+
+-- change row-level security policy
+ALTER TABLE document RESET ROW LEVEL SECURITY;      -- failed
+ERROR:  must be owner of relation document
+ALTER TABLE document SET ROW LEVEL SECURITY (true); -- failed
+ERROR:  must be owner of relation document
+SET SESSION AUTHORIZATION rls_regress_user0;
+-- switch to category based control from level based
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dcategory & (SELECT ~scategory FROM account WHERE pguser = current_user) = B'0000');
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(---A)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(--BA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 180 |      2 | 0001      | this document is secret, category(---A)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 200 |      2 | 0011      | this document is secret, category(--BA)
+(12 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user1,20,07-01-2012)
+NOTICE:  f_leak => (rls_regress_user1,40,07-02-2012)
+NOTICE:  f_leak => (rls_regress_user1,110,07-03-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+(3 rows)
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(-C--)
+NOTICE:  f_leak => this document is secret, category(-CB-)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 210 |      2 | 0100      | this document is secret, category(-C--)
+ 230 |      2 | 0110      | this document is secret, category(-CB-)
+(12 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user2,30,07-04-2012)
+NOTICE:  f_leak => (rls_regress_user2,50,07-05-2012)
+NOTICE:  f_leak => (rls_regress_user2,90,07-06-2012)
+NOTICE:  f_leak => (rls_regress_user2,130,07-07-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,190,07-09-2012)
+NOTICE:  f_leak => (rls_regress_user2,210,07-10-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  30 |      0 | 0010      | this document is unclassified, category(--B-) | rls_regress_user2 | 07-04-2012
+  50 |      0 | 0100      | this document is unclassified, category(-C--) | rls_regress_user2 | 07-05-2012
+  90 |      1 | 0000      | this document is classified, category(----)   | rls_regress_user2 | 07-06-2012
+ 130 |      1 | 0100      | this document is classified, category(-C--)   | rls_regress_user2 | 07-07-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 190 |      2 | 0010      | this document is secret, category(--B-)       | rls_regress_user2 | 07-09-2012
+ 210 |      2 | 0100      | this document is secret, category(-C--)       | rls_regress_user2 | 07-10-2012
+(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
+         Filter: ((dcategory & $0) = B'0000'::"bit")
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account
+                 Index Cond: (pguser = "current_user"())
+(7 rows)
+
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Nested Loop  (cost=8.27..55.90 rows=1 width=117)
+   Join Filter: (rls_regress_schema.document.did = browse.did)
+   ->  Subquery Scan on browse  (cost=0.00..22.06 rows=1 width=72)
+         Filter: f_leak((browse.browse)::text)
+         ->  Seq Scan on browse  (cost=0.00..22.00 rows=4 width=168)
+               Filter: (pguser = "current_user"())
+   ->  Seq Scan on document  (cost=8.27..33.72 rows=5 width=49)
+         Filter: ((dcategory & $0) = B'0000'::"bit")
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account  (cost=0.00..8.27 rows=1 width=9)
+                 Index Cond: (pguser = "current_user"())
+(11 rows)
+
+-- Failed to update PK row referenced by invisible FK
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document NATURAL LEFT JOIN browse;
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  10 |      0 | 0000      | this document is unclassified, category(----) |                   | 
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  30 |      0 | 0010      | this document is unclassified, category(--B-) |                   | 
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+  90 |      1 | 0000      | this document is classified, category(----)   |                   | 
+ 100 |      1 | 0001      | this document is classified, category(---A)   |                   | 
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+ 120 |      1 | 0011      | this document is classified, category(--BA)   |                   | 
+ 170 |      2 | 0000      | this document is secret, category(----)       |                   | 
+ 180 |      2 | 0001      | this document is secret, category(---A)       |                   | 
+ 190 |      2 | 0010      | this document is secret, category(--B-)       |                   | 
+ 200 |      2 | 0011      | this document is secret, category(--BA)       |                   | 
+(12 rows)
+
+DELETE FROM document WHERE did = 30;			-- failed
+ERROR:  update or delete on table "document" violates foreign key constraint "browse_did_fkey" on table "browse"
+DETAIL:  Key (did)=(30) is still referenced from table "browse".
+UPDATE document SET did = 9999 WHERE did = 90;	-- failed
+ERROR:  update or delete on table "document" violates foreign key constraint "browse_did_fkey" on table "browse"
+DETAIL:  Key (did)=(90) is still referenced from table "browse".
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE t1 (a int, b text, c text) WITH OIDS;
+ALTER TABLE t1 DROP COLUMN b;    -- just a disturbing factor
+GRANT ALL ON t1 TO public;
+COPY t1 FROM stdin WITH (oids);
+CREATE TABLE t2 (d float) INHERITS (t1);
+COPY t2 FROM stdin WITH (oids);
+CREATE TABLE t3 (e text) INHERITS (t1);
+COPY t3 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 |  c  
+---+-----
+ 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
+         ->  Seq Scan on t1
+               Filter: ((a % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(7 rows)
+
+SELECT * FROM t1 WHERE f_leak(c);
+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 |  c  
+---+-----
+ 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(c);
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.c)
+               ->  Seq Scan on t1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               Filter: f_leak(t2.c)
+               ->  Seq Scan on t2
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+               Filter: f_leak(c)
+(12 rows)
+
+-- reference to system column
+SELECT oid, * FROM t1;
+ oid | a |  c  
+-----+---+-----
+ 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
+         ->  Seq Scan on t1
+               Filter: ((a % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(7 rows)
+
+-- reference to whole-row reference
+SELECT *,t1 FROM t1;
+ a |  c  |   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
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2
+                     Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  Seq Scan on t3
+(10 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ a |  c  
+---+-----
+ 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                    
+-------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  LockRows
+                     ->  Seq Scan on t1
+                           Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  LockRows
+                     ->  Seq Scan on t2
+                           Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  LockRows
+                     ->  Seq Scan on t3
+(13 rows)
+
+SELECT * FROM t1 WHERE f_leak(c) 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 |  c  
+---+-----
+ 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(c) FOR SHARE;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.c)
+               ->  LockRows
+                     ->  Seq Scan on t1
+                           Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               Filter: f_leak(t2.c)
+               ->  LockRows
+                     ->  Seq Scan on t2
+                           Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  LockRows
+                     ->  Seq Scan on t3
+                           Filter: f_leak(c)
+(16 rows)
+
+-- Now COPY TO command does not support RLS
+-- COPY t1 TO stdin;
+-- prepared statement with rls_regress_user0 privilege
+PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;
+EXECUTE p1(2);
+ a |  c  
+---+-----
+ 2 | bbb
+ 1 | abc
+ 1 | xxx
+ 2 | yyy
+(4 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+                     QUERY PLAN                     
+----------------------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a <= 2) AND ((a % 2) = 0))
+         ->  Seq Scan on t2
+               Filter: ((a <= 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a <= 2)
+(8 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1 WHERE f_leak(c);
+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 |  c  
+---+-----
+ 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(c);
+           QUERY PLAN            
+---------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: f_leak(c)
+         ->  Seq Scan on t2 t1
+               Filter: f_leak(c)
+         ->  Seq Scan on t3 t1
+               Filter: f_leak(c)
+(8 rows)
+
+-- plan cache should be invalidated
+EXECUTE p1(2);
+ a |  c  
+---+-----
+ 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 t1
+               Filter: (a <= 2)
+         ->  Seq Scan on t3 t1
+               Filter: (a <= 2)
+(8 rows)
+
+PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;
+EXECUTE p2(2);
+ a |  c  
+---+-----
+ 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 t1
+               Filter: (a = 2)
+         ->  Seq Scan on t3 t1
+               Filter: (a = 2)
+(8 rows)
+
+-- also, case when privilege switch from superuser
+SET SESSION AUTHORIZATION rls_regress_user0;
+EXECUTE p2(2);
+ a |  c  
+---+-----
+ 2 | bbb
+ 2 | yyy
+(2 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+                    QUERY PLAN                     
+---------------------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a = 2) AND ((a % 2) = 0))
+         ->  Seq Scan on t2
+               Filter: ((a = 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a = 2)
+(8 rows)
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rls_regress_schema CASCADE;
+NOTICE:  drop cascades to 7 other objects
+DETAIL:  drop cascades to table account
+drop cascades to function f_leak(text)
+drop cascades to table document
+drop cascades to table browse
+drop cascades to table t1
+drop cascades to table t2
+drop cascades to table t3
+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 ac29194..f0541c0 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
 
 test: misc
 # rules cannot run concurrently with any test that creates a view
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 8576a7f..e789395 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..a4d1ccd
--- /dev/null
+++ b/src/test/regress/sql/rowlevelsec.sql
@@ -0,0 +1,237 @@
+--
+-- 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;
+
+CREATE TABLE account (
+	   pguser       name primary key,
+	   slevel		int,
+	   scategory	bit(4)
+);
+GRANT SELECT,REFERENCES ON TABLE account TO public;
+INSERT INTO account VALUES
+  ('rls_regress_user1', 1, B'0011'),
+  ('rls_regress_user2', 2, B'0110'),
+  ('rls_regress_user3', 0, B'0101');
+
+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;
+
+-- Creation of Test Data
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+CREATE TABLE document (
+       did           int primary key,
+       dlevel        int,
+       dcategory     bit(4),
+	   dtitle        text
+);
+GRANT ALL ON document TO public;
+INSERT INTO document VALUES
+  ( 10, 0, B'0000', 'this document is unclassified, category(----)'),
+  ( 20, 0, B'0001', 'this document is unclassified, category(---A)'),
+  ( 30, 0, B'0010', 'this document is unclassified, category(--B-)'),
+  ( 40, 0, B'0011', 'this document is unclassified, category(--BA)'),
+  ( 50, 0, B'0100', 'this document is unclassified, category(-C--)'),
+  ( 60, 0, B'0101', 'this document is unclassified, category(-C-A)'),
+  ( 70, 0, B'0110', 'this document is unclassified, category(-CB-)'),
+  ( 80, 0, B'0111', 'this document is unclassified, category(-CBA)'),
+  ( 90, 1, B'0000', 'this document is classified, category(----)'),
+  (100, 1, B'0001', 'this document is classified, category(---A)'),
+  (110, 1, B'0010', 'this document is classified, category(--B-)'),
+  (120, 1, B'0011', 'this document is classified, category(--BA)'),
+  (130, 1, B'0100', 'this document is classified, category(-C--)'),
+  (140, 1, B'0101', 'this document is classified, category(-C-A)'),
+  (150, 1, B'0110', 'this document is classified, category(-CB-)'),
+  (160, 1, B'0111', 'this document is classified, category(-CBA)'),
+  (170, 2, B'0000', 'this document is secret, category(----)'),
+  (180, 2, B'0001', 'this document is secret, category(---A)'),
+  (190, 2, B'0010', 'this document is secret, category(--B-)'),
+  (200, 2, B'0011', 'this document is secret, category(--BA)'),
+  (210, 2, B'0100', 'this document is secret, category(-C--)'),
+  (220, 2, B'0101', 'this document is secret, category(-C-A)'),
+  (230, 2, B'0110', 'this document is secret, category(-CB-)'),
+  (240, 2, B'0111', 'this document is secret, category(-CBA)');
+
+CREATE TABLE browse (
+  pguser       name references account(pguser),
+  did          int  references document(did),
+  ymd          date
+);
+GRANT ALL ON browse TO public;
+INSERT INTO browse VALUES
+  ('rls_regress_user1',  20, '2012-07-01'),
+  ('rls_regress_user1',  40, '2012-07-02'),
+  ('rls_regress_user1', 110, '2012-07-03'),
+  ('rls_regress_user2',  30, '2012-07-04'),
+  ('rls_regress_user2',  50, '2012-07-05'),
+  ('rls_regress_user2',  90, '2012-07-06'),
+  ('rls_regress_user2', 130, '2012-07-07'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 190, '2012-07-09'),
+  ('rls_regress_user2', 210, '2012-07-10'),
+  ('rls_regress_user3',  10, '2012-07-11'),
+  ('rls_regress_user3',  50, '2012-07-12');
+
+-- user's sensitivity level must higher than document's level
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dlevel <= (SELECT slevel FROM account WHERE pguser = current_user));
+ALTER TABLE browse SET ROW LEVEL SECURITY
+  (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 browse WHERE f_leak(browse::text);
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+EXPLAIN (costs off) SELECT * FROM document WHERE f_leak(dtitle);
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- change row-level security policy
+ALTER TABLE document RESET ROW LEVEL SECURITY;      -- failed
+ALTER TABLE document SET ROW LEVEL SECURITY (true); -- failed
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+-- switch to category based control from level based
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dcategory & (SELECT ~scategory FROM account WHERE pguser = current_user) = B'0000');
+
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+EXPLAIN (costs off) SELECT * FROM document WHERE f_leak(dtitle);
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- Failed to update PK row referenced by invisible FK
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document NATURAL LEFT JOIN browse;
+DELETE FROM document WHERE did = 30;			-- failed
+UPDATE document SET did = 9999 WHERE did = 90;	-- failed
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+CREATE TABLE t1 (a int, b text, c text) WITH OIDS;
+ALTER TABLE t1 DROP COLUMN b;    -- 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 (d 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 (e text) INHERITS (t1);
+COPY t3 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(c);
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c);
+
+-- 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(c) FOR SHARE;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c) FOR SHARE;
+
+-- Now COPY TO command does not support RLS
+-- COPY t1 TO stdin;
+
+-- 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(c);
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c);
+
+-- 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);
+
+--
+-- 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;