 doc/src/sgml/rules.sgml                    |   66 ++++++++++++
 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/optimizer/plan/initsplan.c     |   96 ++++++++++++++----
 src/backend/optimizer/prep/prepjointree.c  |    7 ++
 src/backend/optimizer/util/clauses.c       |  113 ++++++++++++++++++++
 src/backend/utils/cache/lsyscache.c        |   19 ++++
 src/include/nodes/primnodes.h              |    1 +
 src/include/optimizer/clauses.h            |    1 +
 src/include/utils/lsyscache.h              |    1 +
 src/test/regress/expected/select_views.out |  153 +++++++++++++++++++++++++---
 src/test/regress/sql/select_views.sql      |   67 ++++++++++++
 14 files changed, 493 insertions(+), 35 deletions(-)

diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml
index 1b06519..1b5ae6f 100644
--- a/doc/src/sgml/rules.sgml
+++ b/doc/src/sgml/rules.sgml
@@ -1856,6 +1856,72 @@ 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 <literal>security_berrier</literal> option of views enables
+    to prevent both of the scenarios, instead of a bit performance
+    trade-off. If and when a particular view is defined with
+    <literal>security_berrier=TRUE</literal>, any exogenetic qualifiers
+    cannot be pushed-down except for a limited number of trusted
+    operators being transformed into index scan, even if the supplied
+    qualifier references only one-side of relation joins. In addition,
+    underlying qualifiers of security barrier view shall be launched
+    earlier than others, even if sub-queries are pulled-up and 
+    qualifiers got merged due to the optimization.
+</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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 4fb60a1..2af5113 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_barrier);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index d201f22..4e001f7 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_barrier);
 
 	return true;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 04a8760..3a263aa 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_barrier);
 }
 
 /*****************************************************************************
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 098f3c3..b7fcb25 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1161,6 +1161,7 @@ _readFromExpr(void)
 
 	READ_NODE_FIELD(fromlist);
 	READ_NODE_FIELD(quals);
+	READ_BOOL_FIELD(security_barrier);
 
 	READ_DONE();
 }
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 333ede2..cd8c499 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_barrier, &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_barrier,
+											   &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 ac622a3..ae96b4f 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -705,6 +705,13 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 		pull_up_subqueries(subroot, (Node *) subquery->jointree, NULL, NULL);
 
 	/*
+	 * If and when the sub-query was originally defined as a view with
+	 * "security_barrier" option, we need to mark this FromExpr as a
+	 * security barrier to prevent unexpected distribution of qualifiers.
+	 */
+	((FromExpr *)subquery->jointree)->security_barrier = rte->security_barrier;
+
+	/*
 	 * 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 be0935d..ddd1b0e 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 restrains 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))
+	{
+		/*
+		 * Right now, we have no way to distinguish safe functions with
+		 * leakable ones, so, we treat all the function call possibly
+		 * leakable.
+		 */
+		return true;
+	}
+	else if (IsA(node, OpExpr))
+	{
+		OpExpr *expr = (OpExpr *) node;
+
+		/*
+		 * Right now, we assume operators implemented by built-in functions
+		 * are not leakable, so it does not 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/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 28d18b0..55571f1 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1387,6 +1387,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/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f1e20ef..cfef93f 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_barrier;	/* Come from security-barrier view? */
 } FromExpr;
 
 #endif   /* PRIMNODES_H */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index dde6d82..09cf54f 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/test/regress/expected/select_views.out b/src/test/regress/expected/select_views.out
index 6cd317c..5375aec 100644
--- a/src/test/regress/expected/select_views.out
+++ b/src/test/regress/expected/select_views.out
@@ -467,6 +467,20 @@ SELECT name, #thepath FROM iexit ORDER BY 1, 2;
  I- 580                             |       21
  I- 580                             |       22
  I- 580                             |       22
+ I- 580/I-680                  Ramp |        2
+ I- 580/I-680                  Ramp |        2
+ I- 580/I-680                  Ramp |        2
+ I- 580/I-680                  Ramp |        2
+ I- 580/I-680                  Ramp |        2
+ I- 580/I-680                  Ramp |        2
+ I- 580/I-680                  Ramp |        4
+ I- 580/I-680                  Ramp |        4
+ I- 580/I-680                  Ramp |        4
+ I- 580/I-680                  Ramp |        4
+ I- 580/I-680                  Ramp |        5
+ I- 580/I-680                  Ramp |        6
+ I- 580/I-680                  Ramp |        6
+ I- 580/I-680                  Ramp |        6
  I- 580                        Ramp |        2
  I- 580                        Ramp |        2
  I- 580                        Ramp |        2
@@ -717,20 +731,6 @@ SELECT name, #thepath FROM iexit ORDER BY 1, 2;
  I- 580                        Ramp |        8
  I- 580                        Ramp |        8
  I- 580                        Ramp |        8
- I- 580/I-680                  Ramp |        2
- I- 580/I-680                  Ramp |        2
- I- 580/I-680                  Ramp |        2
- I- 580/I-680                  Ramp |        2
- I- 580/I-680                  Ramp |        2
- I- 580/I-680                  Ramp |        2
- I- 580/I-680                  Ramp |        4
- I- 580/I-680                  Ramp |        4
- I- 580/I-680                  Ramp |        4
- I- 580/I-680                  Ramp |        4
- I- 580/I-680                  Ramp |        5
- I- 580/I-680                  Ramp |        6
- I- 580/I-680                  Ramp |        6
- I- 580/I-680                  Ramp |        6
  I- 680                             |        2
  I- 680                             |        2
  I- 680                             |        2
@@ -1247,3 +1247,128 @@ SELECT * FROM toyemp WHERE name = 'sharon';
  sharon |  25 | (15,12)  |     12000
 (1 row)
 
+--
+-- Test for leaky-vew
+--
+CREATE USER alice;
+CREATE FUNCTION f_leak(text, text)
+    RETURNS bool LANGUAGE 'plpgsql'
+    COST 0.00000001
+    AS 'begin raise notice ''% => %'', $1, $2; return true; end';
+CREATE TABLE employee (
+    eid      int primary key,
+	ename    text,
+    etitle   text
+);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "employee_pkey" for table "employee"
+CREATE TABLE salary (
+    eid      int references employee(eid),
+    salary   int,
+    ymd      date
+);
+CREATE INDEX salary_ymd on salary (ymd);
+INSERT INTO employee (eid, ename, etitle)
+    VALUES (100, 'alice', 'staff'),
+           (110, 'bob', 'manager'),
+           (120, 'eve', 'chief');
+INSERT INTO salary (eid, salary, ymd)
+    VALUES (100, 2000, '2011-06-01'),
+           (100, 2025, '2011-07-01'),
+           (110, 2500, '2011-06-01'),
+           (110, 2400, '2011-07-01'),
+           (120, 2200, '2011-07-01');
+CREATE VIEW my_salary_normal AS
+    SELECT ename,etitle,salary,ymd
+    FROM employee e JOIN salary s ON s.eid = s.eid
+    WHERE ename = getpgusername();
+CREATE VIEW my_salary_secure WITH (security_barrier) AS
+    SELECT ename,etitle,salary,ymd
+    FROM employee e JOIN salary s ON e.eid = s.eid
+    WHERE ename = getpgusername();
+GRANT SELECT ON my_salary_normal TO public;
+GRANT SELECT ON my_salary_secure TO public;
+-- run leaky view
+SET SESSION AUTHORIZATION alice;
+SELECT * FROM my_salary_normal
+    WHERE f_leak(ymd::text,salary::text);
+NOTICE:  06-01-2011 => 2000
+NOTICE:  07-01-2011 => 2025
+NOTICE:  06-01-2011 => 2500
+NOTICE:  07-01-2011 => 2400
+NOTICE:  07-01-2011 => 2200
+ ename | etitle | salary |    ymd     
+-------+--------+--------+------------
+ alice | staff  |   2000 | 06-01-2011
+ alice | staff  |   2025 | 07-01-2011
+ alice | staff  |   2500 | 06-01-2011
+ alice | staff  |   2400 | 07-01-2011
+ alice | staff  |   2200 | 07-01-2011
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM my_salary_normal
+        WHERE f_leak(ymd::text,salary::text);
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Nested Loop
+   ->  Seq Scan on employee e
+         Filter: (ename = (getpgusername())::text)
+   ->  Materialize
+         ->  Seq Scan on salary s
+               Filter: ((eid = eid) AND f_leak((ymd)::text, (salary)::text))
+(6 rows)
+
+SELECT * FROM my_salary_secure
+    WHERE f_leak(ymd::text,salary::text);
+NOTICE:  06-01-2011 => 2000
+NOTICE:  07-01-2011 => 2025
+ ename | etitle | salary |    ymd     
+-------+--------+--------+------------
+ alice | staff  |   2000 | 06-01-2011
+ alice | staff  |   2025 | 07-01-2011
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM my_salary_secure
+        WHERE f_leak(ymd::text,salary::text);
+                                            QUERY PLAN                                            
+--------------------------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (s.eid = e.eid)
+   Join Filter: ((e.ename = (getpgusername())::text) AND f_leak((s.ymd)::text, (s.salary)::text))
+   ->  Seq Scan on salary s
+   ->  Hash
+         ->  Seq Scan on employee e
+(6 rows)
+
+SELECT * FROM my_salary_secure
+    WHERE f_leak(ymd::text,salary::text) AND ymd = '2011-06-01';
+NOTICE:  06-01-2011 => 2000
+ ename | etitle | salary |    ymd     
+-------+--------+--------+------------
+ alice | staff  |   2000 | 06-01-2011
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM my_salary_secure
+        WHERE f_leak(ymd::text,salary::text) AND ymd = '2011-06-01';
+                                            QUERY PLAN                                            
+--------------------------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (e.eid = s.eid)
+   Join Filter: ((e.ename = (getpgusername())::text) AND f_leak((s.ymd)::text, (s.salary)::text))
+   ->  Seq Scan on employee e
+   ->  Hash
+         ->  Bitmap Heap Scan on salary s
+               Recheck Cond: (ymd = '06-01-2011'::date)
+               ->  Bitmap Index Scan on salary_ymd
+                     Index Cond: (ymd = '06-01-2011'::date)
+(9 rows)
+
+\c -
+-- cleanups
+DROP ROLE IF EXISTS alice;
+DROP FUNCTION IF EXISTS f_leak(text);
+NOTICE:  function f_leak(text) does not exist, skipping
+DROP TABLE IF EXISTS employee CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to view my_salary_normal
+drop cascades to constraint salary_eid_fkey on table salary
+drop cascades to view my_salary_secure
diff --git a/src/test/regress/sql/select_views.sql b/src/test/regress/sql/select_views.sql
index 14f1be8..61151bc 100644
--- a/src/test/regress/sql/select_views.sql
+++ b/src/test/regress/sql/select_views.sql
@@ -8,3 +8,70 @@ SELECT * FROM street;
 SELECT name, #thepath FROM iexit ORDER BY 1, 2;
 
 SELECT * FROM toyemp WHERE name = 'sharon';
+
+--
+-- Test for leaky-vew
+--
+
+CREATE USER alice;
+CREATE FUNCTION f_leak(text, text)
+    RETURNS bool LANGUAGE 'plpgsql'
+    COST 0.00000001
+    AS 'begin raise notice ''% => %'', $1, $2; return true; end';
+
+CREATE TABLE employee (
+    eid      int primary key,
+	ename    text,
+    etitle   text
+);
+
+CREATE TABLE salary (
+    eid      int references employee(eid),
+    salary   int,
+    ymd      date
+);
+CREATE INDEX salary_ymd on salary (ymd);
+
+INSERT INTO employee (eid, ename, etitle)
+    VALUES (100, 'alice', 'staff'),
+           (110, 'bob', 'manager'),
+           (120, 'eve', 'chief');
+INSERT INTO salary (eid, salary, ymd)
+    VALUES (100, 2000, '2011-06-01'),
+           (100, 2025, '2011-07-01'),
+           (110, 2500, '2011-06-01'),
+           (110, 2400, '2011-07-01'),
+           (120, 2200, '2011-07-01');
+CREATE VIEW my_salary_normal AS
+    SELECT ename,etitle,salary,ymd
+    FROM employee e JOIN salary s ON s.eid = s.eid
+    WHERE ename = getpgusername();
+CREATE VIEW my_salary_secure WITH (security_barrier) AS
+    SELECT ename,etitle,salary,ymd
+    FROM employee e JOIN salary s ON e.eid = s.eid
+    WHERE ename = getpgusername();
+
+GRANT SELECT ON my_salary_normal TO public;
+GRANT SELECT ON my_salary_secure TO public;
+-- run leaky view
+SET SESSION AUTHORIZATION alice;
+
+SELECT * FROM my_salary_normal
+    WHERE f_leak(ymd::text,salary::text);
+EXPLAIN (COSTS OFF) SELECT * FROM my_salary_normal
+        WHERE f_leak(ymd::text,salary::text);
+
+SELECT * FROM my_salary_secure
+    WHERE f_leak(ymd::text,salary::text);
+EXPLAIN (COSTS OFF) SELECT * FROM my_salary_secure
+        WHERE f_leak(ymd::text,salary::text);
+
+SELECT * FROM my_salary_secure
+    WHERE f_leak(ymd::text,salary::text) AND ymd = '2011-06-01';
+EXPLAIN (COSTS OFF) SELECT * FROM my_salary_secure
+        WHERE f_leak(ymd::text,salary::text) AND ymd = '2011-06-01';
+\c -
+-- cleanups
+DROP ROLE IF EXISTS alice;
+DROP FUNCTION IF EXISTS f_leak(text);
+DROP TABLE IF EXISTS employee CASCADE;
\ No newline at end of file
