commit 4acf23d0308da002a62f0a58db8d11f5d0a71c29
Author: Zheng Li <zhelli@amazon.com>
Date:   Fri Feb 22 21:00:46 2019 +0000

    NOT IN to ANTI JOIN transformation
    
    The semantics of ANTI JOIN were created to match the semantics of NOT
    EXISTS, which enables NOT EXISTS subqueries to be efficiently executed
    as a kind of join.  NOT IN subqueries have different NULL semantics than
    NOT EXISTS, but since there is no special join operator for NOT IN it is
    generally executed as a nested sub-plan.  It is possible, however, to
    transform NOT IN to a correlated NOT EXISTS so that it can be executed
    it as an ANTI JOIN with additional correlated predicates.
    
    A general transformation from NOT IN to NOT EXISTS for the
    single-expression case (the multi-expression case is just ANDs of the
    single-expressions) is:
        t1.x NOT IN (SELECT t2.y from t2 where p) <=> NOT EXISTS (select 1
                from t2 where (y=x or y is NULL or x is NULL) and p).
    
    If x or y is non-nullable, we can safely remove the predicate "x
    is NULL" or "y is NULL", and if there is no predicate p,
    then "x is NULL" may be factored out of the subquery.
    Experiments show that if we can remove one or the other ORed
    predicates, or if we can factor out the "x is NULL", then
    execution is typically much faster.
    
    Basically, for the single expression case (we also handle the multi
    expression case), we try to do the following transformation:
    When p does not exist:
        t1.x not in (t2.y) => ANTI JOIN t1(Filter: x is not null), t2 on
        join condition: t1.x=t2.y or t2.y is null.
    When x is non-nullable:
        t1.x not in (t2.y where p) => ANTI JOIN t1, t2 on join condition:
        (t1.x=t2.y or t2.y is null) and p.
    
    We implemented a nullability test routine is_node_nonnullable().
    Currently it handles Var, TargetEntry, CoalesceExpr and Const. Outer
    joins are taken into consideration in the nullability test.
    
    We apply reduce_outer_joins() before the transformation so that the
    outer joins have an opportunity to be converted to inner joins prior
    to the transformation.
    
    Using this transformation, we measured performance improvements of
    two to three orders of magnitude on most queries in a development
    environment. In our performance experiments, table s (small) has 11
    rows, table l (large) has 1 million rows. s.n and l.n have NULL
    values. s.nn and l.nn are NOT NULL.
    
        s.n not in (l.n)       1150 ms -> 0.49 ms
        s.nn not in (l.nn)     1120 ms -> 0.45 ms
        l.n not in (l.n)   over 20 min -> 1700 ms
        l.nn not in (l.nn) over 20 min -> 220 ms
        l.n not in (s.n)         63 ms -> 750 ms
        l.nn not in (s.nn)       58 ms -> 46 ms
    
    For the only case that performance drops - l.n not in (s.n). It is
    likely to be resolved by ending the nested loop anti join early as
    soon as we find NULL inner tuple entry/entries that satisfies the
    join condition during execution. This is still under investigation.
    
    Authors: Zheng Li, Jim Finnerty
    Discussion:
    https://www.postgresql.org/message-id/1550706289606-0.post%40n3.nabble.com

diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bc81535905..e5091d2595 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -976,7 +976,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	 * preprocessing.
 	 */
 	if (hasOuterJoins)
-		reduce_outer_joins(root);
+		reduce_outer_joins(parse);
 
 	/*
 	 * If we have any RTE_RESULT relations, see if they can be deleted from
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index c721054787..d076d89227 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -33,6 +33,7 @@
 #include "optimizer/prep.h"
 #include "optimizer/subselect.h"
 #include "parser/parse_relation.h"
+#include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -1107,6 +1108,439 @@ inline_cte_walker(Node *node, inline_cte_walker_context *context)
 	return expression_tree_walker(node, inline_cte_walker, context);
 }
 
+/*
+ * check if relid appears on the NULL-padded side of an outer join.
+ */
+static bool
+on_null_sideof_outerjoin(Index relid, Node *jtnode)
+{
+	if(!jtnode)
+	{
+		return false;
+	}
+	if(IsA(jtnode, FromExpr))
+	{
+		ListCell		*lc;
+		FromExpr	*fexpr = (FromExpr *)jtnode;
+		foreach(lc, fexpr->fromlist)
+		{
+			if(on_null_sideof_outerjoin(relid, lfirst(lc)))
+			{
+				return true;
+			}
+		}
+	}
+	else if(IsA(jtnode, JoinExpr))
+	{
+		JoinExpr *expr = (JoinExpr *)jtnode;
+		Relids relids = NULL;
+		Relids relids2 = NULL;
+		switch(expr->jointype)
+		{
+		case JOIN_LEFT:
+			relids = get_relids_in_jointree(expr->rarg, true);
+
+			return bms_is_member(relid, relids) ||
+						on_null_sideof_outerjoin(relid, expr->larg);
+		case JOIN_FULL:
+			relids = get_relids_in_jointree(expr->larg, true);
+			relids2 = get_relids_in_jointree(expr->rarg, true);
+
+			return bms_is_member(relid, relids) ||
+						bms_is_member(relid, relids2);
+		case JOIN_RIGHT:
+			relids = get_relids_in_jointree(expr->larg, true);
+
+			return bms_is_member(relid, relids) ||
+						on_null_sideof_outerjoin(relid, expr->rarg);
+		case JOIN_INNER:
+			return on_null_sideof_outerjoin(relid, expr->larg) ||
+						on_null_sideof_outerjoin(relid, expr->rarg);
+
+		case JOIN_SEMI:
+		case JOIN_ANTI:
+		case JOIN_UNIQUE_OUTER:
+		case JOIN_UNIQUE_INNER:
+			/*
+			 * JOIN_SEMI and JOIN_ANTI should not appear yet since they
+			 * are introduced only by pull_up_sublinks and this routine is called
+			 * before we replace root->parse->jointree in pull_up_sublinks().
+			 * I don't think we will see JOIN_UNIQUE_OUTER or
+			 * JOIN_UNIQUE_INNER since they are only used internally in
+			 * the planner in a much later phase (in standard_join_search).
+			 */
+			break;
+
+		default:
+			elog(ERROR, "unrecognized join type: %d",
+							 (int) expr->jointype);
+			break;
+		}
+	}
+	/* should be RangeTblRef from the following three cases:
+	 * plain / under JOIN_INNER / on inner side of an outer join
+	 */
+	return false;
+}
+
+/* Returns a List of Nodes from the testexpr of an Any SubLink */
+static List *
+getTestExpr(SubLink *sublink)
+{
+	Node * testexpr = sublink->testexpr;
+	Assert(testexpr);
+
+	/* single expression */
+	if(IsA(testexpr, OpExpr))
+	{
+		OpExpr	*opexpr = (OpExpr *) testexpr;
+		Node	*testnode = linitial(opexpr->args);
+
+		return list_make1(testnode);
+	}
+	/* multi-expression */
+	else if(IsA(testexpr, BoolExpr))
+	{
+		BoolExpr	*bexpr = (BoolExpr *) testexpr;
+		ListCell		*lc;
+		Node		*node;
+		List			*result = NULL;
+
+		foreach(lc, bexpr->args)
+		{
+			node = lfirst(lc);
+			if(IsA(node, OpExpr))
+			{
+				OpExpr *expr = (OpExpr *) node;
+				result = lappend(result, linitial(expr->args));
+			}
+			else
+			{
+				elog(ERROR, "unrecognized node type for testexpr: %d",
+						(int) nodeTag(node));
+			}
+		}
+		return result;
+	}
+	else
+	{
+		elog(ERROR, "unrecognized node type for testexpr: %d",
+				(int) nodeTag(testexpr));
+	}
+}
+
+/*
+ * Returns true if the Node passed in is nonnullable. Currently handles Var,
+ * TargetEntry, CoaleseExpr and Const.
+ * TODO: Add more supporting cases.
+ * A Var is nonnullable if:
+ * it does not appear on  the NULL-padded side of an outer join and it has NOT NULL constraint.
+ * A CoalesceExpr is nonnullable if it has a non-null argument
+ */
+static bool
+is_node_nonnullable(Node * node, Query *parse)
+{
+	AttrNumber	attno = InvalidAttrNumber;
+	Oid				reloid;
+	Index			relid = 0;
+
+	switch (nodeTag(node))
+	{
+		case T_Var:
+		{
+			Var 					*var;
+			RangeTblEntry	*rte;
+
+			var = (Var *) node;
+			relid = var->varno;
+			attno = var->varattno;
+			rte = rt_fetch(relid, parse->rtable);
+			reloid = rte->relid;
+			break;
+		}
+		case T_TargetEntry:
+		{
+			TargetEntry	*te = (TargetEntry *)node;
+			switch(nodeTag(te->expr))
+			{
+				case T_Var:
+				{
+					attno = te->resorigcol;
+					reloid = te->resorigtbl;
+					relid = get_relid_oid(reloid, parse);
+					break;
+				}
+				/* recurse into is_node_nonnullable for other types of Node */
+				default:
+					return is_node_nonnullable((Node *)te->expr, parse);
+			}
+			break;
+		}
+		case T_CoalesceExpr:
+		{
+			ListCell   			*arg;
+			CoalesceExpr	*cexpr = (CoalesceExpr *)node;
+
+			/* handle COALESCE Function by looking for non-null argument */
+			foreach(arg, cexpr->args)
+			{
+				Node	*e = lfirst(arg);
+
+				/* recurse into is_node_nonnullable */
+				if (is_node_nonnullable(e, parse))
+				{
+					return true;
+				}
+			}
+			break;
+		}
+		case T_Const:
+		{
+			if(!((Const *) node)->constisnull)
+			{
+				return true;
+			}
+			break;
+		}
+		/*
+		 * TODO: handle more cases to make the nullability test more accurate
+		 * Assume unhandled cases are nullable.
+		 */
+		default:
+			return false;
+	}
+
+	/* check if the Var appears on the NULL-padded side of an outer join */
+	if(relid &&
+		on_null_sideof_outerjoin(relid, (Node *)parse->jointree))
+	{
+		return false;
+	}
+	/* check for NOT NULL Constraint */
+	if(attno != InvalidAttrNumber &&
+			get_attnotnull(reloid, attno))
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * Returns true if at least one Node in the input list is non-nullable
+ */
+static bool
+list_hasnonnullable(List * list, Query *parse)
+{
+	ListCell	*lc;
+	Node	*node;
+
+	foreach(lc, list)
+	{
+		node = lfirst(lc);
+		if(is_node_nonnullable(node, parse))
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+/* Try to reduce outer joins if there is one in the Query */
+static void
+reduce_outer_joins_NOT_IN(Query *parse)
+{
+	ListCell				*lc;
+	RangeTblEntry	*rte;
+
+	foreach(lc, parse->rtable)
+	{
+		rte = (RangeTblEntry *) lfirst(lc);
+		/* try to reduce outer joins if there is one */
+		if (rte->rtekind == RTE_JOIN &&
+				IS_OUTER_JOIN(rte->jointype))
+		{
+			reduce_outer_joins(parse);
+			return;
+		}
+	}
+}
+
+/*
+* Convert x NOT IN (y) to ANTI JOIN. See detailed comments below.
+*/
+static bool
+convert_NOT_IN_to_join(PlannerInfo *root, Node **quals,
+									SubLink *sublink, List *subquery_vars,
+									Node **pullout)
+{
+	Query			*parse = root->parse;
+	NullTest			*nt;
+	TargetEntry	*te;
+	List				*testnodes = getTestExpr(sublink);
+	bool				 isxnonnullable;
+	bool				 isynonnullable;
+	Query			*subselect = (Query *) sublink->subselect;
+	bool				 hassubquals = subselect->jointree->quals ? true: false;
+
+	/*
+	 * Try reduce_outer_joins since outer join affects the nullability test that's coming up next.
+	 * We have to call reduce_outer_joins for outer and inner query separately because we
+	 * don't have a global range table yet.
+	 */
+	reduce_outer_joins_NOT_IN(parse);
+
+	reduce_outer_joins_NOT_IN(subselect);
+
+	Assert(testnodes);
+	isxnonnullable =
+					list_hasnonnullable(testnodes, parse);
+	isynonnullable =
+					list_hasnonnullable(subselect->targetList, subselect);
+	/*
+	 * We consider the conversion: t1.x not in (t2.y where p) => ANTI JOIN
+	 * t1, t2 on join condition (t1.x=t2.y or t2.y is null or t1.x is null) and p.
+	 * This is logically equivalent to NOT EXISTS (select 1 from t2 where
+	 * (t1.x=t2.y or t2.y is null or t1.x is null) and p).
+	 * In general, the above conversion does not result in a faster plan due to the
+	 * multiple ORs in the join condition. However, if we can remove t1.x is null (in
+	 * the case when x is nonnullable) or pull "OR x is null" out as "AND x is not null"
+	 * (in the case where p does not exist and x is not a multi-expr (x1,x2...xn))
+	 * we will have a much faster plan after the conversion because it's eligible to use
+	 * bitmap index scan and bitmap ORs or index scan(if y is nonnullable too) by
+	 * downstream optimizations.
+	 * Basically we aim to do the following conversions:
+	 * when p does not exist:
+	 * t1.x not in (t2.y) => ANTI JOIN t1(Filter: x is not null), t2 on join
+	 * condition: t1.x=t2.y or t2.y is null.
+	 * when x is nonnullable:
+	 * t1.x not in (t2.y where p) => ANTI JOIN t1, t2 on join
+	 * condition: (t1.x=t2.y or t2.y is null) and p.
+	 * So do NOT convert if (p exists and the test expr x is nullable) or
+	 * if x is a multi-expr (x1,x2,...xn) and has no nonnullable
+	 * var. In other words, we only do the conversion when
+	 * x is nonnull or (p does not exist and x is a single test expression
+	 * (i.e. quals is of type OpExpr. If x is a multiple expression,
+	 * qulas will be of type BoolExpr))
+	 */
+	if(!(isxnonnullable ||
+		(!hassubquals && IsA(*quals, OpExpr))))
+		return false;
+
+	/* If there is only one node in the testexpr, do the following conversion:
+	 * when p does not exist:
+	 * t1.x not in (t2.y) => ANTI JOIN t1(Filter: x is not null), t2 on
+	 * join condition: t1.x=t2.y or t2.y is null.
+	 * when x is nonnullable:
+	 * t1.x not in (t2.y where p) => ANTI JOIN t1, t2 on join condition:
+	 * (t1.x=t2.y or t2.y is null) and p.
+	 * Notice the case where p exits and x is nullable has been ruled out earlier.
+	 */
+	if(IsA(*quals, OpExpr))
+	{
+		/* add "OR y IS NULL" if y is nullable */
+		if(!isynonnullable)
+		{
+			/* make expr y IS NULL */
+			nt = makeNode(NullTest);
+			nt->arg = (Expr *)linitial(subquery_vars);
+			nt->nulltesttype = IS_NULL;
+			nt->argisrow = false;
+			nt->location = -1;
+
+			/* make orclause (x = y OR y IS NULL) */
+			*quals = (Node *)make_orclause(list_make2(*quals,
+								(Node *)nt));
+		}
+		/* pullout "AND x IS NOT NULL" if x is nullable */
+		if(!isxnonnullable)
+		{
+			/* make expr x IS NOT NULL */
+			nt = makeNode(NullTest);
+			nt->arg = (Expr *)linitial(testnodes);
+			nt->argisrow = false;
+			nt->nulltesttype = IS_NOT_NULL;
+			nt->location = -1;
+			*pullout = (Node *)nt;
+		}
+	}
+	/*
+	 * When there are multiple nodes in the testexpr:
+	 * i.e. (x1, x2, ... xn) not in (y1, y2, ... yn ) convert it to
+	 * ANTI JOIN t1, t2 on join condition: (t1.x1 = t2.y1 or
+	 * t2.y1 is NULL or t1.x1 is null) and ...(t1.xi = t2.yi or t2.yi is NULL) ...
+	 * and (t1.xn = t2.yn or t2.yn is NULL or t1.xn is NULL). Notice there will
+	 * be at least one xi that is nonnullable due to the nullability test earlier.
+	 * Downstream optimization will pick the shorter clause
+	 * (t1.xi = t2.yi or t2.yi is NULL) for bitmap index scan on t2 and bitmap OR
+	 * (or index scan on t2 if t2.yi is also nonnullable).
+	 */
+	else if(IsA(*quals, BoolExpr))
+	{
+		ListCell    *qualc;
+		ListCell    *xc = list_head(testnodes);
+		ListCell    *yc = list_head(subquery_vars);
+		ListCell    *ytlc = list_head(subselect->targetList);
+		List        *quallist = ((BoolExpr *)*quals)->args;
+		Node		*tempnode = NULL;
+
+		/* Reconstruct quals in the loop */
+		*quals = NULL;
+		foreach(qualc, quallist)
+		{
+			te = (TargetEntry *)lfirst(ytlc);
+			isynonnullable = is_node_nonnullable((Node*)te, subselect);
+			/* x_i = y_i */
+			tempnode = lfirst(qualc);
+			/* append y_i IS NULL if y_i nonnullable */
+			if(!isynonnullable)
+			{
+				/* make expr y_i IS NULL */
+				nt = makeNode(NullTest);
+				nt->arg = (Expr *)lfirst(yc);
+				nt->nulltesttype = IS_NULL;
+				nt->argisrow = false;
+				nt->location = -1;
+
+				/* make orclause (x_i = y_i OR y_i IS NULL) */
+				tempnode = (Node *)make_orclause(list_make2(lfirst(qualc),
+										(Node *)nt));
+			}
+			isxnonnullable = is_node_nonnullable(lfirst(xc), parse);
+			/*
+			 * append x_i IS NULL if x_i nonnullable, can't pull it out as
+			 * x_i is NOT NULL due to multiple AND clauses.
+			 */
+			if(!isxnonnullable)
+			{
+				/* make expr x_i IS NULL */
+				nt = makeNode(NullTest);
+				nt->arg = (Expr *)lfirst(xc);
+				nt->argisrow = false;
+				nt->nulltesttype = IS_NULL;
+				nt->location = -1;
+				/* make orclause (x_i = y_i OR y_i IS NULL OR x_i IS NULL) */
+				tempnode = (Node *)make_orclause(list_make2(tempnode,
+															(Node *)nt));
+			}
+			/*
+			 * Now append tempnode to quals as one andclause.
+			 * (x_i = y_i OR y_i IS NULL OR x_i IS NULL) AND
+			 * (x_j = y_j OR y_j IS NULL OR x_j IS NULL)...
+			 */
+			*quals = (Node *)make_andclause(list_make2(*quals, tempnode));
+			xc = lnext(xc);
+			yc = lnext(yc);
+			ytlc = lnext(ytlc);
+		}
+	}
+	/* quals should be either OpExpr or BoolExpr, otherwise don't convert */
+	else
+	{
+		return false;
+	}
+	return true;
+}
 
 /*
  * convert_ANY_sublink_to_join: try to convert an ANY SubLink to a join
@@ -1144,6 +1578,7 @@ inline_cte_walker(Node *node, inline_cte_walker_context *context)
  */
 JoinExpr *
 convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink,
+							bool under_not, Node **pullout,
 							Relids available_rels)
 {
 	JoinExpr   *result;
@@ -1224,11 +1659,22 @@ convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	 */
 	quals = convert_testexpr(root, sublink->testexpr, subquery_vars);
 
+	/*
+	 * Try converting x NOT IN (y) to ANTI JOIN.
+	 */
+	if(under_not &&
+			!convert_NOT_IN_to_join(root, &quals,
+					sublink, subquery_vars, pullout))
+	{
+		return NULL;
+	}
+
 	/*
 	 * And finally, build the JoinExpr node.
 	 */
 	result = makeNode(JoinExpr);
-	result->jointype = JOIN_SEMI;
+	/* NOT IN will be converted to ANTI JOIN */
+	result->jointype = under_not ? JOIN_ANTI : JOIN_SEMI;
 	result->isNatural = false;
 	result->larg = NULL;		/* caller must fill this in */
 	result->rarg = (Node *) rtr;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index aebe162713..184d71cd7c 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -104,7 +104,7 @@ static Query *pullup_replace_vars_subquery(Query *query,
 static reduce_outer_joins_state *reduce_outer_joins_pass1(Node *jtnode);
 static void reduce_outer_joins_pass2(Node *jtnode,
 						 reduce_outer_joins_state *state,
-						 PlannerInfo *root,
+						 Query *parse,
 						 Relids nonnullable_rels,
 						 List *nonnullable_vars,
 						 List *forced_null_vars);
@@ -256,6 +256,13 @@ pull_up_sublinks_jointree_recurse(PlannerInfo *root, Node *jtnode,
 		}
 		/* Build the replacement FromExpr; no quals yet */
 		newf = makeFromExpr(newfromlist, NULL);
+		/*
+		 * Replace parse->jointree with newf now because we might modify join types
+		 * during reduce_outer_joins() in convert_NOT_IN_to_join() which is called
+		 * in pull_up_sublinks_qual_recurse() that's coming up next.
+		 */
+		newf->quals = f->quals;
+		root->parse->jointree = newf;
 		/* Set up a link representing the rebuilt jointree */
 		jtlink = (Node *) newf;
 		/* Now process qual --- all children are available for use */
@@ -388,7 +395,7 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
 		/* Is it a convertible ANY or EXISTS clause? */
 		if (sublink->subLinkType == ANY_SUBLINK)
 		{
-			if ((j = convert_ANY_sublink_to_join(root, sublink,
+			if ((j = convert_ANY_sublink_to_join(root, sublink, false, NULL,
 												 available_rels1)) != NULL)
 			{
 				/* Yes; insert the new join node into the join tree */
@@ -414,7 +421,7 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
 				return NULL;
 			}
 			if (available_rels2 != NULL &&
-				(j = convert_ANY_sublink_to_join(root, sublink,
+				(j = convert_ANY_sublink_to_join(root, sublink, false, NULL,
 												 available_rels2)) != NULL)
 			{
 				/* Yes; insert the new join node into the join tree */
@@ -560,6 +567,69 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
 					return NULL;
 				}
 			}
+			else if (sublink->subLinkType == ANY_SUBLINK)
+			{
+				Node *pullout = NULL;
+
+				if ((j = convert_ANY_sublink_to_join(root, sublink, true, &pullout,
+													 available_rels1)) != NULL)
+				{
+					/* Yes; insert the new join node into the join tree */
+					j->larg = *jtlink1;
+					*jtlink1 = (Node *) j;
+					/* Recursively process pulled-up jointree nodes */
+					j->rarg = pull_up_sublinks_jointree_recurse(root,
+																j->rarg,
+																&child_rels);
+
+					/*
+					 * Now recursively process the pulled-up quals.  Any inserted
+					 * joins can get stacked onto either j->larg or j->rarg,
+					 * depending on which rels they reference.
+					 */
+					j->quals = pull_up_sublinks_qual_recurse(root,
+															 j->quals,
+															 &j->larg,
+															 available_rels1,
+															 &j->rarg,
+															 child_rels);
+					/*
+					 * Return pullout predicate (x is NOT NULL) if it's not null,
+					 * otherwise return NULL representing constant TRUE.
+					 */
+					return pullout? pullout : NULL;
+				}
+				if (available_rels2 != NULL &&
+					(j = convert_ANY_sublink_to_join(root, sublink, true, &pullout,
+													 available_rels2)) != NULL)
+				{
+					Node *pullout = NULL;
+					/* Yes; insert the new join node into the join tree */
+					j->larg = *jtlink2;
+					*jtlink2 = (Node *) j;
+					/* Recursively process pulled-up jointree nodes */
+					j->rarg = pull_up_sublinks_jointree_recurse(root,
+																j->rarg,
+																&child_rels);
+
+					/*
+					 * Now recursively process the pulled-up quals.  Any inserted
+					 * joins can get stacked onto either j->larg or j->rarg,
+					 * depending on which rels they reference.
+					 */
+					j->quals = pull_up_sublinks_qual_recurse(root,
+															 j->quals,
+															 &j->larg,
+															 available_rels2,
+															 &j->rarg,
+															 child_rels);
+					/*
+					 * Return pullout predicate (x is NOT NULL) if it's not null,
+					 * otherwise return NULL representing constant TRUE.
+					 */
+					return pullout? pullout : NULL;
+				}
+			}
 		}
 		/* Else return it unmodified */
 		return node;
@@ -2377,7 +2447,7 @@ flatten_simple_union_all(PlannerInfo *root)
  * alias-var expansion).
  */
 void
-reduce_outer_joins(PlannerInfo *root)
+reduce_outer_joins(Query *parse)
 {
 	reduce_outer_joins_state *state;
 
@@ -2390,14 +2460,14 @@ reduce_outer_joins(PlannerInfo *root)
 	 * join(s) below each side of each join clause. The second pass examines
 	 * qual clauses and changes join types as it descends the tree.
 	 */
-	state = reduce_outer_joins_pass1((Node *) root->parse->jointree);
+	state = reduce_outer_joins_pass1((Node *) parse->jointree);
 
 	/* planner.c shouldn't have called me if no outer joins */
 	if (state == NULL || !state->contains_outer)
 		elog(ERROR, "so where are the outer joins?");
 
-	reduce_outer_joins_pass2((Node *) root->parse->jointree,
-							 state, root, NULL, NIL, NIL);
+	reduce_outer_joins_pass2((Node *) parse->jointree,
+							 state, parse, NULL, NIL, NIL);
 }
 
 /*
@@ -2480,7 +2550,7 @@ reduce_outer_joins_pass1(Node *jtnode)
 static void
 reduce_outer_joins_pass2(Node *jtnode,
 						 reduce_outer_joins_state *state,
-						 PlannerInfo *root,
+						 Query *parse,
 						 Relids nonnullable_rels,
 						 List *nonnullable_vars,
 						 List *forced_null_vars)
@@ -2520,7 +2590,7 @@ reduce_outer_joins_pass2(Node *jtnode,
 			reduce_outer_joins_state *sub_state = lfirst(s);
 
 			if (sub_state->contains_outer)
-				reduce_outer_joins_pass2(lfirst(l), sub_state, root,
+				reduce_outer_joins_pass2(lfirst(l), sub_state, parse,
 										 pass_nonnullable_rels,
 										 pass_nonnullable_vars,
 										 pass_forced_null_vars);
@@ -2632,7 +2702,7 @@ reduce_outer_joins_pass2(Node *jtnode,
 		/* Apply the jointype change, if any, to both jointree node and RTE */
 		if (rtindex && jointype != j->jointype)
 		{
-			RangeTblEntry *rte = rt_fetch(rtindex, root->parse->rtable);
+			RangeTblEntry *rte = rt_fetch(rtindex, parse->rtable);
 
 			Assert(rte->rtekind == RTE_JOIN);
 			Assert(rte->jointype == j->jointype);
@@ -2717,7 +2787,7 @@ reduce_outer_joins_pass2(Node *jtnode,
 					pass_nonnullable_vars = NIL;
 					pass_forced_null_vars = NIL;
 				}
-				reduce_outer_joins_pass2(j->larg, left_state, root,
+				reduce_outer_joins_pass2(j->larg, left_state, parse,
 										 pass_nonnullable_rels,
 										 pass_nonnullable_vars,
 										 pass_forced_null_vars);
@@ -2739,7 +2809,7 @@ reduce_outer_joins_pass2(Node *jtnode,
 					pass_nonnullable_vars = NIL;
 					pass_forced_null_vars = NIL;
 				}
-				reduce_outer_joins_pass2(j->rarg, right_state, root,
+				reduce_outer_joins_pass2(j->rarg, right_state, parse,
 										 pass_nonnullable_rels,
 										 pass_nonnullable_vars,
 										 pass_forced_null_vars);
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 4130514952..96563bb180 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -28,6 +28,7 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "partitioning/partbounds.h"
+#include "parser/parsetree.h"
 #include "utils/hsearch.h"
 
 
@@ -65,6 +66,23 @@ static void build_child_join_reltarget(PlannerInfo *root,
 						   AppendRelInfo **appinfos);
 
 
+/* get RangeTable index from Oid of a relation and the Query Tree */
+Index get_relid_oid(Oid reloid, Query *parse)
+{
+	Index				 rti;
+	RangeTblEntry	*rte;
+
+	for(rti = 1; rti <  list_length(parse->rtable) + 1; rti++)
+	{
+		rte = rt_fetch(rti, parse->rtable);
+		if(rte->relid == reloid)
+		{
+			return rti;
+		}
+	}
+	return 0;
+}
+
 /*
  * setup_simple_rel_arrays
  *	  Prepare the arrays we use for quickly accessing base relations.
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index e88c45d268..0b9436628a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -878,6 +878,35 @@ get_atttypetypmodcoll(Oid relid, AttrNumber attnum,
 	ReleaseSysCache(tp);
 }
 
+/*
+ * get_attnotnull
+ *		Given the relation id and the attribute number,
+ *		return the "attnotnull" field from the attribute relation.
+ */
+bool
+get_attnotnull(Oid relid, AttrNumber attnum)
+{
+	HeapTuple	  tp;
+	Form_pg_attribute att_tup;
+	bool              result;
+
+	tp = SearchSysCache2(ATTNUM,
+			ObjectIdGetDatum(relid),
+			Int16GetDatum(attnum));
+
+	if (!HeapTupleIsValid(tp))
+	{
+		elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+			 attnum, relid);
+	}
+
+	att_tup = (Form_pg_attribute) GETSTRUCT(tp);
+
+	result = att_tup->attnotnull;
+	ReleaseSysCache(tp);
+	return result;
+}
+
 /*				---------- COLLATION CACHE ----------					 */
 
 /*
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 574bb85b50..5e85be2eca 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -275,6 +275,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path,
 /*
  * prototypes for relnode.c
  */
+extern Index get_relid_oid(Oid reloid, Query *parse);
 extern void setup_simple_rel_arrays(PlannerInfo *root);
 extern void setup_append_rel_array(PlannerInfo *root);
 extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index a9b2c9026c..ec8af5abf4 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -26,7 +26,7 @@ extern void pull_up_sublinks(PlannerInfo *root);
 extern void inline_set_returning_functions(PlannerInfo *root);
 extern void pull_up_subqueries(PlannerInfo *root);
 extern void flatten_simple_union_all(PlannerInfo *root);
-extern void reduce_outer_joins(PlannerInfo *root);
+extern void reduce_outer_joins(Query *parse);
 extern void remove_useless_result_rtes(PlannerInfo *root);
 extern Relids get_relids_in_jointree(Node *jtnode, bool include_joins);
 extern Relids get_relids_for_join(Query *query, int joinrelid);
diff --git a/src/include/optimizer/subselect.h b/src/include/optimizer/subselect.h
index 2d2c3bcbc0..ea2abaa131 100644
--- a/src/include/optimizer/subselect.h
+++ b/src/include/optimizer/subselect.h
@@ -19,6 +19,8 @@
 extern void SS_process_ctes(PlannerInfo *root);
 extern JoinExpr *convert_ANY_sublink_to_join(PlannerInfo *root,
 							SubLink *sublink,
+							bool under_not,
+							Node ** pullout,
 							Relids available_rels);
 extern JoinExpr *convert_EXISTS_sublink_to_join(PlannerInfo *root,
 							   SubLink *sublink,
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 16b0b1d2dc..71370d8877 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -89,6 +89,7 @@ extern AttrNumber get_attnum(Oid relid, const char *attname);
 extern Oid	get_atttype(Oid relid, AttrNumber attnum);
 extern void get_atttypetypmodcoll(Oid relid, AttrNumber attnum,
 					  Oid *typid, int32 *typmod, Oid *collid);
+extern bool get_attnotnull(Oid relid, AttrNumber attnum);
 extern char *get_collation_name(Oid colloid);
 extern char *get_constraint_name(Oid conoid);
 extern char *get_language_name(Oid langoid, bool missing_ok);
diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
index cc3f5f3737..1a36d6c150 100644
--- a/src/test/regress/expected/subselect.out
+++ b/src/test/regress/expected/subselect.out
@@ -1310,3 +1310,1232 @@ select * from x for update;
    Output: subselect_tbl.f1, subselect_tbl.f2, subselect_tbl.f3
 (2 rows)
 
+-- test NON IN to ANTI JOIN conversion
+CREATE TABLE s (u INTEGER NOT NULL, n INTEGER NULL, nn INTEGER NOT NULL, p VARCHAR(128) NULL);
+insert into s (u, n, nn, p)
+    select
+    generate_series(1,3) as u,
+	generate_series(1,3) as n,
+	generate_series(1,3) as nn,
+	'foo' as p;
+insert into s values(1000002, 1000002, 1000002, 'foofoo');
+UPDATE s set n = NULL WHERE n = 3;
+CREATE UNIQUE INDEX s_u ON s (u);
+CREATE INDEX s_n ON s (n);
+CREATE INDEX s_nn ON s (nn);
+CREATE TABLE l (u INTEGER NOT NULL, n INTEGER NULL, nn INTEGER NOT NULL, p VARCHAR(128) NULL);
+insert into l (u, n, nn, p)
+	select
+    generate_series(1,10000 ) as u,
+	generate_series(1,10000 ) as n,
+	generate_series(1,10000 ) as nn,
+	'bar' as p;
+UPDATE l set n = NULL WHERE n = 7;
+CREATE UNIQUE INDEX l_u ON l (u);
+CREATE INDEX l_n ON l (n);
+CREATE INDEX l_nn ON l (nn);
+CREATE TABLE s1 (u INTEGER NOT NULL, n INTEGER NULL, nn INTEGER NOT NULL, p VARCHAR(128) NULL);
+insert into s1 (u, n, nn, p)
+    select
+    generate_series(1,3) as u,
+	generate_series(1,3) as n,
+	generate_series(1,3) as nn,
+	'foo' as p;
+insert into s1 values(1000003, 1000003, 1000003, 'foofoo');
+insert into s1 values(1003, 1003, 1003, 'foofoo');
+UPDATE s1 set n = NULL WHERE n = 3;
+-- test single expression
+explain (costs false) select * from s where n not in (select n from l);
+                    QUERY PLAN                    
+--------------------------------------------------
+ Nested Loop Anti Join
+   ->  Seq Scan on s
+         Filter: (n IS NOT NULL)
+   ->  Bitmap Heap Scan on l
+         Recheck Cond: ((s.n = n) OR (n IS NULL))
+         ->  BitmapOr
+               ->  Bitmap Index Scan on l_n
+                     Index Cond: (n = s.n)
+               ->  Bitmap Index Scan on l_n
+                     Index Cond: (n IS NULL)
+(10 rows)
+
+select * from s where n not in (select n from l);
+ u | n | nn | p 
+---+---+----+---
+(0 rows)
+
+explain (costs false) select * from s where u not in (select u from l);
+              QUERY PLAN              
+--------------------------------------
+ Nested Loop Anti Join
+   ->  Seq Scan on s
+   ->  Index Only Scan using l_u on l
+         Index Cond: (u = s.u)
+(4 rows)
+
+select * from s where u not in (select u from l);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+ 1000002 | 1000002 | 1000002 | foofoo
+(1 row)
+
+explain (costs false) select * from s where 3*n not in (select n from l);
+                       QUERY PLAN                       
+--------------------------------------------------------
+ Nested Loop Anti Join
+   ->  Seq Scan on s
+         Filter: ((3 * n) IS NOT NULL)
+   ->  Bitmap Heap Scan on l
+         Recheck Cond: (((3 * s.n) = n) OR (n IS NULL))
+         ->  BitmapOr
+               ->  Bitmap Index Scan on l_n
+                     Index Cond: (n = (3 * s.n))
+               ->  Bitmap Index Scan on l_n
+                     Index Cond: (n IS NULL)
+(10 rows)
+
+select * from s where 3*n not in (select n from l);
+ u | n | nn | p 
+---+---+----+---
+(0 rows)
+
+explain (costs false) select * from s where n not in (select 3*n from l);
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Nested Loop Anti Join
+   Join Filter: ((s.n = (3 * l.n)) OR ((3 * l.n) IS NULL))
+   ->  Seq Scan on s
+         Filter: (n IS NOT NULL)
+   ->  Materialize
+         ->  Seq Scan on l
+(6 rows)
+
+select * from s where n not in (select 3*n from l);
+ u | n | nn | p 
+---+---+----+---
+(0 rows)
+
+-- test multi expression
+explain (costs false) select * from s where (n,u) not in (select n,u from l);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Nested Loop Anti Join
+   ->  Seq Scan on s
+   ->  Index Scan using l_u on l
+         Index Cond: (u = s.u)
+         Filter: ((s.n = n) OR (n IS NULL) OR (s.n IS NULL))
+(5 rows)
+
+select * from s where (n,u) not in (select n,u from l);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+ 1000002 | 1000002 | 1000002 | foofoo
+(1 row)
+
+explain (costs false) select * from s where (u, nn) not in (select u, nn from l);
+           QUERY PLAN            
+---------------------------------
+ Nested Loop Anti Join
+   ->  Seq Scan on s
+   ->  Index Scan using l_u on l
+         Index Cond: (u = s.u)
+         Filter: (s.nn = nn)
+(5 rows)
+
+select * from s where (u, nn) not in (select u, nn from l);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+ 1000002 | 1000002 | 1000002 | foofoo
+(1 row)
+
+explain (costs false) select * from s where (n,u) not in (select u,n from l);
+                    QUERY PLAN                    
+--------------------------------------------------
+ Nested Loop Anti Join
+   ->  Seq Scan on s
+   ->  Bitmap Heap Scan on l
+         Recheck Cond: ((s.u = n) OR (n IS NULL))
+         Filter: ((s.n = u) OR (s.n IS NULL))
+         ->  BitmapOr
+               ->  Bitmap Index Scan on l_n
+                     Index Cond: (n = s.u)
+               ->  Bitmap Index Scan on l_n
+                     Index Cond: (n IS NULL)
+(10 rows)
+
+select * from s where (n,u) not in (select u,n from l);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+ 1000002 | 1000002 | 1000002 | foofoo
+(1 row)
+
+explain (costs false) select * from s where (n,u,nn) not in (select u,n,nn from l);
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Nested Loop Anti Join
+   ->  Seq Scan on s
+   ->  Bitmap Heap Scan on l
+         Recheck Cond: ((s.nn = nn) AND ((s.u = n) OR (n IS NULL)))
+         Filter: ((s.n = u) OR (s.n IS NULL))
+         ->  BitmapAnd
+               ->  Bitmap Index Scan on l_nn
+                     Index Cond: (nn = s.nn)
+               ->  BitmapOr
+                     ->  Bitmap Index Scan on l_n
+                           Index Cond: (n = s.u)
+                     ->  Bitmap Index Scan on l_n
+                           Index Cond: (n IS NULL)
+(13 rows)
+
+select * from s where (n,u,nn) not in (select u,n,nn from l);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+ 1000002 | 1000002 | 1000002 | foofoo
+(1 row)
+
+explain (costs false) select * from s where (n,u,nn) not in (select u,n,nn from l where u > 1000);
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Nested Loop Anti Join
+   ->  Seq Scan on s
+   ->  Bitmap Heap Scan on l
+         Recheck Cond: ((s.nn = nn) AND ((s.u = n) OR (n IS NULL)))
+         Filter: ((u > 1000) AND ((s.n = u) OR (s.n IS NULL)))
+         ->  BitmapAnd
+               ->  Bitmap Index Scan on l_nn
+                     Index Cond: (nn = s.nn)
+               ->  BitmapOr
+                     ->  Bitmap Index Scan on l_n
+                           Index Cond: (n = s.u)
+                     ->  Bitmap Index Scan on l_n
+                           Index Cond: (n IS NULL)
+(13 rows)
+
+select * from s where (n,u,nn) not in (select u,n,nn from l where u > 1000);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+       1 |       1 |       1 | foo
+       2 |       2 |       2 | foo
+ 1000002 | 1000002 | 1000002 | foofoo
+       3 |         |       3 | foo
+(4 rows)
+
+-- test multi-table
+explain (costs false) select count(*) from s, l where s.n not in (select n from l);
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Aggregate
+   ->  Nested Loop
+         ->  Seq Scan on l
+         ->  Materialize
+               ->  Nested Loop Anti Join
+                     ->  Seq Scan on s
+                           Filter: (n IS NOT NULL)
+                     ->  Bitmap Heap Scan on l l_1
+                           Recheck Cond: ((s.n = n) OR (n IS NULL))
+                           ->  BitmapOr
+                                 ->  Bitmap Index Scan on l_n
+                                       Index Cond: (n = s.n)
+                                 ->  Bitmap Index Scan on l_n
+                                       Index Cond: (n IS NULL)
+(14 rows)
+
+select count(*) from s, l where s.n not in (select n from l);
+ count 
+-------
+     0
+(1 row)
+
+explain (costs false) select count(*) from s, l where s.nn not in (select nn from l);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Nested Loop
+         ->  Seq Scan on l
+         ->  Materialize
+               ->  Nested Loop Anti Join
+                     ->  Seq Scan on s
+                     ->  Index Only Scan using l_nn on l l_1
+                           Index Cond: (nn = s.nn)
+(8 rows)
+
+select count(*) from s, l where s.nn not in (select nn from l);
+ count 
+-------
+ 10000
+(1 row)
+
+-- test null padded results from outer join
+explain (costs false) select * from s where n not in (select s.nn from l left join s on l.nn = s.nn);
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Nested Loop Anti Join
+   Join Filter: ((s.n = s_1.nn) OR (s_1.nn IS NULL))
+   ->  Seq Scan on s
+         Filter: (n IS NOT NULL)
+   ->  Hash Left Join
+         Hash Cond: (l.nn = s_1.nn)
+         ->  Seq Scan on l
+         ->  Hash
+               ->  Seq Scan on s s_1
+(9 rows)
+
+select * from s where n not in (select s.nn from l left join s on l.nn = s.nn);
+ u | n | nn | p 
+---+---+----+---
+(0 rows)
+
+explain (costs false) select * from s where n not in (select s.nn from s right join l on s.nn = l.nn);
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Nested Loop Anti Join
+   Join Filter: ((s.n = s_1.nn) OR (s_1.nn IS NULL))
+   ->  Seq Scan on s
+         Filter: (n IS NOT NULL)
+   ->  Hash Left Join
+         Hash Cond: (l.nn = s_1.nn)
+         ->  Seq Scan on l
+         ->  Hash
+               ->  Seq Scan on s s_1
+(9 rows)
+
+select * from s where n not in (select s.nn from s right join l on s.nn = l.nn);
+ u | n | nn | p 
+---+---+----+---
+(0 rows)
+
+explain (costs false) select count(*) from s right join l on s.nn = l.nn where l.nn not in (select nn from s);
+                QUERY PLAN                 
+-------------------------------------------
+ Aggregate
+   ->  Hash Left Join
+         Hash Cond: (l.nn = s.nn)
+         ->  Hash Anti Join
+               Hash Cond: (l.nn = s_1.nn)
+               ->  Seq Scan on l
+               ->  Hash
+                     ->  Seq Scan on s s_1
+         ->  Hash
+               ->  Seq Scan on s
+(10 rows)
+
+select count(*) from s right join l on s.nn = l.nn where l.nn not in (select nn from s);
+ count 
+-------
+  9997
+(1 row)
+
+explain (costs false) select count(*) from s right join l on s.nn = l.nn where s.nn not in (select nn from s);
+                  QUERY PLAN                  
+----------------------------------------------
+ Aggregate
+   ->  Nested Loop
+         ->  Hash Anti Join
+               Hash Cond: (s.nn = s_1.nn)
+               ->  Seq Scan on s
+                     Filter: (nn IS NOT NULL)
+               ->  Hash
+                     ->  Seq Scan on s s_1
+         ->  Bitmap Heap Scan on l
+               Recheck Cond: (nn = s.nn)
+               ->  Bitmap Index Scan on l_nn
+                     Index Cond: (nn = s.nn)
+(12 rows)
+
+select count(*) from s right join l on s.nn = l.nn where s.nn not in (select nn from s);
+ count 
+-------
+     0
+(1 row)
+
+explain (costs false) select count(*) from s right join l on s.nn=l.nn where l.nn not in (select l.nn from l left join s on l.nn = s.nn);
+                       QUERY PLAN                       
+--------------------------------------------------------
+ Aggregate
+   ->  Hash Left Join
+         Hash Cond: (l.nn = s.nn)
+         ->  Hash Anti Join
+               Hash Cond: (l.nn = l_1.nn)
+               ->  Seq Scan on l
+               ->  Hash
+                     ->  Hash Left Join
+                           Hash Cond: (l_1.nn = s_1.nn)
+                           ->  Seq Scan on l l_1
+                           ->  Hash
+                                 ->  Seq Scan on s s_1
+         ->  Hash
+               ->  Seq Scan on s
+(14 rows)
+
+select count(*) from s right join l on s.nn=l.nn where l.nn not in (select l.nn from l left join s on l.nn = s.nn);
+ count 
+-------
+     0
+(1 row)
+
+explain (costs false) select count(*) from s right join l on s.nn=l.nn where s.nn not in (select s.nn from l left join s on l.nn = s.nn);
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Aggregate
+   ->  Nested Loop
+         ->  Nested Loop Anti Join
+               Join Filter: ((s.nn = s_1.nn) OR (s_1.nn IS NULL))
+               ->  Seq Scan on s
+                     Filter: (nn IS NOT NULL)
+               ->  Hash Left Join
+                     Hash Cond: (l_1.nn = s_1.nn)
+                     ->  Seq Scan on l l_1
+                     ->  Hash
+                           ->  Seq Scan on s s_1
+         ->  Bitmap Heap Scan on l
+               Recheck Cond: (nn = s.nn)
+               ->  Bitmap Index Scan on l_nn
+                     Index Cond: (nn = s.nn)
+(15 rows)
+
+select count(*) from s right join l on s.nn=l.nn where s.nn not in (select s.nn from l left join s on l.nn = s.nn);
+ count 
+-------
+     0
+(1 row)
+
+explain (costs false) select count(*) from s left join s1 on s.u=s1.u join l on s.u=l.u where s.nn not in (select nn from l);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Hash Right Join
+         Hash Cond: (s1.u = s.u)
+         ->  Seq Scan on s1
+         ->  Hash
+               ->  Nested Loop
+                     ->  Nested Loop Anti Join
+                           ->  Seq Scan on s
+                           ->  Index Only Scan using l_nn on l l_1
+                                 Index Cond: (nn = s.nn)
+                     ->  Index Only Scan using l_u on l
+                           Index Cond: (u = s.u)
+(12 rows)
+
+select count(*) from s left join s1 on s.u=s1.u join l on s.u=l.u where s.nn not in (select nn from l);
+ count 
+-------
+     0
+(1 row)
+
+explain (costs false) select count(*) from s left join s1 on s.u=s1.u right join l on s.u=l.u where s.nn not in (select nn from l);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Hash Right Join
+         Hash Cond: (s1.u = s.u)
+         ->  Seq Scan on s1
+         ->  Hash
+               ->  Nested Loop
+                     ->  Nested Loop Anti Join
+                           ->  Seq Scan on s
+                                 Filter: (nn IS NOT NULL)
+                           ->  Index Only Scan using l_nn on l l_1
+                                 Index Cond: (nn = s.nn)
+                     ->  Index Only Scan using l_u on l
+                           Index Cond: (u = s.u)
+(13 rows)
+
+select count(*) from s left join s1 on s.u=s1.u right join l on s.u=l.u where s.nn not in (select nn from l);
+ count 
+-------
+     0
+(1 row)
+
+explain (costs false) select count(*) from s left join s1 on s.u=s1.u left join l on s.u=l.u where s.nn not in (select nn from l);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Aggregate
+   ->  Hash Right Join
+         Hash Cond: (s1.u = s.u)
+         ->  Seq Scan on s1
+         ->  Hash
+               ->  Nested Loop Anti Join
+                     ->  Seq Scan on s
+                     ->  Index Only Scan using l_nn on l
+                           Index Cond: (nn = s.nn)
+(9 rows)
+
+select count(*) from s left join s1 on s.u=s1.u left join l on s.u=l.u where s.nn not in (select nn from l);
+ count 
+-------
+     1
+(1 row)
+
+explain (costs false) select count(*) from s right join s1 on s.u=s1.u join l on s.u=l.u where s.nn not in (select nn from l);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Nested Loop
+         Join Filter: (s.u = l.u)
+         ->  Hash Join
+               Hash Cond: (s1.u = s.u)
+               ->  Seq Scan on s1
+               ->  Hash
+                     ->  Nested Loop Anti Join
+                           ->  Seq Scan on s
+                           ->  Index Only Scan using l_nn on l l_1
+                                 Index Cond: (nn = s.nn)
+         ->  Index Only Scan using l_u on l
+               Index Cond: (u = s1.u)
+(13 rows)
+
+select count(*) from s right join s1 on s.u=s1.u join l on s.u=l.u where s.nn not in (select nn from l);
+ count 
+-------
+     0
+(1 row)
+
+explain (costs false) select count(*) from s join s1 on s.u=s1.u right join l on s.u=l.u where s.nn not in (select nn from l);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Nested Loop
+         Join Filter: (s.u = l.u)
+         ->  Hash Join
+               Hash Cond: (s1.u = s.u)
+               ->  Seq Scan on s1
+               ->  Hash
+                     ->  Nested Loop Anti Join
+                           ->  Seq Scan on s
+                                 Filter: (nn IS NOT NULL)
+                           ->  Index Only Scan using l_nn on l l_1
+                                 Index Cond: (nn = s.nn)
+         ->  Index Only Scan using l_u on l
+               Index Cond: (u = s1.u)
+(14 rows)
+
+select * from s join s1 on s.u=s1.u right join l on s.u=l.u where s.nn not in (select nn from l);
+ u | n | nn | p | u | n | nn | p | u | n | nn | p 
+---+---+----+---+---+---+----+---+---+---+----+---
+(0 rows)
+
+explain (costs false) select count(*) from s full join s1 on s.u=s1.u join l on s.u=l.u where s.nn not in (select nn from l);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Hash Right Join
+         Hash Cond: (s1.u = s.u)
+         ->  Seq Scan on s1
+         ->  Hash
+               ->  Nested Loop
+                     ->  Nested Loop Anti Join
+                           ->  Seq Scan on s
+                           ->  Index Only Scan using l_nn on l l_1
+                                 Index Cond: (nn = s.nn)
+                     ->  Index Only Scan using l_u on l
+                           Index Cond: (u = s.u)
+(12 rows)
+
+select count(*) from s full join s1 on s.u=s1.u join l on s.u=l.u where s.nn not in (select nn from l);
+ count 
+-------
+     0
+(1 row)
+
+explain (costs false) select count(*) from s join s1 on s.u=s1.u full join l on s.u=l.u where s.nn not in (select nn from l);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Aggregate
+   ->  Hash Join
+         Hash Cond: (s1.u = s.u)
+         ->  Seq Scan on s1
+         ->  Hash
+               ->  Nested Loop Anti Join
+                     ->  Seq Scan on s
+                           Filter: (nn IS NOT NULL)
+                     ->  Index Only Scan using l_nn on l
+                           Index Cond: (nn = s.nn)
+(10 rows)
+
+select count(*) from s join s1 on s.u=s1.u full join l on s.u=l.u where s.nn not in (select nn from l);
+ count 
+-------
+     0
+(1 row)
+
+explain (costs false) select * from s where s.nn not in (select l.nn from l left join s on l.nn=s.nn left join s1 on l.nn=s1.nn);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Nested Loop Anti Join
+   ->  Seq Scan on s
+   ->  Hash Right Join
+         Hash Cond: (s1.nn = l.nn)
+         ->  Seq Scan on s1
+         ->  Hash
+               ->  Hash Left Join
+                     Hash Cond: (l.nn = s_1.nn)
+                     ->  Bitmap Heap Scan on l
+                           Recheck Cond: (s.nn = nn)
+                           ->  Bitmap Index Scan on l_nn
+                                 Index Cond: (nn = s.nn)
+                     ->  Hash
+                           ->  Seq Scan on s s_1
+(14 rows)
+
+select * from s where s.nn not in (select l.nn from l left join s on l.nn=s.nn left join s1 on l.nn=s1.nn);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+ 1000002 | 1000002 | 1000002 | foofoo
+(1 row)
+
+explain (costs false) select * from s where s.nn not in (select l.nn from l left join s on l.nn=s.nn right join s1 on l.nn=s1.nn);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Nested Loop Anti Join
+   Join Filter: ((s.nn = l.nn) OR (l.nn IS NULL))
+   ->  Seq Scan on s
+   ->  Materialize
+         ->  Merge Right Join
+               Merge Cond: (l.nn = s1.nn)
+               ->  Merge Left Join
+                     Merge Cond: (l.nn = s_1.nn)
+                     ->  Index Only Scan using l_nn on l
+                     ->  Sort
+                           Sort Key: s_1.nn
+                           ->  Seq Scan on s s_1
+               ->  Sort
+                     Sort Key: s1.nn
+                     ->  Seq Scan on s1
+(15 rows)
+
+select * from s where s.nn not in (select l.nn from l left join s on l.nn=s.nn right join s1 on l.nn=s1.nn);
+ u | n | nn | p 
+---+---+----+---
+(0 rows)
+
+explain (costs false) select * from s where (n,u,nn) not in (select l.n,l.u,l.nn from l left join s on l.nn = s.nn);
+                                     QUERY PLAN                                      
+-------------------------------------------------------------------------------------
+ Nested Loop Anti Join
+   ->  Seq Scan on s
+   ->  Nested Loop Left Join
+         ->  Index Scan using l_u on l
+               Index Cond: (u = s.u)
+               Filter: (((s.n = n) OR (n IS NULL) OR (s.n IS NULL)) AND (s.nn = nn))
+         ->  Index Only Scan using s_nn on s s_1
+               Index Cond: (nn = l.nn)
+(8 rows)
+
+select * from s where (n,u,nn) not in (select l.n,l.u,l.nn from l left join s on l.nn = s.nn);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+ 1000002 | 1000002 | 1000002 | foofoo
+(1 row)
+
+explain (costs false) select * from s where (n,u,nn) not in (select l.n,l.u,l.nn from l right join s on l.nn = s.nn);
+                                                                QUERY PLAN                                                                 
+-------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop Anti Join
+   Join Filter: (((s.n = l.n) OR (l.n IS NULL) OR (s.n IS NULL)) AND ((s.u = l.u) OR (l.u IS NULL)) AND ((s.nn = l.nn) OR (l.nn IS NULL)))
+   ->  Seq Scan on s
+   ->  Materialize
+         ->  Nested Loop Left Join
+               ->  Seq Scan on s s_1
+               ->  Bitmap Heap Scan on l
+                     Recheck Cond: (nn = s_1.nn)
+                     ->  Bitmap Index Scan on l_nn
+                           Index Cond: (nn = s_1.nn)
+(10 rows)
+
+select * from s where (n,u,nn) not in (select l.n,l.u,l.nn from l left join s on l.nn = s.nn);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+ 1000002 | 1000002 | 1000002 | foofoo
+(1 row)
+
+--test reduce outer joins from outer query
+explain (costs false) select count(*) from s right join l on s.nn = l.nn where s.nn not in (select nn from l);
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Aggregate
+   ->  Nested Loop
+         ->  Nested Loop Anti Join
+               ->  Seq Scan on s
+                     Filter: (nn IS NOT NULL)
+               ->  Index Only Scan using l_nn on l l_1
+                     Index Cond: (nn = s.nn)
+         ->  Bitmap Heap Scan on l
+               Recheck Cond: (nn = s.nn)
+               ->  Bitmap Index Scan on l_nn
+                     Index Cond: (nn = s.nn)
+(11 rows)
+
+select count(*) from s right join l on s.nn = l.nn where s.nn not in (select nn from l);
+ count 
+-------
+     0
+(1 row)
+
+explain (costs false) select count(*) from s right join l on s.nn = l.nn where s.nn not in (select nn from l) and s.u>0;
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Aggregate
+   ->  Nested Loop
+         ->  Nested Loop Anti Join
+               ->  Seq Scan on s
+                     Filter: (u > 0)
+               ->  Index Only Scan using l_nn on l l_1
+                     Index Cond: (nn = s.nn)
+         ->  Bitmap Heap Scan on l
+               Recheck Cond: (nn = s.nn)
+               ->  Bitmap Index Scan on l_nn
+                     Index Cond: (nn = s.nn)
+(11 rows)
+
+select count(*) from s right join l on s.nn = l.nn where s.nn not in (select nn from l) and s.u>0;
+ count 
+-------
+     0
+(1 row)
+
+explain (costs false) select count(*) from s right join l on s.nn = l.nn join s1 on s.u = s1.u where s.nn not in (select nn from l);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Hash Join
+         Hash Cond: (s.u = s1.u)
+         ->  Nested Loop
+               ->  Nested Loop Anti Join
+                     ->  Seq Scan on s
+                     ->  Index Only Scan using l_nn on l l_1
+                           Index Cond: (nn = s.nn)
+               ->  Bitmap Heap Scan on l
+                     Recheck Cond: (nn = s.nn)
+                     ->  Bitmap Index Scan on l_nn
+                           Index Cond: (nn = s.nn)
+         ->  Hash
+               ->  Seq Scan on s1
+(14 rows)
+
+select count(*) from s right join l on s.nn = l.nn join s1 on s.u = s1.u where s.nn not in (select nn from l);
+ count 
+-------
+     0
+(1 row)
+
+explain (costs false) select count(*) from s right join l on s.nn = l.nn right join s1 on s.u = s1.u where s.nn not in (select nn from l);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Hash Join
+         Hash Cond: (s.u = s1.u)
+         ->  Nested Loop
+               ->  Nested Loop Anti Join
+                     ->  Seq Scan on s
+                           Filter: (nn IS NOT NULL)
+                     ->  Index Only Scan using l_nn on l l_1
+                           Index Cond: (nn = s.nn)
+               ->  Bitmap Heap Scan on l
+                     Recheck Cond: (nn = s.nn)
+                     ->  Bitmap Index Scan on l_nn
+                           Index Cond: (nn = s.nn)
+         ->  Hash
+               ->  Seq Scan on s1
+(15 rows)
+
+select count(*) from s right join l on s.nn = l.nn right join s1 on s.u = s1.u where s.nn not in (select nn from l);
+ count 
+-------
+     0
+(1 row)
+
+explain (costs false) select count(*) from s right join l on s.nn = l.nn left join s1 on s.u = s1.u where s.nn not in (select nn from l);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Hash Left Join
+         Hash Cond: (s.u = s1.u)
+         ->  Nested Loop
+               ->  Nested Loop Anti Join
+                     ->  Seq Scan on s
+                           Filter: (nn IS NOT NULL)
+                     ->  Index Only Scan using l_nn on l l_1
+                           Index Cond: (nn = s.nn)
+               ->  Bitmap Heap Scan on l
+                     Recheck Cond: (nn = s.nn)
+                     ->  Bitmap Index Scan on l_nn
+                           Index Cond: (nn = s.nn)
+         ->  Hash
+               ->  Seq Scan on s1
+(15 rows)
+
+select count(*) from s right join l on s.nn = l.nn left join s1 on s.u = s1.u where s.nn not in (select nn from l);
+ count 
+-------
+     0
+(1 row)
+
+--test reduce outer joins from subquery
+explain (costs false) select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn);
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Nested Loop Anti Join
+   Join Filter: ((s.nn = l.nn) OR (l.nn IS NULL))
+   ->  Seq Scan on s
+   ->  Materialize
+         ->  Nested Loop Left Join
+               ->  Seq Scan on s s_1
+               ->  Bitmap Heap Scan on l
+                     Recheck Cond: (nn = s_1.nn)
+                     ->  Bitmap Index Scan on l_nn
+                           Index Cond: (nn = s_1.nn)
+(10 rows)
+
+select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn);
+ u | n | nn | p 
+---+---+----+---
+(0 rows)
+
+explain (costs false) select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn where l.u > 9);
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Nested Loop Anti Join
+   ->  Seq Scan on s
+   ->  Nested Loop
+         ->  Seq Scan on s s_1
+         ->  Index Scan using l_nn on l
+               Index Cond: ((nn = s.nn) AND (nn = s_1.nn))
+               Filter: (u > 9)
+(7 rows)
+
+select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn where l.u > 9);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+       1 |       1 |       1 | foo
+       2 |       2 |       2 | foo
+ 1000002 | 1000002 | 1000002 | foofoo
+       3 |         |       3 | foo
+(4 rows)
+
+explain (costs false) select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn where s.u > 9);
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Nested Loop Anti Join
+   Join Filter: ((s.nn = l.nn) OR (l.nn IS NULL))
+   ->  Seq Scan on s
+   ->  Materialize
+         ->  Nested Loop Left Join
+               ->  Seq Scan on s s_1
+                     Filter: (u > 9)
+               ->  Bitmap Heap Scan on l
+                     Recheck Cond: (nn = s_1.nn)
+                     ->  Bitmap Index Scan on l_nn
+                           Index Cond: (nn = s_1.nn)
+(11 rows)
+
+select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn where s.u > 9);
+ u | n | nn | p 
+---+---+----+---
+(0 rows)
+
+explain (costs false) select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn join s1 on l.n = s1.n);
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
+ Nested Loop Anti Join
+   ->  Seq Scan on s
+   ->  Hash Join
+         Hash Cond: (s1.n = l.n)
+         ->  Seq Scan on s1
+         ->  Hash
+               ->  Nested Loop
+                     ->  Seq Scan on s s_1
+                     ->  Index Scan using l_nn on l
+                           Index Cond: ((nn = s.nn) AND (nn = s_1.nn))
+(10 rows)
+
+select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn join s1 on l.n = s1.n);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+ 1000002 | 1000002 | 1000002 | foofoo
+       3 |         |       3 | foo
+(2 rows)
+
+explain (costs false) select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn right join s1 on l.n = s1.n);
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Nested Loop Anti Join
+   Join Filter: ((s.nn = l.nn) OR (l.nn IS NULL))
+   ->  Seq Scan on s
+   ->  Materialize
+         ->  Hash Left Join
+               Hash Cond: (s1.n = l.n)
+               ->  Seq Scan on s1
+               ->  Hash
+                     ->  Nested Loop
+                           ->  Seq Scan on s s_1
+                           ->  Bitmap Heap Scan on l
+                                 Recheck Cond: (nn = s_1.nn)
+                                 ->  Bitmap Index Scan on l_nn
+                                       Index Cond: (nn = s_1.nn)
+(14 rows)
+
+select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn right join s1 on l.n = s1.n);
+ u | n | nn | p 
+---+---+----+---
+(0 rows)
+
+explain (costs false) select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn left join s1 on l.n = s1.n);
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Nested Loop Anti Join
+   Join Filter: ((s.nn = l.nn) OR (l.nn IS NULL))
+   ->  Seq Scan on s
+   ->  Materialize
+         ->  Nested Loop Left Join
+               ->  Seq Scan on s s_1
+               ->  Hash Right Join
+                     Hash Cond: (s1.n = l.n)
+                     ->  Seq Scan on s1
+                     ->  Hash
+                           ->  Bitmap Heap Scan on l
+                                 Recheck Cond: (nn = s_1.nn)
+                                 ->  Bitmap Index Scan on l_nn
+                                       Index Cond: (nn = s_1.nn)
+(14 rows)
+
+select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn left join s1 on l.n = s1.n);
+ u | n | nn | p 
+---+---+----+---
+(0 rows)
+
+--test reduce outer join on outer and sub-query
+explain (costs false) select count(*) from s right join l on s.nn = l.nn join s1 on s.u = s1.u where s.nn not in (select l.nn from l right join s on l.nn = s.nn join s1 on l.n = s1.n);
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Aggregate
+   ->  Hash Join
+         Hash Cond: (s.u = s1.u)
+         ->  Nested Loop
+               ->  Nested Loop Anti Join
+                     ->  Seq Scan on s
+                     ->  Hash Join
+                           Hash Cond: (s1_1.n = l_1.n)
+                           ->  Seq Scan on s1 s1_1
+                           ->  Hash
+                                 ->  Nested Loop
+                                       ->  Seq Scan on s s_1
+                                       ->  Index Scan using l_nn on l l_1
+                                             Index Cond: ((nn = s.nn) AND (nn = s_1.nn))
+               ->  Bitmap Heap Scan on l
+                     Recheck Cond: (nn = s.nn)
+                     ->  Bitmap Index Scan on l_nn
+                           Index Cond: (nn = s.nn)
+         ->  Hash
+               ->  Seq Scan on s1
+(20 rows)
+
+select count(*) from s right join l on s.nn = l.nn join s1 on s.u = s1.u where s.nn not in (select l.nn from l right join s on l.nn = s.nn join s1 on l.n = s1.n);
+ count 
+-------
+     1
+(1 row)
+
+explain (costs false) select count(*) from s right join l on s.nn = l.nn left join s1 on s.u = s1.u where s.nn not in (select l.nn from l right join s on l.nn = s.nn left join s1 on l.n = s1.n);
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate
+   ->  Hash Left Join
+         Hash Cond: (s.u = s1.u)
+         ->  Nested Loop
+               ->  Nested Loop Anti Join
+                     Join Filter: ((s.nn = l_1.nn) OR (l_1.nn IS NULL))
+                     ->  Seq Scan on s
+                           Filter: (nn IS NOT NULL)
+                     ->  Materialize
+                           ->  Nested Loop Left Join
+                                 ->  Seq Scan on s s_1
+                                 ->  Hash Right Join
+                                       Hash Cond: (s1_1.n = l_1.n)
+                                       ->  Seq Scan on s1 s1_1
+                                       ->  Hash
+                                             ->  Bitmap Heap Scan on l l_1
+                                                   Recheck Cond: (nn = s_1.nn)
+                                                   ->  Bitmap Index Scan on l_nn
+                                                         Index Cond: (nn = s_1.nn)
+               ->  Bitmap Heap Scan on l
+                     Recheck Cond: (nn = s.nn)
+                     ->  Bitmap Index Scan on l_nn
+                           Index Cond: (nn = s.nn)
+         ->  Hash
+               ->  Seq Scan on s1
+(25 rows)
+
+select count(*) from s right join l on s.nn = l.nn left join s1 on s.u = s1.u where s.nn not in (select l.nn from l right join s on l.nn = s.nn left join s1 on l.n = s1.n);
+ count 
+-------
+     0
+(1 row)
+
+-- test union all
+explain (costs false) select * from s as t where not exists
+(select 1 from (select n as y from l union all
+				select u as y from s union all
+				select nn as y from s) as v where t.n=v.y or v.y is null) and n is not null;
+                       QUERY PLAN                       
+--------------------------------------------------------
+ Nested Loop Anti Join
+   ->  Seq Scan on s t
+         Filter: (n IS NOT NULL)
+   ->  Append
+         ->  Bitmap Heap Scan on l
+               Recheck Cond: ((t.n = n) OR (n IS NULL))
+               ->  BitmapOr
+                     ->  Bitmap Index Scan on l_n
+                           Index Cond: (n = t.n)
+                     ->  Bitmap Index Scan on l_n
+                           Index Cond: (n IS NULL)
+         ->  Seq Scan on s
+               Filter: ((t.n = u) OR (u IS NULL))
+         ->  Seq Scan on s s_1
+               Filter: ((t.n = nn) OR (nn IS NULL))
+(15 rows)
+
+select * from s as t where not exists
+(select 1 from (select n as y from l union all
+				select u as y from s union all
+				select nn as y from s) as v where t.n=v.y or v.y is null) and n is not null;
+ u | n | nn | p 
+---+---+----+---
+(0 rows)
+
+explain (costs false) select * from s where n not in
+(select n as y from l union all
+ select u as y from s union all
+ select nn as y from s);
+                       QUERY PLAN                       
+--------------------------------------------------------
+ Nested Loop Anti Join
+   ->  Seq Scan on s
+         Filter: (n IS NOT NULL)
+   ->  Append
+         ->  Bitmap Heap Scan on l
+               Recheck Cond: ((s.n = n) OR (n IS NULL))
+               ->  BitmapOr
+                     ->  Bitmap Index Scan on l_n
+                           Index Cond: (n = s.n)
+                     ->  Bitmap Index Scan on l_n
+                           Index Cond: (n IS NULL)
+         ->  Seq Scan on s s_1
+               Filter: ((s.n = u) OR (u IS NULL))
+         ->  Seq Scan on s s_2
+               Filter: ((s.n = nn) OR (nn IS NULL))
+(15 rows)
+
+ 
+select * from s where n not in
+(select n as y from l union all
+ select u as y from s union all
+ select nn as y from s);
+ u | n | nn | p 
+---+---+----+---
+(0 rows)
+
+-- test multi-levels of NOT IN
+explain (costs false) select * from s where n not in (select n from s where n not in (select n from l));
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Seq Scan on s
+   Filter: (NOT (hashed SubPlan 1))
+   SubPlan 1
+     ->  Nested Loop Anti Join
+           ->  Seq Scan on s s_1
+                 Filter: (n IS NOT NULL)
+           ->  Bitmap Heap Scan on l
+                 Recheck Cond: ((s_1.n = n) OR (n IS NULL))
+                 ->  BitmapOr
+                       ->  Bitmap Index Scan on l_n
+                             Index Cond: (n = s_1.n)
+                       ->  Bitmap Index Scan on l_n
+                             Index Cond: (n IS NULL)
+(13 rows)
+
+select * from s where n not in (select n from s where n not in (select n from l));
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+       1 |       1 |       1 | foo
+       2 |       2 |       2 | foo
+ 1000002 | 1000002 | 1000002 | foofoo
+       3 |         |       3 | foo
+(4 rows)
+
+explain (costs false) select * from s where n not in (select n from s where n not in (select u from l));
+                  QUERY PLAN                  
+----------------------------------------------
+ Seq Scan on s
+   Filter: (NOT (hashed SubPlan 1))
+   SubPlan 1
+     ->  Nested Loop Anti Join
+           ->  Seq Scan on s s_1
+                 Filter: (n IS NOT NULL)
+           ->  Index Only Scan using l_u on l
+                 Index Cond: (u = s_1.n)
+(8 rows)
+
+select * from s where n not in (select n from s where n not in (select u from l));
+ u | n | nn |  p  
+---+---+----+-----
+ 1 | 1 |  1 | foo
+ 2 | 2 |  2 | foo
+(2 rows)
+
+--test COALESCE
+explain (costs false) select * from s where COALESCE(n, -1) not in (select COALESCE(n, -1) from l);
+                                 QUERY PLAN                                 
+----------------------------------------------------------------------------
+ Hash Anti Join
+   Hash Cond: (COALESCE(s.n, '-1'::integer) = COALESCE(l.n, '-1'::integer))
+   ->  Seq Scan on s
+   ->  Hash
+         ->  Seq Scan on l
+(5 rows)
+
+select * from s where COALESCE(n, -1) not in (select COALESCE(n, -1) from l);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+ 1000002 | 1000002 | 1000002 | foofoo
+(1 row)
+
+explain (costs false) select * from s where COALESCE(n, NULL, -1) not in (select COALESCE(n, NULL, -1) from l);
+                                 QUERY PLAN                                 
+----------------------------------------------------------------------------
+ Hash Anti Join
+   Hash Cond: (COALESCE(s.n, '-1'::integer) = COALESCE(l.n, '-1'::integer))
+   ->  Seq Scan on s
+   ->  Hash
+         ->  Seq Scan on l
+(5 rows)
+
+select * from s where COALESCE(n, NULL, -1) not in (select COALESCE(n, NULL, -1) from l);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+ 1000002 | 1000002 | 1000002 | foofoo
+(1 row)
+
+explain (costs false) select * from s where COALESCE(n, NULL, NULL) not in (select COALESCE(n, NULL, NULL) from l);
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Nested Loop Anti Join
+   Join Filter: ((COALESCE(s.n) = COALESCE(l.n)) OR (COALESCE(l.n) IS NULL))
+   ->  Seq Scan on s
+         Filter: (COALESCE(n) IS NOT NULL)
+   ->  Materialize
+         ->  Seq Scan on l
+(6 rows)
+
+select * from s where COALESCE(n, NULL, NULL) not in (select COALESCE(n, NULL, NULL) from l);
+ u | n | nn | p 
+---+---+----+---
+(0 rows)
+
+explain (costs false) select * from s where COALESCE(n, nn) not in (select COALESCE(n, nn) from l);
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Hash Anti Join
+   Hash Cond: (COALESCE(s.n, s.nn) = COALESCE(l.n, l.nn))
+   ->  Seq Scan on s
+   ->  Hash
+         ->  Seq Scan on l
+(5 rows)
+
+select * from s where COALESCE(n, nn) not in (select COALESCE(n, nn) from l);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+ 1000002 | 1000002 | 1000002 | foofoo
+(1 row)
+
+explain (costs false) select * from s where COALESCE(nn, NULL) not in (select COALESCE(nn, NULL) from l);
+                   QUERY PLAN                   
+------------------------------------------------
+ Hash Anti Join
+   Hash Cond: (COALESCE(s.nn) = COALESCE(l.nn))
+   ->  Seq Scan on s
+   ->  Hash
+         ->  Seq Scan on l
+(5 rows)
+
+select * from s where COALESCE(nn, NULL) not in (select COALESCE(nn, NULL) from l);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+ 1000002 | 1000002 | 1000002 | foofoo
+(1 row)
+
+explain (costs false) select * from s where (COALESCE(n, -1), nn, COALESCE(n, u)) not in (select COALESCE(n, -1), nn, COALESCE(n, u) from l);
+                                                       QUERY PLAN                                                        
+-------------------------------------------------------------------------------------------------------------------------
+ Nested Loop Anti Join
+   ->  Seq Scan on s
+   ->  Bitmap Heap Scan on l
+         Recheck Cond: (s.nn = nn)
+         Filter: ((COALESCE(s.n, '-1'::integer) = COALESCE(n, '-1'::integer)) AND (COALESCE(s.n, s.u) = COALESCE(n, u)))
+         ->  Bitmap Index Scan on l_nn
+               Index Cond: (nn = s.nn)
+(7 rows)
+
+select * from s where (COALESCE(n, -1), nn, COALESCE(n, u)) not in (select COALESCE(n, -1), nn, COALESCE(n, u) from l);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+ 1000002 | 1000002 | 1000002 | foofoo
+       3 |         |       3 | foo
+(2 rows)
+
+-- non conversion cases
+explain (costs false) select * from s where n not in (select n from l where u > 500);
+             QUERY PLAN             
+------------------------------------
+ Seq Scan on s
+   Filter: (NOT (hashed SubPlan 1))
+   SubPlan 1
+     ->  Seq Scan on l
+           Filter: (u > 500)
+(5 rows)
+
+select * from s where n not in (select n from l where u > 500);
+    u    |    n    |   nn    |   p    
+---------+---------+---------+--------
+       1 |       1 |       1 | foo
+       2 |       2 |       2 | foo
+ 1000002 | 1000002 | 1000002 | foofoo
+(3 rows)
+
+explain (costs false) select * from s where (n,n) not in (select n,n from l);
+             QUERY PLAN             
+------------------------------------
+ Seq Scan on s
+   Filter: (NOT (hashed SubPlan 1))
+   SubPlan 1
+     ->  Seq Scan on l
+(4 rows)
+
+select * from s where (n,n) not in (select n,n from l);
+ u | n | nn | p 
+---+---+----+---
+(0 rows)
+
+explain (costs false) select * from s right join l on s.nn = l.nn where (s.n,s.u,s.nn) not in (select n,u,nn from l);
+             QUERY PLAN             
+------------------------------------
+ Hash Left Join
+   Hash Cond: (l.nn = s.nn)
+   Filter: (NOT (hashed SubPlan 1))
+   ->  Seq Scan on l
+   ->  Hash
+         ->  Seq Scan on s
+   SubPlan 1
+     ->  Seq Scan on l l_1
+(8 rows)
+
+select * from s right join l on s.nn = l.nn where (s.n,s.u,s.nn) not in (select n,u,nn from l);
+ u | n | nn | p | u | n | nn | p 
+---+---+----+---+---+---+----+---
+(0 rows)
+
+-- clean up
+drop table s;
+drop table s1;
+drop table l;
diff --git a/src/test/regress/sql/subselect.sql b/src/test/regress/sql/subselect.sql
index 8bca1f5d55..1368d3af47 100644
--- a/src/test/regress/sql/subselect.sql
+++ b/src/test/regress/sql/subselect.sql
@@ -683,3 +683,287 @@ select * from (with x as (select 2 as y) select * from x) ss;
 explain (verbose, costs off)
 with x as (select * from subselect_tbl)
 select * from x for update;
+
+-- test NON IN to ANTI JOIN conversion
+CREATE TABLE s (u INTEGER NOT NULL, n INTEGER NULL, nn INTEGER NOT NULL, p VARCHAR(128) NULL);
+insert into s (u, n, nn, p)
+    select
+    generate_series(1,3) as u,
+	generate_series(1,3) as n,
+	generate_series(1,3) as nn,
+	'foo' as p;
+insert into s values(1000002, 1000002, 1000002, 'foofoo');
+UPDATE s set n = NULL WHERE n = 3;
+
+CREATE UNIQUE INDEX s_u ON s (u);
+CREATE INDEX s_n ON s (n);
+CREATE INDEX s_nn ON s (nn);
+
+CREATE TABLE l (u INTEGER NOT NULL, n INTEGER NULL, nn INTEGER NOT NULL, p VARCHAR(128) NULL);
+insert into l (u, n, nn, p)
+	select
+    generate_series(1,10000 ) as u,
+	generate_series(1,10000 ) as n,
+	generate_series(1,10000 ) as nn,
+	'bar' as p;
+UPDATE l set n = NULL WHERE n = 7;
+
+CREATE UNIQUE INDEX l_u ON l (u);
+CREATE INDEX l_n ON l (n);
+CREATE INDEX l_nn ON l (nn);
+
+CREATE TABLE s1 (u INTEGER NOT NULL, n INTEGER NULL, nn INTEGER NOT NULL, p VARCHAR(128) NULL);
+insert into s1 (u, n, nn, p)
+    select
+    generate_series(1,3) as u,
+	generate_series(1,3) as n,
+	generate_series(1,3) as nn,
+	'foo' as p;
+insert into s1 values(1000003, 1000003, 1000003, 'foofoo');
+insert into s1 values(1003, 1003, 1003, 'foofoo');
+UPDATE s1 set n = NULL WHERE n = 3;
+
+-- test single expression
+explain (costs false) select * from s where n not in (select n from l);
+
+select * from s where n not in (select n from l);
+
+explain (costs false) select * from s where u not in (select u from l);
+
+select * from s where u not in (select u from l);
+
+explain (costs false) select * from s where 3*n not in (select n from l);
+
+select * from s where 3*n not in (select n from l);
+
+explain (costs false) select * from s where n not in (select 3*n from l);
+
+select * from s where n not in (select 3*n from l);
+
+-- test multi expression
+explain (costs false) select * from s where (n,u) not in (select n,u from l);
+
+select * from s where (n,u) not in (select n,u from l);
+
+explain (costs false) select * from s where (u, nn) not in (select u, nn from l);
+
+select * from s where (u, nn) not in (select u, nn from l);
+
+explain (costs false) select * from s where (n,u) not in (select u,n from l);
+
+select * from s where (n,u) not in (select u,n from l);
+
+explain (costs false) select * from s where (n,u,nn) not in (select u,n,nn from l);
+
+select * from s where (n,u,nn) not in (select u,n,nn from l);
+
+explain (costs false) select * from s where (n,u,nn) not in (select u,n,nn from l where u > 1000);
+
+select * from s where (n,u,nn) not in (select u,n,nn from l where u > 1000);
+
+-- test multi-table
+explain (costs false) select count(*) from s, l where s.n not in (select n from l);
+
+select count(*) from s, l where s.n not in (select n from l);
+
+explain (costs false) select count(*) from s, l where s.nn not in (select nn from l);
+
+select count(*) from s, l where s.nn not in (select nn from l);
+
+-- test null padded results from outer join
+explain (costs false) select * from s where n not in (select s.nn from l left join s on l.nn = s.nn);
+
+select * from s where n not in (select s.nn from l left join s on l.nn = s.nn);
+
+explain (costs false) select * from s where n not in (select s.nn from s right join l on s.nn = l.nn);
+
+select * from s where n not in (select s.nn from s right join l on s.nn = l.nn);
+
+explain (costs false) select count(*) from s right join l on s.nn = l.nn where l.nn not in (select nn from s);
+
+select count(*) from s right join l on s.nn = l.nn where l.nn not in (select nn from s);
+
+explain (costs false) select count(*) from s right join l on s.nn = l.nn where s.nn not in (select nn from s);
+
+select count(*) from s right join l on s.nn = l.nn where s.nn not in (select nn from s);
+
+explain (costs false) select count(*) from s right join l on s.nn=l.nn where l.nn not in (select l.nn from l left join s on l.nn = s.nn);
+
+select count(*) from s right join l on s.nn=l.nn where l.nn not in (select l.nn from l left join s on l.nn = s.nn);
+
+explain (costs false) select count(*) from s right join l on s.nn=l.nn where s.nn not in (select s.nn from l left join s on l.nn = s.nn);
+
+select count(*) from s right join l on s.nn=l.nn where s.nn not in (select s.nn from l left join s on l.nn = s.nn);
+
+explain (costs false) select count(*) from s left join s1 on s.u=s1.u join l on s.u=l.u where s.nn not in (select nn from l);
+
+select count(*) from s left join s1 on s.u=s1.u join l on s.u=l.u where s.nn not in (select nn from l);
+
+explain (costs false) select count(*) from s left join s1 on s.u=s1.u right join l on s.u=l.u where s.nn not in (select nn from l);
+
+select count(*) from s left join s1 on s.u=s1.u right join l on s.u=l.u where s.nn not in (select nn from l);
+
+explain (costs false) select count(*) from s left join s1 on s.u=s1.u left join l on s.u=l.u where s.nn not in (select nn from l);
+
+select count(*) from s left join s1 on s.u=s1.u left join l on s.u=l.u where s.nn not in (select nn from l);
+
+explain (costs false) select count(*) from s right join s1 on s.u=s1.u join l on s.u=l.u where s.nn not in (select nn from l);
+
+select count(*) from s right join s1 on s.u=s1.u join l on s.u=l.u where s.nn not in (select nn from l);
+
+explain (costs false) select count(*) from s join s1 on s.u=s1.u right join l on s.u=l.u where s.nn not in (select nn from l);
+
+select * from s join s1 on s.u=s1.u right join l on s.u=l.u where s.nn not in (select nn from l);
+
+explain (costs false) select count(*) from s full join s1 on s.u=s1.u join l on s.u=l.u where s.nn not in (select nn from l);
+
+select count(*) from s full join s1 on s.u=s1.u join l on s.u=l.u where s.nn not in (select nn from l);
+
+explain (costs false) select count(*) from s join s1 on s.u=s1.u full join l on s.u=l.u where s.nn not in (select nn from l);
+
+select count(*) from s join s1 on s.u=s1.u full join l on s.u=l.u where s.nn not in (select nn from l);
+
+explain (costs false) select * from s where s.nn not in (select l.nn from l left join s on l.nn=s.nn left join s1 on l.nn=s1.nn);
+
+select * from s where s.nn not in (select l.nn from l left join s on l.nn=s.nn left join s1 on l.nn=s1.nn);
+
+explain (costs false) select * from s where s.nn not in (select l.nn from l left join s on l.nn=s.nn right join s1 on l.nn=s1.nn);
+
+select * from s where s.nn not in (select l.nn from l left join s on l.nn=s.nn right join s1 on l.nn=s1.nn);
+
+explain (costs false) select * from s where (n,u,nn) not in (select l.n,l.u,l.nn from l left join s on l.nn = s.nn);
+
+select * from s where (n,u,nn) not in (select l.n,l.u,l.nn from l left join s on l.nn = s.nn);
+
+explain (costs false) select * from s where (n,u,nn) not in (select l.n,l.u,l.nn from l right join s on l.nn = s.nn);
+
+select * from s where (n,u,nn) not in (select l.n,l.u,l.nn from l left join s on l.nn = s.nn);
+
+--test reduce outer joins from outer query
+explain (costs false) select count(*) from s right join l on s.nn = l.nn where s.nn not in (select nn from l);
+
+select count(*) from s right join l on s.nn = l.nn where s.nn not in (select nn from l);
+
+explain (costs false) select count(*) from s right join l on s.nn = l.nn where s.nn not in (select nn from l) and s.u>0;
+
+select count(*) from s right join l on s.nn = l.nn where s.nn not in (select nn from l) and s.u>0;
+
+explain (costs false) select count(*) from s right join l on s.nn = l.nn join s1 on s.u = s1.u where s.nn not in (select nn from l);
+
+select count(*) from s right join l on s.nn = l.nn join s1 on s.u = s1.u where s.nn not in (select nn from l);
+
+explain (costs false) select count(*) from s right join l on s.nn = l.nn right join s1 on s.u = s1.u where s.nn not in (select nn from l);
+
+select count(*) from s right join l on s.nn = l.nn right join s1 on s.u = s1.u where s.nn not in (select nn from l);
+
+explain (costs false) select count(*) from s right join l on s.nn = l.nn left join s1 on s.u = s1.u where s.nn not in (select nn from l);
+
+select count(*) from s right join l on s.nn = l.nn left join s1 on s.u = s1.u where s.nn not in (select nn from l);
+
+--test reduce outer joins from subquery
+explain (costs false) select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn);
+
+select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn);
+
+explain (costs false) select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn where l.u > 9);
+
+select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn where l.u > 9);
+
+explain (costs false) select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn where s.u > 9);
+
+select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn where s.u > 9);
+
+explain (costs false) select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn join s1 on l.n = s1.n);
+
+select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn join s1 on l.n = s1.n);
+
+explain (costs false) select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn right join s1 on l.n = s1.n);
+
+select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn right join s1 on l.n = s1.n);
+
+explain (costs false) select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn left join s1 on l.n = s1.n);
+
+select * from s where nn not in (select l.nn from l right join s on l.nn = s.nn left join s1 on l.n = s1.n);
+
+--test reduce outer join on outer and sub-query
+explain (costs false) select count(*) from s right join l on s.nn = l.nn join s1 on s.u = s1.u where s.nn not in (select l.nn from l right join s on l.nn = s.nn join s1 on l.n = s1.n);
+
+select count(*) from s right join l on s.nn = l.nn join s1 on s.u = s1.u where s.nn not in (select l.nn from l right join s on l.nn = s.nn join s1 on l.n = s1.n);
+
+explain (costs false) select count(*) from s right join l on s.nn = l.nn left join s1 on s.u = s1.u where s.nn not in (select l.nn from l right join s on l.nn = s.nn left join s1 on l.n = s1.n);
+
+select count(*) from s right join l on s.nn = l.nn left join s1 on s.u = s1.u where s.nn not in (select l.nn from l right join s on l.nn = s.nn left join s1 on l.n = s1.n);
+
+-- test union all
+explain (costs false) select * from s as t where not exists
+(select 1 from (select n as y from l union all
+				select u as y from s union all
+				select nn as y from s) as v where t.n=v.y or v.y is null) and n is not null;
+
+select * from s as t where not exists
+(select 1 from (select n as y from l union all
+				select u as y from s union all
+				select nn as y from s) as v where t.n=v.y or v.y is null) and n is not null;
+
+explain (costs false) select * from s where n not in
+(select n as y from l union all
+ select u as y from s union all
+ select nn as y from s);
+ 
+select * from s where n not in
+(select n as y from l union all
+ select u as y from s union all
+ select nn as y from s);
+
+-- test multi-levels of NOT IN
+explain (costs false) select * from s where n not in (select n from s where n not in (select n from l));
+
+select * from s where n not in (select n from s where n not in (select n from l));
+
+explain (costs false) select * from s where n not in (select n from s where n not in (select u from l));
+
+select * from s where n not in (select n from s where n not in (select u from l));
+
+--test COALESCE
+explain (costs false) select * from s where COALESCE(n, -1) not in (select COALESCE(n, -1) from l);
+
+select * from s where COALESCE(n, -1) not in (select COALESCE(n, -1) from l);
+
+explain (costs false) select * from s where COALESCE(n, NULL, -1) not in (select COALESCE(n, NULL, -1) from l);
+
+select * from s where COALESCE(n, NULL, -1) not in (select COALESCE(n, NULL, -1) from l);
+
+explain (costs false) select * from s where COALESCE(n, NULL, NULL) not in (select COALESCE(n, NULL, NULL) from l);
+
+select * from s where COALESCE(n, NULL, NULL) not in (select COALESCE(n, NULL, NULL) from l);
+
+explain (costs false) select * from s where COALESCE(n, nn) not in (select COALESCE(n, nn) from l);
+
+select * from s where COALESCE(n, nn) not in (select COALESCE(n, nn) from l);
+
+explain (costs false) select * from s where COALESCE(nn, NULL) not in (select COALESCE(nn, NULL) from l);
+
+select * from s where COALESCE(nn, NULL) not in (select COALESCE(nn, NULL) from l);
+
+explain (costs false) select * from s where (COALESCE(n, -1), nn, COALESCE(n, u)) not in (select COALESCE(n, -1), nn, COALESCE(n, u) from l);
+
+select * from s where (COALESCE(n, -1), nn, COALESCE(n, u)) not in (select COALESCE(n, -1), nn, COALESCE(n, u) from l);
+
+-- non conversion cases
+explain (costs false) select * from s where n not in (select n from l where u > 500);
+
+select * from s where n not in (select n from l where u > 500);
+
+explain (costs false) select * from s where (n,n) not in (select n,n from l);
+
+select * from s where (n,n) not in (select n,n from l);
+
+explain (costs false) select * from s right join l on s.nn = l.nn where (s.n,s.u,s.nn) not in (select n,u,nn from l);
+
+select * from s right join l on s.nn = l.nn where (s.n,s.u,s.nn) not in (select n,u,nn from l);
+
+-- clean up
+drop table s;
+drop table s1;
+drop table l;
+
