[CALCITE-2329] Improve rewrite for "constant IN (sub-query)"

Improve and refactor SubQueryRemoveRule.

Add tests for partially-null right-hand side. (Julian Hyde)

Close apache/calcite#700


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/1ae6a526
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/1ae6a526
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/1ae6a526

Branch: refs/heads/master
Commit: 1ae6a52603006741a4cb6bf558c58d9ac08e866a
Parents: 03dce13
Author: Volodymyr Vysotskyi <vvo...@gmail.com>
Authored: Mon May 21 20:38:03 2018 +0300
Committer: Julian Hyde <jh...@apache.org>
Committed: Tue Jun 12 19:01:21 2018 -0500

----------------------------------------------------------------------
 .../calcite/rel/rules/SubQueryRemoveRule.java   |  684 ++++++----
 .../java/org/apache/calcite/rex/RexUtil.java    |   17 +-
 .../org/apache/calcite/test/RelOptRulesTest.xml |    4 +-
 core/src/test/resources/sql/blank.iq            |    2 +-
 core/src/test/resources/sql/sub-query.iq        | 1270 +++++++++++++++++-
 5 files changed, 1722 insertions(+), 255 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/1ae6a526/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
index 12ee805..628219f 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/SubQueryRemoveRule.java
@@ -52,6 +52,7 @@ import com.google.common.collect.ImmutableSet;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Transform that converts IN, EXISTS and scalar sub-queries into joins.
@@ -65,88 +66,13 @@ import java.util.Set;
  */
 public abstract class SubQueryRemoveRule extends RelOptRule {
   public static final SubQueryRemoveRule PROJECT =
-      new SubQueryRemoveRule(
-          operand(Project.class, null, 
RexUtil.SubQueryFinder.PROJECT_PREDICATE,
-              any()),
-          RelFactories.LOGICAL_BUILDER, "SubQueryRemoveRule:Project") {
-        public void onMatch(RelOptRuleCall call) {
-          final Project project = call.rel(0);
-          final RelBuilder builder = call.builder();
-          final RexSubQuery e =
-              RexUtil.SubQueryFinder.find(project.getProjects());
-          assert e != null;
-          final RelOptUtil.Logic logic =
-              LogicVisitor.find(RelOptUtil.Logic.TRUE_FALSE_UNKNOWN,
-                  project.getProjects(), e);
-          builder.push(project.getInput());
-          final int fieldCount = builder.peek().getRowType().getFieldCount();
-          final RexNode target = apply(e, ImmutableSet.<CorrelationId>of(),
-              logic, builder, 1, fieldCount);
-          final RexShuttle shuttle = new ReplaceSubQueryShuttle(e, target);
-          builder.project(shuttle.apply(project.getProjects()),
-              project.getRowType().getFieldNames());
-          call.transformTo(builder.build());
-        }
-      };
+      new SubQueryProjectRemoveRule(RelFactories.LOGICAL_BUILDER);
 
   public static final SubQueryRemoveRule FILTER =
-      new SubQueryRemoveRule(
-          operand(Filter.class, null, RexUtil.SubQueryFinder.FILTER_PREDICATE,
-              any()),
-          RelFactories.LOGICAL_BUILDER, "SubQueryRemoveRule:Filter") {
-        public void onMatch(RelOptRuleCall call) {
-          final Filter filter = call.rel(0);
-          final RelBuilder builder = call.builder();
-          builder.push(filter.getInput());
-          int count = 0;
-          RexNode c = filter.getCondition();
-          for (;;) {
-            final RexSubQuery e = RexUtil.SubQueryFinder.find(c);
-            if (e == null) {
-              assert count > 0;
-              break;
-            }
-            ++count;
-            final RelOptUtil.Logic logic =
-                LogicVisitor.find(RelOptUtil.Logic.TRUE, ImmutableList.of(c),
-                    e);
-            final Set<CorrelationId>  variablesSet =
-                RelOptUtil.getVariablesUsed(e.rel);
-            final RexNode target = apply(e, variablesSet, logic,
-                builder, 1, builder.peek().getRowType().getFieldCount());
-            final RexShuttle shuttle = new ReplaceSubQueryShuttle(e, target);
-            c = c.accept(shuttle);
-          }
-          builder.filter(c);
-          builder.project(fields(builder, 
filter.getRowType().getFieldCount()));
-          call.transformTo(builder.build());
-        }
-      };
+      new SubQueryFilterRemoveRule(RelFactories.LOGICAL_BUILDER);
 
   public static final SubQueryRemoveRule JOIN =
-      new SubQueryRemoveRule(
-          operand(Join.class, null, RexUtil.SubQueryFinder.JOIN_PREDICATE,
-              any()), RelFactories.LOGICAL_BUILDER, "SubQueryRemoveRule:Join") 
{
-        public void onMatch(RelOptRuleCall call) {
-          final Join join = call.rel(0);
-          final RelBuilder builder = call.builder();
-          final RexSubQuery e =
-              RexUtil.SubQueryFinder.find(join.getCondition());
-          assert e != null;
-          final RelOptUtil.Logic logic =
-              LogicVisitor.find(RelOptUtil.Logic.TRUE,
-                  ImmutableList.of(join.getCondition()), e);
-          builder.push(join.getLeft());
-          builder.push(join.getRight());
-          final int fieldCount = join.getRowType().getFieldCount();
-          final RexNode target = apply(e, ImmutableSet.<CorrelationId>of(),
-              logic, builder, 2, fieldCount);
-          final RexShuttle shuttle = new ReplaceSubQueryShuttle(e, target);
-          builder.join(join.getJoinType(), shuttle.apply(join.getCondition()));
-          builder.project(fields(builder, join.getRowType().getFieldCount()));
-          call.transformTo(builder.build());
-        }
-      };
+      new SubQueryJoinRemoveRule(RelFactories.LOGICAL_BUILDER);
 
   /**
    * Creates a SubQueryRemoveRule.
@@ -166,137 +92,297 @@ public abstract class SubQueryRemoveRule extends 
RelOptRule {
       RelBuilder builder, int inputCount, int offset) {
     switch (e.getKind()) {
     case SCALAR_QUERY:
-      builder.push(e.rel);
-      final RelMetadataQuery mq = e.rel.getCluster().getMetadataQuery();
-      final Boolean unique = mq.areColumnsUnique(builder.peek(),
-          ImmutableBitSet.of());
-      if (unique == null || !unique) {
-        builder.aggregate(builder.groupKey(),
-            builder.aggregateCall(SqlStdOperatorTable.SINGLE_VALUE, false,
-                false, null, null, builder.field(0)));
-      }
-      builder.join(JoinRelType.LEFT, builder.literal(true), variablesSet);
-      return field(builder, inputCount, offset);
-
+      return rewriteScalarQuery(e, variablesSet, builder, inputCount, offset);
     case SOME:
-      // Most general case, where the left and right keys might have nulls, and
-      // caller requires 3-valued logic return.
-      //
-      // select e.deptno, e.deptno < some (select deptno from emp) as v
-      // from emp as e
-      //
-      // becomes
-      //
-      // select e.deptno,
-      //   case
-      //   when q.c = 0 then false // sub-query is empty
-      //   when (e.deptno < q.m) is true then true
-      //   when q.c > q.d then unknown // sub-query has at least one null
-      //   else e.deptno < q.m
-      //   end as v
-      // from emp as e
-      // cross join (
-      //   select max(deptno) as m, count(*) as c, count(deptno) as d
-      //   from emp) as q
-      //
-      final SqlQuantifyOperator op = (SqlQuantifyOperator) e.op;
-      builder.push(e.rel)
-          .aggregate(builder.groupKey(),
-              op.comparisonKind == SqlKind.GREATER_THAN
-                  || op.comparisonKind == SqlKind.GREATER_THAN_OR_EQUAL
-                  ? builder.min("m", builder.field(0))
-                  : builder.max("m", builder.field(0)),
-              builder.count(false, "c"),
-              builder.count(false, "d", builder.field(0)))
-          .as("q")
-          .join(JoinRelType.INNER);
-      return builder.call(SqlStdOperatorTable.CASE,
-          builder.call(SqlStdOperatorTable.EQUALS,
-              builder.field("q", "c"), builder.literal(0)),
-          builder.literal(false),
-          builder.call(SqlStdOperatorTable.IS_TRUE,
-              builder.call(RelOptUtil.op(op.comparisonKind, null),
-                  e.operands.get(0), builder.field("q", "m"))),
-          builder.literal(true),
-          builder.call(SqlStdOperatorTable.GREATER_THAN,
-              builder.field("q", "c"), builder.field("q", "d")),
-          builder.literal(null),
-          builder.call(RelOptUtil.op(op.comparisonKind, null),
-              e.operands.get(0), builder.field("q", "m")));
-
+      return rewriteSome(e, builder);
     case IN:
+      return rewriteIn(e, variablesSet, logic, builder, offset);
     case EXISTS:
-      // Most general case, where the left and right keys might have nulls, and
-      // caller requires 3-valued logic return.
-      //
-      // select e.deptno, e.deptno in (select deptno from emp)
-      // from emp as e
-      //
-      // becomes
-      //
-      // select e.deptno,
-      //   case
-      //   when ct.c = 0 then false
-      //   when dt.i is not null then true
-      //   when e.deptno is null then null
-      //   when ct.ck < ct.c then null
-      //   else false
-      //   end
-      // from emp as e
-      // left join (
-      //   (select count(*) as c, count(deptno) as ck from emp) as ct
-      //   cross join (select distinct deptno, true as i from emp)) as dt
-      //   on e.deptno = dt.deptno
-      //
-      // If keys are not null we can remove "ct" and simplify to
-      //
-      // select e.deptno,
-      //   case
-      //   when dt.i is not null then true
-      //   else false
-      //   end
-      // from emp as e
-      // left join (select distinct deptno, true as i from emp) as dt
-      //   on e.deptno = dt.deptno
-      //
-      // We could further simplify to
-      //
-      // select e.deptno,
-      //   dt.i is not null
-      // from emp as e
-      // left join (select distinct deptno, true as i from emp) as dt
-      //   on e.deptno = dt.deptno
-      //
-      // but have not yet.
-      //
-      // If the logic is TRUE we can just kill the record if the condition
-      // evaluates to FALSE or UNKNOWN. Thus the query simplifies to an inner
-      // join:
-      //
-      // select e.deptno,
-      //   true
-      // from emp as e
-      // inner join (select distinct deptno from emp) as dt
-      //   on e.deptno = dt.deptno
-      //
-
-      builder.push(e.rel);
-      final List<RexNode> fields = new ArrayList<>();
-      switch (e.getKind()) {
-      case IN:
-        fields.addAll(builder.fields());
-      }
+      return rewriteExists(e, variablesSet, logic, builder);
+    default:
+      throw new AssertionError(e.getKind());
+    }
+  }
+
+  /**
+   * Rewrites a scalar sub-query into an
+   * {@link org.apache.calcite.rel.core.Aggregate}.
+   *
+   * @param e            IN sub-query to rewrite
+   * @param variablesSet A set of variables used by a relational
+   *                     expression of the specified RexSubQuery
+   * @param builder      Builder
+   * @param offset       Offset to shift {@link RexInputRef}
+   *
+   * @return Expression that may be used to replace the RexSubQuery
+   */
+  private RexNode rewriteScalarQuery(RexSubQuery e, Set<CorrelationId> 
variablesSet,
+      RelBuilder builder, int inputCount, int offset) {
+    builder.push(e.rel);
+    final RelMetadataQuery mq = e.rel.getCluster().getMetadataQuery();
+    final Boolean unique = mq.areColumnsUnique(builder.peek(),
+        ImmutableBitSet.of());
+    if (unique == null || !unique) {
+      builder.aggregate(builder.groupKey(),
+          builder.aggregateCall(SqlStdOperatorTable.SINGLE_VALUE, false,
+              false, null, null, builder.field(0)));
+    }
+    builder.join(JoinRelType.LEFT, builder.literal(true), variablesSet);
+    return field(builder, inputCount, offset);
+  }
+
+  /**
+   * Rewrites a SOME sub-query into a {@link Join}.
+   *
+   * @param e            SOME sub-query to rewrite
+   * @param builder      Builder
+   *
+   * @return Expression that may be used to replace the RexSubQuery
+   */
+  private RexNode rewriteSome(RexSubQuery e, RelBuilder builder) {
+    // Most general case, where the left and right keys might have nulls, and
+    // caller requires 3-valued logic return.
+    //
+    // select e.deptno, e.deptno < some (select deptno from emp) as v
+    // from emp as e
+    //
+    // becomes
+    //
+    // select e.deptno,
+    //   case
+    //   when q.c = 0 then false // sub-query is empty
+    //   when (e.deptno < q.m) is true then true
+    //   when q.c > q.d then unknown // sub-query has at least one null
+    //   else e.deptno < q.m
+    //   end as v
+    // from emp as e
+    // cross join (
+    //   select max(deptno) as m, count(*) as c, count(deptno) as d
+    //   from emp) as q
+    //
+    final SqlQuantifyOperator op = (SqlQuantifyOperator) e.op;
+    builder.push(e.rel)
+        .aggregate(builder.groupKey(),
+            op.comparisonKind == SqlKind.GREATER_THAN
+              || op.comparisonKind == SqlKind.GREATER_THAN_OR_EQUAL
+              ? builder.min("m", builder.field(0))
+              : builder.max("m", builder.field(0)),
+            builder.count(false, "c"),
+            builder.count(false, "d", builder.field(0)))
+        .as("q")
+        .join(JoinRelType.INNER);
+    return builder.call(SqlStdOperatorTable.CASE,
+      builder.call(SqlStdOperatorTable.EQUALS,
+          builder.field("q", "c"), builder.literal(0)),
+      builder.literal(false),
+      builder.call(SqlStdOperatorTable.IS_TRUE,
+          builder.call(RelOptUtil.op(op.comparisonKind, null),
+              e.operands.get(0), builder.field("q", "m"))),
+      builder.literal(true),
+      builder.call(SqlStdOperatorTable.GREATER_THAN,
+          builder.field("q", "c"), builder.field("q", "d")),
+      builder.literal(null),
+      builder.call(RelOptUtil.op(op.comparisonKind, null),
+          e.operands.get(0), builder.field("q", "m")));
+  }
 
-      // First, the cross join
+  /**
+   * Rewrites an EXISTS RexSubQuery into a {@link Join}.
+   *
+   * @param e            EXISTS sub-query to rewrite
+   * @param variablesSet A set of variables used by a relational
+   *                     expression of the specified RexSubQuery
+   * @param logic        Logic for evaluating
+   * @param builder      Builder
+   *
+   * @return Expression that may be used to replace the RexSubQuery
+   */
+  private RexNode rewriteExists(RexSubQuery e, Set<CorrelationId> variablesSet,
+      RelOptUtil.Logic logic, RelBuilder builder) {
+    builder.push(e.rel);
+
+    builder.project(builder.alias(builder.literal(true), "i"));
+    switch (logic) {
+    case TRUE:
+      // Handles queries with single EXISTS in filter condition:
+      // select e.deptno from emp as e
+      // where exists (select deptno from emp)
+      builder.aggregate(builder.groupKey(0));
+      builder.as("dt");
+      builder.join(JoinRelType.INNER, builder.literal(true), variablesSet);
+      return builder.literal(true);
+    default:
+      builder.distinct();
+    }
+
+    builder.as("dt");
+
+    builder.join(JoinRelType.LEFT, builder.literal(true), variablesSet);
+
+    return builder.isNotNull(Util.last(builder.fields()));
+  }
+
+  /**
+   * Rewrites an IN RexSubQuery into a {@link Join}.
+   *
+   * @param e            IN sub-query to rewrite
+   * @param variablesSet A set of variables used by a relational
+   *                     expression of the specified RexSubQuery
+   * @param logic        Logic for evaluating
+   * @param builder      Builder
+   * @param offset       Offset to shift {@link RexInputRef}
+   *
+   * @return Expression that may be used to replace the RexSubQuery
+   */
+  private RexNode rewriteIn(RexSubQuery e, Set<CorrelationId> variablesSet,
+      RelOptUtil.Logic logic, RelBuilder builder, int offset) {
+    // Most general case, where the left and right keys might have nulls, and
+    // caller requires 3-valued logic return.
+    //
+    // select e.deptno, e.deptno in (select deptno from emp)
+    // from emp as e
+    //
+    // becomes
+    //
+    // select e.deptno,
+    //   case
+    //   when ct.c = 0 then false
+    //   when e.deptno is null then null
+    //   when dt.i is not null then true
+    //   when ct.ck < ct.c then null
+    //   else false
+    //   end
+    // from emp as e
+    // left join (
+    //   (select count(*) as c, count(deptno) as ck from emp) as ct
+    //   cross join (select distinct deptno, true as i from emp)) as dt
+    //   on e.deptno = dt.deptno
+    //
+    // If keys are not null we can remove "ct" and simplify to
+    //
+    // select e.deptno,
+    //   case
+    //   when dt.i is not null then true
+    //   else false
+    //   end
+    // from emp as e
+    // left join (select distinct deptno, true as i from emp) as dt
+    //   on e.deptno = dt.deptno
+    //
+    // We could further simplify to
+    //
+    // select e.deptno,
+    //   dt.i is not null
+    // from emp as e
+    // left join (select distinct deptno, true as i from emp) as dt
+    //   on e.deptno = dt.deptno
+    //
+    // but have not yet.
+    //
+    // If the logic is TRUE we can just kill the record if the condition
+    // evaluates to FALSE or UNKNOWN. Thus the query simplifies to an inner
+    // join:
+    //
+    // select e.deptno,
+    //   true
+    // from emp as e
+    // inner join (select distinct deptno from emp) as dt
+    //   on e.deptno = dt.deptno
+    //
+
+    builder.push(e.rel);
+    final List<RexNode> fields = new ArrayList<>(builder.fields());
+
+    // for the case when IN has only literal operands, it may be handled
+    // in the simpler way:
+    //
+    // select e.deptno, 123456 in (select deptno from emp)
+    // from emp as e
+    //
+    // becomes
+    //
+    // select e.deptno,
+    //   case
+    //   when dt.c IS NULL THEN FALSE
+    //   when e.deptno IS NULL THEN NULL
+    //   when dt.cs IS FALSE THEN NULL
+    //   when dt.cs IS NOT NULL THEN TRUE
+    //   else false
+    //   end
+    // from emp AS e
+    // cross join (
+    //   select distinct deptno is not null as cs, count(*) as c
+    //   from emp
+    //   where deptno = 123456 or deptno is null or e.deptno is null
+    //   order by cs desc limit 1) as dt
+    //
+
+    boolean allLiterals = RexUtil.allLiterals(e.getOperands());
+    final List<RexNode> expressionOperands = new ArrayList<>(e.getOperands());
+
+    final List<RexNode> keyIsNulls = e.getOperands().stream()
+        .filter(operand -> operand.getType().isNullable())
+        .map(builder::isNull)
+        .collect(Collectors.toList());
+
+    if (allLiterals) {
+      final List<RexNode> conditions =
+          Pair.zip(expressionOperands, fields).stream()
+              .map(pair -> builder.equals(pair.left, pair.right))
+              .collect(Collectors.toList());
+      switch (logic) {
+      case TRUE:
+      case TRUE_FALSE:
+        builder.filter(conditions);
+        builder.project(builder.alias(builder.literal(true), "cs"));
+        builder.distinct();
+        break;
+      default:
+        List<RexNode> isNullOpperands = fields.stream()
+            .map(builder::isNull)
+            .collect(Collectors.toList());
+        // uses keyIsNulls conditions in the filter to avoid empty results
+        isNullOpperands.addAll(keyIsNulls);
+        builder.filter(
+            builder.or(
+                builder.and(conditions),
+                builder.or(
+                    isNullOpperands
+                )));
+        RexNode project = builder.and(
+            fields.stream()
+                .map(builder::isNotNull)
+                .collect(Collectors.toList()));
+        builder.project(builder.alias(project, "cs"));
+
+        if (variablesSet.isEmpty()) {
+          builder.aggregate(builder.groupKey(builder.field("cs")),
+              builder.count(false, "c"));
+
+          // sorts input with desc order since we are interested
+          // only in the case when one of the values is true.
+          // When true value is absent then we are interested
+          // only in false value.
+          builder.sortLimit(0, 1,
+              ImmutableList.of(
+                  builder.call(SqlStdOperatorTable.DESC,
+                      builder.field("cs"))));
+        } else {
+          builder.distinct();
+        }
+      }
+      // clears expressionOperands and fields lists since
+      // all expressions were used in the filter
+      expressionOperands.clear();
+      fields.clear();
+    } else {
       switch (logic) {
+      case TRUE:
+        builder.aggregate(builder.groupKey(fields));
+        break;
       case TRUE_FALSE_UNKNOWN:
       case UNKNOWN_AS_TRUE:
-        // Since EXISTS/NOT EXISTS are not affected by presence of
-        // null keys we do not need to generate count(*), count(c)
-        if (e.getKind() == SqlKind.EXISTS) {
-          logic = RelOptUtil.Logic.TRUE_FALSE;
-          break;
-        }
+        // Builds the cross join
         builder.aggregate(builder.groupKey(),
             builder.count(false, "c"),
             builder.aggregateCall(SqlStdOperatorTable.COUNT, false, false, 
null,
@@ -309,76 +395,78 @@ public abstract class SubQueryRemoveRule extends 
RelOptRule {
         }
         offset += 2;
         builder.push(e.rel);
-        break;
-      }
-
-      // Now the left join
-      switch (logic) {
-      case TRUE:
-        if (fields.isEmpty()) {
-          builder.project(builder.alias(builder.literal(true), "i"));
-          builder.aggregate(builder.groupKey(0));
-        } else {
-          builder.aggregate(builder.groupKey(fields));
-        }
-        break;
+        // fall through
       default:
         fields.add(builder.alias(builder.literal(true), "i"));
         builder.project(fields);
         builder.distinct();
       }
-      builder.as("dt");
-      final List<RexNode> conditions = new ArrayList<>();
-      for (Pair<RexNode, RexNode> pair
-          : Pair.zip(e.getOperands(), builder.fields())) {
-        conditions.add(
-            builder.equals(pair.left, RexUtil.shift(pair.right, offset)));
-      }
-      switch (logic) {
-      case TRUE:
-        builder.join(JoinRelType.INNER, builder.and(conditions), variablesSet);
-        return builder.literal(true);
-      }
-      builder.join(JoinRelType.LEFT, builder.and(conditions), variablesSet);
+    }
 
-      final List<RexNode> keyIsNulls = new ArrayList<>();
-      for (RexNode operand : e.getOperands()) {
-        if (operand.getType().isNullable()) {
-          keyIsNulls.add(builder.isNull(operand));
+    builder.as("dt");
+    int refOffset = offset;
+    final List<RexNode> conditions =
+        Pair.zip(expressionOperands, builder.fields()).stream()
+            .map(pair -> builder.equals(pair.left, RexUtil.shift(pair.right, 
refOffset)))
+            .collect(Collectors.toList());
+    switch (logic) {
+    case TRUE:
+      builder.join(JoinRelType.INNER, builder.and(conditions), variablesSet);
+      return builder.literal(true);
+    }
+    // Now the left join
+    builder.join(JoinRelType.LEFT, builder.and(conditions), variablesSet);
+
+    final ImmutableList.Builder<RexNode> operands = ImmutableList.builder();
+    Boolean b = true;
+    switch (logic) {
+    case TRUE_FALSE_UNKNOWN:
+      b = null;
+      // fall through
+    case UNKNOWN_AS_TRUE:
+      if (allLiterals) {
+        // Considers case when right side of IN is empty
+        // for the case of non-correlated sub-queries
+        if (variablesSet.isEmpty()) {
+          operands.add(
+              builder.isNull(builder.field("c")),
+              builder.literal(false));
         }
-      }
-      final ImmutableList.Builder<RexNode> operands = ImmutableList.builder();
-      switch (logic) {
-      case TRUE_FALSE_UNKNOWN:
-      case UNKNOWN_AS_TRUE:
+        operands.add(
+            builder.equals(builder.field("cs"), builder.literal(false)),
+            builder.literal(b));
+      } else {
         operands.add(
             builder.equals(builder.field("ct", "c"), builder.literal(0)),
             builder.literal(false));
-        break;
       }
+      break;
+    }
+
+    if (!keyIsNulls.isEmpty()) {
+      operands.add(builder.or(keyIsNulls), builder.literal(null));
+    }
+
+    if (allLiterals) {
+      operands.add(builder.isNotNull(builder.field("cs")),
+          builder.literal(true));
+    } else {
       operands.add(builder.isNotNull(Util.last(builder.fields())),
           builder.literal(true));
-      if (!keyIsNulls.isEmpty()) {
-        operands.add(builder.or(keyIsNulls), builder.literal(null));
-      }
-      Boolean b = true;
+    }
+
+    if (!allLiterals) {
       switch (logic) {
       case TRUE_FALSE_UNKNOWN:
-        b = null;
-        // fall through
       case UNKNOWN_AS_TRUE:
         operands.add(
             builder.call(SqlStdOperatorTable.LESS_THAN,
                 builder.field("ct", "ck"), builder.field("ct", "c")),
             builder.literal(b));
-        break;
       }
-      operands.add(builder.literal(false));
-      return builder.call(SqlStdOperatorTable.CASE, operands.build());
-
-    default:
-      throw new AssertionError(e.getKind());
     }
+    operands.add(builder.literal(false));
+    return builder.call(SqlStdOperatorTable.CASE, operands.build());
   }
 
   /** Returns a reference to a particular field, by offset, across several
@@ -404,6 +492,102 @@ public abstract class SubQueryRemoveRule extends 
RelOptRule {
     return projects;
   }
 
+  /** Rule that converts sub-queries from project expressions into
+   * {@link Correlate} instances. */
+  public static class SubQueryProjectRemoveRule extends SubQueryRemoveRule {
+    public SubQueryProjectRemoveRule(RelBuilderFactory relBuilderFactory) {
+      super(
+          operand(Project.class, null, 
RexUtil.SubQueryFinder.PROJECT_PREDICATE,
+              any()), relBuilderFactory, "SubQueryRemoveRule:Project");
+    }
+
+    public void onMatch(RelOptRuleCall call) {
+      final Project project = call.rel(0);
+      final RelBuilder builder = call.builder();
+      final RexSubQuery e =
+          RexUtil.SubQueryFinder.find(project.getProjects());
+      assert e != null;
+      final RelOptUtil.Logic logic =
+          LogicVisitor.find(RelOptUtil.Logic.TRUE_FALSE_UNKNOWN,
+              project.getProjects(), e);
+      builder.push(project.getInput());
+      final int fieldCount = builder.peek().getRowType().getFieldCount();
+      final RexNode target = apply(e, ImmutableSet.of(),
+          logic, builder, 1, fieldCount);
+      final RexShuttle shuttle = new ReplaceSubQueryShuttle(e, target);
+      builder.project(shuttle.apply(project.getProjects()),
+          project.getRowType().getFieldNames());
+      call.transformTo(builder.build());
+    }
+  }
+
+  /** Rule that converts a sub-queries from filter expressions into
+   * {@link Correlate} instances. */
+  public static class SubQueryFilterRemoveRule extends SubQueryRemoveRule {
+    public SubQueryFilterRemoveRule(RelBuilderFactory relBuilderFactory) {
+      super(
+          operand(Filter.class, null, RexUtil.SubQueryFinder.FILTER_PREDICATE,
+              any()), relBuilderFactory, "SubQueryRemoveRule:Filter");
+    }
+
+    public void onMatch(RelOptRuleCall call) {
+      final Filter filter = call.rel(0);
+      final RelBuilder builder = call.builder();
+      builder.push(filter.getInput());
+      int count = 0;
+      RexNode c = filter.getCondition();
+      while (true) {
+        final RexSubQuery e = RexUtil.SubQueryFinder.find(c);
+        if (e == null) {
+          assert count > 0;
+          break;
+        }
+        ++count;
+        final RelOptUtil.Logic logic =
+            LogicVisitor.find(RelOptUtil.Logic.TRUE, ImmutableList.of(c), e);
+        final Set<CorrelationId>  variablesSet =
+            RelOptUtil.getVariablesUsed(e.rel);
+        final RexNode target = apply(e, variablesSet, logic,
+            builder, 1, builder.peek().getRowType().getFieldCount());
+        final RexShuttle shuttle = new ReplaceSubQueryShuttle(e, target);
+        c = c.accept(shuttle);
+      }
+      builder.filter(c);
+      builder.project(fields(builder, filter.getRowType().getFieldCount()));
+      call.transformTo(builder.build());
+    }
+  }
+
+  /** Rule that converts sub-queries from join expressions into
+   * {@link Correlate} instances. */
+  public static class SubQueryJoinRemoveRule extends SubQueryRemoveRule {
+    public SubQueryJoinRemoveRule(RelBuilderFactory relBuilderFactory) {
+      super(
+          operand(Join.class, null, RexUtil.SubQueryFinder.JOIN_PREDICATE,
+              any()), relBuilderFactory, "SubQueryRemoveRule:Join");
+    }
+
+    public void onMatch(RelOptRuleCall call) {
+      final Join join = call.rel(0);
+      final RelBuilder builder = call.builder();
+      final RexSubQuery e =
+          RexUtil.SubQueryFinder.find(join.getCondition());
+      assert e != null;
+      final RelOptUtil.Logic logic =
+          LogicVisitor.find(RelOptUtil.Logic.TRUE,
+              ImmutableList.of(join.getCondition()), e);
+      builder.push(join.getLeft());
+      builder.push(join.getRight());
+      final int fieldCount = join.getRowType().getFieldCount();
+      final RexNode target = apply(e, ImmutableSet.of(),
+          logic, builder, 2, fieldCount);
+      final RexShuttle shuttle = new ReplaceSubQueryShuttle(e, target);
+      builder.join(join.getJoinType(), shuttle.apply(join.getCondition()));
+      builder.project(fields(builder, join.getRowType().getFieldCount()));
+      call.transformTo(builder.build());
+    }
+  }
+
   /** Shuttle that replaces occurrences of a given
    * {@link org.apache.calcite.rex.RexSubQuery} with a replacement
    * expression. */

http://git-wip-us.apache.org/repos/asf/calcite/blob/1ae6a526/core/src/main/java/org/apache/calcite/rex/RexUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rex/RexUtil.java 
b/core/src/main/java/org/apache/calcite/rex/RexUtil.java
index 6f467be..1e0fb15 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexUtil.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexUtil.java
@@ -260,7 +260,7 @@ public class RexUtil {
    */
   public static boolean isLiteral(RexNode node, boolean allowCast) {
     assert node != null;
-    if (node instanceof RexLiteral) {
+    if (node.isA(SqlKind.LITERAL)) {
       return true;
     }
     if (allowCast) {
@@ -276,6 +276,21 @@ public class RexUtil {
   }
 
   /**
+   * Returns whether every expression in a list is a literal.
+   *
+   * @param expressionOperands list of expressions to check
+   * @return true if every expression from the specified list is literal.
+   */
+  public static boolean allLiterals(List<RexNode> expressionOperands) {
+    for (RexNode rexNode : expressionOperands) {
+      if (!isLiteral(rexNode, true)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
    * Returns whether a node represents an input reference or field access.
    *
    * @param node The node, never null.

http://git-wip-us.apache.org/repos/asf/calcite/blob/1ae6a526/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
----------------------------------------------------------------------
diff --git 
a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml 
b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index af39d3c..36ef8ab 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -7378,7 +7378,7 @@ LogicalFilter(condition=[<($0, 20)])
         </Resource>
         <Resource name="planAfter">
             <![CDATA[
-LogicalProject(EMPNO=[$0], D=[CASE(IS NOT NULL($9), true, false)])
+LogicalProject(EMPNO=[$0], D=[IS NOT NULL($9)])
   LogicalJoin(condition=[true], joinType=[left])
     LogicalTableScan(table=[[CATALOG, SALES, EMP]])
     LogicalAggregate(group=[{0}])
@@ -7839,7 +7839,7 @@ LogicalProject(DEPTNO=[$1])
         </Resource>
         <Resource name="planAfter">
             <![CDATA[
-LogicalProject(EMPNO=[$0], D=[CASE(=($2, 0), false, IS NOT NULL($5), true, IS 
NULL($1), null, <($3, $2), null, false)])
+LogicalProject(EMPNO=[$0], D=[CASE(=($2, 0), false, IS NULL($1), null, IS NOT 
NULL($5), true, <($3, $2), null, false)])
   LogicalJoin(condition=[=($1, $4)], joinType=[left])
     LogicalJoin(condition=[true], joinType=[inner])
       LogicalProject(EMPNO=[$0], DEPTNO=[CASE(true, CAST($7):INTEGER, null)])

http://git-wip-us.apache.org/repos/asf/calcite/blob/1ae6a526/core/src/test/resources/sql/blank.iq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/blank.iq 
b/core/src/test/resources/sql/blank.iq
index bcaa9e8..620f123 100644
--- a/core/src/test/resources/sql/blank.iq
+++ b/core/src/test/resources/sql/blank.iq
@@ -73,7 +73,7 @@ insert into table2 values (NULL, 1), (2, 1);
 # Checked on Oracle
 !set lateDecorrelate true
 select i, j from table1 where table1.j NOT IN (select i from table2 where 
table1.i=table2.j);
-EnumerableCalc(expr#0..7=[{inputs}], expr#8=[0], expr#9=[=($t3, $t8)], 
expr#10=[false], expr#11=[IS NOT NULL($t7)], expr#12=[true], expr#13=[IS 
NULL($t1)], expr#14=[null], expr#15=[<($t4, $t3)], expr#16=[CASE($t9, $t10, 
$t11, $t12, $t13, $t14, $t15, $t12, $t10)], expr#17=[NOT($t16)], 
proj#0..1=[{exprs}], $condition=[$t17])
+EnumerableCalc(expr#0..7=[{inputs}], expr#8=[0], expr#9=[=($t3, $t8)], 
expr#10=[false], expr#11=[IS NULL($t1)], expr#12=[null], expr#13=[IS NOT 
NULL($t7)], expr#14=[true], expr#15=[<($t4, $t3)], expr#16=[CASE($t9, $t10, 
$t11, $t12, $t13, $t14, $t15, $t14, $t10)], expr#17=[NOT($t16)], 
proj#0..1=[{exprs}], $condition=[$t17])
   EnumerableJoin(condition=[AND(=($0, $6), =($1, $5))], joinType=[left])
     EnumerableJoin(condition=[=($0, $2)], joinType=[left])
       EnumerableTableScan(table=[[BLANK, TABLE1]])

http://git-wip-us.apache.org/repos/asf/calcite/blob/1ae6a526/core/src/test/resources/sql/sub-query.iq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/sub-query.iq 
b/core/src/test/resources/sql/sub-query.iq
index c9142ea..5048445 100644
--- a/core/src/test/resources/sql/sub-query.iq
+++ b/core/src/test/resources/sql/sub-query.iq
@@ -32,7 +32,7 @@ where t1.x not in (select t2.x from t2);
 (0 rows)
 
 !ok
-EnumerableCalc(expr#0..4=[{inputs}], expr#5=[0], expr#6=[=($t1, $t5)], 
expr#7=[false], expr#8=[IS NOT NULL($t4)], expr#9=[true], expr#10=[IS 
NULL($t0)], expr#11=[null], expr#12=[<($t2, $t1)], expr#13=[CASE($t6, $t7, $t8, 
$t9, $t10, $t11, $t12, $t9, $t7)], expr#14=[NOT($t13)], EXPR$0=[$t0], 
$condition=[$t14])
+EnumerableCalc(expr#0..4=[{inputs}], expr#5=[0], expr#6=[=($t1, $t5)], 
expr#7=[false], expr#8=[IS NULL($t0)], expr#9=[null], expr#10=[IS NOT 
NULL($t4)], expr#11=[true], expr#12=[<($t2, $t1)], expr#13=[CASE($t6, $t7, $t8, 
$t9, $t10, $t11, $t12, $t11, $t7)], expr#14=[NOT($t13)], EXPR$0=[$t0], 
$condition=[$t14])
   EnumerableJoin(condition=[=($0, $3)], joinType=[left])
     EnumerableJoin(condition=[true], joinType=[inner])
       EnumerableUnion(all=[true])
@@ -757,4 +757,1272 @@ and empno in (7876, 7698, 7900);
 
 !ok
 
+!set outputformat psql
+
+!set expand false
+
+# [CALCITE-2329] Enhance SubQueryRemoveRule to rewrite IN operator with the 
constant from the left side more optimally
+# Test project null IN null
+select sal,
+  cast(null as int) IN (
+    select cast(null as int)
+    from "scott".dept)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | null
+ 1250.00 | null
+ 1250.00 | null
+ 1300.00 | null
+ 1500.00 | null
+ 1600.00 | null
+ 2450.00 | null
+ 2850.00 | null
+ 2975.00 | null
+ 3000.00 | null
+ 3000.00 | null
+ 5000.00 | null
+  800.00 | null
+  950.00 | null
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[null], expr#8=[null], expr#9=[IS NULL($t8)], 
expr#10=[IS NOT NULL($t2)], expr#11=[true], expr#12=[CASE($t4, $t5, $t6, $t7, 
$t9, $t7, $t10, $t11, $t5)], SAL=[$t1], EXPR$1=[$t12])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[false], expr#4=[null], 
expr#5=[=($t4, $t4)], expr#6=[IS NULL($t4)], expr#7=[OR($t5, $t6)], cs=[$t3], 
$condition=[$t7])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test project literal IN null non-correlated
+select sal,
+  123 IN (
+    select cast(null as int)
+    from "scott".dept)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | null
+ 1250.00 | null
+ 1250.00 | null
+ 1300.00 | null
+ 1500.00 | null
+ 1600.00 | null
+ 2450.00 | null
+ 2850.00 | null
+ 2975.00 | null
+ 3000.00 | null
+ 3000.00 | null
+ 5000.00 | null
+  800.00 | null
+  950.00 | null
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[null], expr#8=[IS NOT NULL($t2)], expr#9=[true], 
expr#10=[CASE($t4, $t5, $t6, $t7, $t8, $t9, $t5)], SAL=[$t1], EXPR$1=[$t10])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[false], expr#4=[123], 
expr#5=[null], expr#6=[=($t4, $t5)], expr#7=[IS NULL($t5)], expr#8=[OR($t6, 
$t7)], cs=[$t3], $condition=[$t8])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test project null IN literal non-correlated
+select sal,
+  cast(null as int) IN (
+    select 1
+    from "scott".dept)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | null
+ 1250.00 | null
+ 1250.00 | null
+ 1300.00 | null
+ 1500.00 | null
+ 1600.00 | null
+ 2450.00 | null
+ 2850.00 | null
+ 2975.00 | null
+ 3000.00 | null
+ 3000.00 | null
+ 5000.00 | null
+  800.00 | null
+  950.00 | null
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[null], expr#8=[null], expr#9=[IS NULL($t8)], 
expr#10=[IS NOT NULL($t2)], expr#11=[true], expr#12=[CASE($t4, $t5, $t6, $t7, 
$t9, $t7, $t10, $t11, $t5)], SAL=[$t1], EXPR$1=[$t12])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[1], expr#6=[=($t4, $t5)], expr#7=[IS NULL($t4)], expr#8=[OR($t6, $t7)], 
cs=[$t3], $condition=[$t8])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test project null IN required
+select sal,
+  cast(null as int) IN (
+    select deptno
+    from "scott".dept)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | null
+ 1250.00 | null
+ 1250.00 | null
+ 1300.00 | null
+ 1500.00 | null
+ 1600.00 | null
+ 2450.00 | null
+ 2850.00 | null
+ 2975.00 | null
+ 3000.00 | null
+ 3000.00 | null
+ 5000.00 | null
+  800.00 | null
+  950.00 | null
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[null], expr#8=[null], expr#9=[IS NULL($t8)], 
expr#10=[IS NOT NULL($t2)], expr#11=[true], expr#12=[CASE($t4, $t5, $t6, $t7, 
$t9, $t7, $t10, $t11, $t5)], SAL=[$t1], EXPR$1=[$t12])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[=($t4, $t0)], expr#6=[IS NULL($t4)], expr#7=[OR($t5, $t6)], cs=[$t3], 
$condition=[$t7])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test project null IN nullable
+select sal,
+  cast(null as int) IN (
+    select case when true then deptno else null end
+    from "scott".dept)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | null
+ 1250.00 | null
+ 1250.00 | null
+ 1300.00 | null
+ 1500.00 | null
+ 1600.00 | null
+ 2450.00 | null
+ 2850.00 | null
+ 2975.00 | null
+ 3000.00 | null
+ 3000.00 | null
+ 5000.00 | null
+  800.00 | null
+  950.00 | null
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[null], expr#8=[null], expr#9=[IS NULL($t8)], 
expr#10=[IS NOT NULL($t2)], expr#11=[true], expr#12=[CASE($t4, $t5, $t6, $t7, 
$t9, $t7, $t10, $t11, $t5)], SAL=[$t1], EXPR$1=[$t12])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[CAST($t0):TINYINT], expr#6=[=($t4, $t5)], expr#7=[IS NULL($t5)], 
expr#8=[IS NULL($t4)], expr#9=[OR($t6, $t7, $t8)], cs=[$t3], $condition=[$t9])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test project literal IN required
+select sal,
+  10 IN (
+    select deptno
+    from "scott".dept)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | true
+ 1250.00 | true
+ 1250.00 | true
+ 1300.00 | true
+ 1500.00 | true
+ 1600.00 | true
+ 2450.00 | true
+ 2850.00 | true
+ 2975.00 | true
+ 3000.00 | true
+ 3000.00 | true
+ 5000.00 | true
+  800.00 | true
+  950.00 | true
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..2=[{inputs}], expr#3=[IS NOT NULL($t2)], SAL=[$t1], 
EXPR$1=[$t3])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableAggregate(group=[{0}])
+      EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[10], 
expr#5=[=($t4, $t0)], cs=[$t3], $condition=[$t5])
+        EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test project literal IN nullable
+select sal,
+  10 IN (
+    select case when true then deptno else null end
+    from "scott".dept)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | true
+ 1250.00 | true
+ 1250.00 | true
+ 1300.00 | true
+ 1500.00 | true
+ 1600.00 | true
+ 2450.00 | true
+ 2850.00 | true
+ 2975.00 | true
+ 3000.00 | true
+ 3000.00 | true
+ 5000.00 | true
+  800.00 | true
+  950.00 | true
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[null], expr#8=[IS NOT NULL($t2)], expr#9=[true], 
expr#10=[CASE($t4, $t5, $t6, $t7, $t8, $t9, $t5)], SAL=[$t1], EXPR$1=[$t10])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[10], 
expr#5=[CAST($t0):TINYINT], expr#6=[=($t4, $t5)], expr#7=[IS NULL($t5)], 
expr#8=[OR($t6, $t7)], cs=[$t3], $condition=[$t8])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test project null NOT IN null non-correlated
+select sal,
+  cast(null as int) NOT IN (
+    select cast(null as int)
+    from "scott".dept)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | null
+ 1250.00 | null
+ 1250.00 | null
+ 1300.00 | null
+ 1500.00 | null
+ 1600.00 | null
+ 2450.00 | null
+ 2850.00 | null
+ 2975.00 | null
+ 3000.00 | null
+ 3000.00 | null
+ 5000.00 | null
+  800.00 | null
+  950.00 | null
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[null], expr#8=[null], expr#9=[IS NULL($t8)], 
expr#10=[IS NOT NULL($t2)], expr#11=[true], expr#12=[CASE($t4, $t5, $t6, $t7, 
$t9, $t7, $t10, $t11, $t5)], expr#13=[NOT($t12)], SAL=[$t1], EXPR$1=[$t13])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[false], expr#4=[null], 
expr#5=[=($t4, $t4)], expr#6=[IS NULL($t4)], expr#7=[OR($t5, $t6)], cs=[$t3], 
$condition=[$t7])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test project literal NOT IN null non-correlated
+select sal,
+  123 NOT IN (
+    select cast(null as int)
+    from "scott".dept)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | null
+ 1250.00 | null
+ 1250.00 | null
+ 1300.00 | null
+ 1500.00 | null
+ 1600.00 | null
+ 2450.00 | null
+ 2850.00 | null
+ 2975.00 | null
+ 3000.00 | null
+ 3000.00 | null
+ 5000.00 | null
+  800.00 | null
+  950.00 | null
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[null], expr#8=[IS NOT NULL($t2)], expr#9=[true], 
expr#10=[CASE($t4, $t5, $t6, $t7, $t8, $t9, $t5)], expr#11=[NOT($t10)], 
SAL=[$t1], EXPR$1=[$t11])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[false], expr#4=[123], 
expr#5=[null], expr#6=[=($t4, $t5)], expr#7=[IS NULL($t5)], expr#8=[OR($t6, 
$t7)], cs=[$t3], $condition=[$t8])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test project null NOT IN literal non-correlated
+select sal,
+  cast(null as int) NOT IN (
+    select 1
+    from "scott".dept)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | null
+ 1250.00 | null
+ 1250.00 | null
+ 1300.00 | null
+ 1500.00 | null
+ 1600.00 | null
+ 2450.00 | null
+ 2850.00 | null
+ 2975.00 | null
+ 3000.00 | null
+ 3000.00 | null
+ 5000.00 | null
+  800.00 | null
+  950.00 | null
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[null], expr#8=[null], expr#9=[IS NULL($t8)], 
expr#10=[IS NOT NULL($t2)], expr#11=[true], expr#12=[CASE($t4, $t5, $t6, $t7, 
$t9, $t7, $t10, $t11, $t5)], expr#13=[NOT($t12)], SAL=[$t1], EXPR$1=[$t13])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[1], expr#6=[=($t4, $t5)], expr#7=[IS NULL($t4)], expr#8=[OR($t6, $t7)], 
cs=[$t3], $condition=[$t8])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test project null NOT IN required
+select sal,
+  cast(null as int) NOT IN (
+    select deptno
+    from "scott".dept)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | null
+ 1250.00 | null
+ 1250.00 | null
+ 1300.00 | null
+ 1500.00 | null
+ 1600.00 | null
+ 2450.00 | null
+ 2850.00 | null
+ 2975.00 | null
+ 3000.00 | null
+ 3000.00 | null
+ 5000.00 | null
+  800.00 | null
+  950.00 | null
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[null], expr#8=[null], expr#9=[IS NULL($t8)], 
expr#10=[IS NOT NULL($t2)], expr#11=[true], expr#12=[CASE($t4, $t5, $t6, $t7, 
$t9, $t7, $t10, $t11, $t5)], expr#13=[NOT($t12)], SAL=[$t1], EXPR$1=[$t13])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[=($t4, $t0)], expr#6=[IS NULL($t4)], expr#7=[OR($t5, $t6)], cs=[$t3], 
$condition=[$t7])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test project null NOT IN nullable
+select sal,
+  cast(null as int) NOT IN (
+    select case when true then deptno else null end
+    from "scott".dept)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | null
+ 1250.00 | null
+ 1250.00 | null
+ 1300.00 | null
+ 1500.00 | null
+ 1600.00 | null
+ 2450.00 | null
+ 2850.00 | null
+ 2975.00 | null
+ 3000.00 | null
+ 3000.00 | null
+ 5000.00 | null
+  800.00 | null
+  950.00 | null
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[null], expr#8=[null], expr#9=[IS NULL($t8)], 
expr#10=[IS NOT NULL($t2)], expr#11=[true], expr#12=[CASE($t4, $t5, $t6, $t7, 
$t9, $t7, $t10, $t11, $t5)], expr#13=[NOT($t12)], SAL=[$t1], EXPR$1=[$t13])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[CAST($t0):TINYINT], expr#6=[=($t4, $t5)], expr#7=[IS NULL($t5)], 
expr#8=[IS NULL($t4)], expr#9=[OR($t6, $t7, $t8)], cs=[$t3], $condition=[$t9])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test project literal NOT IN required
+select sal,
+  10 NOT IN (
+    select deptno
+    from "scott".dept)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | false
+ 1250.00 | false
+ 1250.00 | false
+ 1300.00 | false
+ 1500.00 | false
+ 1600.00 | false
+ 2450.00 | false
+ 2850.00 | false
+ 2975.00 | false
+ 3000.00 | false
+ 3000.00 | false
+ 5000.00 | false
+  800.00 | false
+  950.00 | false
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..2=[{inputs}], expr#3=[IS NOT NULL($t2)], expr#4=[true], 
expr#5=[false], expr#6=[CASE($t3, $t4, $t5)], expr#7=[NOT($t6)], SAL=[$t1], 
EXPR$1=[$t7])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableAggregate(group=[{0}])
+      EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[10], 
expr#5=[=($t4, $t0)], cs=[$t3], $condition=[$t5])
+        EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test project literal NOT IN nullable
+select sal,
+  10 NOT IN (
+    select case when true then deptno else null end
+    from "scott".dept)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | false
+ 1250.00 | false
+ 1250.00 | false
+ 1300.00 | false
+ 1500.00 | false
+ 1600.00 | false
+ 2450.00 | false
+ 2850.00 | false
+ 2975.00 | false
+ 3000.00 | false
+ 3000.00 | false
+ 5000.00 | false
+  800.00 | false
+  950.00 | false
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[null], expr#8=[IS NOT NULL($t2)], expr#9=[true], 
expr#10=[CASE($t4, $t5, $t6, $t7, $t8, $t9, $t5)], expr#11=[NOT($t10)], 
SAL=[$t1], EXPR$1=[$t11])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[10], 
expr#5=[CAST($t0):TINYINT], expr#6=[=($t4, $t5)], expr#7=[IS NULL($t5)], 
expr#8=[OR($t6, $t7)], cs=[$t3], $condition=[$t8])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test project null IN required is unknown
+select sal,
+  cast(null as int) IN (
+    select deptno
+    from "scott".dept) is unknown
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | true
+ 1250.00 | true
+ 1250.00 | true
+ 1300.00 | true
+ 1500.00 | true
+ 1600.00 | true
+ 2450.00 | true
+ 2850.00 | true
+ 2975.00 | true
+ 3000.00 | true
+ 3000.00 | true
+ 5000.00 | true
+  800.00 | true
+  950.00 | true
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[null], expr#8=[null], expr#9=[IS NULL($t8)], 
expr#10=[IS NOT NULL($t2)], expr#11=[true], expr#12=[CASE($t4, $t5, $t6, $t7, 
$t9, $t7, $t10, $t11, $t5)], expr#13=[IS NULL($t12)], SAL=[$t1], EXPR$1=[$t13])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[=($t4, $t0)], expr#6=[IS NULL($t4)], expr#7=[OR($t5, $t6)], cs=[$t3], 
$condition=[$t7])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test filter null IN null
+select sal from "scott".emp
+  where cast(null as int) IN (
+    select cast(null as int)
+    from "scott".dept);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableValues(tuples=[[]])
+!plan
+
+# Test filter literal IN null non-correlated
+select sal from "scott".emp
+  where 123 IN (
+    select cast(null as int)
+    from "scott".dept);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..2=[{inputs}], SAL=[$t2])
+  EnumerableJoin(condition=[true], joinType=[inner])
+    EnumerableAggregate(group=[{0}])
+      EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[123], 
expr#5=[null], expr#6=[=($t4, $t5)], cs=[$t3], $condition=[$t6])
+        EnumerableTableScan(table=[[scott, DEPT]])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+# Test filter null IN literal non-correlated
+select sal from "scott".emp
+  where cast(null as int) IN (
+    select 1
+    from "scott".dept);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..2=[{inputs}], SAL=[$t2])
+  EnumerableJoin(condition=[true], joinType=[inner])
+    EnumerableAggregate(group=[{0}])
+      EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[1], expr#6=[=($t4, $t5)], cs=[$t3], $condition=[$t6])
+        EnumerableTableScan(table=[[scott, DEPT]])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+# Test filter null IN required
+select sal from "scott".emp
+  where cast(null as int) IN (
+    select deptno
+    from "scott".dept);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..2=[{inputs}], SAL=[$t2])
+  EnumerableJoin(condition=[true], joinType=[inner])
+    EnumerableAggregate(group=[{0}])
+      EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[=($t4, $t0)], cs=[$t3], $condition=[$t5])
+        EnumerableTableScan(table=[[scott, DEPT]])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+# Test filter null IN nullable
+select sal from "scott".emp
+  where cast(null as int) IN (
+    select case when true then deptno else null end
+    from "scott".dept);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..2=[{inputs}], SAL=[$t2])
+  EnumerableJoin(condition=[true], joinType=[inner])
+    EnumerableAggregate(group=[{0}])
+      EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[CAST($t0):TINYINT], expr#6=[=($t4, $t5)], cs=[$t3], $condition=[$t6])
+        EnumerableTableScan(table=[[scott, DEPT]])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+# Test filter literal IN required
+select sal from "scott".emp
+  where 10 IN (
+    select deptno
+    from "scott".dept);
+ SAL
+---------
+ 1100.00
+ 1250.00
+ 1250.00
+ 1300.00
+ 1500.00
+ 1600.00
+ 2450.00
+ 2850.00
+ 2975.00
+ 3000.00
+ 3000.00
+ 5000.00
+  800.00
+  950.00
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..2=[{inputs}], SAL=[$t2])
+  EnumerableJoin(condition=[true], joinType=[inner])
+    EnumerableAggregate(group=[{0}])
+      EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[10], 
expr#5=[=($t4, $t0)], cs=[$t3], $condition=[$t5])
+        EnumerableTableScan(table=[[scott, DEPT]])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+# Test filter literal IN nullable
+select sal from "scott".emp
+  where 10 IN (
+    select case when true then deptno else null end
+    from "scott".dept);
+ SAL
+---------
+ 1100.00
+ 1250.00
+ 1250.00
+ 1300.00
+ 1500.00
+ 1600.00
+ 2450.00
+ 2850.00
+ 2975.00
+ 3000.00
+ 3000.00
+ 5000.00
+  800.00
+  950.00
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..2=[{inputs}], SAL=[$t2])
+  EnumerableJoin(condition=[true], joinType=[inner])
+    EnumerableAggregate(group=[{0}])
+      EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[10], 
expr#5=[CAST($t0):TINYINT], expr#6=[=($t4, $t5)], cs=[$t3], $condition=[$t6])
+        EnumerableTableScan(table=[[scott, DEPT]])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+# Test filter null NOT IN null non-correlated
+select sal from "scott".emp
+  where cast(null as int) NOT IN (
+    select cast(null as int)
+    from "scott".dept);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[true], expr#8=[null], expr#9=[IS NULL($t8)], 
expr#10=[null], expr#11=[IS NOT NULL($t2)], expr#12=[CASE($t4, $t5, $t6, $t7, 
$t9, $t10, $t11, $t7, $t5)], expr#13=[NOT($t12)], SAL=[$t1], $condition=[$t13])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[false], expr#4=[null], 
expr#5=[=($t4, $t4)], expr#6=[IS NULL($t4)], expr#7=[OR($t5, $t6)], cs=[$t3], 
$condition=[$t7])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test filter literal NOT IN null non-correlated
+select sal from "scott".emp
+  where 123 NOT IN (
+    select cast(null as int)
+    from "scott".dept);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[true], expr#8=[IS NOT NULL($t2)], 
expr#9=[CASE($t4, $t5, $t6, $t7, $t8, $t7, $t5)], expr#10=[NOT($t9)], 
SAL=[$t1], $condition=[$t10])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[false], expr#4=[123], 
expr#5=[null], expr#6=[=($t4, $t5)], expr#7=[IS NULL($t5)], expr#8=[OR($t6, 
$t7)], cs=[$t3], $condition=[$t8])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test filter null NOT IN literal non-correlated
+select sal from "scott".emp
+  where cast(null as int) NOT IN (
+    select 1
+    from "scott".dept);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[true], expr#8=[null], expr#9=[IS NULL($t8)], 
expr#10=[null], expr#11=[IS NOT NULL($t2)], expr#12=[CASE($t4, $t5, $t6, $t7, 
$t9, $t10, $t11, $t7, $t5)], expr#13=[NOT($t12)], SAL=[$t1], $condition=[$t13])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[1], expr#6=[=($t4, $t5)], expr#7=[IS NULL($t4)], expr#8=[OR($t6, $t7)], 
cs=[$t3], $condition=[$t8])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test filter null NOT IN required
+select sal from "scott".emp
+  where cast(null as int) NOT IN (
+    select deptno
+    from "scott".dept);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[true], expr#8=[null], expr#9=[IS NULL($t8)], 
expr#10=[null], expr#11=[IS NOT NULL($t2)], expr#12=[CASE($t4, $t5, $t6, $t7, 
$t9, $t10, $t11, $t7, $t5)], expr#13=[NOT($t12)], SAL=[$t1], $condition=[$t13])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[=($t4, $t0)], expr#6=[IS NULL($t4)], expr#7=[OR($t5, $t6)], cs=[$t3], 
$condition=[$t7])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test filter null NOT IN nullable
+select sal from "scott".emp
+  where cast(null as int) NOT IN (
+    select case when true then deptno else null end
+    from "scott".dept);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[true], expr#8=[null], expr#9=[IS NULL($t8)], 
expr#10=[null], expr#11=[IS NOT NULL($t2)], expr#12=[CASE($t4, $t5, $t6, $t7, 
$t9, $t10, $t11, $t7, $t5)], expr#13=[NOT($t12)], SAL=[$t1], $condition=[$t13])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[CAST($t0):TINYINT], expr#6=[=($t4, $t5)], expr#7=[IS NULL($t5)], 
expr#8=[IS NULL($t4)], expr#9=[OR($t6, $t7, $t8)], cs=[$t3], $condition=[$t9])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test filter literal NOT IN required
+select sal from "scott".emp
+  where 10 NOT IN (
+    select deptno
+    from "scott".dept);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[true], expr#8=[IS NOT NULL($t2)], 
expr#9=[CASE($t4, $t5, $t6, $t7, $t8, $t7, $t5)], expr#10=[NOT($t9)], 
SAL=[$t1], $condition=[$t10])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[10], 
expr#5=[=($t4, $t0)], cs=[$t3], $condition=[$t5])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test filter literal NOT IN nullable
+select sal from "scott".emp
+  where 10 NOT IN (
+    select case when true then deptno else null end
+    from "scott".dept);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[true], expr#8=[IS NOT NULL($t2)], 
expr#9=[CASE($t4, $t5, $t6, $t7, $t8, $t7, $t5)], expr#10=[NOT($t9)], 
SAL=[$t1], $condition=[$t10])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[10], 
expr#5=[CAST($t0):TINYINT], expr#6=[=($t4, $t5)], expr#7=[IS NULL($t5)], 
expr#8=[OR($t6, $t7)], cs=[$t3], $condition=[$t8])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test filter null IN required is unknown
+select sal from "scott".emp
+  where cast(null as int) IN (
+    select deptno
+    from "scott".dept) is unknown;
+ SAL
+---------
+ 1100.00
+ 1250.00
+ 1250.00
+ 1300.00
+ 1500.00
+ 1600.00
+ 2450.00
+ 2850.00
+ 2975.00
+ 3000.00
+ 3000.00
+ 5000.00
+  800.00
+  950.00
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[IS NULL($t3)], expr#5=[false], 
expr#6=[=($t2, $t5)], expr#7=[null], expr#8=[null], expr#9=[IS NULL($t8)], 
expr#10=[IS NOT NULL($t2)], expr#11=[true], expr#12=[CASE($t4, $t5, $t6, $t7, 
$t9, $t7, $t10, $t11, $t5)], expr#13=[IS NULL($t12)], SAL=[$t1], 
$condition=[$t13])
+  EnumerableJoin(condition=[true], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableLimit(fetch=[1])
+      EnumerableSort(sort0=[$0], dir0=[DESC])
+        EnumerableAggregate(group=[{0}], c=[COUNT()])
+          EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[=($t4, $t0)], expr#6=[IS NULL($t4)], expr#7=[OR($t5, $t6)], cs=[$t3], 
$condition=[$t7])
+            EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+#-------------------------------
+
+# Test filter null IN null correlated
+select sal from "scott".emp e
+  where cast(null as int) IN (
+    select cast(null as int)
+    from "scott".dept d where e.deptno=d.deptno);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableValues(tuples=[[]])
+!plan
+
+# Test filter literal IN null correlated
+select sal from "scott".emp e
+  where 123 IN (
+    select cast(null as int)
+    from "scott".dept d where e.deptno=d.deptno);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], SAL=[$t2])
+  EnumerableJoin(condition=[=($0, $3)], joinType=[inner])
+    EnumerableCalc(expr#0..2=[{inputs}], expr#3=[123], expr#4=[null], 
expr#5=[=($t3, $t4)], DEPTNO=[$t0], $condition=[$t5])
+      EnumerableTableScan(table=[[scott, DEPT]])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5], DEPTNO=[$t7])
+      EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+# Test filter null IN literal correlated
+select sal from "scott".emp e
+  where cast(null as int) IN (
+    select 1
+    from "scott".dept d where e.deptno=d.deptno);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], SAL=[$t2])
+  EnumerableJoin(condition=[=($0, $3)], joinType=[inner])
+    EnumerableCalc(expr#0..2=[{inputs}], expr#3=[null], expr#4=[1], 
expr#5=[=($t3, $t4)], DEPTNO=[$t0], $condition=[$t5])
+      EnumerableTableScan(table=[[scott, DEPT]])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5], DEPTNO=[$t7])
+      EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+# Test filter null IN required correlated
+select sal from "scott".emp e
+  where cast(null as int) IN (
+    select deptno
+    from "scott".dept d where e.deptno=d.deptno);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], SAL=[$t2])
+  EnumerableJoin(condition=[=($0, $3)], joinType=[inner])
+    EnumerableCalc(expr#0..2=[{inputs}], expr#3=[null], expr#4=[=($t3, $t0)], 
DEPTNO=[$t0], $condition=[$t4])
+      EnumerableTableScan(table=[[scott, DEPT]])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5], DEPTNO=[$t7])
+      EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+# Test filter null IN nullable correlated
+select sal from "scott".emp e
+  where cast(null as int) IN (
+    select case when true then deptno else null end
+    from "scott".dept d where e.deptno=d.deptno);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], SAL=[$t2])
+  EnumerableJoin(condition=[=($0, $3)], joinType=[inner])
+    EnumerableCalc(expr#0..2=[{inputs}], expr#3=[null], 
expr#4=[CAST($t0):TINYINT], expr#5=[=($t3, $t4)], DEPTNO=[$t0], 
$condition=[$t5])
+      EnumerableTableScan(table=[[scott, DEPT]])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5], DEPTNO=[$t7])
+      EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+# Test filter literal IN required correlated
+select sal from "scott".emp e
+  where 10 IN (
+    select deptno
+    from "scott".dept d where e.deptno=d.deptno);
+ SAL
+---------
+ 1300.00
+ 2450.00
+ 5000.00
+(3 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], SAL=[$t2])
+  EnumerableJoin(condition=[=($0, $3)], joinType=[inner])
+    EnumerableCalc(expr#0..2=[{inputs}], expr#3=[10], expr#4=[=($t3, $t0)], 
DEPTNO=[$t0], $condition=[$t4])
+      EnumerableTableScan(table=[[scott, DEPT]])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5], DEPTNO=[$t7])
+      EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+# Test filter literal IN nullable correlated
+select sal from "scott".emp e
+  where 10 IN (
+    select case when true then deptno else null end
+    from "scott".dept d where e.deptno=d.deptno);
+ SAL
+---------
+ 1300.00
+ 2450.00
+ 5000.00
+(3 rows)
+
+!ok
+EnumerableCalc(expr#0..3=[{inputs}], SAL=[$t2])
+  EnumerableJoin(condition=[=($0, $3)], joinType=[inner])
+    EnumerableCalc(expr#0..2=[{inputs}], expr#3=[10], 
expr#4=[CAST($t0):TINYINT], expr#5=[=($t3, $t4)], DEPTNO=[$t0], 
$condition=[$t5])
+      EnumerableTableScan(table=[[scott, DEPT]])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5], DEPTNO=[$t7])
+      EnumerableTableScan(table=[[scott, EMP]])
+!plan
+
+# Test filter null NOT IN null correlated
+select sal from "scott".emp e
+  where cast(null as int) NOT IN (
+    select cast(null as int)
+    from "scott".dept d where e.deptno=d.deptno);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..4=[{inputs}], expr#5=[false], expr#6=[=($t3, $t5)], 
expr#7=[true], expr#8=[null], expr#9=[IS NULL($t8)], expr#10=[null], 
expr#11=[IS NOT NULL($t3)], expr#12=[CASE($t6, $t7, $t9, $t10, $t11, $t7, 
$t5)], expr#13=[NOT($t12)], SAL=[$t1], $condition=[$t13])
+  EnumerableJoin(condition=[=($2, $4)], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5], DEPTNO=[$t7])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableCalc(expr#0..2=[{inputs}], expr#3=[false], expr#4=[null], 
expr#5=[IS NULL($t4)], cs=[$t3], DEPTNO=[$t0], $condition=[$t5])
+      EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test filter literal NOT IN null correlated
+select sal from "scott".emp e
+  where 123 NOT IN (
+    select cast(null as int)
+    from "scott".dept d where e.deptno=d.deptno);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..4=[{inputs}], expr#5=[false], expr#6=[=($t3, $t5)], 
expr#7=[true], expr#8=[IS NOT NULL($t3)], expr#9=[CASE($t6, $t7, $t8, $t7, 
$t5)], expr#10=[NOT($t9)], SAL=[$t1], $condition=[$t10])
+  EnumerableJoin(condition=[=($2, $4)], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5], DEPTNO=[$t7])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableCalc(expr#0..2=[{inputs}], expr#3=[false], expr#4=[123], 
expr#5=[null], expr#6=[=($t4, $t5)], expr#7=[IS NULL($t5)], expr#8=[OR($t6, 
$t7)], cs=[$t3], DEPTNO=[$t0], $condition=[$t8])
+      EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test filter null NOT IN literal correlated
+select sal from "scott".emp e
+  where cast(null as int) NOT IN (
+    select 1
+    from "scott".dept d where e.deptno=d.deptno);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..4=[{inputs}], expr#5=[false], expr#6=[=($t4, $t5)], 
expr#7=[true], expr#8=[null], expr#9=[IS NULL($t8)], expr#10=[null], 
expr#11=[IS NOT NULL($t4)], expr#12=[CASE($t6, $t7, $t9, $t10, $t11, $t7, 
$t5)], expr#13=[NOT($t12)], SAL=[$t1], $condition=[$t13])
+  EnumerableJoin(condition=[=($2, $3)], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5], DEPTNO=[$t7])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[1], expr#6=[=($t4, $t5)], expr#7=[IS NULL($t4)], expr#8=[OR($t6, $t7)], 
DEPTNO=[$t0], $f1=[$t3], $condition=[$t8])
+      EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test filter null NOT IN required correlated
+select sal from "scott".emp e
+  where cast(null as int) NOT IN (
+    select deptno
+    from "scott".dept d where e.deptno=d.deptno);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..4=[{inputs}], expr#5=[false], expr#6=[=($t4, $t5)], 
expr#7=[true], expr#8=[null], expr#9=[IS NULL($t8)], expr#10=[null], 
expr#11=[IS NOT NULL($t4)], expr#12=[CASE($t6, $t7, $t9, $t10, $t11, $t7, 
$t5)], expr#13=[NOT($t12)], SAL=[$t1], $condition=[$t13])
+  EnumerableJoin(condition=[=($2, $3)], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5], DEPTNO=[$t7])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[=($t4, $t0)], expr#6=[IS NULL($t4)], expr#7=[OR($t5, $t6)], 
DEPTNO1=[$t0], $f1=[$t3], $condition=[$t7])
+      EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test filter null NOT IN nullable correlated
+select sal from "scott".emp e
+  where cast(null as int) NOT IN (
+    select case when true then deptno else null end
+    from "scott".dept d where e.deptno=d.deptno);
+ SAL
+-----
+(0 rows)
+
+!ok
+EnumerableCalc(expr#0..4=[{inputs}], expr#5=[false], expr#6=[=($t3, $t5)], 
expr#7=[true], expr#8=[null], expr#9=[IS NULL($t8)], expr#10=[null], 
expr#11=[IS NOT NULL($t3)], expr#12=[CASE($t6, $t7, $t9, $t10, $t11, $t7, 
$t5)], expr#13=[NOT($t12)], SAL=[$t1], $condition=[$t13])
+  EnumerableJoin(condition=[=($2, $4)], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5], DEPTNO=[$t7])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[CAST($t0):TINYINT], expr#6=[=($t4, $t5)], expr#7=[IS NULL($t5)], 
expr#8=[IS NULL($t4)], expr#9=[OR($t6, $t7, $t8)], cs=[$t3], DEPTNO=[$t0], 
$condition=[$t9])
+      EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test filter literal NOT IN required correlated
+select sal from "scott".emp e
+  where 10 NOT IN (
+    select deptno
+    from "scott".dept d where e.deptno=d.deptno);
+ SAL
+---------
+ 1100.00
+ 1250.00
+ 1250.00
+ 1500.00
+ 1600.00
+ 2850.00
+ 2975.00
+ 3000.00
+ 3000.00
+  800.00
+  950.00
+(11 rows)
+
+!ok
+EnumerableCalc(expr#0..4=[{inputs}], expr#5=[false], expr#6=[=($t4, $t5)], 
expr#7=[true], expr#8=[IS NOT NULL($t4)], expr#9=[CASE($t6, $t7, $t8, $t7, 
$t5)], expr#10=[NOT($t9)], SAL=[$t1], $condition=[$t10])
+  EnumerableJoin(condition=[=($2, $3)], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5], DEPTNO=[$t7])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[10], 
expr#5=[=($t4, $t0)], DEPTNO1=[$t0], $f1=[$t3], $condition=[$t5])
+      EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test filter literal NOT IN nullable correlated
+select sal from "scott".emp e
+  where 10 NOT IN (
+    select case when true then deptno else null end
+    from "scott".dept d where e.deptno=d.deptno);
+ SAL
+---------
+ 1100.00
+ 1250.00
+ 1250.00
+ 1500.00
+ 1600.00
+ 2850.00
+ 2975.00
+ 3000.00
+ 3000.00
+  800.00
+  950.00
+(11 rows)
+
+!ok
+EnumerableCalc(expr#0..4=[{inputs}], expr#5=[false], expr#6=[=($t3, $t5)], 
expr#7=[true], expr#8=[IS NOT NULL($t3)], expr#9=[CASE($t6, $t7, $t8, $t7, 
$t5)], expr#10=[NOT($t9)], SAL=[$t1], $condition=[$t10])
+  EnumerableJoin(condition=[=($2, $4)], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5], DEPTNO=[$t7])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[10], 
expr#5=[CAST($t0):TINYINT], expr#6=[=($t4, $t5)], expr#7=[IS NULL($t5)], 
expr#8=[OR($t6, $t7)], cs=[$t3], DEPTNO=[$t0], $condition=[$t8])
+      EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+# Test filter null IN required is unknown correlated
+select sal from "scott".emp e
+  where cast(null as int) IN (
+    select deptno
+    from "scott".dept d where e.deptno=d.deptno) is unknown;
+ SAL
+---------
+ 1100.00
+ 1250.00
+ 1250.00
+ 1300.00
+ 1500.00
+ 1600.00
+ 2450.00
+ 2850.00
+ 2975.00
+ 3000.00
+ 3000.00
+ 5000.00
+  800.00
+  950.00
+(14 rows)
+
+!ok
+EnumerableCalc(expr#0..4=[{inputs}], expr#5=[false], expr#6=[=($t4, $t5)], 
expr#7=[null], expr#8=[null], expr#9=[IS NULL($t8)], expr#10=[IS NOT 
NULL($t4)], expr#11=[true], expr#12=[CASE($t6, $t7, $t9, $t7, $t10, $t11, 
$t5)], expr#13=[IS NULL($t12)], SAL=[$t1], $condition=[$t13])
+  EnumerableJoin(condition=[=($2, $3)], joinType=[left])
+    EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], SAL=[$t5], DEPTNO=[$t7])
+      EnumerableTableScan(table=[[scott, EMP]])
+    EnumerableCalc(expr#0..2=[{inputs}], expr#3=[true], expr#4=[null], 
expr#5=[=($t4, $t0)], expr#6=[IS NULL($t4)], expr#7=[OR($t5, $t6)], 
DEPTNO1=[$t0], $f1=[$t3], $condition=[$t7])
+      EnumerableTableScan(table=[[scott, DEPT]])
+!plan
+
+
+# Test project constant IN an expression that is sometimes null
+select sal,
+  20 IN (
+    select case when deptno > 10 then deptno else null end
+    from "scott".dept)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | true
+ 1250.00 | true
+ 1250.00 | true
+ 1300.00 | true
+ 1500.00 | true
+ 1600.00 | true
+ 2450.00 | true
+ 2850.00 | true
+ 2975.00 | true
+ 3000.00 | true
+ 3000.00 | true
+ 5000.00 | true
+  800.00 | true
+  950.00 | true
+(14 rows)
+
+!ok
+
+# Test project constant IN an nullable expression in an empty relation
+select sal,
+  20 IN (
+    select case when deptno > 10 then deptno else null end
+    from "scott".dept
+    where deptno < 0)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | false
+ 1250.00 | false
+ 1250.00 | false
+ 1300.00 | false
+ 1500.00 | false
+ 1600.00 | false
+ 2450.00 | false
+ 2850.00 | false
+ 2975.00 | false
+ 3000.00 | false
+ 3000.00 | false
+ 5000.00 | false
+  800.00 | false
+  950.00 | false
+(14 rows)
+
+!ok
+
+# Test project null IN an nullable expression in an empty relation
+select sal,
+  cast(null as integer) IN (
+    select case when deptno > 10 then deptno else null end
+    from "scott".dept
+    where deptno < 0)
+from "scott".emp;
+ SAL     | EXPR$1
+---------+--------
+ 1100.00 | false
+ 1250.00 | false
+ 1250.00 | false
+ 1300.00 | false
+ 1500.00 | false
+ 1600.00 | false
+ 2450.00 | false
+ 2850.00 | false
+ 2975.00 | false
+ 3000.00 | false
+ 3000.00 | false
+ 5000.00 | false
+  800.00 | false
+  950.00 | false
+(14 rows)
+
+!ok
+
 # End sub-query.iq

Reply via email to