pgsql-fix-leaky-view-part-2.patch

application/octet-stream

Filename: pgsql-fix-leaky-view-part-2.patch
Type: application/octet-stream
Part: 0
Message: [v9.2] Fix leaky-view problem, part 2

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
File+
doc/src/sgml/ref/create_view.sgml 21 1
doc/src/sgml/rules.sgml 61 0
src/backend/access/common/reloptions.c 14 1
src/backend/commands/tablecmds.c 2 1
src/backend/commands/view.c 28 6
src/backend/nodes/copyfuncs.c 2 0
src/backend/nodes/equalfuncs.c 2 0
src/backend/nodes/outfuncs.c 2 0
src/backend/nodes/readfuncs.c 2 0
src/backend/optimizer/plan/initsplan.c 75 21
src/backend/optimizer/prep/prepjointree.c 6 0
src/backend/optimizer/util/clauses.c 113 0
src/backend/parser/gram.y 17 9
src/backend/rewrite/rewriteHandler.c 1 0
src/backend/utils/cache/lsyscache.c 19 0
src/backend/utils/cache/relcache.c 1 0
src/include/access/reloptions.h 2 1
src/include/nodes/parsenodes.h 3 0
src/include/nodes/primnodes.h 1 0
src/include/optimizer/clauses.h 1 0
src/include/utils/lsyscache.h 1 0
src/include/utils/rel.h 10 1
src/test/regress/expected/create_view.out 57 1
src/test/regress/sql/create_view.sql 21 0
 doc/src/sgml/ref/create_view.sgml         |   22 ++++++-
 doc/src/sgml/rules.sgml                   |   61 ++++++++++++++++
 src/backend/access/common/reloptions.c    |   15 ++++-
 src/backend/commands/tablecmds.c          |    3 +-
 src/backend/commands/view.c               |   34 +++++++--
 src/backend/nodes/copyfuncs.c             |    2 +
 src/backend/nodes/equalfuncs.c            |    2 +
 src/backend/nodes/outfuncs.c              |    2 +
 src/backend/nodes/readfuncs.c             |    2 +
 src/backend/optimizer/plan/initsplan.c    |   96 +++++++++++++++++++------
 src/backend/optimizer/prep/prepjointree.c |    6 ++
 src/backend/optimizer/util/clauses.c      |  113 +++++++++++++++++++++++++++++
 src/backend/parser/gram.y                 |   26 +++++---
 src/backend/rewrite/rewriteHandler.c      |    1 +
 src/backend/utils/cache/lsyscache.c       |   19 +++++
 src/backend/utils/cache/relcache.c        |    1 +
 src/include/access/reloptions.h           |    3 +-
 src/include/nodes/parsenodes.h            |    3 +
 src/include/nodes/primnodes.h             |    1 +
 src/include/optimizer/clauses.h           |    1 +
 src/include/utils/lsyscache.h             |    1 +
 src/include/utils/rel.h                   |   11 +++-
 src/test/regress/expected/create_view.out |   58 +++++++++++++++-
 src/test/regress/sql/create_view.sql      |   21 ++++++
 24 files changed, 462 insertions(+), 42 deletions(-)

diff --git a/doc/src/sgml/ref/create_view.sgml b/doc/src/sgml/ref/create_view.sgml
index 417f8c3..a76e646 100644
--- a/doc/src/sgml/ref/create_view.sgml
+++ b/doc/src/sgml/ref/create_view.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
+CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] [SECURITY] VIEW <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
     AS <replaceable class="PARAMETER">query</replaceable>
 </synopsis>
  </refsynopsisdiv>
@@ -80,6 +80,26 @@ CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW <replaceable class="PARAMETER">n
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SECURITY</literal></term>
+    <listitem>
+     <para>
+      If specified, a part of query optimization shall be restricted
+      to prevent unexpected information leaks.
+      We recommend to apply this option when the view is defined for
+      row-level security purpose, 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">name</replaceable></term>
     <listitem>
      <para>
diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml
index 1b06519..9905044 100644
--- a/doc/src/sgml/rules.sgml
+++ b/doc/src/sgml/rules.sgml
@@ -1856,6 +1856,67 @@ SELECT * FROM phone_number WHERE tricky(person, phone);
 </para>
 
 <para>
+    In addition, you might be able to leak contents of invisible tuples
+    using the following scenario:
+<programlisting>
+CREATE VIEW your_credit AS
+    SELECT a.rolname, c.number, c.expire
+    FROM pg_authid a JOIN credit_cards c ON a.oid = c.id
+    WHERE a.rolname = getpgusername();
+</programlisting>
+    This view also might seem secure, since any <command>SELECT</command>
+    from <literal>your_credit</literal> shall be rewritten into a
+    <command>SELECT</command> from the join of <literal>pg_authid</>
+    and <literal>credit_cards</> with a qualifier that filters out
+    any entries except for your credit card number.
+
+    But if a user appends his or her own functions that references
+    only columns come from a particular side of join loop, the optimizer
+    shall relocate this qualifier into the most deep level, independent
+    from cost estimation of the function.
+<programlisting>
+postgres=> SELECT * FROM your_credit WHERE tricky(number, expire);
+NOTICE:  1111-2222-3333-4444 => Jan-01
+NOTICE:  5555-6666-7777-8888 => Feb-02
+NOTICE:  1234-5678-9012-3456 => Mar-03
+ rolname |       number        | expire
+---------+---------------------+--------
+ alice   | 5555-6666-7777-8888 | Feb-02
+(1 row)
+</programlisting>
+    The reason is obvious from the result of <command>EXPLAIN</command>.
+<programlisting>
+postgres=> EXPLAIN SELECT * FROM your_credit WHERE tricky(number, expire);
+                               QUERY PLAN
+------------------------------------------------------------------------
+ Hash Join  (cost=1.03..20.38 rows=1 width=128)
+   Hash Cond: (c.id = a.oid)
+   ->  Seq Scan on credit_cards c  (cost=0.00..18.30 rows=277 width=68)
+         Filter: tricky(number, expire)
+   ->  Hash  (cost=1.01..1.01 rows=1 width=68)
+         ->  Seq Scan on pg_authid a  (cost=0.00..1.01 rows=1 width=68)
+               Filter: (rolname = getpgusername())
+(7 rows)
+</programlisting>
+    The supplied <function>tricky</function> only references
+    <literal>number</literal> and <literal>expire</literal> columns,
+    however, the qualifier to filter invisible tuples performs on
+    the scan of <literal>pg_authid</literal>.
+    Then, since the optimizer tries to minimize the number of tuples
+    being joined, the supplied qualifer got attached on the scan of
+    <literal>credit_cards</literal>.
+    In the result, it allows <function>tricky</function> to reference
+    contents of the <literal>credit_cards</literal> table.
+</para>
+<para>
+    The <command>CREATE SECURITY VIEW</command> enables to prevent
+    unexpected push-down of qualifiers supplied from outside of the
+    view, however, of course, it is not a fully optimized query plan
+    from the perspective of performance.
+    So, pay attention to the trade-off between security and performance.
+</para>
+
+<para>
     Similar considerations apply to update rules. In the examples of
     the previous section, the owner of the tables in the example
     database could grant the privileges <literal>SELECT</>,
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 4657425..9c3669b 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_view",
+			"Restrict maximum optimization to prevent data leaks",
+			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_view", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, security_view)},
 	};
 
 	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 bdbcdff..4649b59 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2911,7 +2911,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;
@@ -7686,6 +7686,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..e1449d1 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, bool security)
 {
 	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 ...
@@ -205,20 +208,32 @@ DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
 		checkViewTupleDesc(descriptor, rel->rd_att);
 
 		/*
+		 * If "SECURITY" flag is not compatible to the view being replaced,
+		 * we need to modify "security_view" reloption here.
+		 */
+		if (RelationIsSecurityView(rel) != security)
+		{
+			DefElem *defel = makeDefElem("security_view", NULL);
+
+			atcmd = makeNode(AlterTableCmd);
+			atcmd->subtype = security ? AT_SetRelOptions : AT_ResetRelOptions;
+			atcmd->def = list_make1((Node *) defel);
+
+			atcmds = lappend(atcmds, atcmd);
+		}
+
+		/*
 		 * If new attributes have been added, we must add pg_attribute entries
 		 * for them.  It is convenient (although overkill) to use the ALTER
 		 * TABLE ADD COLUMN infrastructure for this.
 		 */
 		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,8 +244,9 @@ DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
 				atcmd->def = (Node *) lfirst(c);
 				atcmds = lappend(atcmds, atcmd);
 			}
-			AlterTableInternal(viewOid, atcmds, true);
 		}
+		if (atcmds != NIL)
+			AlterTableInternal(viewOid, atcmds, true);
 
 		/*
 		 * Seems okay, so return the OID of the pre-existing view.
@@ -256,6 +272,12 @@ DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
 		createStmt->tablespacename = NULL;
 		createStmt->if_not_exists = false;
 
+		if (security)
+		{
+			DefElem *defel = makeDefElem("security_view", NULL);
+			createStmt->options = lappend(createStmt->options, defel);
+		}
+
 		/*
 		 * 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 +532,7 @@ DefineView(ViewStmt *stmt, const char *queryString)
 	 * aborted.
 	 */
 	viewOid = DefineVirtualRelation(view, viewParse->targetList,
-									stmt->replace);
+									stmt->replace, stmt->security);
 
 	/*
 	 * 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..ce762cc 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1789,6 +1789,7 @@ _copyFromExpr(FromExpr *from)
 
 	COPY_NODE_FIELD(fromlist);
 	COPY_NODE_FIELD(quals);
+	COPY_SCALAR_FIELD(security_view);
 
 	return newnode;
 }
@@ -1944,6 +1945,7 @@ _copyRangeTblEntry(RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relid);
 	COPY_SCALAR_FIELD(relkind);
 	COPY_NODE_FIELD(subquery);
+	COPY_SCALAR_FIELD(security_view);
 	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..89cf310 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -772,6 +772,7 @@ _equalFromExpr(FromExpr *a, FromExpr *b)
 {
 	COMPARE_NODE_FIELD(fromlist);
 	COMPARE_NODE_FIELD(quals);
+	COMPARE_SCALAR_FIELD(security_view);
 
 	return true;
 }
@@ -2303,6 +2304,7 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relid);
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_NODE_FIELD(subquery);
+	COMPARE_SCALAR_FIELD(security_view);
 	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..3ad7067 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1447,6 +1447,7 @@ _outFromExpr(StringInfo str, FromExpr *node)
 
 	WRITE_NODE_FIELD(fromlist);
 	WRITE_NODE_FIELD(quals);
+	WRITE_BOOL_FIELD(security_view);
 }
 
 /*****************************************************************************
@@ -2311,6 +2312,7 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
+			WRITE_BOOL_FIELD(security_view);
 			break;
 		case RTE_JOIN:
 			WRITE_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 2288514..a63236d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1160,6 +1160,7 @@ _readFromExpr(void)
 
 	READ_NODE_FIELD(fromlist);
 	READ_NODE_FIELD(quals);
+	READ_BOOL_FIELD(security_view);
 
 	READ_DONE();
 }
@@ -1190,6 +1191,7 @@ _readRangeTblEntry(void)
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
+			READ_BOOL_FIELD(security_view);
 			break;
 		case RTE_JOIN:
 			READ_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 333ede2..628a84e 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -41,7 +41,8 @@ int			join_collapse_limit;
 
 static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
 					bool below_outer_join,
-					Relids *qualscope, Relids *inner_join_rels);
+					Relids *qualscope, Relids *inner_join_rels,
+					bool below_sec_barriers, Relids *sec_barriers);
 static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root,
 				   Relids left_rels, Relids right_rels,
 				   Relids inner_join_rels,
@@ -52,7 +53,8 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 						JoinType jointype,
 						Relids qualscope,
 						Relids ojscope,
-						Relids outerjoin_nonnullable);
+						Relids outerjoin_nonnullable,
+						Relids sec_barriers);
 static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p,
 					  Relids *nullable_relids_p, bool is_pushed_down);
 static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
@@ -240,13 +242,15 @@ deconstruct_jointree(PlannerInfo *root)
 {
 	Relids		qualscope;
 	Relids		inner_join_rels;
+	Relids		sec_barriers;
+	FromExpr   *f = (FromExpr *)root->parse->jointree;
 
 	/* Start recursion at top of jointree */
-	Assert(root->parse->jointree != NULL &&
-		   IsA(root->parse->jointree, FromExpr));
+	Assert(root->parse->jointree != NULL && IsA(f, FromExpr));
 
-	return deconstruct_recurse(root, (Node *) root->parse->jointree, false,
-							   &qualscope, &inner_join_rels);
+	return deconstruct_recurse(root, (Node *) f, false,
+							   &qualscope, &inner_join_rels,
+							   f->security_view, &sec_barriers);
 }
 
 /*
@@ -270,7 +274,8 @@ deconstruct_jointree(PlannerInfo *root)
  */
 static List *
 deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
-					Relids *qualscope, Relids *inner_join_rels)
+					Relids *qualscope, Relids *inner_join_rels,
+					bool below_sec_barriers, Relids *sec_barriers)
 {
 	List	   *joinlist;
 
@@ -289,6 +294,9 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 		/* A single baserel does not create an inner join */
 		*inner_join_rels = NULL;
 		joinlist = list_make1(jtnode);
+		/* Is it in security barrier? */
+		*sec_barriers = (below_sec_barriers ?
+						 bms_make_singleton(varno) : NULL);
 	}
 	else if (IsA(jtnode, FromExpr))
 	{
@@ -304,6 +312,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 		 */
 		*qualscope = NULL;
 		*inner_join_rels = NULL;
+		*sec_barriers = NULL;
 		joinlist = NIL;
 		remaining = list_length(f->fromlist);
 		foreach(l, f->fromlist)
@@ -311,12 +320,17 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 			Relids		sub_qualscope;
 			List	   *sub_joinlist;
 			int			sub_members;
+			Relids		sub_barriers;
 
 			sub_joinlist = deconstruct_recurse(root, lfirst(l),
 											   below_outer_join,
 											   &sub_qualscope,
-											   inner_join_rels);
+											   inner_join_rels,
+											   below_sec_barriers ?
+											   true : f->security_view,
+											   &sub_barriers);
 			*qualscope = bms_add_members(*qualscope, sub_qualscope);
+			*sec_barriers = bms_add_members(*sec_barriers, sub_barriers);
 			sub_members = list_length(sub_joinlist);
 			remaining--;
 			if (sub_members <= 1 ||
@@ -345,7 +359,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 
 			distribute_qual_to_rels(root, qual,
 									false, below_outer_join, JOIN_INNER,
-									*qualscope, NULL, NULL);
+									*qualscope, NULL, NULL, *sec_barriers);
 		}
 	}
 	else if (IsA(jtnode, JoinExpr))
@@ -355,6 +369,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 					rightids,
 					left_inners,
 					right_inners,
+					left_barriers,
+					right_barriers,
 					nonnullable_rels,
 					ojscope;
 		List	   *leftjoinlist,
@@ -379,12 +395,17 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 			case JOIN_INNER:
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
-												   &leftids, &left_inners);
+												   &leftids, &left_inners,
+												   below_sec_barriers,
+												   &left_barriers);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													below_outer_join,
-													&rightids, &right_inners);
+													&rightids, &right_inners,
+													below_sec_barriers,
+													&right_barriers);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = *qualscope;
+				*sec_barriers = bms_union(left_barriers, right_barriers);
 				/* Inner join adds no restrictions for quals */
 				nonnullable_rels = NULL;
 				break;
@@ -392,35 +413,50 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 			case JOIN_ANTI:
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
-												   &leftids, &left_inners);
+												   &leftids, &left_inners,
+												   below_sec_barriers,
+												   &left_barriers);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													true,
-													&rightids, &right_inners);
+													&rightids, &right_inners,
+													below_sec_barriers,
+													&right_barriers);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
+				*sec_barriers = bms_union(left_barriers, right_barriers);
 				nonnullable_rels = leftids;
 				break;
 			case JOIN_SEMI:
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
-												   &leftids, &left_inners);
+												   &leftids, &left_inners,
+												   below_sec_barriers,
+												   &left_barriers);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													below_outer_join,
-													&rightids, &right_inners);
+													&rightids, &right_inners,
+													below_sec_barriers,
+													&right_barriers);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
+				*sec_barriers = bms_union(left_barriers, right_barriers);
 				/* Semi join adds no restrictions for quals */
 				nonnullable_rels = NULL;
 				break;
 			case JOIN_FULL:
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   true,
-												   &leftids, &left_inners);
+												   &leftids, &left_inners,
+												   below_sec_barriers,
+												   &left_barriers);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													true,
-													&rightids, &right_inners);
+													&rightids, &right_inners,
+													below_sec_barriers,
+													&right_barriers);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
+				*sec_barriers = bms_union(left_barriers, right_barriers);
 				/* each side is both outer and inner */
 				nonnullable_rels = *qualscope;
 				break;
@@ -469,7 +505,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 			distribute_qual_to_rels(root, qual,
 									false, below_outer_join, j->jointype,
 									*qualscope,
-									ojscope, nonnullable_rels);
+									ojscope, nonnullable_rels,
+									*sec_barriers);
 		}
 
 		/* Now we can add the SpecialJoinInfo to join_info_list */
@@ -793,7 +830,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 						JoinType jointype,
 						Relids qualscope,
 						Relids ojscope,
-						Relids outerjoin_nonnullable)
+						Relids outerjoin_nonnullable,
+						Relids sec_barriers)
 {
 	Relids		relids;
 	bool		is_pushed_down;
@@ -801,6 +839,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	bool		pseudoconstant = false;
 	bool		maybe_equivalence;
 	bool		maybe_outer_join;
+	bool		maybe_leakable_clause = false;
 	Relids		nullable_relids;
 	RestrictInfo *restrictinfo;
 
@@ -873,6 +912,21 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 		}
 	}
 
+	/*
+	 * If and when the supplied clause contains a leakable functions,
+	 * it might be used to bypass row-level security using views.
+	 * In this case, we should not push down the clause to prevent
+	 * the leakable clause being evaluated prior to row-level policy
+	 * functions.
+	 */
+	if (!bms_is_empty(sec_barriers) &&
+		contain_leakable_functions(clause) &&
+		bms_overlap(relids, sec_barriers))
+	{
+		maybe_leakable_clause = true;
+		relids = bms_add_members(relids, sec_barriers);
+	}
+
 	/*----------
 	 * Check to see if clause application must be delayed by outer-join
 	 * considerations.
@@ -1075,7 +1129,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	 * process_equivalence is successful, it will take care of that;
 	 * otherwise, we have to call initialize_mergeclause_eclasses to do it.
 	 */
-	if (restrictinfo->mergeopfamilies)
+	if (!maybe_leakable_clause && restrictinfo->mergeopfamilies)
 	{
 		if (maybe_equivalence)
 		{
@@ -1417,7 +1471,7 @@ process_implied_equality(PlannerInfo *root,
 	 */
 	distribute_qual_to_rels(root, (Node *) clause,
 							true, below_outer_join, JOIN_INNER,
-							qualscope, NULL, NULL);
+							qualscope, NULL, NULL, NULL);
 }
 
 /*
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 5d16329..bc23936 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -699,6 +699,12 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 		pull_up_subqueries(subroot, (Node *) subquery->jointree, NULL, NULL);
 
 	/*
+	 * If the sub-query is originated from security-view, we mark it here
+	 * to prevent too-much optimization in later phase.
+	 */
+	((FromExpr *) subquery->jointree)->security_view = rte->security_view;
+
+	/*
 	 * Now we must recheck whether the subquery is still simple enough to pull
 	 * up.	If not, abandon processing it.
 	 *
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 8b0d862..3e0b2e8 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -93,6 +93,7 @@ static bool contain_subplans_walker(Node *node, void *context);
 static bool contain_mutable_functions_walker(Node *node, void *context);
 static bool contain_volatile_functions_walker(Node *node, void *context);
 static bool contain_nonstrict_functions_walker(Node *node, void *context);
+static bool contain_leakable_functions_walker(Node *node, void *context);
 static Relids find_nonnullable_rels_walker(Node *node, bool top_level);
 static List *find_nonnullable_vars_walker(Node *node, bool top_level);
 static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
@@ -1164,6 +1165,118 @@ contain_nonstrict_functions_walker(Node *node, void *context)
 								  context);
 }
 
+/*****************************************************************************
+ *        Check clauses for leakable functions
+ *****************************************************************************/
+
+/*
+ * contain_leakable_functions
+ *      Recursively search for leakable functions within a clause.
+ *
+ * Returns true if any function call with side-effect is found.
+ * ie, some type-input/output handler will raise an error when given
+ *     argument does not have a valid format.
+ *
+ * When people uses views for row-level security purpose, given qualifiers
+ * come from outside of the view should not be pushed down into the views,
+ * if they have side-effect, because contents of tuples to be filtered out
+ * may be leaked via side-effectable functions within the qualifiers.
+ *
+ * The idea here is that the planner restrain a part of optimization when
+ * the qualifiers contains leakable functions.
+ * This routine checks whether the given clause contains leakable functions,
+ * or not. If we return false, then the clause is clean.
+ */
+bool
+contain_leakable_functions(Node *clause)
+{
+	return contain_leakable_functions_walker(clause, NULL);
+}
+
+static bool
+contain_leakable_functions_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		/*
+		 * currently, we have no way to distinguish a safe function and
+		 * a leakable one, so all the function call shall be considered
+		 * as leakable one.
+		 */
+		return true;
+	}
+	else if (IsA(node, OpExpr))
+	{
+		OpExpr *expr = (OpExpr *) node;
+
+		/*
+		 * we assume built-in functions to implement operators are not
+		 * leakable, so don't need to prevent optimization.
+		 */
+		set_opfuncid(expr);
+		if (get_func_lang(expr->opfuncid) != INTERNALlanguageId)
+			return true;
+		/* else fall through to check args */
+	}
+	else if (IsA(node, DistinctExpr))
+	{
+		DistinctExpr *expr = (DistinctExpr *) node;
+
+		set_opfuncid((OpExpr *) expr);
+		if (get_func_lang(expr->opfuncid) != INTERNALlanguageId)
+			return true;
+		/* else fall through to check args */
+	}
+	else if (IsA(node, ScalarArrayOpExpr))
+	{
+		ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
+
+		set_sa_opfuncid(expr);
+		if (get_func_lang(expr->opfuncid) != INTERNALlanguageId)
+			return true;
+		/* else fall through to check args */
+	}
+	else if (IsA(node, CoerceViaIO) ||
+			 IsA(node, ArrayCoerceExpr))
+	{
+		/*
+		 * we assume type-in/out handlers are leakable, even if built-in
+		 * functions.
+		 * ie, int4in() raises an error message with given argument,
+		 *     if it does not have valid format for numeric value.
+		 */
+		return true;
+	}
+	else if (IsA(node, NullIfExpr))
+	{
+		NullIfExpr *expr = (NullIfExpr *) node;
+
+		set_opfuncid((OpExpr *) expr);  /* rely on struct equivalence */
+		if (get_func_lang(expr->opfuncid) != INTERNALlanguageId)
+			return true;
+		/* else fall through to check args */
+	}
+	else if (IsA(node, RowCompareExpr))
+	{
+		/* RowCompare probably can't have volatile ops, but check anyway */
+		RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+		ListCell   *opid;
+
+		foreach(opid, rcexpr->opnos)
+		{
+			Oid     funcId = get_opcode(lfirst_oid(opid));
+
+			if (get_func_lang(funcId) != INTERNALlanguageId)
+				return true;
+		}
+		/* else fall through to check args */
+	}
+	return expression_tree_walker(node, contain_leakable_functions_walker,
+								  context);
+}
 
 /*
  * find_nonnullable_rels
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1d39674..ebe5dff 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -325,6 +325,7 @@ static void SplitColQualList(List *qualList,
 
 %type <boolean>  opt_trusted opt_restart_seqs
 %type <ival>	 OptTemp
+%type <boolean>	 OptSecurity
 %type <oncommit> OnCommitOption
 
 %type <node>	for_locking_item
@@ -7264,35 +7265,42 @@ transaction_mode_list_or_empty:
 /*****************************************************************************
  *
  *	QUERY:
- *		CREATE [ OR REPLACE ] [ TEMP ] VIEW <viewname> '('target-list ')'
+ *		CREATE [ OR REPLACE ] [ TEMP ] [ SECURITY ]
+ *			VIEW <viewname> '('target-list ')'
  *			AS <query> [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]
  *
  *****************************************************************************/
 
-ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list
+ViewStmt: CREATE OptTemp OptSecurity VIEW qualified_name opt_column_list
 				AS SelectStmt opt_check_option
 				{
 					ViewStmt *n = makeNode(ViewStmt);
-					n->view = $4;
+					n->view = $5;
 					n->view->relpersistence = $2;
-					n->aliases = $5;
-					n->query = $7;
+					n->aliases = $6;
+					n->query = $8;
 					n->replace = false;
+					n->security = $3;
 					$$ = (Node *) n;
 				}
-		| CREATE OR REPLACE OptTemp VIEW qualified_name opt_column_list
+		| CREATE OR REPLACE OptTemp OptSecurity VIEW qualified_name opt_column_list
 				AS SelectStmt opt_check_option
 				{
 					ViewStmt *n = makeNode(ViewStmt);
-					n->view = $6;
+					n->view = $7;
 					n->view->relpersistence = $4;
-					n->aliases = $7;
-					n->query = $9;
+					n->aliases = $8;
+					n->query = $10;
 					n->replace = true;
+					n->security = $5;
 					$$ = (Node *) n;
 				}
 		;
 
+OptSecurity:	SECURITY			{ $$ = TRUE; }
+		|		/* EMPTY */			{ $$ = FALSE; }
+		;
+
 opt_check_option:
 		WITH CHECK OPTION
 				{
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index bfc8fd7..7d25e39 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1344,6 +1344,7 @@ ApplyRetrieveRule(Query *parsetree,
 	rte->rtekind = RTE_SUBQUERY;
 	rte->relid = InvalidOid;
 	rte->subquery = rule_action;
+	rte->security_view = RelationIsSecurityView(relation);
 	rte->inh = false;			/* must not be set for a subquery */
 
 	/*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index d3b2a5a..a7e4d3e 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1390,6 +1390,25 @@ get_func_namespace(Oid funcid)
 }
 
 /*
+ * get_func_lang
+ *		Given procedure id, return the function's language
+ */
+Oid
+get_func_lang(Oid funcid)
+{
+	HeapTuple	tp;
+	Oid			result;
+
+	tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for function %u", funcid);
+
+	result = ((Form_pg_proc) GETSTRUCT(tp))->prolang;
+	ReleaseSysCache(tp);
+	return result;
+}
+
+/*
  * get_func_rettype
  *		Given procedure id, return the function's result type.
  */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index d7e94ff..ab8146a 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/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 ee1881b..e274984 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_view;	/* Is the sub-query come from security view? */
+
 	/*
 	 * Fields valid for a join RTE (else NULL/zero):
 	 *
@@ -2336,6 +2338,7 @@ typedef struct ViewStmt
 	List	   *aliases;		/* target column names */
 	Node	   *query;			/* the SELECT query */
 	bool		replace;		/* replace an existing view? */
+	bool		security;		/* view is defined for row-level security? */
 } ViewStmt;
 
 /* ----------------------
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f1e20ef..5e2a788 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1259,6 +1259,7 @@ typedef struct FromExpr
 	NodeTag		type;
 	List	   *fromlist;		/* List of join subtrees */
 	Node	   *quals;			/* qualifiers on join, if any */
+	bool		security_view;  /* It came from security views */
 } FromExpr;
 
 #endif   /* PRIMNODES_H */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 4af772d..035bdf5 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -62,6 +62,7 @@ extern bool contain_subplans(Node *clause);
 extern bool contain_mutable_functions(Node *clause);
 extern bool contain_volatile_functions(Node *clause);
 extern bool contain_nonstrict_functions(Node *clause);
+extern bool contain_leakable_functions(Node *clause);
 extern Relids find_nonnullable_rels(Node *clause);
 extern List *find_nonnullable_vars(Node *clause);
 extern List *find_forced_null_vars(Node *clause);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 0a419dc..659cdc0 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -79,6 +79,7 @@ extern RegProcedure get_oprrest(Oid opno);
 extern RegProcedure get_oprjoin(Oid opno);
 extern char *get_func_name(Oid funcid);
 extern Oid	get_func_namespace(Oid funcid);
+extern Oid	get_func_lang(Oid funcid);
 extern Oid	get_func_rettype(Oid funcid);
 extern int	get_func_nargs(Oid funcid);
 extern Oid	get_func_signature(Oid funcid, Oid **argtypes, int *nargs);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index e2c2fa9..3f25949 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_view;	/* restrict fully optimization, if true */
 } 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_view : 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..ceaa701 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -239,6 +239,58 @@ 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);
+CREATE FUNCTION f_leak(text) RETURNS bool LANGUAGE 'plpgsql'
+AS 'BEGIN raise notice ''leak => %'', $1; RETURN true; END';
+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_view AS
+    SELECT * FROM lvtest1 JOIN lvtest2 ON a = x WHERE a % 2 = 0;
+SELECT * FROM leaky_view WHERE f_leak(y);
+NOTICE:  leak => xxx
+NOTICE:  leak => yyy
+NOTICE:  leak => zzz
+NOTICE:  leak => xyz
+ a  |  b  | x  |  y  
+----+-----+----+-----
+ 12 | ccc | 12 | yyy
+(1 row)
+
+EXPLAIN SELECT * FROM leaky_view WHERE f_leak(y);
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Hash Join  (cost=28.52..359.98 rows=12 width=72)
+   Hash Cond: (lvtest2.x = lvtest1.a)
+   ->  Seq Scan on lvtest2  (cost=0.00..329.80 rows=410 width=36)
+         Filter: f_leak(y)
+   ->  Hash  (cost=28.45..28.45 rows=6 width=36)
+         ->  Seq Scan on lvtest1  (cost=0.00..28.45 rows=6 width=36)
+               Filter: ((a % 2) = 0)
+(7 rows)
+
+CREATE OR REPLACE SECURITY VIEW leaky_view AS
+    SELECT * FROM lvtest1 JOIN lvtest2 ON a = x WHERE a % 2 = 0;
+SELECT * FROM leaky_view WHERE f_leak(y);
+NOTICE:  leak => yyy
+ a  |  b  | x  |  y  
+----+-----+----+-----
+ 12 | ccc | 12 | yyy
+(1 row)
+
+EXPLAIN SELECT * FROM leaky_view WHERE f_leak(y);
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Hash Join  (cost=28.52..65.06 rows=12 width=72)
+   Hash Cond: (lvtest2.x = lvtest1.a)
+   Join Filter: f_leak(lvtest2.y)
+   ->  Seq Scan on lvtest2  (cost=0.00..22.30 rows=1230 width=36)
+   ->  Hash  (cost=28.45..28.45 rows=6 width=36)
+         ->  Seq Scan on lvtest1  (cost=0.00..28.45 rows=6 width=36)
+               Filter: ((a % 2) = 0)
+(7 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 +316,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 +333,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 function f_leak(text)
+drop cascades to view leaky_view
 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..d9b7fa1 100644
--- a/src/test/regress/sql/create_view.sql
+++ b/src/test/regress/sql/create_view.sql
@@ -191,6 +191,27 @@ 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);
+CREATE FUNCTION f_leak(text) RETURNS bool LANGUAGE 'plpgsql'
+AS 'BEGIN raise notice ''leak => %'', $1; RETURN true; END';
+
+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_view AS
+    SELECT * FROM lvtest1 JOIN lvtest2 ON a = x WHERE a % 2 = 0;
+
+SELECT * FROM leaky_view WHERE f_leak(y);
+EXPLAIN SELECT * FROM leaky_view WHERE f_leak(y);
+
+CREATE OR REPLACE SECURITY VIEW leaky_view AS
+    SELECT * FROM lvtest1 JOIN lvtest2 ON a = x WHERE a % 2 = 0;
+
+SELECT * FROM leaky_view WHERE f_leak(y);
+EXPLAIN SELECT * FROM leaky_view WHERE f_leak(y);
+
 DROP SCHEMA temp_view_test CASCADE;
 DROP SCHEMA testviewschm2 CASCADE;