 doc/src/sgml/ref/create_view.sgml         |   24 ++++++++++++++++++++
 src/backend/access/common/reloptions.c    |   15 ++++++++++++-
 src/backend/commands/tablecmds.c          |    3 +-
 src/backend/commands/view.c               |   29 +++++++++++++++++++-----
 src/backend/nodes/copyfuncs.c             |    1 +
 src/backend/nodes/equalfuncs.c            |    1 +
 src/backend/nodes/outfuncs.c              |    1 +
 src/backend/nodes/readfuncs.c             |    1 +
 src/backend/parser/gram.y                 |   10 +++++---
 src/backend/rewrite/rewriteHandler.c      |    1 +
 src/backend/utils/cache/relcache.c        |    1 +
 src/bin/pg_dump/pg_dump.c                 |    6 +++-
 src/include/access/reloptions.h           |    3 +-
 src/include/nodes/parsenodes.h            |    3 ++
 src/include/utils/rel.h                   |   11 ++++++++-
 src/test/regress/expected/create_view.out |   34 ++++++++++++++++++++++++++++-
 src/test/regress/sql/create_view.sql      |   19 ++++++++++++++++
 17 files changed, 146 insertions(+), 17 deletions(-)

diff --git a/doc/src/sgml/ref/create_view.sgml b/doc/src/sgml/ref/create_view.sgml
index 417f8c3..504ba27 100644
--- a/doc/src/sgml/ref/create_view.sgml
+++ b/doc/src/sgml/ref/create_view.sgml
@@ -22,6 +22,7 @@ PostgreSQL documentation
  <refsynopsisdiv>
 <synopsis>
 CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
+    [ WITH ( parameter [= value] [, ... ] ) ]
     AS <replaceable class="PARAMETER">query</replaceable>
 </synopsis>
  </refsynopsisdiv>
@@ -99,6 +100,29 @@ CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW <replaceable class="PARAMETER">n
    </varlistentry>
 
    <varlistentry>
+    <term><literal>WITH (<replaceable class="parameter">parameter [= value]</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This clause allows to specify optional parameters for a view.
+     </para>
+     <para>
+      If <literal>security_barrier=TRUE</literal> is specified, this view
+      shall performs as security barrier that prevent unexpected information
+      leaks. It is a recommendable configuration when the view is defined
+      to apply row-level security, in spite of performance trade-off.
+     </para>
+     <para>
+      It is a commonly-used technique that using views to filter out
+      tuple to be invisible to particular users, however, please note
+      that here is a known-problem that allows malicious users to
+      reference invisible tuples using a function with side-effect
+      because of interaction with query optimization.
+      See <xref linkend="rules-privileges"> for more detailed scenario.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><replaceable class="parameter">query</replaceable></term>
     <listitem>
      <para>
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 4657425..57f3b46 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -66,6 +66,14 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	{
+		{
+			"security_barrier",
+			"Prevent information leaks using functions with side-effects",
+			RELOPT_KIND_VIEW
+		},
+		false
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -776,6 +784,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, Oid amoptions)
 	{
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
+		case RELKIND_VIEW:
 		case RELKIND_UNCATALOGED:
 			options = heap_reloptions(classForm->relkind, datum, false);
 			break;
@@ -1134,7 +1143,9 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		{"autovacuum_vacuum_scale_factor", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, vacuum_scale_factor)},
 		{"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL,
-		offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, analyze_scale_factor)}
+		offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, analyze_scale_factor)},
+		{"security_barrier", RELOPT_TYPE_BOOL,
+		 offsetof(StdRdOptions, security_barrier)},
 	};
 
 	options = parseRelOptions(reloptions, validate, kind, &numoptions);
@@ -1176,6 +1187,8 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
 			return (bytea *) rdopts;
 		case RELKIND_RELATION:
 			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
+		case RELKIND_VIEW:
+			return default_reloptions(reloptions, validate, RELOPT_KIND_VIEW);
 		default:
 			/* other relkinds are not supported */
 			return NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cfc685b..8e67330 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2926,7 +2926,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			break;
 		case AT_SetRelOptions:	/* SET (...) */
 		case AT_ResetRelOptions:		/* RESET (...) */
-			ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX);
+			ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX | ATT_VIEW);
 			/* This command never recurses */
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
@@ -7881,6 +7881,7 @@ ATExecSetRelOptions(Relation rel, List *defList, bool isReset, LOCKMODE lockmode
 	{
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
+		case RELKIND_VIEW:
 			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
 			break;
 		case RELKIND_INDEX:
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index be681e3..92102e7 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -97,7 +97,8 @@ isViewOnTempTable_walker(Node *node, void *context)
  *---------------------------------------------------------------------
  */
 static Oid
-DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
+DefineVirtualRelation(const RangeVar *relation, List *tlist,
+					  bool replace, List *options)
 {
 	Oid			viewOid,
 				namespaceId;
@@ -167,6 +168,8 @@ DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
 	{
 		Relation	rel;
 		TupleDesc	descriptor;
+		List	   *atcmds = NIL;
+		AlterTableCmd *atcmd;
 
 		/*
 		 * Yes.  Get exclusive lock on the existing view ...
@@ -211,14 +214,11 @@ DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
 		 */
 		if (list_length(attrList) > rel->rd_att->natts)
 		{
-			List	   *atcmds = NIL;
 			ListCell   *c;
 			int			skip = rel->rd_att->natts;
 
 			foreach(c, attrList)
 			{
-				AlterTableCmd *atcmd;
-
 				if (skip > 0)
 				{
 					skip--;
@@ -229,10 +229,24 @@ DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
 				atcmd->def = (Node *) lfirst(c);
 				atcmds = lappend(atcmds, atcmd);
 			}
-			AlterTableInternal(viewOid, atcmds, true);
 		}
 
 		/*
+		 * If optional parameters are specified, we must set options
+		 * using ALTER TABLE SET OPTION internally.
+		 */
+		if (list_length(options) > 0)
+		{
+			atcmd = makeNode(AlterTableCmd);
+			atcmd->subtype = AT_SetRelOptions;
+			atcmd->def = options;
+
+			atcmds = lappend(atcmds, atcmd);
+		}
+		if (atcmds != NIL)
+			AlterTableInternal(viewOid, atcmds, true);
+
+		/*
 		 * Seems okay, so return the OID of the pre-existing view.
 		 */
 		relation_close(rel, NoLock);	/* keep the lock! */
@@ -256,6 +270,9 @@ DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
 		createStmt->tablespacename = NULL;
 		createStmt->if_not_exists = false;
 
+		if (options != NIL)
+			createStmt->options = list_concat(createStmt->options, options);
+
 		/*
 		 * finally create the relation (this will error out if there's an
 		 * existing view, so we don't need more code to complain if "replace"
@@ -510,7 +527,7 @@ DefineView(ViewStmt *stmt, const char *queryString)
 	 * aborted.
 	 */
 	viewOid = DefineVirtualRelation(view, viewParse->targetList,
-									stmt->replace);
+									stmt->replace, stmt->options);
 
 	/*
 	 * The relation we have just created is not visible to any other commands
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c9133dd..4fb60a1 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1944,6 +1944,7 @@ _copyRangeTblEntry(RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relid);
 	COPY_SCALAR_FIELD(relkind);
 	COPY_NODE_FIELD(subquery);
+	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
 	COPY_NODE_FIELD(joinaliasvars);
 	COPY_NODE_FIELD(funcexpr);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3a0267c..d201f22 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2303,6 +2303,7 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relid);
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_NODE_FIELD(subquery);
+	COMPARE_SCALAR_FIELD(security_barrier);
 	COMPARE_SCALAR_FIELD(jointype);
 	COMPARE_NODE_FIELD(joinaliasvars);
 	COMPARE_NODE_FIELD(funcexpr);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 681f5f8..04a8760 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2311,6 +2311,7 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
+			WRITE_BOOL_FIELD(security_barrier);
 			break;
 		case RTE_JOIN:
 			WRITE_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 29a0e8f..098f3c3 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1191,6 +1191,7 @@ _readRangeTblEntry(void)
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
+			READ_BOOL_FIELD(security_barrier);
 			break;
 		case RTE_JOIN:
 			READ_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7226032..517cdf3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -7266,26 +7266,28 @@ transaction_mode_list_or_empty:
  *
  *****************************************************************************/
 
-ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list
+ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list opt_reloptions
 				AS SelectStmt opt_check_option
 				{
 					ViewStmt *n = makeNode(ViewStmt);
 					n->view = $4;
 					n->view->relpersistence = $2;
 					n->aliases = $5;
-					n->query = $7;
+					n->query = $8;
 					n->replace = false;
+					n->options = $6;
 					$$ = (Node *) n;
 				}
-		| CREATE OR REPLACE OptTemp VIEW qualified_name opt_column_list
+		| CREATE OR REPLACE OptTemp VIEW qualified_name opt_column_list opt_reloptions
 				AS SelectStmt opt_check_option
 				{
 					ViewStmt *n = makeNode(ViewStmt);
 					n->view = $6;
 					n->view->relpersistence = $4;
 					n->aliases = $7;
-					n->query = $9;
+					n->query = $10;
 					n->replace = true;
+					n->options = $8;
 					$$ = (Node *) n;
 				}
 		;
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 6ef20a5..affe103 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1382,6 +1382,7 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->rtekind = RTE_SUBQUERY;
 	rte->relid = InvalidOid;
 	rte->subquery = rule_action;
+	rte->security_barrier = RelationIsSecurityView(relation);
 	rte->inh = false;			/* must not be set for a subquery */
 
 	/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 0b9d77a..4eb1ee8 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -377,6 +377,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 		case RELKIND_RELATION:
 		case RELKIND_TOASTVALUE:
 		case RELKIND_INDEX:
+		case RELKIND_VIEW:
 			break;
 		default:
 			return;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 9e69b0f..ab2b0cb 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12023,8 +12023,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (binary_upgrade)
 			binary_upgrade_set_pg_class_oids(q, tbinfo->dobj.catId.oid, false);
 
-		appendPQExpBuffer(q, "CREATE VIEW %s AS\n    %s\n",
-						  fmtId(tbinfo->dobj.name), viewdef);
+		appendPQExpBuffer(q, "CREATE VIEW %s", fmtId(tbinfo->dobj.name));
+		if (tbinfo->reloptions && strlen(tbinfo->reloptions) > 0)
+			appendPQExpBuffer(q, " WITH (%s)", tbinfo->reloptions);
+		appendPQExpBuffer(q, " AS\n    %s\n", viewdef);
 
 		appendPQExpBuffer(labelq, "VIEW %s",
 						  fmtId(tbinfo->dobj.name));
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index c7709cc..586236e 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -42,8 +42,9 @@ typedef enum relopt_kind
 	RELOPT_KIND_GIST = (1 << 5),
 	RELOPT_KIND_ATTRIBUTE = (1 << 6),
 	RELOPT_KIND_TABLESPACE = (1 << 7),
+	RELOPT_KIND_VIEW = (1 << 8),
 	/* if you add a new kind, make sure you update "last_default" too */
-	RELOPT_KIND_LAST_DEFAULT = RELOPT_KIND_TABLESPACE,
+	RELOPT_KIND_LAST_DEFAULT = RELOPT_KIND_VIEW,
 	/* some compilers treat enums as signed ints, so we can't use 1 << 31 */
 	RELOPT_KIND_MAX = (1 << 30)
 } relopt_kind;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 00c1269..4723c28 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -706,6 +706,8 @@ typedef struct RangeTblEntry
 	 */
 	Query	   *subquery;		/* the sub-query */
 
+	bool		security_barrier;	/* Was a security barrier view? */
+
 	/*
 	 * Fields valid for a join RTE (else NULL/zero):
 	 *
@@ -2339,6 +2341,7 @@ typedef struct ViewStmt
 	List	   *aliases;		/* target column names */
 	Node	   *query;			/* the SELECT query */
 	bool		replace;		/* replace an existing view? */
+	List	   *options;		/* options from WITH clause */
 } ViewStmt;
 
 /* ----------------------
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index e2c2fa9..e1272af 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -221,7 +221,7 @@ typedef struct RelationData
 
 /*
  * StdRdOptions
- *		Standard contents of rd_options for heaps and generic indexes.
+ *		Standard contents of rd_options for heaps, views and generic indexes.
  *
  * RelationGetFillFactor() and RelationGetTargetPageFreeSpace() can only
  * be applied to relations that use this format or a superset for
@@ -247,6 +247,7 @@ typedef struct StdRdOptions
 	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	int			fillfactor;		/* page fill factor in percent (0..100) */
 	AutoVacOpts autovacuum;		/* autovacuum-related options */
+	bool		security_barrier;	/* performs as security-barrier view */
 } StdRdOptions;
 
 #define HEAP_MIN_FILLFACTOR			10
@@ -275,6 +276,14 @@ typedef struct StdRdOptions
 	(BLCKSZ * (100 - RelationGetFillFactor(relation, defaultff)) / 100)
 
 /*
+ * RelationIsSecurityView
+ *             Returns whether the relation is security view, or not
+ */
+#define RelationIsSecurityView(relation)	\
+	((relation)->rd_options ?				\
+	 ((StdRdOptions *) (relation)->rd_options)->security_barrier : false)
+
+/*
  * RelationIsValid
  *		True iff relation descriptor is valid.
  */
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index f2c0685..7cc3000 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -239,6 +239,34 @@ And relnamespace IN (SELECT OID FROM pg_namespace WHERE nspname LIKE 'pg_temp%')
      1
 (1 row)
 
+--Should work correctly to leaky-view scenario
+CREATE TABLE lvtest1 (a int, b text);
+CREATE TABLE lvtest2 (x int, y text);
+INSERT INTO lvtest1 VALUES (10, 'aaa'), (11, 'bbb'), (12, 'ccc'), (13, 'ddd');
+INSERT INTO lvtest2 VALUES (11, 'xxx'), (12, 'yyy'), (13, 'zzz'), (14, 'xyz');
+CREATE OR REPLACE VIEW leaky_v1 WITH (security_barrier=true) AS
+	   SELECT * FROM lvtest1 JOIN lvtest2 ON a = x WHERE a % 2 = 0;	-- OK
+CREATE OR REPLACE VIEW leaky_v2 WITH (security_barrier=false) AS
+	   SELECT * FROM lvtest1 JOIN lvtest2 ON a = x WHERE a % 2 = 1;	-- OK
+CREATE OR REPLACE VIEW leaky_v3 WITH (invalid_option=1234) AS
+	   SELECT * FROM lvtest1 JOIN lvtest2 ON a = x WHERE a % 2 = 1;	-- Fail
+ERROR:  unrecognized parameter "invalid_option"
+SELECT relname, reloptions FROM pg_class WHERE relname like 'leaky_v%';
+ relname  |        reloptions        
+----------+--------------------------
+ leaky_v1 | {security_barrier=true}
+ leaky_v2 | {security_barrier=false}
+(2 rows)
+
+ALTER TABLE leaky_v1 RESET ( security_barrier );
+ALTER TABLE leaky_v2 SET ( security_barrier );
+SELECT relname, reloptions FROM pg_class WHERE relname like 'leaky_v%';
+ relname  |       reloptions        
+----------+-------------------------
+ leaky_v1 | 
+ leaky_v2 | {security_barrier=true}
+(2 rows)
+
 DROP SCHEMA temp_view_test CASCADE;
 NOTICE:  drop cascades to 22 other objects
 DETAIL:  drop cascades to table temp_view_test.base_table
@@ -264,7 +292,7 @@ drop cascades to view temp_view_test.v8
 drop cascades to sequence temp_view_test.seq1
 drop cascades to view temp_view_test.v9
 DROP SCHEMA testviewschm2 CASCADE;
-NOTICE:  drop cascades to 16 other objects
+NOTICE:  drop cascades to 20 other objects
 DETAIL:  drop cascades to table t1
 drop cascades to view temporal1
 drop cascades to view temporal2
@@ -281,4 +309,8 @@ drop cascades to table tbl3
 drop cascades to table tbl4
 drop cascades to view mytempview
 drop cascades to view pubview
+drop cascades to table lvtest1
+drop cascades to table lvtest2
+drop cascades to view leaky_v1
+drop cascades to view leaky_v2
 SET search_path to public;
diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql
index 86cfc51..fbcd25f 100644
--- a/src/test/regress/sql/create_view.sql
+++ b/src/test/regress/sql/create_view.sql
@@ -191,6 +191,25 @@ AND NOT EXISTS (SELECT g FROM tbl4 LEFT JOIN tmptbl ON tbl4.h = tmptbl.j);
 SELECT count(*) FROM pg_class where relname LIKE 'mytempview'
 And relnamespace IN (SELECT OID FROM pg_namespace WHERE nspname LIKE 'pg_temp%');
 
+--Should work correctly to leaky-view scenario
+CREATE TABLE lvtest1 (a int, b text);
+CREATE TABLE lvtest2 (x int, y text);
+
+INSERT INTO lvtest1 VALUES (10, 'aaa'), (11, 'bbb'), (12, 'ccc'), (13, 'ddd');
+INSERT INTO lvtest2 VALUES (11, 'xxx'), (12, 'yyy'), (13, 'zzz'), (14, 'xyz');
+
+CREATE OR REPLACE VIEW leaky_v1 WITH (security_barrier=true) AS
+	   SELECT * FROM lvtest1 JOIN lvtest2 ON a = x WHERE a % 2 = 0;	-- OK
+CREATE OR REPLACE VIEW leaky_v2 WITH (security_barrier=false) AS
+	   SELECT * FROM lvtest1 JOIN lvtest2 ON a = x WHERE a % 2 = 1;	-- OK
+CREATE OR REPLACE VIEW leaky_v3 WITH (invalid_option=1234) AS
+	   SELECT * FROM lvtest1 JOIN lvtest2 ON a = x WHERE a % 2 = 1;	-- Fail
+SELECT relname, reloptions FROM pg_class WHERE relname like 'leaky_v%';
+
+ALTER TABLE leaky_v1 RESET ( security_barrier );
+ALTER TABLE leaky_v2 SET ( security_barrier );
+SELECT relname, reloptions FROM pg_class WHERE relname like 'leaky_v%';
+
 DROP SCHEMA temp_view_test CASCADE;
 DROP SCHEMA testviewschm2 CASCADE;
 
