This is an automated email from the ASF dual-hosted git repository.

zabetak pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new c2407f59c3 [CALCITE-1045][CALCITE-5127] Support correlation variables 
in project
c2407f59c3 is described below

commit c2407f59c32d1690d16b641d556bb27f8f1783ac
Author: Benchao Li <libenc...@gmail.com>
AuthorDate: Sun May 22 19:42:24 2022 +0800

    [CALCITE-1045][CALCITE-5127] Support correlation variables in project
    
    To some extend correlation in project was already supported even before
    this change. However, the fact that the correlation variables were not
    explicitly present (and returned by the operator) creates problems
    cause we cannot safely deduce if a column/field is used and thus we may
    wrongly remove those fields when using the RelFieldTrimmer, when
    merging projections, etc.; see queries and discussion under the
    respective JIRAs.
    
    The addition of correlation variables in project also aligns the code
    with Filter, Join; the latter explicitly set correlation variables.
    
    Co-authored-by: korlov42 <kor...@gridgain.com>
    
    Close apache/calcite#2813
    Close apache/calcite#2623
---
 .../adapter/cassandra/CassandraProject.java        |   3 +-
 .../calcite/adapter/cassandra/CassandraRules.java  |   3 +-
 .../adapter/enumerable/EnumerableProject.java      |   3 +-
 .../adapter/enumerable/EnumerableProjectRule.java  |   6 ++
 .../adapter/enumerable/EnumerableRelFactories.java |   7 +-
 .../org/apache/calcite/adapter/jdbc/JdbcRules.java |  12 ++-
 .../org/apache/calcite/interpreter/Bindables.java  |   8 +-
 .../java/org/apache/calcite/plan/RelOptUtil.java   |  10 +-
 .../apache/calcite/prepare/LixToRelTranslator.java |   4 +-
 .../calcite/prepare/QueryableRelBuilder.java       |   4 +-
 .../main/java/org/apache/calcite/rel/RelNode.java  |   3 -
 .../main/java/org/apache/calcite/rel/RelRoot.java  |   3 +-
 .../java/org/apache/calcite/rel/core/Project.java  |  39 ++++++-
 .../org/apache/calcite/rel/core/RelFactories.java  |  27 ++++-
 .../apache/calcite/rel/externalize/RelJson.java    |   5 +-
 .../apache/calcite/rel/logical/LogicalProject.java |  63 +++++++++--
 .../calcite/rel/rel2sql/RelToSqlConverter.java     |   3 +-
 .../rel/rules/FilterProjectTransposeRule.java      |   5 +-
 .../rel/rules/ProjectWindowTransposeRule.java      |   5 +-
 .../org/apache/calcite/rel/stream/StreamRules.java |   3 +-
 .../apache/calcite/sql2rel/SqlToRelConverter.java  |  18 +++-
 .../java/org/apache/calcite/tools/RelBuilder.java  |  68 ++++++++++--
 .../org/apache/calcite/plan/RelOptUtilTest.java    |   7 +-
 .../org/apache/calcite/plan/RelWriterTest.java     |  18 +++-
 .../calcite/plan/volcano/TraitPropagationTest.java |   6 +-
 .../org/apache/calcite/test/RelMetadataTest.java   |   6 +-
 .../org/apache/calcite/test/RelOptRulesTest.java   |   3 +-
 .../apache/calcite/test/SqlToRelConverterTest.java |  24 +++++
 .../org/apache/calcite/test/RelOptRulesTest.xml    |   4 +-
 .../apache/calcite/test/SqlToRelConverterTest.xml  |  70 ++++++++++++-
 core/src/test/resources/sql/agg.iq                 |   9 +-
 core/src/test/resources/sql/sub-query.iq           | 116 +++++++++++++++------
 .../apache/calcite/adapter/druid/DruidRules.java   |   5 +
 .../elasticsearch/ElasticsearchProject.java        |   3 +-
 .../adapter/elasticsearch/ElasticsearchRules.java  |   6 ++
 .../calcite/adapter/geode/rel/GeodeProject.java    |   3 +-
 .../calcite/adapter/geode/rel/GeodeRules.java      |   7 +-
 .../calcite/adapter/innodb/InnodbProject.java      |   3 +-
 .../apache/calcite/adapter/innodb/InnodbRules.java |   3 +-
 .../calcite/adapter/mongodb/MongoProject.java      |   3 +-
 .../apache/calcite/adapter/mongodb/MongoRules.java |   6 ++
 .../org/apache/calcite/adapter/pig/PigProject.java |   3 +-
 .../org/apache/calcite/adapter/pig/PigRules.java   |   6 ++
 .../calcite/adapter/splunk/SplunkPushDownRule.java |   2 +-
 .../apache/calcite/test/RelMetadataFixture.java    |   3 +
 .../calcite/test/catalog/MockCatalogReader.java    |   4 +-
 46 files changed, 510 insertions(+), 112 deletions(-)

diff --git 
a/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraProject.java
 
b/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraProject.java
index b5a77c74f8..947d6e5cd0 100644
--- 
a/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraProject.java
+++ 
b/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraProject.java
@@ -28,6 +28,7 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.Pair;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
@@ -42,7 +43,7 @@ import java.util.Map;
 public class CassandraProject extends Project implements CassandraRel {
   public CassandraProject(RelOptCluster cluster, RelTraitSet traitSet,
       RelNode input, List<? extends RexNode> projects, RelDataType rowType) {
-    super(cluster, traitSet, ImmutableList.of(), input, projects, rowType);
+    super(cluster, traitSet, ImmutableList.of(), input, projects, rowType, 
ImmutableSet.of());
     assert getConvention() == CassandraRel.CONVENTION;
     assert getConvention() == input.getConvention();
   }
diff --git 
a/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraRules.java
 
b/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraRules.java
index ce38ed3d18..6fc1f47432 100644
--- 
a/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraRules.java
+++ 
b/cassandra/src/main/java/org/apache/calcite/adapter/cassandra/CassandraRules.java
@@ -273,8 +273,7 @@ public class CassandraRules {
           return false;
         }
       }
-
-      return true;
+      return project.getVariablesSet().isEmpty();
     }
 
     @Override public RelNode convert(RelNode rel) {
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableProject.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableProject.java
index 4bc516c520..8b4c0c5ded 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableProject.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableProject.java
@@ -29,6 +29,7 @@ import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
@@ -54,7 +55,7 @@ public class EnumerableProject extends Project implements 
EnumerableRel {
       RelNode input,
       List<? extends RexNode> projects,
       RelDataType rowType) {
-    super(cluster, traitSet, ImmutableList.of(), input, projects, rowType);
+    super(cluster, traitSet, ImmutableList.of(), input, projects, rowType, 
ImmutableSet.of());
     assert getConvention() instanceof EnumerableConvention;
   }
 
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableProjectRule.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableProjectRule.java
index b1b69220a6..3bd8e697bb 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableProjectRule.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableProjectRule.java
@@ -17,6 +17,7 @@
 package org.apache.calcite.adapter.enumerable;
 
 import org.apache.calcite.plan.Convention;
+import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.convert.ConverterRule;
 import org.apache.calcite.rel.core.Project;
@@ -42,6 +43,11 @@ class EnumerableProjectRule extends ConverterRule {
     super(config);
   }
 
+  @Override public boolean matches(RelOptRuleCall call) {
+    Project project = call.rel(0);
+    return project.getVariablesSet().isEmpty();
+  }
+
   @Override public RelNode convert(RelNode rel) {
     final Project project = (Project) rel;
     return EnumerableProject.create(
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableRelFactories.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableRelFactories.java
index ad109c44d0..05c0890189 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableRelFactories.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableRelFactories.java
@@ -26,6 +26,8 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.sql.validate.SqlValidatorUtil;
 
+import com.google.common.base.Preconditions;
+
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.util.List;
@@ -68,7 +70,10 @@ public class EnumerableRelFactories {
       implements org.apache.calcite.rel.core.RelFactories.ProjectFactory {
     @Override public RelNode createProject(RelNode input, List<RelHint> hints,
         List<? extends RexNode> childExprs,
-        @Nullable List<? extends @Nullable String> fieldNames) {
+        @Nullable List<? extends @Nullable String> fieldNames,
+        Set<CorrelationId> variablesSet) {
+      Preconditions.checkArgument(variablesSet.isEmpty(),
+          "EnumerableProject does not allow variables");
       final RelDataType rowType =
           RexUtil.createStructType(input.getCluster().getTypeFactory(), 
childExprs,
               fieldNames, SqlValidatorUtil.F_SUGGESTER);
diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java 
b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java
index 6c9b545e69..2a97773614 100644
--- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java
+++ b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java
@@ -73,6 +73,7 @@ import org.apache.calcite.util.trace.CalciteTrace;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.slf4j.Logger;
@@ -96,7 +97,9 @@ public class JdbcRules {
   protected static final Logger LOGGER = CalciteTrace.getPlannerTracer();
 
   static final RelFactories.ProjectFactory PROJECT_FACTORY =
-      (input, hints, projects, fieldNames) -> {
+      (input, hints, projects, fieldNames, variablesSet) -> {
+        Preconditions.checkArgument(variablesSet.isEmpty(),
+            "JdbcProject does not allow variables");
         final RelOptCluster cluster = input.getCluster();
         final RelDataType rowType =
             RexUtil.createStructType(cluster.getTypeFactory(), projects,
@@ -510,6 +513,11 @@ public class JdbcRules {
       return false;
     }
 
+    @Override public boolean matches(RelOptRuleCall call) {
+      Project project = call.rel(0);
+      return project.getVariablesSet().isEmpty();
+    }
+
     @Override public @Nullable RelNode convert(RelNode rel) {
       final Project project = (Project) rel;
 
@@ -535,7 +543,7 @@ public class JdbcRules {
         RelNode input,
         List<? extends RexNode> projects,
         RelDataType rowType) {
-      super(cluster, traitSet, ImmutableList.of(), input, projects, rowType);
+      super(cluster, traitSet, ImmutableList.of(), input, projects, rowType, 
ImmutableSet.of());
       assert getConvention() instanceof JdbcConvention;
     }
 
diff --git a/core/src/main/java/org/apache/calcite/interpreter/Bindables.java 
b/core/src/main/java/org/apache/calcite/interpreter/Bindables.java
index 77b5287fc2..9f879da917 100644
--- a/core/src/main/java/org/apache/calcite/interpreter/Bindables.java
+++ b/core/src/main/java/org/apache/calcite/interpreter/Bindables.java
@@ -78,6 +78,7 @@ import org.apache.calcite.util.ImmutableIntList;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.immutables.value.Value;
@@ -386,6 +387,11 @@ public class Bindables {
       super(config);
     }
 
+    @Override public boolean matches(RelOptRuleCall call) {
+      final LogicalProject project = call.rel(0);
+      return project.getVariablesSet().isEmpty();
+    }
+
     @Override public RelNode convert(RelNode rel) {
       final LogicalProject project = (LogicalProject) rel;
       return new BindableProject(rel.getCluster(),
@@ -403,7 +409,7 @@ public class Bindables {
   public static class BindableProject extends Project implements BindableRel {
     public BindableProject(RelOptCluster cluster, RelTraitSet traitSet,
         RelNode input, List<? extends RexNode> projects, RelDataType rowType) {
-      super(cluster, traitSet, ImmutableList.of(), input, projects, rowType);
+      super(cluster, traitSet, ImmutableList.of(), input, projects, rowType, 
ImmutableSet.of());
       assert getConvention() instanceof BindableConvention;
     }
 
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java 
b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
index 0b5f1ddd12..97ca0bf469 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
@@ -889,6 +889,7 @@ public abstract class RelOptUtil {
     List<RexNode> castExps;
     RelNode input;
     List<RelHint> hints = ImmutableList.of();
+    Set<CorrelationId> correlationVariables;
     if (rel instanceof Project) {
       // No need to create another project node if the rel
       // is already a project.
@@ -899,21 +900,23 @@ public abstract class RelOptUtil {
           ((Project) rel).getProjects());
       input = rel.getInput(0);
       hints = project.getHints();
+      correlationVariables = project.getVariablesSet();
     } else {
       castExps = RexUtil.generateCastExpressions(
           rexBuilder,
           castRowType,
           rowType);
       input = rel;
+      correlationVariables = ImmutableSet.of();
     }
     if (rename) {
       // Use names and types from castRowType.
       return projectFactory.createProject(input, hints, castExps,
-          castRowType.getFieldNames());
+          castRowType.getFieldNames(), correlationVariables);
     } else {
       // Use names from rowType, types from castRowType.
       return projectFactory.createProject(input, hints, castExps,
-          rowType.getFieldNames());
+          rowType.getFieldNames(), correlationVariables);
     }
   }
 
@@ -3623,7 +3626,8 @@ public abstract class RelOptUtil {
               : fieldNames.get(i));
       exprList.add(rexBuilder.makeInputRef(rel, source));
     }
-    return projectFactory.createProject(rel, ImmutableList.of(), exprList, 
outputNameList);
+    return projectFactory.createProject(rel, ImmutableList.of(), exprList, 
outputNameList,
+        ImmutableSet.of());
   }
 
   /** Predicate for if a {@link Calc} does not contain windowed aggregates. */
diff --git 
a/core/src/main/java/org/apache/calcite/prepare/LixToRelTranslator.java 
b/core/src/main/java/org/apache/calcite/prepare/LixToRelTranslator.java
index f7d02f3e2c..2bd4eb0384 100644
--- a/core/src/main/java/org/apache/calcite/prepare/LixToRelTranslator.java
+++ b/core/src/main/java/org/apache/calcite/prepare/LixToRelTranslator.java
@@ -39,6 +39,7 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.BuiltInMethod;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import java.lang.reflect.Type;
 import java.util.ArrayList;
@@ -108,7 +109,8 @@ class LixToRelTranslator {
         return LogicalProject.create(input,
             ImmutableList.of(),
             toRex(input, (FunctionExpression) call.expressions.get(0)),
-            (List<String>) null);
+            (List<String>) null,
+            ImmutableSet.of());
 
       case WHERE:
         input = translate(getTargetExpression(call));
diff --git 
a/core/src/main/java/org/apache/calcite/prepare/QueryableRelBuilder.java 
b/core/src/main/java/org/apache/calcite/prepare/QueryableRelBuilder.java
index 5385c6d9e7..4731e029dd 100644
--- a/core/src/main/java/org/apache/calcite/prepare/QueryableRelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/prepare/QueryableRelBuilder.java
@@ -49,6 +49,7 @@ import org.apache.calcite.schema.TranslatableTable;
 import org.apache.calcite.schema.impl.AbstractTableQueryable;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.checkerframework.checker.nullness.qual.PolyNull;
@@ -549,7 +550,8 @@ class QueryableRelBuilder<T> implements QueryableFactory<T> 
{
     RelNode child = toRel(source);
     List<RexNode> nodes = translator.toRexList(selector, child);
     setRel(
-        LogicalProject.create(child, ImmutableList.of(), nodes, (List<String>) 
 null));
+        LogicalProject.create(child, ImmutableList.of(), nodes, (List<String>) 
 null,
+            ImmutableSet.of()));
     return castNonNull(null);
   }
 
diff --git a/core/src/main/java/org/apache/calcite/rel/RelNode.java 
b/core/src/main/java/org/apache/calcite/rel/RelNode.java
index 21f6586cc2..ef3b878ce7 100644
--- a/core/src/main/java/org/apache/calcite/rel/RelNode.java
+++ b/core/src/main/java/org/apache/calcite/rel/RelNode.java
@@ -152,9 +152,6 @@ public interface RelNode extends RelOptNode, Cloneable {
    * expression but also used and therefore not available to parents of this
    * relational expression.
    *
-   * <p>Note: only {@link org.apache.calcite.rel.core.Correlate} should set
-   * variables.
-   *
    * @return Names of variables which are set in this relational
    *   expression
    */
diff --git a/core/src/main/java/org/apache/calcite/rel/RelRoot.java 
b/core/src/main/java/org/apache/calcite/rel/RelRoot.java
index d72c0ae310..7e6ba78e56 100644
--- a/core/src/main/java/org/apache/calcite/rel/RelRoot.java
+++ b/core/src/main/java/org/apache/calcite/rel/RelRoot.java
@@ -27,6 +27,7 @@ import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.mapping.Mappings;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -166,7 +167,7 @@ public class RelRoot {
     for (Pair<Integer, String> field : fields) {
       projects.add(rexBuilder.makeInputRef(rel, field.left));
     }
-    return LogicalProject.create(rel, hints, projects, Pair.right(fields));
+    return LogicalProject.create(rel, hints, projects, Pair.right(fields), 
ImmutableSet.of());
   }
 
   public boolean isNameTrivial() {
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Project.java 
b/core/src/main/java/org/apache/calcite/rel/core/Project.java
index f4a2e3ac3d..4a1033c3de 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Project.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Project.java
@@ -45,6 +45,7 @@ import org.apache.calcite.util.mapping.MappingType;
 import org.apache.calcite.util.mapping.Mappings;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import org.apiguardian.api.API;
 import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
@@ -53,6 +54,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 
 import static java.util.Objects.requireNonNull;
@@ -70,6 +72,8 @@ public abstract class Project extends SingleRel implements 
Hintable {
 
   protected final ImmutableList<RelHint> hints;
 
+  protected final ImmutableSet<CorrelationId> variablesSet;
+
   //~ Constructors -----------------------------------------------------------
 
   /**
@@ -81,6 +85,8 @@ public abstract class Project extends SingleRel implements 
Hintable {
    * @param input    Input relational expression
    * @param projects List of expressions for the input columns
    * @param rowType  Output row type
+   * @param variableSet Correlation variables set by this relational expression
+   *                    to be used by nested expressions
    */
   @SuppressWarnings("method.invocation.invalid")
   protected Project(
@@ -89,25 +95,38 @@ public abstract class Project extends SingleRel implements 
Hintable {
       List<RelHint> hints,
       RelNode input,
       List<? extends RexNode> projects,
-      RelDataType rowType) {
+      RelDataType rowType,
+      Set<CorrelationId> variableSet) {
     super(cluster, traits, input);
     assert rowType != null;
     this.exps = ImmutableList.copyOf(projects);
     this.hints = ImmutableList.copyOf(hints);
     this.rowType = rowType;
+    this.variablesSet = ImmutableSet.copyOf(variableSet);
     assert isValid(Litmus.THROW, null);
   }
 
+  @Deprecated // to be removed before 2.0
+  protected Project(
+      RelOptCluster cluster,
+      RelTraitSet traits,
+      List<RelHint> hints,
+      RelNode input,
+      List<? extends RexNode> projects,
+      RelDataType rowType) {
+    this(cluster, traits, hints, input, projects, rowType, ImmutableSet.of());
+  }
+
   @Deprecated // to be removed before 2.0
   protected Project(RelOptCluster cluster, RelTraitSet traits,
       RelNode input, List<? extends RexNode> projects, RelDataType rowType) {
-    this(cluster, traits, ImmutableList.of(), input, projects, rowType);
+    this(cluster, traits, ImmutableList.of(), input, projects, rowType, 
ImmutableSet.of());
   }
 
   @Deprecated // to be removed before 2.0
   protected Project(RelOptCluster cluster, RelTraitSet traitSet, RelNode input,
       List<? extends RexNode> projects, RelDataType rowType, int flags) {
-    this(cluster, traitSet, ImmutableList.of(), input, projects, rowType);
+    this(cluster, traitSet, ImmutableList.of(), input, projects, rowType, 
ImmutableSet.of());
     Util.discard(flags);
   }
 
@@ -120,7 +139,14 @@ public abstract class Project extends SingleRel implements 
Hintable {
         ImmutableList.of(),
         input.getInput(),
         requireNonNull(input.getExpressionList("exprs"), "exprs"),
-        input.getRowType("exprs", "fields"));
+        input.getRowType("exprs", "fields"),
+        ImmutableSet.copyOf(
+            Util.transform(
+                Optional.ofNullable(input.getIntegerList("variablesSet"))
+                    .orElse(ImmutableList.of()),
+                id -> new CorrelationId(id)
+            )
+        ));
   }
 
   //~ Methods ----------------------------------------------------------------
@@ -264,8 +290,13 @@ public abstract class Project extends SingleRel implements 
Hintable {
     return refs.size();
   }
 
+  @Override public Set<CorrelationId> getVariablesSet() {
+    return variablesSet;
+  }
+
   @Override public RelWriter explainTerms(RelWriter pw) {
     super.explainTerms(pw);
+    pw.itemIf("variablesSet", variablesSet, !variablesSet.isEmpty());
     // Skip writing field names so the optimizer can reuse the projects that 
differ in
     // field names only
     if (pw.getDetailLevel() == SqlExplainLevel.DIGEST_ATTRIBUTES) {
diff --git a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java 
b/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
index 6d5e2c0d65..3a0d543351 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
@@ -163,9 +163,29 @@ public class RelFactories {
      * @param childExprs The projection expressions
      * @param fieldNames The projection field names
      * @return a project
+     * @deprecated Use {@link #createProject(RelNode, List, List, List, Set)} 
instead
+     */
+    @Deprecated // to be removed before 2.0
+    default RelNode createProject(RelNode input, List<RelHint> hints,
+        List<? extends RexNode> childExprs, @Nullable List<? extends @Nullable 
String> fieldNames) {
+      return createProject(input, hints, childExprs, fieldNames, 
ImmutableSet.of());
+    }
+
+    /**
+     * Creates a project.
+     *
+     * @param input The input
+     * @param hints The hints
+     * @param childExprs The projection expressions
+     * @param fieldNames The projection field names
+     * @param variablesSet Correlating variables that are set when reading a 
row
+     *                     from the input, and which may be referenced from the
+     *                     projection expressions
+     * @return a project
      */
     RelNode createProject(RelNode input, List<RelHint> hints,
-        List<? extends RexNode> childExprs, @Nullable List<? extends @Nullable 
String> fieldNames);
+        List<? extends RexNode> childExprs, @Nullable List<? extends @Nullable 
String> fieldNames,
+        Set<CorrelationId> variablesSet);
   }
 
   /**
@@ -174,8 +194,9 @@ public class RelFactories {
    */
   private static class ProjectFactoryImpl implements ProjectFactory {
     @Override public RelNode createProject(RelNode input, List<RelHint> hints,
-        List<? extends RexNode> childExprs, @Nullable List<? extends @Nullable 
String> fieldNames) {
-      return LogicalProject.create(input, hints, childExprs, fieldNames);
+        List<? extends RexNode> childExprs, @Nullable List<? extends @Nullable 
String> fieldNames,
+        Set<CorrelationId> variablesSet) {
+      return LogicalProject.create(input, hints, childExprs, fieldNames, 
variablesSet);
     }
   }
 
diff --git a/core/src/main/java/org/apache/calcite/rel/externalize/RelJson.java 
b/core/src/main/java/org/apache/calcite/rel/externalize/RelJson.java
index d39947dfdf..f3460b4c59 100644
--- a/core/src/main/java/org/apache/calcite/rel/externalize/RelJson.java
+++ b/core/src/main/java/org/apache/calcite/rel/externalize/RelJson.java
@@ -73,6 +73,7 @@ import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -396,9 +397,9 @@ public class RelJson {
       return toJson((RexWindowBound) value);
     } else if (value instanceof CorrelationId) {
       return toJson((CorrelationId) value);
-    } else if (value instanceof List) {
+    } else if (value instanceof List || value instanceof Set) {
       final List<@Nullable Object> list = jsonBuilder().list();
-      for (Object o : (List<?>) value) {
+      for (Object o : (Collection<?>) value) {
         list.add(toJson(o));
       }
       return list;
diff --git 
a/core/src/main/java/org/apache/calcite/rel/logical/LogicalProject.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalProject.java
index cb64b37270..2cfe9e0b9c 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalProject.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalProject.java
@@ -24,6 +24,7 @@ import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelShuttle;
+import org.apache.calcite.rel.core.CorrelationId;
 import org.apache.calcite.rel.core.Project;
 import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.metadata.RelMdCollation;
@@ -35,10 +36,12 @@ import org.apache.calcite.sql.validate.SqlValidatorUtil;
 import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * Sub-class of {@link org.apache.calcite.rel.core.Project} not
@@ -58,6 +61,8 @@ public final class LogicalProject extends Project {
    * @param input    Input relational expression
    * @param projects List of expressions for the input columns
    * @param rowType  Output row type
+   * @param variablesSet Correlation variables set by this relational 
expression
+   *                     to be used by nested expressions
    */
   public LogicalProject(
       RelOptCluster cluster,
@@ -65,22 +70,34 @@ public final class LogicalProject extends Project {
       List<RelHint> hints,
       RelNode input,
       List<? extends RexNode> projects,
-      RelDataType rowType) {
-    super(cluster, traitSet, hints, input, projects, rowType);
+      RelDataType rowType,
+      Set<CorrelationId> variablesSet) {
+    super(cluster, traitSet, hints, input, projects, rowType, variablesSet);
     assert traitSet.containsIfApplicable(Convention.NONE);
   }
 
+  @Deprecated // to be removed before 2.0
+  public LogicalProject(
+      RelOptCluster cluster,
+      RelTraitSet traitSet,
+      List<RelHint> hints,
+      RelNode input,
+      List<? extends RexNode> projects,
+      RelDataType rowType) {
+    this(cluster, traitSet, hints, input, projects, rowType, 
ImmutableSet.of());
+  }
+
   @Deprecated // to be removed before 2.0
   public LogicalProject(RelOptCluster cluster, RelTraitSet traitSet,
       RelNode input, List<? extends RexNode> projects, RelDataType rowType) {
-    this(cluster, traitSet, ImmutableList.of(), input, projects, rowType);
+    this(cluster, traitSet, ImmutableList.of(), input, projects, rowType, 
ImmutableSet.of());
   }
 
   @Deprecated // to be removed before 2.0
   public LogicalProject(RelOptCluster cluster, RelTraitSet traitSet,
       RelNode input, List<? extends RexNode> projects, RelDataType rowType,
       int flags) {
-    this(cluster, traitSet, ImmutableList.of(), input, projects, rowType);
+    this(cluster, traitSet, ImmutableList.of(), input, projects, rowType, 
ImmutableSet.of());
     Util.discard(flags);
   }
 
@@ -90,7 +107,7 @@ public final class LogicalProject extends Project {
     this(cluster, cluster.traitSetOf(RelCollations.EMPTY),
         ImmutableList.of(), input, projects,
         RexUtil.createStructType(cluster.getTypeFactory(), projects,
-            fieldNames, null));
+            fieldNames, null), ImmutableSet.of());
     Util.discard(flags);
   }
 
@@ -103,32 +120,56 @@ public final class LogicalProject extends Project {
 
   //~ Methods ----------------------------------------------------------------
 
-  /** Creates a LogicalProject. */
+  /**
+   * Creates a LogicalProject.
+   * @deprecated Use {@link #create(RelNode, List, List, List, Set)} instead
+   */
+  @Deprecated // to be removed before 2.0
   public static LogicalProject create(final RelNode input, List<RelHint> hints,
       final List<? extends RexNode> projects,
       @Nullable List<? extends @Nullable String> fieldNames) {
+    return create(input, hints, projects, fieldNames, ImmutableSet.of());
+  }
+
+  /** Creates a LogicalProject. */
+  public static LogicalProject create(final RelNode input, List<RelHint> hints,
+      final List<? extends RexNode> projects,
+      @Nullable List<? extends @Nullable String> fieldNames,
+      final Set<CorrelationId> variablesSet) {
     final RelOptCluster cluster = input.getCluster();
     final RelDataType rowType =
         RexUtil.createStructType(cluster.getTypeFactory(), projects,
             fieldNames, SqlValidatorUtil.F_SUGGESTER);
-    return create(input, hints, projects, rowType);
+    return create(input, hints, projects, rowType, variablesSet);
   }
 
-  /** Creates a LogicalProject, specifying row type rather than field names. */
+  /**
+   * Creates a LogicalProject, specifying row type rather than field names.
+   * @deprecated Use {@link #create(RelNode, List, List, RelDataType, Set)} 
instead
+   */
+  @Deprecated // to be removed before 2.0
   public static LogicalProject create(final RelNode input, List<RelHint> hints,
       final List<? extends RexNode> projects, RelDataType rowType) {
+    return create(input, hints, projects, rowType, ImmutableSet.of());
+  }
+
+  /** Creates a LogicalProject, specifying row type rather than field names. */
+  public static LogicalProject create(final RelNode input, List<RelHint> hints,
+      final List<? extends RexNode> projects, RelDataType rowType,
+      final Set<CorrelationId> variablesSet) {
     final RelOptCluster cluster = input.getCluster();
     final RelMetadataQuery mq = cluster.getMetadataQuery();
     final RelTraitSet traitSet =
         cluster.traitSet().replace(Convention.NONE)
             .replaceIfs(RelCollationTraitDef.INSTANCE,
                 () -> RelMdCollation.project(mq, input, projects));
-    return new LogicalProject(cluster, traitSet, hints, input, projects, 
rowType);
+    return new LogicalProject(cluster, traitSet, hints, input, projects, 
rowType, variablesSet);
   }
 
   @Override public LogicalProject copy(RelTraitSet traitSet, RelNode input,
       List<RexNode> projects, RelDataType rowType) {
-    return new LogicalProject(getCluster(), traitSet, hints, input, projects, 
rowType);
+    return new LogicalProject(getCluster(), traitSet, hints, input, projects, 
rowType,
+        variablesSet);
   }
 
   @Override public RelNode accept(RelShuttle shuttle) {
@@ -137,7 +178,7 @@ public final class LogicalProject extends Project {
 
   @Override public RelNode withHints(List<RelHint> hintList) {
     return new LogicalProject(getCluster(), traitSet, hintList,
-        input, getProjects(), getRowType());
+        input, getProjects(), getRowType(), variablesSet);
   }
 
   @Override public boolean deepEquals(@Nullable Object obj) {
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java 
b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java
index dc0ca12871..3d39e9ebdb 100644
--- a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java
+++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java
@@ -901,7 +901,8 @@ public class RelToSqlConverter extends SqlImplementor
                   sort2,
                   ImmutableList.of(),
                   project.getProjects(),
-                  project.getRowType());
+                  project.getRowType(),
+                  project.getVariablesSet());
           return visit(project2);
         }
       }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java
index 13665c4809..5be607f821 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/FilterProjectTransposeRule.java
@@ -187,9 +187,10 @@ public class FilterProjectTransposeRule
     RelNode newProject =
         config.isCopyProject()
             ? project.copy(project.getTraitSet(), newFilterRel,
-                project.getProjects(), project.getRowType())
+            project.getProjects(), project.getRowType())
             : relBuilder.push(newFilterRel)
-                .project(project.getProjects(), 
project.getRowType().getFieldNames())
+                .project(project.getProjects(), 
project.getRowType().getFieldNames(), false,
+                    project.getVariablesSet())
                 .build();
 
     call.transformTo(newProject);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
index 1ed0652788..770052f829 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
@@ -37,6 +37,7 @@ import org.apache.calcite.util.BitSets;
 import org.apache.calcite.util.ImmutableBitSet;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import org.immutables.value.Value;
 
@@ -85,7 +86,7 @@ public class ProjectWindowTransposeRule
       return;
     }
 
-    // Put a DrillProjectRel below LogicalWindow
+    // Put a Project below LogicalWindow
     final List<RexNode> exps = new ArrayList<>();
     final RelDataTypeFactory.Builder builder =
         cluster.getTypeFactory().builder();
@@ -99,7 +100,7 @@ public class ProjectWindowTransposeRule
 
     final LogicalProject projectBelowWindow =
         new LogicalProject(cluster, window.getTraitSet(), ImmutableList.of(),
-            window.getInput(), exps, builder.build());
+            window.getInput(), exps, builder.build(), ImmutableSet.of());
 
     // Create a new LogicalWindow with necessary inputs only
     final List<Window.Group> groups = new ArrayList<>();
diff --git a/core/src/main/java/org/apache/calcite/rel/stream/StreamRules.java 
b/core/src/main/java/org/apache/calcite/rel/stream/StreamRules.java
index 3f3fbd2382..2a27d7c18c 100644
--- a/core/src/main/java/org/apache/calcite/rel/stream/StreamRules.java
+++ b/core/src/main/java/org/apache/calcite/rel/stream/StreamRules.java
@@ -86,7 +86,8 @@ public class StreamRules {
           LogicalProject.create(newDelta,
               project.getHints(),
               project.getProjects(),
-              project.getRowType().getFieldNames());
+              project.getRowType().getFieldNames(),
+              project.getVariablesSet());
       call.transformTo(newProject);
     }
 
diff --git 
a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java 
b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index 571019c589..c32047d183 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -836,7 +836,7 @@ public class SqlToRelConverter {
       }
       rel =
           LogicalProject.create(rel, ImmutableList.of(),
-              Pair.left(newProjects), Pair.right(newProjects));
+              Pair.left(newProjects), Pair.right(newProjects), 
project.getVariablesSet());
       bb.root = rel;
       distinctify(bb, false);
       rel = bb.root();
@@ -857,7 +857,7 @@ public class SqlToRelConverter {
 
       rel =
           LogicalProject.create(rel, ImmutableList.of(),
-              Pair.left(undoProjects), Pair.right(undoProjects));
+              Pair.left(undoProjects), Pair.right(undoProjects), 
ImmutableSet.of());
       bb.setRoot(
           rel,
           false);
@@ -936,7 +936,8 @@ public class SqlToRelConverter {
           LogicalProject.create(bb.root(),
               ImmutableList.of(),
               exprs,
-              rowType.getFieldNames().subList(0, fieldCount)),
+              rowType.getFieldNames().subList(0, fieldCount),
+              ImmutableSet.of()),
           false);
     }
   }
@@ -4540,7 +4541,13 @@ public class SqlToRelConverter {
     final RelNode r;
     final CorrelationUse p = getCorrelationUse(bb, project);
     if (p != null) {
-      r = p.r;
+      assert p.r instanceof Project;
+      // correlation variables have been normalized in p.r, we should use 
expressions
+      // in p.r instead of the original exprs
+      Project project1 = (Project) p.r;
+      r = relBuilder.push(bb.root())
+          .projectNamed(project1.getProjects(), fieldNames, true, 
ImmutableSet.of(p.id))
+          .build();
     } else {
       r = project;
     }
@@ -6626,7 +6633,8 @@ public class SqlToRelConverter {
           newInput,
           project.getHints(),
           newProjections.build(),
-          project.getRowType().getFieldNames());
+          project.getRowType().getFieldNames(),
+          project.getVariablesSet());
     }
 
     private Set<Integer> requiredJsonOutputFromParent(RelNode relNode) {
diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java 
b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
index 6eb38385b3..0c7c530751 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -1817,7 +1817,24 @@ public class RelBuilder {
    */
   public RelBuilder project(Iterable<? extends RexNode> nodes,
       Iterable<? extends @Nullable String> fieldNames, boolean force) {
-    return project_(nodes, fieldNames, ImmutableList.of(), force);
+    return project(nodes, fieldNames, force, ImmutableSet.of());
+  }
+
+  /**
+   * The same with {@link #project(Iterable, Iterable, boolean)}, with 
additional
+   * variablesSet param.
+   *
+   * @param nodes Expressions
+   * @param fieldNames Suggested field names
+   * @param force create project even if it is identity
+   * @param variablesSet Correlating variables that are set when reading a row
+   *                     from the input, and which may be referenced from the
+   *                     projection expressions
+   */
+  public RelBuilder project(Iterable<? extends RexNode> nodes,
+      Iterable<? extends @Nullable String> fieldNames, boolean force,
+      Iterable<CorrelationId> variablesSet) {
+    return project_(nodes, fieldNames, ImmutableList.of(), force, 
variablesSet);
   }
 
   /** Creates a {@link Project} of all original fields, plus the given
@@ -1891,10 +1908,12 @@ public class RelBuilder {
       Iterable<? extends RexNode> nodes,
       Iterable<? extends @Nullable String> fieldNames,
       Iterable<RelHint> hints,
-      boolean force) {
+      boolean force,
+      Iterable<CorrelationId> variablesSet) {
     final Frame frame = requireNonNull(peek_(), "frame stack is empty");
     final RelDataType inputRowType = frame.rel.getRowType();
     final List<RexNode> nodeList = Lists.newArrayList(nodes);
+    final Set<CorrelationId> variables = ImmutableSet.copyOf(variablesSet);
 
     // Perform a quick check for identity. We'll do a deeper check
     // later when we've derived column names.
@@ -1908,9 +1927,11 @@ public class RelBuilder {
       fieldNameList.add(null);
     }
 
+    // Do not merge projection when top projection has correlation variables
     bloat:
     if (frame.rel instanceof Project
-        && config.bloat() >= 0) {
+        && config.bloat() >= 0
+        && variables.isEmpty()) {
       final Project project = (Project) frame.rel;
       // Populate field names. If the upper expression is an input ref and does
       // not have a recommended name, use the name of the underlying field.
@@ -1958,7 +1979,9 @@ public class RelBuilder {
       final ImmutableSet.Builder<RelHint> mergedHints = ImmutableSet.builder();
       mergedHints.addAll(project.getHints());
       mergedHints.addAll(hints);
-      return project_(newNodes, fieldNameList, mergedHints.build(), force);
+      // Keep bottom projection's variablesSet.
+      return project_(newNodes, fieldNameList, mergedHints.build(), force,
+          ImmutableSet.copyOf(project.getVariablesSet()));
     }
 
     // Simplify expressions.
@@ -2043,7 +2066,8 @@ public class RelBuilder {
         struct.projectFactory.createProject(frame.rel,
             ImmutableList.copyOf(hints),
             ImmutableList.copyOf(nodeList),
-            fieldNameList);
+            fieldNameList,
+            variables);
     stack.pop();
     stack.push(new Frame(project, fields.build()));
     return this;
@@ -2068,6 +2092,32 @@ public class RelBuilder {
    */
   public RelBuilder projectNamed(Iterable<? extends RexNode> nodes,
       @Nullable Iterable<? extends @Nullable String> fieldNames, boolean 
force) {
+    return projectNamed(nodes, fieldNames, force, ImmutableSet.of());
+  }
+
+  /** Creates a {@link Project} of the given
+   * expressions and field names, and optionally optimizing.
+   *
+   * <p>If {@code fieldNames} is null, or if a particular entry in
+   * {@code fieldNames} is null, derives field names from the input
+   * expressions.
+   *
+   * <p>If {@code force} is false,
+   * and the input is a {@code Project},
+   * and the expressions  make the trivial projection ($0, $1, ...),
+   * modifies the input.
+   *
+   * @param nodes       Expressions
+   * @param fieldNames  Suggested field names, or null to generate
+   * @param force       Whether to create a renaming Project if the
+   *                    projections are trivial
+   * @param variablesSet Correlating variables that are set when reading a row
+   *                     from the input, and which may be referenced from the
+   *                     projection expressions
+   */
+  public RelBuilder projectNamed(Iterable<? extends RexNode> nodes,
+      @Nullable Iterable<? extends @Nullable String> fieldNames, boolean force,
+      Iterable<CorrelationId> variablesSet) {
     @SuppressWarnings("unchecked") final List<? extends RexNode> nodeList =
         nodes instanceof List ? (List) nodes : ImmutableList.copyOf(nodes);
     final List<@Nullable String> fieldNameList =
@@ -2103,7 +2153,7 @@ public class RelBuilder {
         stack.push(new Frame(newValues, frame.fields));
       }
     } else {
-      project(nodeList, rowType.getFieldNames(), force);
+      project(nodeList, rowType.getFieldNames(), force, variablesSet);
     }
     return this;
   }
@@ -2344,8 +2394,7 @@ public class RelBuilder {
           newProjects.add(project.getProjects().get(i));
           builder.add(project.getRowType().getFieldList().get(i));
         }
-        r = project.copy(cluster.traitSet(), project.getInput(), newProjects,
-            builder.build());
+        r = project.copy(cluster.traitSet(), project.getInput(), newProjects, 
builder.build());
       }
     }
 
@@ -3278,7 +3327,8 @@ public class RelBuilder {
                 struct.projectFactory.createProject(sort,
                     project.getHints(),
                     project.getProjects(),
-                    Pair.right(project.getNamedProjects())));
+                    Pair.right(project.getNamedProjects()),
+                    project.getVariablesSet()));
             return this;
           }
         }
diff --git a/core/src/test/java/org/apache/calcite/plan/RelOptUtilTest.java 
b/core/src/test/java/org/apache/calcite/plan/RelOptUtilTest.java
index 2f22e78ee7..54e9c88d16 100644
--- a/core/src/test/java/org/apache/calcite/plan/RelOptUtilTest.java
+++ b/core/src/test/java/org/apache/calcite/plan/RelOptUtilTest.java
@@ -49,6 +49,7 @@ import org.apache.calcite.util.TestUtil;
 import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
@@ -693,7 +694,8 @@ class RelOptUtilTest {
             ImmutableList.of(
                 fieldEmpno.getName(),
                 fieldEname.getName(),
-                "JOB_CNT"));
+                "JOB_CNT"),
+            ImmutableSet.of());
     assertThat(castNode1.explain(), is(expectNode1.explain()));
     // Change the field JOB_CNT field name again.
     // The projection expect to be merged.
@@ -716,7 +718,8 @@ class RelOptUtilTest {
             ImmutableList.of(
                 fieldEmpno.getName(),
                 fieldEname.getName(),
-                "JOB_CNT2"));
+                "JOB_CNT2"),
+            ImmutableSet.of());
     assertThat(castNode2.explain(), is(expectNode2.explain()));
   }
 
diff --git a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java 
b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
index f2277fdc41..798be9dada 100644
--- a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
+++ b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
@@ -592,7 +592,8 @@ class RelWriterTest {
                           RexWindowBounds.following(
                               rexBuilder.makeExactLiteral(BigDecimal.ONE)),
                           false, true, false, false, false)),
-                  ImmutableList.of("field0", "field1", "field2"));
+                  ImmutableList.of("field0", "field1", "field2"),
+                  ImmutableSet.of());
           final RelJsonWriter writer = new RelJsonWriter();
           project.explain(writer);
           return writer.asString();
@@ -998,6 +999,21 @@ class RelWriterTest {
         .assertThatPlan(isLinux(expected));
   }
 
+  @Test void testProjectionWithCorrelationVariables() {
+    final Function<RelBuilder, RelNode> relFn = b -> b.scan("EMP")
+        .project(
+            ImmutableList.of(b.field("ENAME")),
+            ImmutableList.of("ename"),
+            true,
+            ImmutableSet.of(b.getCluster().createCorrel()))
+        .build();
+
+    final String expected = "LogicalProject(variablesSet=[[$cor0]], 
ename=[$1])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    relFn(relFn)
+        .assertThatPlan(isLinux(expected));
+  }
+
   @Test void testOverWithoutOrderKey() {
     // Equivalent SQL:
     //   SELECT count(*) OVER (PARTITION BY deptno) FROM emp
diff --git 
a/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java 
b/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java
index 825e143469..4a24f261b3 100644
--- 
a/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java
+++ 
b/core/src/test/java/org/apache/calcite/plan/volcano/TraitPropagationTest.java
@@ -72,6 +72,7 @@ import org.apache.calcite.tools.RuleSets;
 import org.apache.calcite.util.ImmutableBitSet;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.immutables.value.Value;
@@ -164,7 +165,8 @@ class TraitPropagationTest {
               (RexNode) rexBuilder.makeInputRef(stringType, 0),
               rexBuilder.makeInputRef(integerType, 1)),
           typeFactory.builder().add("s", stringType).add("i", integerType)
-          .build());
+              .build(),
+          ImmutableSet.of());
 
       // aggregate on s, count
       AggregateCall aggCall = AggregateCall.create(SqlStdOperatorTable.COUNT,
@@ -370,7 +372,7 @@ class TraitPropagationTest {
   private static class PhysProj extends Project implements Phys {
     PhysProj(RelOptCluster cluster, RelTraitSet traits, RelNode child,
         List<RexNode> exps, RelDataType rowType) {
-      super(cluster, traits, ImmutableList.of(), child, exps, rowType);
+      super(cluster, traits, ImmutableList.of(), child, exps, rowType, 
ImmutableSet.of());
     }
 
     public static PhysProj create(final RelNode input,
diff --git a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java 
b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
index 307c3a9ba9..5f0c8a87f9 100644
--- a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
@@ -1620,7 +1620,8 @@ public class RelMetadataTest {
     final LogicalProject project = LogicalProject.create(empSort,
         ImmutableList.of(),
         projects,
-        ImmutableList.of("a", "b", "c", "d"));
+        ImmutableList.of("a", "b", "c", "d"),
+        ImmutableSet.of());
 
     final LogicalTableScan deptScan =
         LogicalTableScan.create(cluster, deptTable, ImmutableList.of());
@@ -1868,7 +1869,8 @@ public class RelMetadataTest {
                     rexBuilder.makeExactLiteral(BigDecimal.ONE)),
                 rexBuilder.makeCall(SqlStdOperatorTable.CHAR_LENGTH,
                     rexBuilder.makeInputRef(filter, 1))),
-            (List<String>) null);
+            (List<String>) null,
+            ImmutableSet.of());
     rowSize = mq.getAverageRowSize(deptProject);
     columnSizes = mq.getAverageColumnSizes(deptProject);
     assertThat(columnSizes.size(), equalTo(4));
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java 
b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
index 606c63cb54..97de092806 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -126,6 +126,7 @@ import org.apache.calcite.tools.RuleSets;
 import org.apache.calcite.util.ImmutableBitSet;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import org.immutables.value.Value;
 import org.junit.jupiter.api.Disabled;
@@ -6883,7 +6884,7 @@ class RelOptRulesTest extends RelOptTestBase {
         RelNode input,
         List<? extends RexNode> projects,
         RelDataType rowType) {
-      super(cluster, traitSet, ImmutableList.of(), input, projects, rowType);
+      super(cluster, traitSet, ImmutableList.of(), input, projects, rowType, 
ImmutableSet.of());
     }
 
     public MyProject copy(RelTraitSet traitSet, RelNode input,
diff --git 
a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index aecd47ee4c..0ebe20039d 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -3383,6 +3383,30 @@ class SqlToRelConverterTest extends SqlToRelTestBase {
     sql(sql).withDecorrelate(true).ok();
   }
 
+  @Test void testCorrelationInProjectionWithScan() {
+    final String sql = "select array(select e.deptno) from emp e";
+    sql(sql).withExpand(false).withDecorrelate(false).ok();
+  }
+
+  @Test void testCorrelationInProjectionWithProjection() {
+    final String sql = "select array(select e.deptno)\n"
+        + "from (select deptno, ename from emp) e";
+    sql(sql).withExpand(false).withDecorrelate(false).ok();
+  }
+
+  @Test void testMultiCorrelationInProjectionWithProjection() {
+    final String sql = "select cardinality(array(select e.deptno)), 
array(select e.ename)[0]\n"
+        + "from (select deptno, ename from emp) e";
+    sql(sql).withExpand(false).withDecorrelate(false).ok();
+  }
+
+  @Test void testCorrelationInProjectionWithCorrelatedProjection() {
+    final String sql = "select cardinality(arr) from"
+        + "(select array(select e.deptno) arr\n"
+        + "from (select deptno, ename from emp) e)";
+    sql(sql).withExpand(false).withDecorrelate(false).ok();
+  }
+
   @Test void testCustomColumnResolving() {
     final String sql = "select k0 from struct.t";
     sql(sql).ok();
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 b146e735c5..863a8f1db2 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -11934,7 +11934,7 @@ from emp
     </Resource>
     <Resource name="planBefore">
       <![CDATA[
-LogicalProject(EXPR$0=[> SOME($0, {
+LogicalProject(variablesSet=[[$cor0]], EXPR$0=[> SOME($0, {
 LogicalProject(DEPTNO=[$0])
   LogicalFilter(condition=[=($cor0.JOB, $1)])
     LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
@@ -11976,7 +11976,7 @@ LogicalProject(EXPR$0=[CAST(OR(AND(IS TRUE(>($0, $9)), 
IS NOT TRUE(OR(IS NULL($1
     </Resource>
     <Resource name="planBefore">
       <![CDATA[
-LogicalProject(SAL=[$5], EXPR$1=[NOT(IN($0, {
+LogicalProject(variablesSet=[[$cor0]], SAL=[$5], EXPR$1=[NOT(IN($0, {
 LogicalProject(DEPTNO=[$0])
   LogicalFilter(condition=[=($cor0.JOB, $1)])
     LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
diff --git 
a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml 
b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index 44f6fae8ca..818f2a6086 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -737,7 +737,7 @@ LogicalProject(EMPNO=[$0], JOB=[$2])
   <TestCase name="testCorrelatedScalarSubQueryInSelectList">
     <Resource name="planNotExpanded">
       <![CDATA[
-LogicalProject(DEPTNO=[$0], I0=[$SCALAR_QUERY({
+LogicalProject(variablesSet=[[$cor0]], DEPTNO=[$0], I0=[$SCALAR_QUERY({
 LogicalAggregate(group=[{}], EXPR$0=[MIN($0)])
   LogicalProject($f0=[1])
     LogicalFilter(condition=[>($0, $cor0.DEPTNO)])
@@ -857,7 +857,7 @@ LogicalProject(DEPTNO=[$7], EXPR$1=[$9])
     </Resource>
     <Resource name="planNotExpanded">
       <![CDATA[
-LogicalProject(DEPTNO=[$7], EXPR$1=[$SCALAR_QUERY({
+LogicalProject(variablesSet=[[$cor0]], DEPTNO=[$7], EXPR$1=[$SCALAR_QUERY({
 LogicalProject(NAME=[$0])
   LogicalTableFunctionScan(invocation=[DEDUP($cor0.DEPTNO, $cor0.DEPTNO)], 
rowType=[RecordType(VARCHAR(1024) NAME)])
 })])
@@ -969,6 +969,53 @@ where exists (select * from emp
   and emp.deptno in (dept.deptno, dept.deptno))]]>
     </Resource>
   </TestCase>
+  <TestCase name="testCorrelationInProjectionWithCorrelatedProjection">
+    <Resource name="sql">
+      <![CDATA[select cardinality(arr) from
+(select array(select e.deptno) arr
+from (select deptno, ename from emp) e)]]>
+    </Resource>
+    <Resource name="plan">
+      <![CDATA[
+LogicalProject(variablesSet=[[$cor0]], EXPR$0=[CARDINALITY(ARRAY({
+LogicalProject(DEPTNO=[$cor0.DEPTNO])
+  LogicalValues(tuples=[[{ 0 }]])
+}))])
+  LogicalProject(DEPTNO=[$7], ENAME=[$1])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testCorrelationInProjectionWithProjection">
+    <Resource name="sql">
+      <![CDATA[select array(select e.deptno)
+from (select deptno, ename from emp) e]]>
+    </Resource>
+    <Resource name="plan">
+      <![CDATA[
+LogicalProject(variablesSet=[[$cor0]], EXPR$0=[ARRAY({
+LogicalProject(DEPTNO=[$cor0.DEPTNO])
+  LogicalValues(tuples=[[{ 0 }]])
+})])
+  LogicalProject(DEPTNO=[$7], ENAME=[$1])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testCorrelationInProjectionWithScan">
+    <Resource name="sql">
+      <![CDATA[select array(select e.deptno) from emp e]]>
+    </Resource>
+    <Resource name="plan">
+      <![CDATA[
+LogicalProject(variablesSet=[[$cor0]], EXPR$0=[ARRAY({
+LogicalProject(DEPTNO=[$cor0.DEPTNO])
+  LogicalValues(tuples=[[{ 0 }]])
+})])
+  LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
   <TestCase name="testCorrelationInWithSubQuery">
     <Resource name="plan">
       <![CDATA[
@@ -4379,6 +4426,25 @@ and (deptno = 8 or empno < 100)]]>
 LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], 
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
   LogicalFilter(condition=[AND(<($7, 10), >($7, 5), OR(=($7, 8), <($0, 100)))])
     LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testMultiCorrelationInProjectionWithProjection">
+    <Resource name="sql">
+      <![CDATA[select cardinality(array(select e.deptno)), array(select 
e.ename)[0]
+from (select deptno, ename from emp) e]]>
+    </Resource>
+    <Resource name="plan">
+      <![CDATA[
+LogicalProject(variablesSet=[[$cor0]], EXPR$0=[CARDINALITY(ARRAY({
+LogicalProject(DEPTNO=[$cor0.DEPTNO])
+  LogicalValues(tuples=[[{ 0 }]])
+}))], EXPR$1=[ITEM(ARRAY({
+LogicalProject(ENAME=[$cor0.ENAME])
+  LogicalValues(tuples=[[{ 0 }]])
+}), 0)])
+  LogicalProject(DEPTNO=[$7], ENAME=[$1])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
     </Resource>
   </TestCase>
diff --git a/core/src/test/resources/sql/agg.iq 
b/core/src/test/resources/sql/agg.iq
index ec9b01987e..4adc4fd5f9 100644
--- a/core/src/test/resources/sql/agg.iq
+++ b/core/src/test/resources/sql/agg.iq
@@ -1334,12 +1334,17 @@ GROUP BY deptno;
 !ok
 
 # As above, but with correlation
-!if (fixed.calcite1045) {
 SELECT SUM(
   (select char_length(dname) from "scott".dept where dept.deptno = emp.empno)) 
as s
 FROM "scott".emp;
++---+
+| S |
++---+
+|   |
++---+
+(1 row)
+
 !ok
-!}
 
 # FUSION rolled up using CARDINALITY
 select cardinality(fusion(empnos)) as f_empnos_length
diff --git a/core/src/test/resources/sql/sub-query.iq 
b/core/src/test/resources/sql/sub-query.iq
index e33e094b67..a22ee003f9 100644
--- a/core/src/test/resources/sql/sub-query.iq
+++ b/core/src/test/resources/sql/sub-query.iq
@@ -310,50 +310,97 @@ group by (select deptno from "scott".emp where empno = 
10);
 
 !ok
 
-!if (fixed.calcite1045) {
 # Correlated IN sub-query in WHERE clause of JOIN
 select empno from "scott".emp as e
 join "scott".dept as d using (deptno)
 where e.job in (
   select e2.job from "scott".emp as e2 where e2.deptno > e.deptno);
- EMPNO
--------
-  7369
-  7566
-  7782
-  7876
-  7934
++-------+
+| EMPNO |
++-------+
+|  7369 |
+|  7566 |
+|  7782 |
+|  7876 |
+|  7934 |
++-------+
 (5 rows)
 
 !ok
-EnumerableCalc(expr#0..5=[{inputs}], EMPNO=[$t0])
-  EnumerableHashJoin(condition=[=($2, $5)], joinType=[inner])
+EnumerableCalc(expr#0..4=[{inputs}], EMPNO=[$t0])
+  EnumerableHashJoin(condition=[=($2, $5)], joinType=[semi])
     EnumerableCalc(expr#0..4=[{inputs}], EMPNO=[$t2], JOB=[$t3], DEPTNO=[$t4], 
JOB0=[$t0], DEPTNO0=[$t1])
       EnumerableHashJoin(condition=[AND(=($1, $4), =($0, $3))], 
joinType=[inner])
-        EnumerableCalc(expr#0..1=[{inputs}], JOB=[$t1], DEPTNO=[$t0])
-          EnumerableAggregate(group=[{0, 2}])
-            EnumerableCalc(expr#0..3=[{inputs}], expr#4=[>($t3, $t0)], 
proj#0..3=[{exprs}], $condition=[$t4])
-              EnumerableNestedLoopJoin(condition=[true], joinType=[inner])
-                EnumerableAggregate(group=[{7}])
-                  EnumerableTableScan(table=[[scott, EMP]])
-                EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], JOB=[$t2], 
DEPTNO=[$t7])
+        EnumerableAggregate(group=[{1, 3}])
+          EnumerableNestedLoopJoin(condition=[>($2, $3)], joinType=[inner])
+            EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], JOB=[$t2], 
DEPTNO=[$t7])
+              EnumerableTableScan(table=[[scott, EMP]])
+            EnumerableAggregate(group=[{1}])
+              EnumerableHashJoin(condition=[=($1, $2)], joinType=[semi])
+                EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], DEPTNO=[$t7])
                   EnumerableTableScan(table=[[scott, EMP]])
+                EnumerableCalc(expr#0..2=[{inputs}], DEPTNO=[$t0])
+                  EnumerableTableScan(table=[[scott, DEPT]])
         EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], JOB=[$t2], 
DEPTNO=[$t7])
           EnumerableTableScan(table=[[scott, EMP]])
     EnumerableCalc(expr#0..2=[{inputs}], DEPTNO=[$t0])
       EnumerableTableScan(table=[[scott, DEPT]])
 !plan
-!}
 
-!if (fixed.calcite1045) {
 # Correlated NOT IN sub-query in WHERE clause of JOIN
 select empno from "scott".emp as e
 join "scott".dept as d using (deptno)
 where e.job not in (
   select e2.job from "scott".emp as e2 where e2.deptno > e.deptno);
++-------+
+| EMPNO |
++-------+
+|  7499 |
+|  7521 |
+|  7654 |
+|  7698 |
+|  7788 |
+|  7839 |
+|  7844 |
+|  7900 |
+|  7902 |
++-------+
+(9 rows)
+
 !ok
+EnumerableCalc(expr#0..9=[{inputs}], expr#10=[0], expr#11=[=($t5, $t10)], 
expr#12=[IS NULL($t1)], expr#13=[IS NOT NULL($t9)], expr#14=[<($t6, $t5)], 
expr#15=[OR($t12, $t13, $t14)], expr#16=[IS NOT TRUE($t15)], expr#17=[OR($t11, 
$t16)], EMPNO=[$t0], $condition=[$t17])
+  EnumerableMergeJoin(condition=[AND(=($1, $7), =($2, $8))], joinType=[left])
+    EnumerableSort(sort0=[$1], sort1=[$2], dir0=[ASC], dir1=[ASC])
+      EnumerableHashJoin(condition=[=($2, $4)], joinType=[left])
+        EnumerableCalc(expr#0..3=[{inputs}], EMPNO=[$t1], JOB=[$t2], 
DEPTNO=[$t3], DEPTNO0=[$t0])
+          EnumerableHashJoin(condition=[=($0, $3)], joinType=[inner])
+            EnumerableCalc(expr#0..2=[{inputs}], DEPTNO=[$t0])
+              EnumerableTableScan(table=[[scott, DEPT]])
+            EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], JOB=[$t2], 
DEPTNO=[$t7])
+              EnumerableTableScan(table=[[scott, EMP]])
+        EnumerableAggregate(group=[{3}], c=[COUNT()], ck=[COUNT($1)])
+          EnumerableNestedLoopJoin(condition=[>($2, $3)], joinType=[inner])
+            EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], JOB=[$t2], 
DEPTNO=[$t7])
+              EnumerableTableScan(table=[[scott, EMP]])
+            EnumerableAggregate(group=[{1}])
+              EnumerableHashJoin(condition=[=($1, $2)], joinType=[semi])
+                EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], DEPTNO=[$t7])
+                  EnumerableTableScan(table=[[scott, EMP]])
+                EnumerableCalc(expr#0..2=[{inputs}], DEPTNO=[$t0])
+                  EnumerableTableScan(table=[[scott, DEPT]])
+    EnumerableSort(sort0=[$0], sort1=[$1], dir0=[ASC], dir1=[ASC])
+      EnumerableCalc(expr#0..1=[{inputs}], expr#2=[true], expr#3=[IS NOT 
NULL($t0)], proj#0..2=[{exprs}], $condition=[$t3])
+        EnumerableAggregate(group=[{1, 3}])
+          EnumerableNestedLoopJoin(condition=[>($2, $3)], joinType=[inner])
+            EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], JOB=[$t2], 
DEPTNO=[$t7])
+              EnumerableTableScan(table=[[scott, EMP]])
+            EnumerableAggregate(group=[{1}])
+              EnumerableHashJoin(condition=[=($1, $2)], joinType=[semi])
+                EnumerableCalc(expr#0..7=[{inputs}], EMPNO=[$t0], DEPTNO=[$t7])
+                  EnumerableTableScan(table=[[scott, EMP]])
+                EnumerableCalc(expr#0..2=[{inputs}], DEPTNO=[$t0])
+                  EnumerableTableScan(table=[[scott, DEPT]])
 !plan
-!}
 
 # Condition that returns a NULL key.
 # Tested on Oracle.
@@ -549,23 +596,21 @@ and exists (select 0 from "scott".emp where deptno = 
d.deptno and ename = 'SMITH
 !ok
 
 # Two scalar sub-queries
-!if (fixed.calcite1045) {
 select deptno,
   (select min(1) from "scott".emp where empno > d.deptno) as i0,
   (select min(0) from "scott".emp where deptno = d.deptno and ename = 'SMITH') 
as i1
 from "scott".dept as d;
-+--------+----+---+
-| DEPTNO | I0 | I1|
-+--------+----+---+
-|     10 |  1 |   |
-|     20 |  1 | 0 |
-|     30 |  1 |   |
-|     40 |  1 |   |
-+--------+----+---+
++--------+----+----+
+| DEPTNO | I0 | I1 |
++--------+----+----+
+|     10 |  1 |    |
+|     20 |  1 |  0 |
+|     30 |  1 |    |
+|     40 |  1 |    |
++--------+----+----+
 (4 rows)
 
 !ok
-!}
 
 # Correlated scalar sub-query
 SELECT d.dname,
@@ -3513,4 +3558,15 @@ EnumerableCalc(expr#0..1=[{inputs}], EXPR$0=[$t1])
         EnumerableValues(tuples=[[{ 0 }]])
 !plan
 
+# Test case for correlated sub-query
+SELECT ARRAY(SELECT s.x) FROM (SELECT 1 as x) s;
++--------+
+| EXPR$0 |
++--------+
+| [1]    |
++--------+
+(1 row)
+
+!ok
+
 # End sub-query.iq
diff --git 
a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java 
b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
index 3c7800f380..963ea3cb07 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
@@ -356,6 +356,11 @@ public class DruidRules {
       super(config);
     }
 
+    @Override public boolean matches(RelOptRuleCall call) {
+      final Project project = call.rel(0);
+      return project.getVariablesSet().isEmpty();
+    }
+
     @Override public void onMatch(RelOptRuleCall call) {
       final Project project = call.rel(0);
       final DruidQuery query = call.rel(1);
diff --git 
a/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchProject.java
 
b/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchProject.java
index 0a341c683c..5aadcbceca 100644
--- 
a/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchProject.java
+++ 
b/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchProject.java
@@ -29,6 +29,7 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.Pair;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
@@ -43,7 +44,7 @@ import java.util.stream.Collectors;
 public class ElasticsearchProject extends Project implements ElasticsearchRel {
   ElasticsearchProject(RelOptCluster cluster, RelTraitSet traitSet, RelNode 
input,
       List<? extends RexNode> projects, RelDataType rowType) {
-    super(cluster, traitSet, ImmutableList.of(), input, projects, rowType);
+    super(cluster, traitSet, ImmutableList.of(), input, projects, rowType, 
ImmutableSet.of());
     assert getConvention() == ElasticsearchRel.CONVENTION;
     assert getConvention() == input.getConvention();
   }
diff --git 
a/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchRules.java
 
b/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchRules.java
index 78261b9ae5..00685af824 100644
--- 
a/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchRules.java
+++ 
b/elasticsearch/src/main/java/org/apache/calcite/adapter/elasticsearch/ElasticsearchRules.java
@@ -21,6 +21,7 @@ import 
org.apache.calcite.adapter.enumerable.RexToLixTranslator;
 import org.apache.calcite.adapter.java.JavaTypeFactory;
 import org.apache.calcite.plan.Convention;
 import org.apache.calcite.plan.RelOptRule;
+import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.InvalidRelException;
 import org.apache.calcite.rel.RelCollations;
@@ -293,6 +294,11 @@ class ElasticsearchRules {
       super(config);
     }
 
+    @Override public boolean matches(RelOptRuleCall call) {
+      final LogicalProject project = call.rel(0);
+      return project.getVariablesSet().isEmpty();
+    }
+
     @Override public RelNode convert(RelNode relNode) {
       final LogicalProject project = (LogicalProject) relNode;
       final RelTraitSet traitSet = project.getTraitSet().replace(out);
diff --git 
a/geode/src/main/java/org/apache/calcite/adapter/geode/rel/GeodeProject.java 
b/geode/src/main/java/org/apache/calcite/adapter/geode/rel/GeodeProject.java
index da5cf40a73..fc7a0f33b9 100644
--- a/geode/src/main/java/org/apache/calcite/adapter/geode/rel/GeodeProject.java
+++ b/geode/src/main/java/org/apache/calcite/adapter/geode/rel/GeodeProject.java
@@ -29,6 +29,7 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.Pair;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
@@ -45,7 +46,7 @@ public class GeodeProject extends Project implements GeodeRel 
{
 
   GeodeProject(RelOptCluster cluster, RelTraitSet traitSet,
       RelNode input, List<? extends RexNode> projects, RelDataType rowType) {
-    super(cluster, traitSet, ImmutableList.of(), input, projects, rowType);
+    super(cluster, traitSet, ImmutableList.of(), input, projects, rowType, 
ImmutableSet.of());
     assert getConvention() == GeodeRel.CONVENTION;
     assert getConvention() == input.getConvention();
   }
diff --git 
a/geode/src/main/java/org/apache/calcite/adapter/geode/rel/GeodeRules.java 
b/geode/src/main/java/org/apache/calcite/adapter/geode/rel/GeodeRules.java
index 5fd12fa3e6..4ba331cbbb 100644
--- a/geode/src/main/java/org/apache/calcite/adapter/geode/rel/GeodeRules.java
+++ b/geode/src/main/java/org/apache/calcite/adapter/geode/rel/GeodeRules.java
@@ -40,6 +40,8 @@ import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.validate.SqlValidatorUtil;
 
+import com.google.common.base.Preconditions;
+
 import org.immutables.value.Value;
 
 import java.util.ArrayList;
@@ -145,12 +147,13 @@ public class GeodeRules {
           return false;
         }
       }
-
-      return true;
+      return project.getVariablesSet().isEmpty();
     }
 
     @Override public RelNode convert(RelNode rel) {
       final LogicalProject project = (LogicalProject) rel;
+      Preconditions.checkArgument(project.getVariablesSet().isEmpty(),
+          "GeodeProject does now allow variables");
       final RelTraitSet traitSet =
           project.getTraitSet().replace(getOutConvention());
       return new GeodeProject(
diff --git 
a/innodb/src/main/java/org/apache/calcite/adapter/innodb/InnodbProject.java 
b/innodb/src/main/java/org/apache/calcite/adapter/innodb/InnodbProject.java
index b7e657ac07..202068aadb 100644
--- a/innodb/src/main/java/org/apache/calcite/adapter/innodb/InnodbProject.java
+++ b/innodb/src/main/java/org/apache/calcite/adapter/innodb/InnodbProject.java
@@ -28,6 +28,7 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.Pair;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
@@ -42,7 +43,7 @@ import java.util.Map;
 public class InnodbProject extends Project implements InnodbRel {
   InnodbProject(RelOptCluster cluster, RelTraitSet traitSet,
       RelNode input, List<? extends RexNode> projects, RelDataType rowType) {
-    super(cluster, traitSet, ImmutableList.of(), input, projects, rowType);
+    super(cluster, traitSet, ImmutableList.of(), input, projects, rowType, 
ImmutableSet.of());
     assert getConvention() == InnodbRel.CONVENTION;
     assert getConvention() == input.getConvention();
   }
diff --git 
a/innodb/src/main/java/org/apache/calcite/adapter/innodb/InnodbRules.java 
b/innodb/src/main/java/org/apache/calcite/adapter/innodb/InnodbRules.java
index cf381de247..5b8a67509e 100644
--- a/innodb/src/main/java/org/apache/calcite/adapter/innodb/InnodbRules.java
+++ b/innodb/src/main/java/org/apache/calcite/adapter/innodb/InnodbRules.java
@@ -140,8 +140,7 @@ public class InnodbRules {
           return false;
         }
       }
-
-      return true;
+      return project.getVariablesSet().isEmpty();
     }
 
     @Override public RelNode convert(RelNode rel) {
diff --git 
a/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoProject.java 
b/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoProject.java
index 55bce98e56..ee0721fe4a 100644
--- a/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoProject.java
+++ b/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoProject.java
@@ -30,6 +30,7 @@ import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
@@ -43,7 +44,7 @@ import java.util.List;
 public class MongoProject extends Project implements MongoRel {
   public MongoProject(RelOptCluster cluster, RelTraitSet traitSet,
       RelNode input, List<? extends RexNode> projects, RelDataType rowType) {
-    super(cluster, traitSet, ImmutableList.of(), input, projects, rowType);
+    super(cluster, traitSet, ImmutableList.of(), input, projects, rowType, 
ImmutableSet.of());
     assert getConvention() == MongoRel.CONVENTION;
     assert getConvention() == input.getConvention();
   }
diff --git 
a/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoRules.java 
b/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoRules.java
index 9e9ac37fca..fa10035746 100644
--- a/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoRules.java
+++ b/mongodb/src/main/java/org/apache/calcite/adapter/mongodb/MongoRules.java
@@ -21,6 +21,7 @@ import 
org.apache.calcite.adapter.enumerable.RexToLixTranslator;
 import org.apache.calcite.adapter.java.JavaTypeFactory;
 import org.apache.calcite.plan.Convention;
 import org.apache.calcite.plan.RelOptRule;
+import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.InvalidRelException;
 import org.apache.calcite.rel.RelCollations;
@@ -310,6 +311,11 @@ public class MongoRules {
       super(config);
     }
 
+    @Override public boolean matches(RelOptRuleCall call) {
+      final LogicalProject project = call.rel(0);
+      return project.getVariablesSet().isEmpty();
+    }
+
     @Override public RelNode convert(RelNode rel) {
       final LogicalProject project = (LogicalProject) rel;
       final RelTraitSet traitSet = project.getTraitSet().replace(out);
diff --git a/pig/src/main/java/org/apache/calcite/adapter/pig/PigProject.java 
b/pig/src/main/java/org/apache/calcite/adapter/pig/PigProject.java
index 11d2e90fbd..e62c9106a7 100644
--- a/pig/src/main/java/org/apache/calcite/adapter/pig/PigProject.java
+++ b/pig/src/main/java/org/apache/calcite/adapter/pig/PigProject.java
@@ -25,6 +25,7 @@ import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexNode;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import java.util.List;
 
@@ -35,7 +36,7 @@ public class PigProject extends Project implements PigRel {
   /** Creates a PigProject. */
   public PigProject(RelOptCluster cluster, RelTraitSet traitSet, RelNode input,
       List<? extends RexNode> projects, RelDataType rowType) {
-    super(cluster, traitSet, ImmutableList.of(), input, projects, rowType);
+    super(cluster, traitSet, ImmutableList.of(), input, projects, rowType, 
ImmutableSet.of());
     assert getConvention() == PigRel.CONVENTION;
   }
 
diff --git a/pig/src/main/java/org/apache/calcite/adapter/pig/PigRules.java 
b/pig/src/main/java/org/apache/calcite/adapter/pig/PigRules.java
index 3def5e0c72..d3daa3575c 100644
--- a/pig/src/main/java/org/apache/calcite/adapter/pig/PigRules.java
+++ b/pig/src/main/java/org/apache/calcite/adapter/pig/PigRules.java
@@ -18,6 +18,7 @@ package org.apache.calcite.adapter.pig;
 
 import org.apache.calcite.plan.Convention;
 import org.apache.calcite.plan.RelOptRule;
+import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.convert.ConverterRule;
@@ -108,6 +109,11 @@ public class PigRules {
       super(config);
     }
 
+    @Override public boolean matches(RelOptRuleCall call) {
+      final LogicalProject project = call.rel(0);
+      return project.getVariablesSet().isEmpty();
+    }
+
     @Override public RelNode convert(RelNode rel) {
       final LogicalProject project = (LogicalProject) rel;
       final RelTraitSet traitSet = 
project.getTraitSet().replace(PigRel.CONVENTION);
diff --git 
a/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java
 
b/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java
index 0e7c737822..5422472c99 100644
--- 
a/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java
+++ 
b/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java
@@ -305,7 +305,7 @@ public class SplunkPushDownRule
       return rel;
     }
     return LogicalProject.create(rel, proj.getHints(),
-        proj.getProjects(), proj.getRowType());
+        proj.getProjects(), proj.getRowType(), proj.getVariablesSet());
   }
 
   // TODO: use StringBuilder instead of String
diff --git 
a/testkit/src/main/java/org/apache/calcite/test/RelMetadataFixture.java 
b/testkit/src/main/java/org/apache/calcite/test/RelMetadataFixture.java
index 090816cced..862ca1cc9e 100644
--- a/testkit/src/main/java/org/apache/calcite/test/RelMetadataFixture.java
+++ b/testkit/src/main/java/org/apache/calcite/test/RelMetadataFixture.java
@@ -41,6 +41,7 @@ import org.apache.calcite.sql.test.SqlTester;
 import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.util.ImmutableBitSet;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Iterables;
@@ -195,6 +196,8 @@ public class RelMetadataFixture {
     metadataConfig.applyMetadata(rel.getCluster());
     if (convertAsCalc) {
       Project project = (Project) rel;
+      Preconditions.checkArgument(project.getVariablesSet().isEmpty(),
+          "Calc does not allow variables");
       RexProgram program = RexProgram.create(
           project.getInput().getRowType(),
           project.getProjects(),
diff --git 
a/testkit/src/main/java/org/apache/calcite/test/catalog/MockCatalogReader.java 
b/testkit/src/main/java/org/apache/calcite/test/catalog/MockCatalogReader.java
index 8cc3539697..ca1cbf90c9 100644
--- 
a/testkit/src/main/java/org/apache/calcite/test/catalog/MockCatalogReader.java
+++ 
b/testkit/src/main/java/org/apache/calcite/test/catalog/MockCatalogReader.java
@@ -86,6 +86,7 @@ import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
@@ -903,7 +904,8 @@ public abstract class MockCatalogReader extends 
CalciteCatalogReader {
       return LogicalProject.create(rel,
           ImmutableList.of(),
           Pair.left(projects),
-          Pair.right(projects));
+          Pair.right(projects),
+          ImmutableSet.of());
     }
 
     @Override public <T> T unwrap(Class<T> clazz) {

Reply via email to