You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@flink.apache.org by "ldadima (via GitHub)" <gi...@apache.org> on 2026/03/24 06:25:24 UTC

[PR] [FLINK-39285][table] Ignore project rel node generated by FlinkFilterJoinRule in JoinToMultiJoinRule [flink]

ldadima opened a new pull request, #27815:
URL: https://github.com/apache/flink/pull/27815

   ## What is the purpose of the change
   
   Consider the result of filter pushdown rules in the MultiJoin rule,  according [FLINK-39285](https://issues.apache.org/jira/browse/FLINK-39285)
   
   ## Verifying this change
   
   Run MultiJoinTest#testThreeWayComplexJoinWithConditionsRelPlan
   
   
   ## Does this pull request potentially affect one of the following parts:
   
     - Dependencies (does it add or upgrade a dependency): (no)
     - The public API, i.e., is any changed class annotated with `@Public(Evolving)`: ( no)
     - The serializers: (no)
     - The runtime per-record code paths (performance sensitive): (no)
     - Anything that affects deployment or recovery: JobManager (and its components), Checkpointing, Kubernetes/Yarn, ZooKeeper: (no)
     - The S3 file system connector: (no)
   
   ## Documentation
   
     - Does this pull request introduce a new feature? ( no)
     - If yes, how is the feature documented? (not applicable)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [FLINK-39285][table] Ignore project rel node generated by FlinkFilterJoinRule in JoinToMultiJoinRule [flink]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #27815:
URL: https://github.com/apache/flink/pull/27815#issuecomment-4955465890

   This PR is being marked as stale since it has not had any activity in the last 90 days. 
   If you would like to keep this PR alive, please leave a comment asking for a review. 
   If the PR has merge conflicts, update it with the latest from the base branch.
   
   If you are having difficulty finding a reviewer, please reach out to the 
   community, contact details can be found here: https://flink.apache.org/what-is-flink/community/
   
   If this PR is no longer valid or desired, please feel free to close it. 
   If no activity occurs in the next 30 days, it will be automatically closed.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [FLINK-39285][table] Ignore project rel node generated by FlinkFilterJoinRule in JoinToMultiJoinRule [flink]

Posted by "flinkbot (via GitHub)" <gi...@apache.org>.
flinkbot commented on PR #27815:
URL: https://github.com/apache/flink/pull/27815#issuecomment-4115748829

   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "5d0585bf8eaf414d72d4dc4053d298091bf21b47",
       "status" : "UNKNOWN",
       "url" : "TBD",
       "triggerID" : "5d0585bf8eaf414d72d4dc4053d298091bf21b47",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 5d0585bf8eaf414d72d4dc4053d298091bf21b47 UNKNOWN
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run azure` re-run the last Azure build
   </details>


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [FLINK-39285][table] Ignore project rel node generated by FlinkFilterJoinRule in JoinToMultiJoinRule [flink]

Posted by "snuyanzin (via GitHub)" <gi...@apache.org>.
snuyanzin commented on code in PR #27815:
URL: https://github.com/apache/flink/pull/27815#discussion_r3568820494


##########
flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalProject.java:
##########
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.calcite.rel.logical;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.apache.calcite.plan.Convention;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelCollationTraitDef;
+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;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.sql.validate.SqlValidatorUtil;
+import org.apache.calcite.util.Util;
+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 targeted at any particular engine or
+ * calling convention.
+ */
+public final class LogicalProject extends Project {
+    // ~ Constructors -----------------------------------------------------------
+
+    /**
+     * Creates a LogicalProject.
+     *
+     * <p>Use {@link #create} unless you know what you're doing.
+     *
+     * @param cluster Cluster this relational expression belongs to
+     * @param traitSet Traits of this relational expression
+     * @param hints Hints of this relational expression
+     * @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,
+            RelTraitSet traitSet,
+            List<RelHint> hints,
+            RelNode input,
+            List<? extends RexNode> projects,
+            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, 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, ImmutableSet.of());
+        Util.discard(flags);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public LogicalProject(
+            RelOptCluster cluster,
+            RelNode input,
+            List<RexNode> projects,
+            @Nullable List<? extends @Nullable String> fieldNames,
+            int flags) {
+        this(
+                cluster,
+                cluster.traitSetOf(RelCollations.EMPTY),
+                ImmutableList.of(),
+                input,
+                projects,
+                RexUtil.createStructType(cluster.getTypeFactory(), projects, fieldNames, null),
+                ImmutableSet.of());
+        Util.discard(flags);
+    }
+
+    /** Creates a LogicalProject by parsing serialized output. */
+    public LogicalProject(RelInput input) {
+        super(input);
+    }
+
+    // ~ Methods ----------------------------------------------------------------
+
+    /**
+     * 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, variablesSet);
+    }
+
+    /**
+     * 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, variablesSet);
+    }
+
+    @Override
+    public LogicalProject copy(
+            RelTraitSet traitSet, RelNode input, List<RexNode> projects, RelDataType rowType) {
+        LogicalProject project =
+                new LogicalProject(
+                        getCluster(), traitSet, hints, input, projects, rowType, variablesSet);
+        project.setIgnoreForMultiJoin(this.canBeIgnoredForMultiJoin);
+        return project;
+    }
+
+    @Override
+    public RelNode accept(RelShuttle shuttle) {
+        return shuttle.visit(this);
+    }
+
+    @Override
+    public RelNode withHints(List<RelHint> hintList) {
+        return new LogicalProject(
+                getCluster(), traitSet, hintList, input, getProjects(), getRowType(), variablesSet);
+    }
+
+    @Override
+    public boolean deepEquals(@Nullable Object obj) {
+        return deepEquals0(obj);
+    }
+
+    @Override
+    public int deepHashCode() {
+        return deepHashCode0();
+    }
+
+    // BEGIN FLINK MODIFICATION
+
+    private boolean canBeIgnoredForMultiJoin = false;
+
+    public void setIgnoreForMultiJoin(boolean canBeIgnoredForMultiJoin) {
+        this.canBeIgnoredForMultiJoin = canBeIgnoredForMultiJoin;
+    }
+
+    public boolean canBeIgnoredForMultiJoin() {
+        return canBeIgnoredForMultiJoin;
+    }
+
+    // END FLINK MODIFICATION

Review Comment:
   why should we have it here and do not have in Calcite as a fix?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [FLINK-39285][table] Ignore project rel node generated by FlinkFilterJoinRule in JoinToMultiJoinRule [flink]

Posted by "snuyanzin (via GitHub)" <gi...@apache.org>.
snuyanzin commented on code in PR #27815:
URL: https://github.com/apache/flink/pull/27815#discussion_r3568823930


##########
flink-table/flink-table-planner/src/main/java/org/apache/calcite/tools/RelBuilder.java:
##########
@@ -0,0 +1,5500 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.calcite.tools;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedMultiset;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multiset;
+import com.google.common.collect.Sets;
+import org.apache.calcite.linq4j.Ord;
+import org.apache.calcite.linq4j.function.Experimental;
+import org.apache.calcite.plan.Context;
+import org.apache.calcite.plan.Contexts;
+import org.apache.calcite.plan.Convention;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptPredicateList;
+import org.apache.calcite.plan.RelOptSamplingParameters;
+import org.apache.calcite.plan.RelOptSchema;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.ViewExpanders;
+import org.apache.calcite.prepare.RelOptTableImpl;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.RelDistribution;
+import org.apache.calcite.rel.RelFieldCollation;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Aggregate;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.core.Correlate;
+import org.apache.calcite.rel.core.CorrelationId;
+import org.apache.calcite.rel.core.Filter;
+import org.apache.calcite.rel.core.Intersect;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.Match;
+import org.apache.calcite.rel.core.Minus;
+import org.apache.calcite.rel.core.Project;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.core.RepeatUnion;
+import org.apache.calcite.rel.core.Sample;
+import org.apache.calcite.rel.core.Snapshot;
+import org.apache.calcite.rel.core.Sort;
+import org.apache.calcite.rel.core.Spool;
+import org.apache.calcite.rel.core.TableFunctionScan;
+import org.apache.calcite.rel.core.TableScan;
+import org.apache.calcite.rel.core.TableSpool;
+import org.apache.calcite.rel.core.Uncollect;
+import org.apache.calcite.rel.core.Union;
+import org.apache.calcite.rel.core.Values;
+import org.apache.calcite.rel.hint.Hintable;
+import org.apache.calcite.rel.hint.RelHint;
+import org.apache.calcite.rel.logical.LogicalFilter;
+import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rel.metadata.RelColumnMapping;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.AggregateRemoveRule;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rel.type.RelDataTypeFieldImpl;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexCallBinding;
+import org.apache.calcite.rex.RexCorrelVariable;
+import org.apache.calcite.rex.RexDynamicParam;
+import org.apache.calcite.rex.RexExecutor;
+import org.apache.calcite.rex.RexFieldCollation;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.rex.RexSimplify;
+import org.apache.calcite.rex.RexSubQuery;
+import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.rex.RexWindowBound;
+import org.apache.calcite.rex.RexWindowBounds;
+import org.apache.calcite.runtime.Hook;
+import org.apache.calcite.runtime.ImmutablePairList;
+import org.apache.calcite.runtime.PairList;
+import org.apache.calcite.schema.TransientTable;
+import org.apache.calcite.schema.impl.ListTransientTable;
+import org.apache.calcite.sql.SqlAggFunction;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlStaticAggFunction;
+import org.apache.calcite.sql.SqlUtil;
+import org.apache.calcite.sql.SqlWindow;
+import org.apache.calcite.sql.fun.SqlCountAggFunction;
+import org.apache.calcite.sql.fun.SqlInternalOperators;
+import org.apache.calcite.sql.fun.SqlLikeOperator;
+import org.apache.calcite.sql.fun.SqlQuantifyOperator;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.SqlReturnTypeInference;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.type.TableFunctionReturnTypeInference;
+import org.apache.calcite.sql.validate.SqlValidatorUtil;
+import org.apache.calcite.sql2rel.SqlToRelConverter;
+import org.apache.calcite.util.DateString;
+import org.apache.calcite.util.Holder;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.ImmutableIntList;
+import org.apache.calcite.util.ImmutableNullableList;
+import org.apache.calcite.util.Litmus;
+import org.apache.calcite.util.NlsString;
+import org.apache.calcite.util.Optionality;
+import org.apache.calcite.util.Pair;
+import org.apache.calcite.util.Util;
+import org.apache.calcite.util.mapping.Mapping;
+import org.apache.calcite.util.mapping.Mappings;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.immutables.value.Value;
+
+import java.math.BigDecimal;
+import java.util.AbstractList;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static java.util.Objects.requireNonNull;
+import static org.apache.calcite.linq4j.Nullness.castNonNull;
+import static org.apache.calcite.rel.rules.AggregateRemoveRule.canFlattenStatic;
+import static org.apache.calcite.sql.SqlKind.UNION;
+import static org.apache.calcite.util.Static.RESOURCE;
+
+/**
+ * Builder for relational expressions.
+ *
+ * <p>{@code RelBuilder} does not make possible anything that you could not also accomplish by
+ * calling the factory methods of the particular relational expression. But it makes common tasks
+ * more straightforward and concise.
+ *
+ * <p>{@code RelBuilder} uses factories to create relational expressions. By default, it uses the
+ * default factories, which create logical relational expressions ({@link LogicalFilter}, {@link
+ * LogicalProject} and so forth). But you could override those factories so that, say, {@code
+ * filter} creates instead a {@code HiveFilter}.
+ *
+ * <p>It is not thread-safe.
+ */
+@Value.Enclosing
+public class RelBuilder {
+    protected final RelOptCluster cluster;
+    protected final @Nullable RelOptSchema relOptSchema;
+    private final Deque<Frame> stack = new ArrayDeque<>();
+    private RexSimplify simplifier;
+    private final Config config;
+    private final RelOptTable.ViewExpander viewExpander;
+    private RelFactories.Struct struct;
+
+    protected RelBuilder(
+            @Nullable Context context, RelOptCluster cluster, @Nullable RelOptSchema relOptSchema) {
+        this.cluster = cluster;
+        this.relOptSchema = relOptSchema;
+        if (context == null) {
+            context = Contexts.EMPTY_CONTEXT;
+        }
+        this.config = getConfig(context);
+        this.viewExpander = getViewExpander(cluster, context);
+        this.struct = requireNonNull(RelFactories.Struct.fromContext(context));
+        final RexExecutor executor =
+                context.maybeUnwrap(RexExecutor.class)
+                        .orElse(Util.first(cluster.getPlanner().getExecutor(), RexUtil.EXECUTOR));
+        final RelOptPredicateList predicates = RelOptPredicateList.EMPTY;
+        this.simplifier = new RexSimplify(cluster.getRexBuilder(), predicates, executor);
+    }
+
+    /**
+     * Derives the view expander {@link org.apache.calcite.plan.RelOptTable.ViewExpander} to be used
+     * for this RelBuilder.
+     *
+     * <p>The ViewExpander instance is used for expanding views in the default table scan factory
+     * {@code RelFactories.TableScanFactoryImpl}. You can also define a new table scan factory in
+     * the {@code struct} to override the whole table scan creation.
+     *
+     * <p>The default view expander does not support expanding views.
+     */
+    private static RelOptTable.ViewExpander getViewExpander(
+            RelOptCluster cluster, Context context) {
+        return context.maybeUnwrap(RelOptTable.ViewExpander.class)
+                .orElseGet(() -> ViewExpanders.simpleContext(cluster));
+    }
+
+    /**
+     * Derives the Config to be used for this RelBuilder.
+     *
+     * <p>Overrides {@link RelBuilder.Config#simplify} if {@link Hook#REL_BUILDER_SIMPLIFY} is set.
+     */
+    private static Config getConfig(Context context) {
+        final Config config = context.maybeUnwrap(Config.class).orElse(Config.DEFAULT);
+        boolean simplify = Hook.REL_BUILDER_SIMPLIFY.get(config.simplify());
+        return config.withSimplify(simplify);
+    }
+
+    /** Creates a RelBuilder. */
+    public static RelBuilder create(FrameworkConfig config) {
+        return Frameworks.withPrepare(
+                config,
+                (cluster, relOptSchema, rootSchema, statement) ->
+                        new RelBuilder(config.getContext(), cluster, relOptSchema));
+    }
+
+    /**
+     * Creates a copy of this RelBuilder, with the same state as this, applying a transform to the
+     * config.
+     */
+    public RelBuilder transform(UnaryOperator<Config> transform) {
+        final Context context = Contexts.of(struct, transform.apply(config));
+        return new RelBuilder(context, cluster, relOptSchema);
+    }
+
+    /**
+     * Performs an action on this RelBuilder.
+     *
+     * <p>For example, consider the following code:
+     *
+     * <blockquote>
+     *
+     * <pre>
+     *   RelNode filterAndRename(RelBuilder relBuilder, RelNode rel,
+     *       RexNode condition, List&lt;String&gt; fieldNames) {
+     *     relBuilder.push(rel)
+     *         .filter(condition);
+     *     if (fieldNames != null) {
+     *       relBuilder.rename(fieldNames);
+     *     }
+     *     return relBuilder
+     *         .build();</pre>
+     *
+     * </blockquote>
+     *
+     * <p>The pipeline is disrupted by the 'if'. The {@code let} method allows you to perform the
+     * flow as a single pipeline:
+     *
+     * <blockquote>
+     *
+     * <pre>
+     *   RelNode filterAndRename(RelBuilder relBuilder, RelNode rel,
+     *       RexNode condition, List&lt;String&gt; fieldNames) {
+     *     return relBuilder.push(rel)
+     *         .filter(condition)
+     *         .let(r -&gt; fieldNames == null ? r : r.rename(fieldNames))
+     *         .build();</pre>
+     *
+     * </blockquote>
+     *
+     * <p>In pipelined cases such as this one, the lambda must return this RelBuilder. But {@code
+     * let} return values of other types.
+     */
+    public <R> R let(Function<RelBuilder, R> consumer) {
+        return consumer.apply(this);
+    }
+
+    /**
+     * Converts this RelBuilder to a string. The string is the string representation of all of the
+     * RelNodes on the stack.
+     */
+    @Override
+    public String toString() {
+        return stack.stream()
+                .map(frame -> RelOptUtil.toString(frame.rel))
+                .collect(Collectors.joining(""));
+    }
+
+    /** Returns the type factory. */
+    public RelDataTypeFactory getTypeFactory() {
+        return cluster.getTypeFactory();
+    }
+
+    /**
+     * Returns new RelBuilder that adopts the convention provided. RelNode will be created with such
+     * convention if corresponding factory is provided.
+     */
+    public RelBuilder adoptConvention(Convention convention) {
+        this.struct = convention.getRelFactories();
+        return this;
+    }
+
+    /** Returns the builder for {@link RexNode} expressions. */
+    public RexBuilder getRexBuilder() {
+        return cluster.getRexBuilder();
+    }
+
+    /**
+     * Creates a {@link RelBuilderFactory}, a partially-created RelBuilder. Just add a {@link
+     * RelOptCluster} and a {@link RelOptSchema}
+     */
+    public static RelBuilderFactory proto(final Context context) {
+        return (cluster, schema) -> new RelBuilder(context, cluster, schema);
+    }
+
+    /** Creates a {@link RelBuilderFactory} that uses a given set of factories. */
+    public static RelBuilderFactory proto(Object... factories) {
+        return proto(Contexts.of(factories));
+    }
+
+    public RelOptCluster getCluster() {
+        return cluster;
+    }
+
+    public @Nullable RelOptSchema getRelOptSchema() {
+        return relOptSchema;
+    }
+
+    public RelFactories.TableScanFactory getScanFactory() {
+        return struct.scanFactory;
+    }
+
+    // Methods for manipulating the stack
+
+    /**
+     * Adds a relational expression to be the input to the next relational expression constructed.
+     *
+     * <p>This method is usual when you want to weave in relational expressions that are not
+     * supported by the builder. If, while creating such expressions, you need to use previously
+     * built expressions as inputs, call {@link #build()} to pop those inputs.
+     */
+    public RelBuilder push(RelNode node) {
+        stack.push(new Frame(node));
+        return this;
+    }
+
+    /** Adds a rel node to the top of the stack while preserving the field names and aliases. */
+    private void replaceTop(RelNode node) {
+        final Frame frame = stack.pop();
+        stack.push(new Frame(node, frame.fields));
+    }
+
+    /** Pushes a collection of relational expressions. */
+    public RelBuilder pushAll(Iterable<? extends RelNode> nodes) {
+        for (RelNode node : nodes) {
+            push(node);
+        }
+        return this;
+    }
+
+    /** Returns the size of the stack. */
+    public int size() {
+        return stack.size();
+    }
+
+    /**
+     * Returns the final relational expression.
+     *
+     * <p>Throws if the stack is empty.
+     */
+    public RelNode build() {
+        return stack.pop().rel;
+    }
+
+    /** Returns the relational expression at the top of the stack, but does not remove it. */
+    public RelNode peek() {
+        return castNonNull(peek_()).rel;
+    }
+
+    private @Nullable Frame peek_() {
+        return stack.peek();
+    }
+
+    /**
+     * Returns the relational expression {@code n} positions from the top of the stack, but does not
+     * remove it.
+     */
+    public RelNode peek(int n) {
+        return peek_(n).rel;
+    }
+
+    private Frame peek_(int n) {
+        if (n == 0) {
+            // more efficient than starting an iterator
+            return Objects.requireNonNull(stack.peek(), "stack.peek");
+        }
+        return Iterables.get(stack, n);
+    }
+
+    /**
+     * Returns the relational expression {@code n} positions from the top of the stack, but does not
+     * remove it.
+     */
+    public RelNode peek(int inputCount, int inputOrdinal) {
+        return peek_(inputCount, inputOrdinal).rel;
+    }
+
+    private Frame peek_(int inputCount, int inputOrdinal) {
+        return peek_(inputCount - 1 - inputOrdinal);
+    }
+
+    /**
+     * Returns the number of fields in all inputs before (to the left of) the given input.
+     *
+     * @param inputCount Number of inputs
+     * @param inputOrdinal Input ordinal
+     */
+    private int inputOffset(int inputCount, int inputOrdinal) {
+        int offset = 0;
+        for (int i = 0; i < inputOrdinal; i++) {
+            offset += peek(inputCount, i).getRowType().getFieldCount();
+        }
+        return offset;
+    }
+
+    /** Evaluates an expression with a relational expression temporarily on the stack. */
+    public <E> E with(RelNode r, Function<RelBuilder, E> fn) {
+        try {
+            push(r);
+            return fn.apply(this);
+        } finally {
+            stack.pop();
+        }
+    }
+
+    /** Performs an action with a temporary simplifier. */
+    public <E> E withSimplifier(
+            BiFunction<RelBuilder, RexSimplify, RexSimplify> simplifierTransform,
+            Function<RelBuilder, E> fn) {
+        final RexSimplify previousSimplifier = this.simplifier;
+        try {
+            this.simplifier = simplifierTransform.apply(this, previousSimplifier);
+            return fn.apply(this);
+        } finally {
+            this.simplifier = previousSimplifier;
+        }
+    }
+
+    /** Performs an action using predicates of the {@link #peek() current node} to simplify. */
+    public <E> E withPredicates(RelMetadataQuery mq, Function<RelBuilder, E> fn) {
+        final RelOptPredicateList predicates = mq.getPulledUpPredicates(peek());
+        return withSimplifier((r, s) -> s.withPredicates(predicates), fn);
+    }
+
+    // Methods that return scalar expressions
+
+    /** Creates a literal (constant expression). */
+    public RexLiteral literal(@Nullable Object value) {
+        final RexBuilder rexBuilder = cluster.getRexBuilder();
+        if (value == null) {
+            final RelDataType type = getTypeFactory().createSqlType(SqlTypeName.NULL);
+            return rexBuilder.makeNullLiteral(type);
+        } else if (value instanceof Boolean) {
+            return rexBuilder.makeLiteral((Boolean) value);
+        } else if (value instanceof BigDecimal) {
+            return rexBuilder.makeExactLiteral((BigDecimal) value);
+        } else if (value instanceof Float || value instanceof Double) {
+            return rexBuilder.makeApproxLiteral(BigDecimal.valueOf(((Number) value).doubleValue()));
+        } else if (value instanceof Number) {
+            return rexBuilder.makeExactLiteral(BigDecimal.valueOf(((Number) value).longValue()));
+        } else if (value instanceof String) {
+            return rexBuilder.makeLiteral((String) value);
+        } else if (value instanceof Enum) {
+            return rexBuilder.makeLiteral(
+                    value, getTypeFactory().createSqlType(SqlTypeName.SYMBOL));
+        } else if (value instanceof DateString) {
+            return rexBuilder.makeDateLiteral((DateString) value);
+        } else {
+            throw new IllegalArgumentException(
+                    "cannot convert " + value + " (" + value.getClass() + ") to a constant");
+        }
+    }
+
+    @Deprecated // to be removed before 2.0
+    public RelBuilder variable(Holder<RexCorrelVariable> v) {
+        return variable(v::set);
+    }
+
+    /**
+     * Creates a correlation variable for the current input, and writes it into a Consumer.
+     *
+     * <p>Often the Consumer will write to a {@link Holder}, as follows:
+     *
+     * <blockquote>
+     *
+     * {@code RelBuilder builder; builder.scan("EMP") .variable(v::set)
+     * .filter(builder.equals(builder.field(0), v.get())) }
+     *
+     * </blockquote>
+     */
+    public RelBuilder variable(Consumer<RexCorrelVariable> consumer) {
+        consumer.accept(
+                (RexCorrelVariable)
+                        getRexBuilder().makeCorrel(peek().getRowType(), cluster.createCorrel()));
+        return this;
+    }
+
+    /**
+     * Creates a reference to a field by name.
+     *
+     * <p>Equivalent to {@code field(1, 0, fieldName)}.
+     *
+     * @param fieldName Field name
+     */
+    public RexInputRef field(String fieldName) {
+        return field(1, 0, fieldName);
+    }
+
+    /**
+     * Creates a reference to a field of given input relational expression by name.
+     *
+     * @param inputCount Number of inputs
+     * @param inputOrdinal Input ordinal
+     * @param fieldName Field name
+     */
+    public RexInputRef field(int inputCount, int inputOrdinal, String fieldName) {
+        final Frame frame = peek_(inputCount, inputOrdinal);
+        final List<String> fieldNames = Pair.left(frame.fields());
+        int i = fieldNames.indexOf(fieldName);
+        if (i >= 0) {
+            return field(inputCount, inputOrdinal, i);
+        } else {
+            throw new IllegalArgumentException(
+                    "field [" + fieldName + "] not found; input fields are: " + fieldNames);
+        }
+    }
+
+    /**
+     * Creates a reference to an input field by ordinal.
+     *
+     * <p>Equivalent to {@code field(1, 0, ordinal)}.
+     *
+     * @param fieldOrdinal Field ordinal
+     */
+    public RexInputRef field(int fieldOrdinal) {
+        return (RexInputRef) field(1, 0, fieldOrdinal, false);
+    }
+
+    /**
+     * Creates a reference to a field of a given input relational expression by ordinal.
+     *
+     * @param inputCount Number of inputs
+     * @param inputOrdinal Input ordinal
+     * @param fieldOrdinal Field ordinal within input
+     */
+    public RexInputRef field(int inputCount, int inputOrdinal, int fieldOrdinal) {
+        return (RexInputRef) field(inputCount, inputOrdinal, fieldOrdinal, false);
+    }
+
+    /**
+     * As {@link #field(int, int, int)}, but if {@code alias} is true, the method may apply an alias
+     * to make sure that the field has the same name as in the input frame. If no alias is applied
+     * the expression is definitely a {@link RexInputRef}.
+     */
+    private RexNode field(int inputCount, int inputOrdinal, int fieldOrdinal, boolean alias) {
+        final Frame frame = peek_(inputCount, inputOrdinal);
+        final RelNode input = frame.rel;
+        final RelDataType rowType = input.getRowType();
+        if (fieldOrdinal < 0 || fieldOrdinal > rowType.getFieldCount()) {
+            throw new IllegalArgumentException(
+                    "field ordinal ["
+                            + fieldOrdinal
+                            + "] out of range; input fields are: "
+                            + rowType.getFieldNames());
+        }
+        final RelDataTypeField field = rowType.getFieldList().get(fieldOrdinal);
+        final int offset = inputOffset(inputCount, inputOrdinal);
+        final RexInputRef ref =
+                cluster.getRexBuilder().makeInputRef(field.getType(), offset + fieldOrdinal);
+        final RelDataTypeField aliasField = frame.fields().get(fieldOrdinal);
+        if (!alias || field.getName().equals(aliasField.getName())) {
+            return ref;
+        } else {
+            return alias(ref, aliasField.getName());
+        }
+    }
+
+    /**
+     * Creates a reference to a field of the current record which originated in a relation with a
+     * given alias.
+     */
+    public RexNode field(String alias, String fieldName) {
+        return field(1, alias, fieldName);
+    }
+
+    /**
+     * Creates a reference to a field which originated in a relation with the given alias. Searches
+     * for the relation starting at the top of the stack.
+     */
+    public RexNode field(int inputCount, String alias, String fieldName) {
+        requireNonNull(alias, "alias");
+        requireNonNull(fieldName, "fieldName");
+        for (int inputOrdinal = 0; inputOrdinal < inputCount; ++inputOrdinal) {
+            final Frame frame = peek_(inputOrdinal);
+            final List<ImmutableSet<String>> aliasSets = frame.fields.leftList();
+            final List<RelDataTypeField> fields = frame.fields.rightList();
+            for (int i = 0, n = frame.fields.size(); i < n; i++) {
+                // If alias and field name match, reference that field.
+                if (aliasSets.get(i).contains(alias) && fields.get(i).getName().equals(fieldName)) {
+                    return field(inputCount, inputCount - 1 - inputOrdinal, i);
+                }
+            }
+        }
+
+        // Not found. Build a message.
+        StringBuilder b =
+                new StringBuilder()
+                        .append("{alias=")
+                        .append(alias)
+                        .append(",fieldName=")
+                        .append(fieldName)
+                        .append("} field not found; fields are: ");
+        for (int inputOrdinal = 0; inputOrdinal < inputCount; ++inputOrdinal) {
+            final Frame frame = peek_(inputOrdinal);
+            frame.fields.forEach(
+                    (aliasSet, field) ->
+                            b.append(
+                                    String.format(
+                                            Locale.ROOT,
+                                            "{aliases=%s,fieldName=%s}",
+                                            aliasSet,
+                                            field.getName())));
+        }
+        throw new IllegalArgumentException(b.toString());
+    }
+
+    /** Returns a reference to a given field of a record-valued expression. */
+    public RexNode field(RexNode e, String name) {
+        return getRexBuilder().makeFieldAccess(e, name, false);
+    }
+
+    /** Returns references to the fields of the top input. */
+    public ImmutableList<RexNode> fields() {
+        return fields(1, 0);
+    }
+
+    /** Returns references to the fields of a given input. */
+    public ImmutableList<RexNode> fields(int inputCount, int inputOrdinal) {
+        final RelNode input = peek(inputCount, inputOrdinal);
+        final RelDataType rowType = input.getRowType();
+        final ImmutableList.Builder<RexNode> nodes = ImmutableList.builder();
+        for (int fieldOrdinal : Util.range(rowType.getFieldCount())) {
+            nodes.add(field(inputCount, inputOrdinal, fieldOrdinal));
+        }
+        return nodes.build();
+    }
+
+    /** Returns references to fields for a given collation. */
+    public ImmutableList<RexNode> fields(RelCollation collation) {
+        final ImmutableList.Builder<RexNode> nodes = ImmutableList.builder();
+        for (RelFieldCollation fieldCollation : collation.getFieldCollations()) {
+            RexNode node = field(fieldCollation.getFieldIndex());
+            switch (fieldCollation.direction) {
+                case DESCENDING:
+                    node = desc(node);
+                    break;
+                default:
+                    break;
+            }
+            switch (fieldCollation.nullDirection) {
+                case FIRST:
+                    node = nullsFirst(node);
+                    break;
+                case LAST:
+                    node = nullsLast(node);
+                    break;
+                default:
+                    break;
+            }
+            nodes.add(node);
+        }
+        return nodes.build();
+    }
+
+    /** Returns references to fields for a given list of input ordinals. */
+    public ImmutableList<RexNode> fields(List<? extends Number> ordinals) {
+        final ImmutableList.Builder<RexNode> nodes = ImmutableList.builder();
+        for (Number ordinal : ordinals) {
+            RexNode node = field(1, 0, ordinal.intValue(), false);
+            nodes.add(node);
+        }
+        return nodes.build();
+    }
+
+    /** Returns references to fields for a given bit set of input ordinals. */
+    public ImmutableList<RexNode> fields(ImmutableBitSet ordinals) {
+        return fields(ordinals.asList());
+    }
+
+    /** Returns references to fields identified by name. */
+    public ImmutableList<RexNode> fields(Iterable<String> fieldNames) {
+        final ImmutableList.Builder<RexNode> builder = ImmutableList.builder();
+        for (String fieldName : fieldNames) {
+            builder.add(field(fieldName));
+        }
+        return builder.build();
+    }
+
+    /** Returns references to fields identified by a mapping. */
+    public ImmutableList<RexNode> fields(Mappings.TargetMapping mapping) {
+        return fields(Mappings.asListNonNull(mapping));
+    }
+
+    /** Creates an access to a field by name. */
+    public RexNode dot(RexNode node, String fieldName) {
+        final RexBuilder builder = cluster.getRexBuilder();
+        return builder.makeFieldAccess(node, fieldName, true);
+    }
+
+    /** Creates an access to a field by ordinal. */
+    public RexNode dot(RexNode node, int fieldOrdinal) {
+        final RexBuilder builder = cluster.getRexBuilder();
+        return builder.makeFieldAccess(node, fieldOrdinal);
+    }
+
+    /** Creates a call to a scalar operator. */
+    public RexNode call(SqlOperator operator, RexNode... operands) {
+        return call(operator, ImmutableList.copyOf(operands));
+    }
+
+    /** Creates a call to a scalar operator. */
+    private RexCall call(SqlOperator operator, List<RexNode> operandList) {
+        switch (operator.getKind()) {
+            case LIKE:
+            case SIMILAR:
+                final SqlLikeOperator likeOperator = (SqlLikeOperator) operator;
+                if (likeOperator.isNegated()) {
+                    final SqlOperator notLikeOperator = likeOperator.not();
+                    return (RexCall) not(call(notLikeOperator, operandList));
+                }
+                break;
+            case BETWEEN:
+                assert operandList.size() == 3;
+                return (RexCall)
+                        between(operandList.get(0), operandList.get(1), operandList.get(2));
+            default:
+                break;
+        }
+        final RexBuilder builder = cluster.getRexBuilder();
+        final RelDataType type = builder.deriveReturnType(operator, operandList);
+        return (RexCall) builder.makeCall(type, operator, operandList);
+    }
+
+    /** Creates a call to a scalar operator. */
+    public RexNode call(SqlOperator operator, Iterable<? extends RexNode> operands) {
+        return call(operator, ImmutableList.copyOf(operands));
+    }
+
+    /**
+     * Creates an IN predicate with a list of values.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Emp")
+     *     .filter(b.in(b.field("deptno"), b.literal(10), b.literal(20)))
+     * }</pre>
+     *
+     * is equivalent to SQL
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Emp
+     * WHERE deptno IN (10, 20)
+     * }</pre>
+     */
+    public RexNode in(RexNode arg, RexNode... ranges) {
+        return in(arg, ImmutableList.copyOf(ranges));
+    }
+
+    /**
+     * Creates an IN predicate with a list of values.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Emps")
+     *     .filter(
+     *         b.in(b.field("deptno"),
+     *             Arrays.asList(b.literal(10), b.literal(20))))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Emps
+     * WHERE deptno IN (10, 20)
+     * }</pre>
+     */
+    public RexNode in(RexNode arg, Iterable<? extends RexNode> ranges) {
+        return getRexBuilder().makeIn(arg, ImmutableList.copyOf(ranges));
+    }
+
+    /** Creates an IN predicate with a sub-query. */
+    @Experimental
+    public RexSubQuery in(RelNode rel, Iterable<? extends RexNode> nodes) {
+        return RexSubQuery.in(rel, ImmutableList.copyOf(nodes));
+    }
+
+    /**
+     * Creates an IN predicate with a sub-query.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Emps")
+     *     .filter(
+     *         b.in(b.field("deptno"),
+     *             b2 -> b2.scan("Depts")
+     *                 .filter(
+     *                     b2.eq(b2.field("location"), b2.literal("Boston")))
+     *                 .project(b.field("deptno"))
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Emps
+     * WHERE deptno IN (SELECT deptno FROM Dept WHERE location = 'Boston')
+     * }</pre>
+     */
+    @Experimental
+    public RexNode in(RexNode arg, Function<RelBuilder, RelNode> f) {
+        final RelNode rel = f.apply(this);
+        return RexSubQuery.in(rel, ImmutableList.of(arg));
+    }
+
+    /**
+     * Creates a SOME (or ANY) predicate.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Emps")
+     *     .filter(
+     *         b.some(b.field("commission"),
+     *             SqlStdOperatorTable.GREATER_THAN,
+     *             b2 -> b2.scan("Emps")
+     *                 .filter(
+     *                     b2.eq(b2.field("job"), b2.literal("Manager")))
+     *                 .project(b2.field("sal"))
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Emps
+     * WHERE commission > SOME (SELECT sal FROM Emps WHERE job = 'Manager')
+     * }</pre>
+     *
+     * <p>or (since {@code SOME} and {@code ANY} are synonyms) the SQL
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Emps
+     * WHERE commission > ANY (SELECT sal FROM Emps WHERE job = 'Manager')
+     * }</pre>
+     */
+    @Experimental
+    public RexSubQuery some(RexNode node, SqlOperator op, Function<RelBuilder, RelNode> f) {
+        return some_(node, op.kind, f);
+    }
+
+    private RexSubQuery some_(RexNode node, SqlKind kind, Function<RelBuilder, RelNode> f) {
+        final RelNode rel = f.apply(this);
+        final SqlQuantifyOperator quantifyOperator = SqlStdOperatorTable.some(kind);
+        return RexSubQuery.some(rel, ImmutableList.of(node), quantifyOperator);
+    }
+
+    /**
+     * Creates an ALL predicate.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Emps")
+     *     .filter(
+     *         b.all(b.field("commission"),
+     *             SqlStdOperatorTable.GREATER_THAN,
+     *             b2 -> b2.scan("Emps")
+     *                 .filter(
+     *                     b2.eq(b2.field("job"), b2.literal("Manager")))
+     *                 .project(b2.field("sal"))
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Emps
+     * WHERE commission > ALL (SELECT sal FROM Emps WHERE job = 'Manager')
+     * }</pre>
+     *
+     * <p>Calcite translates {@code ALL} predicates to {@code NOT SOME}. The following SQL is
+     * equivalent to the previous:
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Emps
+     * WHERE NOT (commission <= SOME (SELECT sal FROM Emps WHERE job = 'Manager'))
+     * }</pre>
+     */
+    @Experimental
+    public RexNode all(RexNode node, SqlOperator op, Function<RelBuilder, RelNode> f) {
+        return not(some_(node, op.kind.negateNullSafe(), f));
+    }
+
+    /**
+     * Creates an EXISTS predicate.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Depts")
+     *     .filter(
+     *         b.exists(b2 ->
+     *             b2.scan("Emps")
+     *                 .filter(
+     *                     b2.eq(b2.field("job"), b2.literal("Manager")))
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Depts
+     * WHERE EXISTS (SELECT 1 FROM Emps WHERE job = 'Manager')
+     * }</pre>
+     */
+    @Experimental
+    public RexSubQuery exists(Function<RelBuilder, RelNode> f) {
+        final RelNode rel = f.apply(this);
+        return RexSubQuery.exists(rel);
+    }
+
+    /**
+     * Creates a UNIQUE predicate.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Depts")
+     *     .filter(
+     *         b.exists(b2 ->
+     *             b2.scan("Emps")
+     *                 .filter(
+     *                     b2.eq(b2.field("job"), b2.literal("Manager")))
+     *                 .project(b2.field("deptno")
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Depts
+     * WHERE UNIQUE (SELECT deptno FROM Emps WHERE job = 'Manager')
+     * }</pre>
+     */
+    @Experimental
+    public RexSubQuery unique(Function<RelBuilder, RelNode> f) {
+        final RelNode rel = f.apply(this);
+        return RexSubQuery.unique(rel);
+    }
+
+    /**
+     * Creates a scalar sub-query.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Depts")
+     *     .project(
+     *         b.field("deptno")
+     *         b.scalarQuery(b2 ->
+     *             b2.scan("Emps")
+     *                 .aggregate(
+     *                     b2.eq(b2.field("job"), b2.literal("Manager")))
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT deptno, (SELECT MAX(sal) FROM Emps)
+     * FROM Depts
+     * }</pre>
+     */
+    @Experimental
+    public RexSubQuery scalarQuery(Function<RelBuilder, RelNode> f) {
+        return RexSubQuery.scalar(f.apply(this));
+    }
+
+    /**
+     * Creates an ARRAY sub-query.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Depts")
+     *     .project(
+     *         b.field("deptno")
+     *         b.arrayQuery(b2 ->
+     *             b2.scan("Emps")
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT deptno, ARRAY (SELECT * FROM Emps)
+     * FROM Depts
+     * }</pre>
+     */
+    @Experimental
+    public RexSubQuery arrayQuery(Function<RelBuilder, RelNode> f) {
+        return RexSubQuery.array(f.apply(this));
+    }
+
+    /**
+     * Creates a MULTISET sub-query.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Depts")
+     *     .project(
+     *         b.field("deptno")
+     *         b.multisetQuery(b2 ->
+     *             b2.scan("Emps")
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT deptno, MULTISET (SELECT * FROM Emps)
+     * FROM Depts
+     * }</pre>
+     */
+    @Experimental
+    public RexSubQuery multisetQuery(Function<RelBuilder, RelNode> f) {
+        return RexSubQuery.multiset(f.apply(this));
+    }
+
+    /**
+     * Creates a MAP sub-query.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Depts")
+     *     .project(
+     *         b.field("deptno")
+     *         b.multisetQuery(b2 ->
+     *             b2.scan("Emps")
+     *                 .project(b2.field("empno"), b2.field("job"))
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT deptno, MAP (SELECT empno, job FROM Emps)
+     * FROM Depts
+     * }</pre>
+     */
+    @Experimental
+    public RexSubQuery mapQuery(Function<RelBuilder, RelNode> f) {
+        return RexSubQuery.map(f.apply(this));
+    }
+
+    /** Creates an AND. */
+    public RexNode and(RexNode... operands) {
+        return and(ImmutableList.copyOf(operands));
+    }
+
+    /**
+     * Creates an AND.
+     *
+     * <p>Simplifies the expression a little: {@code e AND TRUE} becomes {@code e}; {@code e AND e2
+     * AND NOT e} becomes {@code e2}.
+     */
+    public RexNode and(Iterable<? extends RexNode> operands) {
+        return RexUtil.composeConjunction(getRexBuilder(), operands);
+    }
+
+    /** Creates an OR. */
+    public RexNode or(RexNode... operands) {
+        return or(ImmutableList.copyOf(operands));
+    }
+
+    /** Creates an OR. */
+    public RexNode or(Iterable<? extends RexNode> operands) {
+        return RexUtil.composeDisjunction(cluster.getRexBuilder(), operands);
+    }
+
+    /** Creates a NOT. */
+    public RexNode not(RexNode operand) {
+        return call(SqlStdOperatorTable.NOT, operand);
+    }
+
+    /** Creates an {@code =}. */
+    public RexNode equals(RexNode operand0, RexNode operand1) {
+        return call(SqlStdOperatorTable.EQUALS, operand0, operand1);
+    }
+
+    /** Creates a {@code >}. */
+    public RexNode greaterThan(RexNode operand0, RexNode operand1) {
+        return call(SqlStdOperatorTable.GREATER_THAN, operand0, operand1);
+    }
+
+    /** Creates a {@code >=}. */
+    public RexNode greaterThanOrEqual(RexNode operand0, RexNode operand1) {
+        return call(SqlStdOperatorTable.GREATER_THAN_OR_EQUAL, operand0, operand1);
+    }
+
+    /** Creates a {@code <}. */
+    public RexNode lessThan(RexNode operand0, RexNode operand1) {
+        return call(SqlStdOperatorTable.LESS_THAN, operand0, operand1);
+    }
+
+    /** Creates a {@code <=}. */
+    public RexNode lessThanOrEqual(RexNode operand0, RexNode operand1) {
+        return call(SqlStdOperatorTable.LESS_THAN_OR_EQUAL, operand0, operand1);
+    }
+
+    /** Creates a {@code <>}. */
+    public RexNode notEquals(RexNode operand0, RexNode operand1) {
+        return call(SqlStdOperatorTable.NOT_EQUALS, operand0, operand1);
+    }
+
+    /**
+     * Creates an expression equivalent to "{@code o0 IS NOT DISTINCT FROM o1}". It is also
+     * equivalent to "{@code o0 = o1 OR (o0 IS NULL AND o1 IS NULL)}".
+     */
+    public RexNode isNotDistinctFrom(RexNode operand0, RexNode operand1) {
+        return RelOptUtil.isDistinctFrom(getRexBuilder(), operand0, operand1, true);
+    }
+
+    /**
+     * Creates an expression equivalent to {@code o0 IS DISTINCT FROM o1}. It is also equivalent to
+     * "{@code NOT (o0 = o1 OR (o0 IS NULL AND o1 IS NULL))}.
+     */
+    public RexNode isDistinctFrom(RexNode operand0, RexNode operand1) {
+        return RelOptUtil.isDistinctFrom(getRexBuilder(), operand0, operand1, false);
+    }
+
+    /** Creates a {@code BETWEEN}. */
+    public RexNode between(RexNode arg, RexNode lower, RexNode upper) {
+        return getRexBuilder().makeBetween(arg, lower, upper);
+    }
+
+    /** Creates ab {@code IS NULL}. */
+    public RexNode isNull(RexNode operand) {
+        return call(SqlStdOperatorTable.IS_NULL, operand);
+    }
+
+    /** Creates an {@code IS NOT NULL}. */
+    public RexNode isNotNull(RexNode operand) {
+        return call(SqlStdOperatorTable.IS_NOT_NULL, operand);
+    }
+
+    /** Creates an expression that casts an expression to a given type. */
+    public RexNode cast(RexNode expr, SqlTypeName typeName) {
+        final RelDataType type = cluster.getTypeFactory().createSqlType(typeName);
+        return cluster.getRexBuilder().makeCast(type, expr);
+    }
+
+    /**
+     * Creates an expression that casts an expression to a type with a given name and precision or
+     * length.
+     */
+    public RexNode cast(RexNode expr, SqlTypeName typeName, int precision) {
+        final RelDataType type = cluster.getTypeFactory().createSqlType(typeName, precision);
+        return cluster.getRexBuilder().makeCast(type, expr);
+    }
+
+    /**
+     * Creates an expression that casts an expression to a type with a given name, precision and
+     * scale.
+     */
+    public RexNode cast(RexNode expr, SqlTypeName typeName, int precision, int scale) {
+        final RelDataType type = cluster.getTypeFactory().createSqlType(typeName, precision, scale);
+        return cluster.getRexBuilder().makeCast(type, expr);
+    }
+
+    /**
+     * Returns an expression wrapped in an alias.
+     *
+     * <p>This method is idempotent: If the expression is already wrapped in the correct alias, does
+     * nothing; if wrapped in an incorrect alias, removes the incorrect alias and applies the
+     * correct alias.
+     *
+     * @see #project
+     */
+    public RexNode alias(RexNode expr, String alias) {
+        final RexNode aliasLiteral = literal(alias);
+        switch (expr.getKind()) {
+            case AS:
+                final RexCall call = (RexCall) expr;
+                if (call.operands.get(1).equals(aliasLiteral)) {
+                    // current alias is correct
+                    return expr;
+                }
+                expr = call.operands.get(0);
+            // strip current (incorrect) alias, and fall through
+            default:
+                return call(SqlStdOperatorTable.AS, expr, aliasLiteral);
+        }
+    }
+
+    private RexNode aliasMaybe(RexNode node, @Nullable String name) {
+        return name == null ? node : alias(node, name);
+    }
+
+    /** Converts a sort expression to descending. */
+    public RexNode desc(RexNode node) {
+        return call(SqlStdOperatorTable.DESC, node);
+    }
+
+    /** Converts a sort expression to nulls last. */
+    public RexNode nullsLast(RexNode node) {
+        return call(SqlStdOperatorTable.NULLS_LAST, node);
+    }
+
+    /** Converts a sort expression to nulls first. */
+    public RexNode nullsFirst(RexNode node) {
+        return call(SqlStdOperatorTable.NULLS_FIRST, node);
+    }
+
+    // Methods that create window bounds
+
+    /**
+     * Creates an {@code UNBOUNDED PRECEDING} window bound, for use in methods such as {@link
+     * OverCall#rowsFrom(RexWindowBound)} and {@link OverCall#rangeBetween(RexWindowBound,
+     * RexWindowBound)}.
+     */
+    public RexWindowBound unboundedPreceding() {
+        return RexWindowBounds.UNBOUNDED_PRECEDING;
+    }
+
+    /**
+     * Creates a {@code bound PRECEDING} window bound, for use in methods such as {@link
+     * OverCall#rowsFrom(RexWindowBound)} and {@link OverCall#rangeBetween(RexWindowBound,
+     * RexWindowBound)}.
+     */
+    public RexWindowBound preceding(RexNode bound) {
+        return RexWindowBounds.preceding(bound);
+    }
+
+    /**
+     * Creates a {@code CURRENT ROW} window bound, for use in methods such as {@link
+     * OverCall#rowsFrom(RexWindowBound)} and {@link OverCall#rangeBetween(RexWindowBound,
+     * RexWindowBound)}.
+     */
+    public RexWindowBound currentRow() {
+        return RexWindowBounds.CURRENT_ROW;
+    }
+
+    /**
+     * Creates a {@code bound FOLLOWING} window bound, for use in methods such as {@link
+     * OverCall#rowsFrom(RexWindowBound)} and {@link OverCall#rangeBetween(RexWindowBound,
+     * RexWindowBound)}.
+     */
+    public RexWindowBound following(RexNode bound) {
+        return RexWindowBounds.following(bound);
+    }
+
+    /**
+     * Creates an {@code UNBOUNDED FOLLOWING} window bound, for use in methods such as {@link
+     * OverCall#rowsFrom(RexWindowBound)} and {@link OverCall#rangeBetween(RexWindowBound,
+     * RexWindowBound)}.
+     */
+    public RexWindowBound unboundedFollowing() {
+        return RexWindowBounds.UNBOUNDED_FOLLOWING;
+    }
+
+    // Methods that create group keys and aggregate calls
+
+    /** Creates an empty group key. */
+    public GroupKey groupKey() {
+        return groupKey(ImmutableList.of());
+    }
+
+    /** Creates a group key. */
+    public GroupKey groupKey(RexNode... nodes) {
+        return groupKey(ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a group key. */
+    public GroupKey groupKey(Iterable<? extends RexNode> nodes) {
+        return new GroupKeyImpl(ImmutableList.copyOf(nodes), null, null);
+    }
+
+    /** Creates a group key with grouping sets. */
+    public GroupKey groupKey(
+            Iterable<? extends RexNode> nodes,
+            Iterable<? extends Iterable<? extends RexNode>> nodeLists) {
+        return groupKey_(nodes, nodeLists);
+    }
+
+    // CHECKSTYLE: IGNORE 1
+    /**
+     * @deprecated Now that indicator is deprecated, use {@link #groupKey(Iterable, Iterable)},
+     *     which has the same behavior as calling this method with {@code indicator = false}.
+     */
+    @Deprecated // to be removed before 2.0
+    public GroupKey groupKey(
+            Iterable<? extends RexNode> nodes,
+            boolean indicator,
+            Iterable<? extends Iterable<? extends RexNode>> nodeLists) {
+        Aggregate.checkIndicator(indicator);
+        return groupKey_(nodes, nodeLists);
+    }
+
+    private static GroupKey groupKey_(
+            Iterable<? extends RexNode> nodes,
+            Iterable<? extends Iterable<? extends RexNode>> nodeLists) {
+        final ImmutableList.Builder<ImmutableList<RexNode>> builder = ImmutableList.builder();
+        for (Iterable<? extends RexNode> nodeList : nodeLists) {
+            builder.add(ImmutableList.copyOf(nodeList));
+        }
+        return new GroupKeyImpl(ImmutableList.copyOf(nodes), builder.build(), null);
+    }
+
+    /** Creates a group key of fields identified by ordinal. */
+    public GroupKey groupKey(int... fieldOrdinals) {
+        return groupKey(fields(ImmutableIntList.of(fieldOrdinals)));
+    }
+
+    /** Creates a group key of fields identified by name. */
+    public GroupKey groupKey(String... fieldNames) {
+        return groupKey(fields(ImmutableList.copyOf(fieldNames)));
+    }
+
+    /**
+     * Creates a group key, identified by field positions in the underlying relational expression.
+     *
+     * <p>This method of creating a group key does not allow you to group on new expressions, only
+     * column projections, but is efficient, especially when you are coming from an existing {@link
+     * Aggregate}.
+     */
+    public GroupKey groupKey(ImmutableBitSet groupSet) {
+        return groupKey_(groupSet, ImmutableList.of(groupSet));
+    }
+
+    /**
+     * Creates a group key with grouping sets, both identified by field positions in the underlying
+     * relational expression.
+     *
+     * <p>This method of creating a group key does not allow you to group on new expressions, only
+     * column projections, but is efficient, especially when you are coming from an existing {@link
+     * Aggregate}.
+     *
+     * <p>It is possible for {@code groupSet} to be strict superset of all {@code groupSets}. For
+     * example, in the pseudo SQL
+     *
+     * <pre>{@code
+     * GROUP BY 0, 1, 2
+     * GROUPING SETS ((0, 1), 0)
+     * }</pre>
+     *
+     * <p>column 2 does not appear in either grouping set. This is not valid SQL. We can approximate
+     * in actual SQL by adding an extra grouping set and filtering out using {@code HAVING}, as
+     * follows:
+     *
+     * <pre>{@code
+     * GROUP BY GROUPING SETS ((0, 1, 2), (0, 1), 0)
+     * HAVING GROUPING_ID(0, 1, 2) <> 0
+     * }</pre>
+     */
+    public GroupKey groupKey(
+            ImmutableBitSet groupSet, Iterable<? extends ImmutableBitSet> groupSets) {
+        return groupKey_(groupSet, ImmutableList.copyOf(groupSets));
+    }
+
+    // CHECKSTYLE: IGNORE 1
+    /**
+     * @deprecated Use {@link #groupKey(ImmutableBitSet, Iterable)}.
+     */
+    @Deprecated // to be removed before 2.0
+    public GroupKey groupKey(
+            ImmutableBitSet groupSet,
+            boolean indicator,
+            @Nullable ImmutableList<ImmutableBitSet> groupSets) {
+        Aggregate.checkIndicator(indicator);
+        return groupKey_(
+                groupSet,
+                groupSets == null ? ImmutableList.of(groupSet) : ImmutableList.copyOf(groupSets));
+    }
+
+    private GroupKey groupKey_(ImmutableBitSet groupSet, ImmutableList<ImmutableBitSet> groupSets) {
+        if (groupSet.length() > peek().getRowType().getFieldCount()) {
+            throw new IllegalArgumentException("out of bounds: " + groupSet);
+        }
+        requireNonNull(groupSets, "groupSets");
+        final ImmutableList<RexNode> nodes = fields(groupSet);
+        return groupKey_(nodes, Util.transform(groupSets, this::fields));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public AggCall aggregateCall(
+            SqlAggFunction aggFunction,
+            boolean distinct,
+            RexNode filter,
+            @Nullable String alias,
+            RexNode... operands) {
+        return aggregateCall(
+                aggFunction,
+                distinct,
+                false,
+                false,
+                filter,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.copyOf(operands));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public AggCall aggregateCall(
+            SqlAggFunction aggFunction,
+            boolean distinct,
+            boolean approximate,
+            RexNode filter,
+            @Nullable String alias,
+            RexNode... operands) {
+        return aggregateCall(
+                aggFunction,
+                distinct,
+                approximate,
+                false,
+                filter,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.copyOf(operands));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public AggCall aggregateCall(
+            SqlAggFunction aggFunction,
+            boolean distinct,
+            RexNode filter,
+            @Nullable String alias,
+            Iterable<? extends RexNode> operands) {
+        return aggregateCall(
+                aggFunction,
+                distinct,
+                false,
+                false,
+                filter,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.copyOf(operands));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public AggCall aggregateCall(
+            SqlAggFunction aggFunction,
+            boolean distinct,
+            boolean approximate,
+            RexNode filter,
+            @Nullable String alias,
+            Iterable<? extends RexNode> operands) {
+        return aggregateCall(
+                aggFunction,
+                distinct,
+                approximate,
+                false,
+                filter,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.copyOf(operands));
+    }
+
+    /**
+     * Creates a call to an aggregate function.
+     *
+     * <p>To add other operands, apply {@link AggCall#distinct()}, {@link
+     * AggCall#approximate(boolean)}, {@link AggCall#filter(RexNode...)}, {@link AggCall#sort},
+     * {@link AggCall#as} to the result.
+     */
+    public AggCall aggregateCall(SqlAggFunction aggFunction, Iterable<? extends RexNode> operands) {
+        return aggregateCall(
+                aggFunction,
+                false,
+                false,
+                false,
+                null,
+                null,
+                ImmutableList.of(),
+                null,
+                ImmutableList.of(),
+                ImmutableList.copyOf(operands));
+    }
+
+    /**
+     * Creates a call to an aggregate function.
+     *
+     * <p>To add other operands, apply {@link AggCall#distinct()}, {@link
+     * AggCall#approximate(boolean)}, {@link AggCall#filter(RexNode...)}, {@link AggCall#sort},
+     * {@link AggCall#as} to the result.
+     */
+    public AggCall aggregateCall(SqlAggFunction aggFunction, RexNode... operands) {
+        return aggregateCall(
+                aggFunction,
+                false,
+                false,
+                false,
+                null,
+                null,
+                ImmutableList.of(),
+                null,
+                ImmutableList.of(),
+                ImmutableList.copyOf(operands));
+    }
+
+    /** Creates a call to an aggregate function as a copy of an {@link AggregateCall}. */
+    public AggCall aggregateCall(AggregateCall a) {
+        return aggregateCall(
+                a.getAggregation(),
+                a.isDistinct(),
+                a.isApproximate(),
+                a.ignoreNulls(),
+                a.filterArg < 0 ? null : field(a.filterArg),
+                a.distinctKeys == null ? null : fields(a.distinctKeys),
+                fields(a.collation),
+                a.name,
+                ImmutableList.copyOf(a.rexList),
+                fields(a.getArgList()));
+    }
+
+    /**
+     * Creates a call to an aggregate function as a copy of an {@link AggregateCall}, applying a
+     * mapping.
+     */
+    public AggCall aggregateCall(AggregateCall a, Mapping mapping) {
+        return aggregateCall(
+                a.getAggregation(),
+                a.isDistinct(),
+                a.isApproximate(),
+                a.ignoreNulls(),
+                a.filterArg < 0 ? null : field(Mappings.apply(mapping, a.filterArg)),
+                a.distinctKeys == null ? null : fields(Mappings.apply(mapping, a.distinctKeys)),
+                fields(RexUtil.apply(mapping, a.collation)),
+                a.name,
+                ImmutableList.copyOf(a.rexList),
+                fields(Mappings.apply2(mapping, a.getArgList())));
+    }
+
+    /** Creates a call to an aggregate function with all applicable operands. */
+    protected AggCall aggregateCall(
+            SqlAggFunction aggFunction,
+            boolean distinct,
+            boolean approximate,
+            boolean ignoreNulls,
+            @Nullable RexNode filter,
+            @Nullable ImmutableList<RexNode> distinctKeys,
+            ImmutableList<RexNode> orderKeys,
+            @Nullable String alias,
+            ImmutableList<RexNode> preOperands,
+            ImmutableList<RexNode> operands) {
+        return new AggCallImpl(
+                aggFunction,
+                distinct,
+                approximate,
+                ignoreNulls,
+                filter,
+                alias,
+                preOperands,
+                operands,
+                distinctKeys,
+                orderKeys);
+    }
+
+    /** Creates a call to the {@code COUNT} aggregate function. */
+    public AggCall count(RexNode... operands) {
+        return count(false, null, operands);
+    }
+
+    /** Creates a call to the {@code COUNT} aggregate function. */
+    public AggCall count(Iterable<? extends RexNode> operands) {
+        return count(false, null, operands);
+    }
+
+    /**
+     * Creates a call to the {@code COUNT} aggregate function, optionally distinct and with an
+     * alias.
+     */
+    public AggCall count(boolean distinct, @Nullable String alias, RexNode... operands) {
+        return aggregateCall(
+                SqlStdOperatorTable.COUNT,
+                distinct,
+                false,
+                false,
+                null,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.copyOf(operands));
+    }
+
+    /**
+     * Creates a call to the {@code COUNT} aggregate function, optionally distinct and with an
+     * alias.
+     */
+    public AggCall count(
+            boolean distinct, @Nullable String alias, Iterable<? extends RexNode> operands) {
+        return aggregateCall(
+                SqlStdOperatorTable.COUNT,
+                distinct,
+                false,
+                false,
+                null,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.copyOf(operands));
+    }
+
+    /** Creates a call to the {@code COUNT(*)} aggregate function. */
+    public AggCall countStar(@Nullable String alias) {
+        return count(false, alias);
+    }
+
+    /** Creates a call to the {@code SUM} aggregate function. */
+    public AggCall sum(RexNode operand) {
+        return sum(false, null, operand);
+    }
+
+    /**
+     * Creates a call to the {@code SUM} aggregate function, optionally distinct and with an alias.
+     */
+    public AggCall sum(boolean distinct, @Nullable String alias, RexNode operand) {
+        return aggregateCall(
+                SqlStdOperatorTable.SUM,
+                distinct,
+                false,
+                false,
+                null,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.of(operand));
+    }
+
+    /** Creates a call to the {@code AVG} aggregate function. */
+    public AggCall avg(RexNode operand) {
+        return avg(false, null, operand);
+    }
+
+    /**
+     * Creates a call to the {@code AVG} aggregate function, optionally distinct and with an alias.
+     */
+    public AggCall avg(boolean distinct, @Nullable String alias, RexNode operand) {
+        return aggregateCall(
+                SqlStdOperatorTable.AVG,
+                distinct,
+                false,
+                false,
+                null,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.of(operand));
+    }
+
+    /** Creates a call to the {@code MIN} aggregate function. */
+    public AggCall min(RexNode operand) {
+        return min(null, operand);
+    }
+
+    /** Creates a call to the {@code MIN} aggregate function, optionally with an alias. */
+    public AggCall min(@Nullable String alias, RexNode operand) {
+        return aggregateCall(
+                SqlStdOperatorTable.MIN,
+                false,
+                false,
+                false,
+                null,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.of(operand));
+    }
+
+    /** Creates a call to the {@code MAX} aggregate function, optionally with an alias. */
+    public AggCall max(RexNode operand) {
+        return max(null, operand);
+    }
+
+    /** Creates a call to the {@code MAX} aggregate function. */
+    public AggCall max(@Nullable String alias, RexNode operand) {
+        return aggregateCall(
+                SqlStdOperatorTable.MAX,
+                false,
+                false,
+                false,
+                null,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.of(operand));
+    }
+
+    /** Creates a call to the {@code LITERAL_AGG} aggregate function. */
+    public AggCall literalAgg(@Nullable Object value) {
+        return aggregateCall(SqlInternalOperators.LITERAL_AGG).preOperands(literal(value));
+    }
+
+    // Methods for patterns
+
+    /**
+     * Creates a reference to a given field of the pattern.
+     *
+     * @param alpha the pattern name
+     * @param type Type of field
+     * @param i Ordinal of field
+     * @return Reference to field of pattern
+     */
+    public RexNode patternField(String alpha, RelDataType type, int i) {
+        return getRexBuilder().makePatternFieldRef(alpha, type, i);
+    }
+
+    /** Creates a call that concatenates patterns; for use in {@link #match}. */
+    public RexNode patternConcat(Iterable<? extends RexNode> nodes) {
+        final ImmutableList<RexNode> list = ImmutableList.copyOf(nodes);
+        if (list.size() > 2) {
+            // Convert into binary calls
+            return patternConcat(patternConcat(Util.skipLast(list)), Util.last(list));
+        }
+        final RelDataType t = getTypeFactory().createSqlType(SqlTypeName.NULL);
+        return getRexBuilder().makeCall(t, SqlStdOperatorTable.PATTERN_CONCAT, list);
+    }
+
+    /** Creates a call that concatenates patterns; for use in {@link #match}. */
+    public RexNode patternConcat(RexNode... nodes) {
+        return patternConcat(ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a call that creates alternate patterns; for use in {@link #match}. */
+    public RexNode patternAlter(Iterable<? extends RexNode> nodes) {
+        final RelDataType t = getTypeFactory().createSqlType(SqlTypeName.NULL);
+        return getRexBuilder()
+                .makeCall(t, SqlStdOperatorTable.PATTERN_ALTER, ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a call that creates alternate patterns; for use in {@link #match}. */
+    public RexNode patternAlter(RexNode... nodes) {
+        return patternAlter(ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a call that creates quantify patterns; for use in {@link #match}. */
+    public RexNode patternQuantify(Iterable<? extends RexNode> nodes) {
+        final RelDataType t = getTypeFactory().createSqlType(SqlTypeName.NULL);
+        return getRexBuilder()
+                .makeCall(t, SqlStdOperatorTable.PATTERN_QUANTIFIER, ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a call that creates quantify patterns; for use in {@link #match}. */
+    public RexNode patternQuantify(RexNode... nodes) {
+        return patternQuantify(ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a call that creates permute patterns; for use in {@link #match}. */
+    public RexNode patternPermute(Iterable<? extends RexNode> nodes) {
+        final RelDataType t = getTypeFactory().createSqlType(SqlTypeName.NULL);
+        return getRexBuilder()
+                .makeCall(t, SqlStdOperatorTable.PATTERN_PERMUTE, ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a call that creates permute patterns; for use in {@link #match}. */
+    public RexNode patternPermute(RexNode... nodes) {
+        return patternPermute(ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a call that creates an exclude pattern; for use in {@link #match}. */
+    public RexNode patternExclude(RexNode node) {
+        final RelDataType t = getTypeFactory().createSqlType(SqlTypeName.NULL);
+        return getRexBuilder()
+                .makeCall(t, SqlStdOperatorTable.PATTERN_EXCLUDE, ImmutableList.of(node));
+    }
+
+    // Methods that create relational expressions
+
+    /**
+     * Creates a {@link TableScan} of the table with a given name.
+     *
+     * <p>Throws if the table does not exist.
+     *
+     * <p>Returns this builder.
+     *
+     * @param tableNames Name of table (can optionally be qualified)
+     */
+    public RelBuilder scan(Iterable<String> tableNames) {
+        final List<String> names = ImmutableList.copyOf(tableNames);
+        requireNonNull(relOptSchema, "relOptSchema");
+        final RelOptTable relOptTable = relOptSchema.getTableForMember(names);
+        if (relOptTable == null) {
+            throw RESOURCE.tableNotFound(String.join(".", names)).ex();
+        }
+        final RelNode scan =
+                struct.scanFactory.createScan(
+                        ViewExpanders.toRelContext(viewExpander, cluster), relOptTable);
+        push(scan);
+        rename(relOptTable.getRowType().getFieldNames());
+
+        // When the node is not a TableScan but from expansion,
+        // we need to explicitly add the alias.
+        if (!(scan instanceof TableScan)) {
+            as(Util.last(ImmutableList.copyOf(tableNames)));
+        }
+        return this;
+    }
+
+    /**
+     * Creates a {@link TableScan} of the table with a given name.
+     *
+     * <p>Throws if the table does not exist.
+     *
+     * <p>Returns this builder.
+     *
+     * @param tableNames Name of table (can optionally be qualified)
+     */
+    public RelBuilder scan(String... tableNames) {
+        return scan(ImmutableList.copyOf(tableNames));
+    }
+
+    /**
+     * Creates a {@link Snapshot} of a given snapshot period.
+     *
+     * <p>Returns this builder.
+     *
+     * @param period Name of table (can optionally be qualified)
+     */
+    public RelBuilder snapshot(RexNode period) {
+        final Frame frame = stack.pop();
+        final RelNode snapshot = struct.snapshotFactory.createSnapshot(frame.rel, period);
+        stack.push(new Frame(snapshot, frame.fields));
+        return this;
+    }
+
+    /**
+     * Gets column mappings of the operator.
+     *
+     * @param op operator instance
+     * @return column mappings associated with this function
+     */
+    private static @Nullable Set<RelColumnMapping> getColumnMappings(SqlOperator op) {
+        SqlReturnTypeInference inference = op.getReturnTypeInference();
+        if (inference instanceof TableFunctionReturnTypeInference) {
+            return ((TableFunctionReturnTypeInference) inference).getColumnMappings();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Creates a RexCall to the {@code CURSOR} function by ordinal.
+     *
+     * @param inputCount Number of inputs
+     * @param ordinal The reference to the relational input
+     * @return RexCall to CURSOR function
+     */
+    public RexNode cursor(int inputCount, int ordinal) {
+        if (inputCount <= ordinal || ordinal < 0) {
+            throw new IllegalArgumentException("bad input count or ordinal");
+        }
+        // Refer to the "ordinal"th input as if it were a field
+        // (because that's how things are laid out inside a TableFunctionScan)
+        final RelNode input = peek(inputCount, ordinal);
+        return call(
+                SqlStdOperatorTable.CURSOR,
+                getRexBuilder().makeInputRef(input.getRowType(), ordinal));
+    }
+
+    /** Creates a {@link TableFunctionScan}. */
+    public RelBuilder functionScan(SqlOperator operator, int inputCount, RexNode... operands) {
+        return functionScan(operator, inputCount, ImmutableList.copyOf(operands));
+    }
+
+    /** Creates a {@link TableFunctionScan}. */
+    public RelBuilder functionScan(
+            SqlOperator operator, int inputCount, Iterable<? extends RexNode> operands) {
+        if (inputCount < 0 || inputCount > stack.size()) {
+            throw new IllegalArgumentException("bad input count");
+        }
+
+        // Gets inputs.
+        final List<RelNode> inputs = new ArrayList<>();
+        for (int i = 0; i < inputCount; i++) {
+            inputs.add(0, build());
+        }
+
+        final RexCall call = call(operator, ImmutableList.copyOf(operands));
+        final RelNode functionScan =
+                struct.tableFunctionScanFactory.createTableFunctionScan(
+                        cluster, inputs, call, null, getColumnMappings(operator));
+        push(functionScan);
+        return this;
+    }
+
+    /**
+     * Creates a {@link Filter} of an array of predicates.
+     *
+     * <p>The predicates are combined using AND, and optimized in a similar way to the {@link #and}
+     * method. If the result is TRUE no filter is created.
+     */
+    public RelBuilder filter(RexNode... predicates) {
+        return filter(ImmutableSet.of(), ImmutableList.copyOf(predicates));
+    }
+
+    /**
+     * Creates a {@link Filter} of a list of predicates.
+     *
+     * <p>The predicates are combined using AND, and optimized in a similar way to the {@link #and}
+     * method. If the result is TRUE no filter is created.
+     */
+    public RelBuilder filter(Iterable<? extends RexNode> predicates) {
+        return filter(ImmutableSet.of(), predicates);
+    }
+
+    /**
+     * Creates a {@link Filter} of a list of correlation variables and an array of predicates.
+     *
+     * <p>The predicates are combined using AND, and optimized in a similar way to the {@link #and}
+     * method. If the result is TRUE no filter is created.
+     */
+    public RelBuilder filter(Iterable<CorrelationId> variablesSet, RexNode... predicates) {
+        return filter(variablesSet, ImmutableList.copyOf(predicates));
+    }
+
+    /**
+     * Creates a {@link Filter} of a list of correlation variables and a list of predicates.
+     *
+     * <p>The predicates are combined using AND, and optimized in a similar way to the {@link #and}
+     * method. If simplification is on and the result is TRUE, no filter is created.
+     */
+    public RelBuilder filter(
+            Iterable<CorrelationId> variablesSet, Iterable<? extends RexNode> predicates) {
+        final RexNode conjunctionPredicates;
+        if (config.simplify()) {
+            conjunctionPredicates = simplifier.simplifyFilterPredicates(predicates);
+        } else {
+            conjunctionPredicates = RexUtil.composeConjunction(simplifier.rexBuilder, predicates);
+        }
+
+        if (conjunctionPredicates == null || conjunctionPredicates.isAlwaysFalse()) {
+            return empty();
+        }
+        if (conjunctionPredicates.isAlwaysTrue()) {
+            return this;
+        }
+
+        final Frame frame = stack.pop();
+        final RelNode filter =
+                struct.filterFactory.createFilter(
+                        frame.rel, conjunctionPredicates, ImmutableSet.copyOf(variablesSet));
+        stack.push(new Frame(filter, frame.fields));
+        return this;
+    }
+
+    /** Creates a {@link Project} of the given expressions. */
+    public RelBuilder project(RexNode... nodes) {
+        return project(ImmutableList.copyOf(nodes));
+    }
+
+    /**
+     * Creates a {@link Project} of the given list of expressions.
+     *
+     * <p>Infers names as would {@link #project(Iterable, Iterable)} if all suggested names were
+     * null.
+     *
+     * @param nodes Expressions
+     */
+    public RelBuilder project(Iterable<? extends RexNode> nodes) {
+        return project(nodes, ImmutableList.of());
+    }
+
+    /**
+     * Creates a {@link Project} of the given list of expressions and field names.
+     *
+     * @param nodes Expressions
+     * @param fieldNames field names for expressions
+     */
+    public RelBuilder project(
+            Iterable<? extends RexNode> nodes, Iterable<? extends @Nullable String> fieldNames) {
+        return project(nodes, fieldNames, false);
+    }
+
+    /**
+     * Creates a {@link Project} of the given list of expressions, using the given names.
+     *
+     * <p>Names are deduced as follows:
+     *
+     * <ul>
+     *   <li>If the length of {@code fieldNames} is greater than the index of the current entry in
+     *       {@code nodes}, and the entry in {@code fieldNames} is not null, uses it; otherwise
+     *   <li>If an expression projects an input field, or is a cast an input field, uses the input
+     *       field name; otherwise
+     *   <li>If an expression is a call to {@link SqlStdOperatorTable#AS} (see {@link #alias}),
+     *       removes the call but uses the intended alias.
+     * </ul>
+     *
+     * <p>After the field names have been inferred, makes the field names unique by appending
+     * numeric suffixes.
+     *
+     * @param nodes Expressions
+     * @param fieldNames Suggested field names
+     * @param force create project even if it is identity
+     */
+    public RelBuilder project(
+            Iterable<? extends RexNode> nodes,
+            Iterable<? extends @Nullable String> fieldNames,
+            boolean 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 expressions. */
+    public RelBuilder projectPlus(RexNode... nodes) {
+        return projectPlus(ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a {@link Project} of all original fields, plus the given list of expressions. */
+    public RelBuilder projectPlus(Iterable<? extends RexNode> nodes) {
+        return project(Iterables.concat(fields(), nodes));
+    }
+
+    /**
+     * Creates a {@link Project} of all original fields, except the given expressions.
+     *
+     * @throws IllegalArgumentException if the given expressions contain duplicates or there is an
+     *     expression that does not match an existing field
+     */
+    public RelBuilder projectExcept(RexNode... expressions) {
+        return projectExcept(ImmutableList.copyOf(expressions));
+    }
+
+    /**
+     * Creates a {@link Project} of all original fields, except the given list of expressions.
+     *
+     * @throws IllegalArgumentException if the given expressions contain duplicates or there is an
+     *     expression that does not match an existing field
+     */
+    public RelBuilder projectExcept(Iterable<RexNode> expressions) {
+        List<RexNode> allExpressions = new ArrayList<>(fields());
+        Set<RexNode> excludeExpressions = new HashSet<>();
+        for (RexNode excludeExp : expressions) {
+            if (!excludeExpressions.add(excludeExp)) {
+                throw new IllegalArgumentException(
+                        "Input list contains duplicates. Expression "
+                                + excludeExp
+                                + " exists multiple times.");
+            }
+            if (!allExpressions.remove(excludeExp)) {
+                throw new IllegalArgumentException(
+                        "Expression " + excludeExp.toString() + " not found.");
+            }
+        }
+        return this.project(allExpressions);
+    }
+
+    /**
+     * Creates a {@link Project} of the given list of expressions, using the given names.
+     *
+     * <p>Names are deduced as follows:
+     *
+     * <ul>
+     *   <li>If the length of {@code fieldNames} is greater than the index of the current entry in
+     *       {@code nodes}, and the entry in {@code fieldNames} is not null, uses it; otherwise
+     *   <li>If an expression projects an input field, or is a cast an input field, uses the input
+     *       field name; otherwise
+     *   <li>If an expression is a call to {@link SqlStdOperatorTable#AS} (see {@link #alias}),
+     *       removes the call but uses the intended alias.
+     * </ul>
+     *
+     * <p>After the field names have been inferred, makes the field names unique by appending
+     * numeric suffixes.
+     *
+     * @param nodes Expressions
+     * @param fieldNames Suggested field names
+     * @param hints Hints
+     * @param force create project even if it is identity
+     */
+    private RelBuilder project_(
+            Iterable<? extends RexNode> nodes,
+            Iterable<? extends @Nullable String> fieldNames,
+            Iterable<RelHint> hints,
+            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.
+        if (!force && Iterables.isEmpty(fieldNames) && RexUtil.isIdentity(nodeList, inputRowType)) {
+            return this;
+        }
+
+        final List<@Nullable String> fieldNameList = Lists.newArrayList(fieldNames);
+        while (fieldNameList.size() < nodeList.size()) {
+            fieldNameList.add(null);
+        }
+
+        // Do not merge projection when top projection has correlation variables
+        bloat:
+        if (frame.rel instanceof Project && 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.
+            for (int i = 0; i < fieldNameList.size(); i++) {
+                if (fieldNameList.get(i) == null) {
+                    final RexNode node = nodeList.get(i);
+                    if (node instanceof RexInputRef) {
+                        final RexInputRef ref = (RexInputRef) node;
+                        fieldNameList.set(
+                                i, project.getRowType().getFieldNames().get(ref.getIndex()));
+                    }
+                }
+            }
+            final List<RexNode> newNodes =
+                    RelOptUtil.pushPastProjectUnlessBloat(nodeList, project, config.bloat());
+            if (newNodes == null) {
+                // The merged expression is more complex than the input expressions.
+                // Do not merge.
+                break bloat;
+            }
+
+            // Carefully build a list of fields, so that table aliases from the input
+            // can be seen for fields that are based on a RexInputRef.
+            final Frame frame1 = stack.pop();
+            final PairList<ImmutableSet<String>, RelDataTypeField> fields = PairList.of();
+            project.getInput()
+                    .getRowType()
+                    .getFieldList()
+                    .forEach(f -> fields.add(ImmutableSet.of(), f));
+            for (Pair<RexNode, ImmutableSet<String>> pair :
+                    Pair.zip(project.getProjects(), frame1.fields.leftList())) {
+                switch (pair.left.getKind()) {
+                    case INPUT_REF:
+                        final int i = ((RexInputRef) pair.left).getIndex();
+                        fields.set(i, pair.right, fields.rightList().get(i));
+                        break;
+                    default:
+                        break;
+                }
+            }
+            stack.push(new Frame(project.getInput(), fields));
+            final ImmutableSet.Builder<RelHint> mergedHints = ImmutableSet.builder();
+            mergedHints.addAll(project.getHints());
+            mergedHints.addAll(hints);
+            // Keep bottom projection's variablesSet.
+            return project_(
+                    newNodes,
+                    fieldNameList,
+                    mergedHints.build(),
+                    force,
+                    ImmutableSet.copyOf(project.getVariablesSet()));
+        }
+
+        // Simplify expressions.
+        if (config.simplify()) {
+            nodeList.replaceAll(e -> simplifier.simplifyPreservingType(e));
+        }
+
+        // Replace null names with generated aliases.
+        for (int i = 0; i < fieldNameList.size(); i++) {
+            if (fieldNameList.get(i) == null) {
+                fieldNameList.set(i, inferAlias(nodeList, nodeList.get(i), i));
+            }
+        }
+
+        final PairList<ImmutableSet<String>, RelDataTypeField> fields = PairList.of();
+        final Set<String> uniqueNameList =
+                getTypeFactory().getTypeSystem().isSchemaCaseSensitive()
+                        ? new HashSet<>()
+                        : new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+        // calculate final names and build field list
+        for (int i = 0; i < fieldNameList.size(); ++i) {
+            final RexNode node = nodeList.get(i);
+            String name = fieldNameList.get(i);
+            String originalName = name;
+            if (name == null || uniqueNameList.contains(name)) {
+                int j = 0;
+                if (name == null) {
+                    j = i;
+                }
+                do {
+                    name = SqlValidatorUtil.F_SUGGESTER.apply(originalName, j, j++);
+                } while (uniqueNameList.contains(name));
+                fieldNameList.set(i, name);
+            }
+            RelDataTypeField fieldType = new RelDataTypeFieldImpl(name, i, node.getType());
+            switch (node.getKind()) {
+                case INPUT_REF:
+                    // preserve rel aliases for INPUT_REF fields
+                    final int index = ((RexInputRef) node).getIndex();
+                    fields.add(frame.fields.leftList().get(index), fieldType);
+                    break;
+                default:
+                    fields.add(ImmutableSet.of(), fieldType);
+                    break;
+            }
+            uniqueNameList.add(name);
+        }
+        if (!force && RexUtil.isIdentity(nodeList, inputRowType)) {
+            if (fieldNameList.equals(inputRowType.getFieldNames())) {
+                // Do not create an identity project if it does not rename any fields
+                return this;
+            } else {
+                // create "virtual" row type for project only rename fields
+                stack.pop();
+                // Ignore the hints.
+                stack.push(new Frame(frame.rel, fields));
+            }
+            return this;
+        }
+
+        // If the expressions are all literals, and the input is a Values with N
+        // rows (or an Aggregate with 1 row), replace with a Values with same tuple
+        // N times.
+        final int rowCount;
+        if (config.simplifyValues()
+                && nodeList.stream().allMatch(e -> e instanceof RexLiteral)
+                && (rowCount = fixedRowCount(frame)) >= 0) {
+            RelNode unused = build();
+            final RelDataTypeFactory.Builder typeBuilder = getTypeFactory().builder();
+            Pair.forEach(
+                    fieldNameList,
+                    nodeList,
+                    (name, expr) -> typeBuilder.add(requireNonNull(name, "name"), expr.getType()));
+            @SuppressWarnings({"unchecked", "rawtypes"})
+            final List<RexLiteral> tuple = (List<RexLiteral>) (List) nodeList;
+            return values(Collections.nCopies(rowCount, tuple), typeBuilder.build());
+        }
+
+        final RelNode project =
+                struct.projectFactory.createProject(
+                        frame.rel,
+                        ImmutableList.copyOf(hints),
+                        ImmutableList.copyOf(nodeList),
+                        fieldNameList,
+                        variables);
+        stack.pop();
+        stack.push(new Frame(project, fields));
+        return this;
+    }
+
+    /**
+     * If current frame will return a known, constant number of rows, returns that number; otherwise
+     * returns -1.
+     */
+    private static int fixedRowCount(Frame frame) {
+        if (frame.rel instanceof Values) {
+            return ((Values) frame.rel).tuples.size();
+        }
+        if (frame.rel instanceof Aggregate) {
+            final Aggregate aggregate = (Aggregate) frame.rel;
+            if (aggregate.getGroupSet().isEmpty()
+                    && aggregate.getGroupType() == Aggregate.Group.SIMPLE) {
+                return 1;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * 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
+     */
+    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", "rawtypes"})
+        final List<? extends RexNode> nodeList =
+                nodes instanceof List ? (List) nodes : ImmutableList.copyOf(nodes);
+        final List<@Nullable String> fieldNameList =
+                fieldNames == null
+                        ? null
+                        : fieldNames instanceof List
+                                ? (List<@Nullable String>) fieldNames
+                                : ImmutableNullableList.copyOf(fieldNames);
+        final RelNode input = peek();
+        final RelDataType rowType =
+                RexUtil.createStructType(
+                        cluster.getTypeFactory(),
+                        nodeList,
+                        fieldNameList,
+                        SqlValidatorUtil.F_SUGGESTER);
+        if (!force && RexUtil.isIdentity(nodeList, input.getRowType())) {
+            if (input instanceof Project && fieldNames != null) {
+                // Rename columns of child projection if desired field names are given.
+                final Frame frame = stack.pop();
+                final Project childProject = (Project) frame.rel;
+                final Project newInput =
+                        childProject.copy(
+                                childProject.getTraitSet(),
+                                childProject.getInput(),
+                                childProject.getProjects(),
+                                rowType);
+                stack.push(new Frame(newInput.attachHints(childProject.getHints()), frame.fields));
+            }
+            if (input instanceof Values && fieldNameList != null) {
+                // Rename columns of child values if desired field names are given.
+                final Frame frame = stack.pop();
+                final Values values = (Values) frame.rel;
+                final RelDataTypeFactory.Builder typeBuilder = getTypeFactory().builder();
+                Pair.forEach(
+                        fieldNameList,
+                        rowType.getFieldList(),
+                        (name, field) ->
+                                typeBuilder.add(requireNonNull(name, "name"), field.getType()));
+                final RelDataType newRowType = typeBuilder.build();
+                final RelNode newValues =
+                        struct.valuesFactory.createValues(cluster, newRowType, values.tuples);
+                stack.push(new Frame(newValues, frame.fields));
+            }
+        } else {
+            project(nodeList, rowType.getFieldNames(), force, variablesSet);
+        }
+        return this;
+    }
+
+    /**
+     * Creates an {@link Uncollect} with given item aliases.
+     *
+     * @param itemAliases Operand item aliases, never null
+     * @param withOrdinality If {@code withOrdinality}, the output contains an extra {@code
+     *     ORDINALITY} column
+     */
+    public RelBuilder uncollect(List<String> itemAliases, boolean withOrdinality) {
+        Frame frame = stack.pop();
+        stack.push(
+                new Frame(
+                        new Uncollect(
+                                cluster,
+                                cluster.traitSetOf(Convention.NONE),
+                                frame.rel,
+                                withOrdinality,
+                                requireNonNull(itemAliases, "itemAliases"))));
+        return this;
+    }
+
+    /**
+     * Ensures that the field names match those given.
+     *
+     * <p>If all fields have the same name, adds nothing; if any fields do not have the same name,
+     * adds a {@link Project}.
+     *
+     * <p>Note that the names can be short-lived. Other {@code RelBuilder} operations make no
+     * guarantees about the field names of the rows they produce.
+     *
+     * @param fieldNames List of desired field names; may contain null values or have fewer fields
+     *     than the current row type
+     */
+    public RelBuilder rename(List<? extends @Nullable String> fieldNames) {
+        final List<String> oldFieldNames = peek().getRowType().getFieldNames();
+        Preconditions.checkArgument(
+                fieldNames.size() <= oldFieldNames.size(), "More names than fields");
+        final List<String> newFieldNames = new ArrayList<>(oldFieldNames);
+        for (int i = 0; i < fieldNames.size(); i++) {
+            final String s = fieldNames.get(i);
+            if (s != null) {
+                newFieldNames.set(i, s);
+            }
+        }
+        if (oldFieldNames.equals(newFieldNames)) {
+            return this;
+        }
+        if (peek() instanceof Values) {
+            // Special treatment for VALUES. Re-build it rather than add a project.
+            final Values v = (Values) build();
+            final RelDataTypeFactory.Builder b = getTypeFactory().builder();
+            for (Pair<String, RelDataTypeField> p :
+                    Pair.zip(newFieldNames, v.getRowType().getFieldList())) {
+                b.add(p.left, p.right.getType());
+            }
+            return values(v.tuples, b.build());
+        }
+
+        return project(fields(), newFieldNames, true);
+    }
+
+    /**
+     * Infers the alias of an expression.
+     *
+     * <p>If the expression was created by {@link #alias}, replaces the expression in the project
+     * list.
+     */
+    private @Nullable String inferAlias(List<RexNode> exprList, RexNode expr, int i) {
+        switch (expr.getKind()) {
+            case INPUT_REF:
+                final RexInputRef ref = (RexInputRef) expr;
+                return requireNonNull(stack.peek(), "empty frame stack")
+                        .fields
+                        .get(ref.getIndex())
+                        .getValue()
+                        .getName();
+            case CAST:
+                return inferAlias(exprList, ((RexCall) expr).getOperands().get(0), -1);
+            case AS:
+                final RexCall call = (RexCall) expr;
+                if (i >= 0) {
+                    exprList.set(i, call.getOperands().get(0));
+                }
+                NlsString value = (NlsString) ((RexLiteral) call.getOperands().get(1)).getValue();
+                return castNonNull(value).getValue();
+            default:
+                return null;
+        }
+    }
+
+    /** Creates an {@link Aggregate} that makes the relational expression distinct on all fields. */
+    public RelBuilder distinct() {
+        return aggregate_((GroupKeyImpl) groupKey(fields()), ImmutableList.of());
+    }
+
+    /** Creates an {@link Aggregate} with an array of calls. */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public RelBuilder aggregate(GroupKey groupKey, AggCall... aggCalls) {
+        return aggregate_((GroupKeyImpl) groupKey, (ImmutableList) ImmutableList.copyOf(aggCalls));
+    }
+
+    /** Creates an {@link Aggregate} with an array of {@link AggregateCall}s. */
+    public RelBuilder aggregate(GroupKey groupKey, List<AggregateCall> aggregateCalls) {
+        return aggregate_(
+                (GroupKeyImpl) groupKey,
+                aggregateCalls.stream()
+                        .map(
+                                aggregateCall ->
+                                        new AggCallImpl2(
+                                                aggregateCall,
+                                                aggregateCall.getArgList().stream()
+                                                        .map(this::field)
+                                                        .collect(toImmutableList())))
+                        .collect(toImmutableList()));
+    }
+
+    /** Creates an {@link Aggregate} with multiple calls. */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public RelBuilder aggregate(GroupKey groupKey, Iterable<? extends AggCall> aggCalls) {
+        return aggregate_(
+                (GroupKeyImpl) groupKey, ImmutableList.<AggCallPlus>copyOf((Iterable) aggCalls));
+    }
+
+    /** Creates an {@link Aggregate} with multiple calls. */
+    private RelBuilder aggregate_(
+            GroupKeyImpl groupKey, final ImmutableList<AggCallPlus> aggCalls) {
+        if (groupKey.nodes.isEmpty() && aggCalls.isEmpty() && config.pruneInputOfAggregate()) {
+            // Query is "SELECT /* no fields */ FROM t GROUP BY ()", which always
+            // returns one row with zero columns.
+            if (config.preventEmptyFieldList()) {
+                // Convert to "VALUES ROW(true)".
+                return values(new String[] {"dummy"}, true);
+            } else {
+                // Convert to "VALUES ROW()".
+                return values(
+                        ImmutableList.of(ImmutableList.of()), getTypeFactory().builder().build());
+            }
+        }
+        final Registrar registrar = new Registrar(fields(), peek().getRowType().getFieldNames());
+        final ImmutableBitSet groupSet =
+                ImmutableBitSet.of(registrar.registerExpressions(groupKey.nodes));
+        if (alreadyUnique(aggCalls, groupKey, groupSet, registrar.extraNodes)) {
+            final List<RexNode> nodes = new ArrayList<>(fields(groupSet));
+            aggCalls.forEach(
+                    c -> {
+                        final AggregateCall call = c.aggregateCall();
+                        final SqlStaticAggFunction staticFun =
+                                call.getAggregation().unwrapOrThrow(SqlStaticAggFunction.class);
+                        final RexNode node =
+                                staticFun.constant(
+                                        getRexBuilder(), groupSet, ImmutableList.of(), call);
+                        nodes.add(aliasMaybe(requireNonNull(node, "node"), call.getName()));
+                    });
+            return project(nodes);
+        }
+
+        ImmutableList<ImmutableBitSet> groupSets;
+        if (groupKey.nodeLists != null) {
+            final int sizeBefore = registrar.extraNodes.size();
+            final List<ImmutableBitSet> groupSetList = new ArrayList<>();
+            for (ImmutableList<RexNode> nodeList : groupKey.nodeLists) {
+                final ImmutableBitSet groupSet2 =
+                        ImmutableBitSet.of(registrar.registerExpressions(nodeList));
+                if (!groupSet.contains(groupSet2)) {
+                    throw new IllegalArgumentException(
+                            "group set element " + nodeList + " must be a subset of group key");
+                }
+                groupSetList.add(groupSet2);
+            }
+            final ImmutableSortedMultiset<ImmutableBitSet> groupSetMultiset =
+                    ImmutableSortedMultiset.copyOf(ImmutableBitSet.COMPARATOR, groupSetList);
+            if (aggCalls.stream().anyMatch(RelBuilder::isGroupId)
+                    || !ImmutableBitSet.ORDERING.isStrictlyOrdered(groupSetMultiset)) {
+                return rewriteAggregateWithDuplicateGroupSets(groupSet, groupSetMultiset, aggCalls);
+            }
+            groupSets = ImmutableList.copyOf(groupSetMultiset.elementSet());
+            if (registrar.extraNodes.size() > sizeBefore) {
+                throw new IllegalArgumentException(
+                        "group sets contained expressions "
+                                + "not in group key: "
+                                + Util.skip(registrar.extraNodes, sizeBefore));
+            }
+        } else {
+            groupSets = ImmutableList.of(groupSet);
+        }
+
+        aggCalls.forEach(aggCall -> aggCall.register(registrar));
+        project(registrar.extraNodes);
+        rename(registrar.names);
+        final Frame frame = stack.pop();
+        RelNode r = frame.rel;
+        final List<AggregateCall> aggregateCalls = new ArrayList<>();
+        for (AggCallPlus aggCall : aggCalls) {
+            aggregateCalls.add(aggCall.aggregateCall(registrar, groupSet, r));
+        }
+
+        assert ImmutableBitSet.ORDERING.isStrictlyOrdered(groupSets) : groupSets;
+        for (ImmutableBitSet set : groupSets) {
+            assert groupSet.contains(set);
+        }
+
+        PairList<ImmutableSet<String>, RelDataTypeField> inFields = frame.fields;
+        final ImmutableBitSet groupSet2;
+        final ImmutableList<ImmutableBitSet> groupSets2;
+        if (config.pruneInputOfAggregate() && r instanceof Project) {
+            final Set<Integer> fieldsUsed = RelOptUtil.getAllFields2(groupSet, aggregateCalls);
+            // Some parts of the system can't handle rows with zero fields, so
+            // pretend that one field is used.
+            if (fieldsUsed.isEmpty()) {
+                r = ((Project) r).getInput();
+                groupSet2 = groupSet;
+                groupSets2 = groupSets;
+            } else if (fieldsUsed.size() < r.getRowType().getFieldCount()) {
+                // Some fields are computed but not used. Prune them.
+                final Map<Integer, Integer> map = new HashMap<>();
+                for (int source : fieldsUsed) {
+                    map.put(source, map.size());
+                }
+
+                groupSet2 = groupSet.permute(map);
+                groupSets2 =
+                        ImmutableBitSet.ORDERING.immutableSortedCopy(
+                                ImmutableBitSet.permute(groupSets, map));
+
+                final Mappings.TargetMapping targetMapping =
+                        Mappings.target(map, r.getRowType().getFieldCount(), fieldsUsed.size());
+                final List<AggregateCall> oldAggregateCalls = new ArrayList<>(aggregateCalls);
+                aggregateCalls.clear();
+                for (AggregateCall aggregateCall : oldAggregateCalls) {
+                    aggregateCalls.add(aggregateCall.transform(targetMapping));
+                }
+                final PairList<ImmutableSet<String>, RelDataTypeField> newInFields = PairList.of();
+                newInFields.addAll(Mappings.permute(inFields, targetMapping.inverse()));
+                inFields = newInFields;
+
+                final Project project = (Project) r;
+                final List<RexNode> newProjects = new ArrayList<>();
+                final RelDataTypeFactory.Builder builder = cluster.getTypeFactory().builder();
+                for (int i : fieldsUsed) {
+                    newProjects.add(project.getProjects().get(i));
+                    builder.add(project.getRowType().getFieldList().get(i));
+                }
+                r =
+                        project.copy(
+                                cluster.traitSet(),
+                                project.getInput(),
+                                newProjects,
+                                builder.build());
+            } else {
+                groupSet2 = groupSet;
+                groupSets2 = groupSets;
+            }
+        } else {
+            groupSet2 = groupSet;
+            groupSets2 = groupSets;
+        }
+
+        if (!config.dedupAggregateCalls() || Util.isDistinct(aggregateCalls)) {
+            return aggregate_(
+                    groupSet2, groupSets2, r, aggregateCalls, registrar.extraNodes, inFields);
+        }
+
+        // There are duplicate aggregate calls. Rebuild the list to eliminate
+        // duplicates, then add a Project.
+        final Set<AggregateCall> callSet = new HashSet<>();
+        final PairList<Integer, @Nullable String> projects = PairList.of();
+        Util.range(groupSet.cardinality()).forEach(i -> projects.add(i, null));
+        final List<AggregateCall> distinctAggregateCalls = new ArrayList<>();
+        for (AggregateCall aggregateCall : aggregateCalls) {
+            final int i;
+            if (callSet.add(aggregateCall)) {
+                i = distinctAggregateCalls.size();
+                distinctAggregateCalls.add(aggregateCall);
+            } else {
+                i = distinctAggregateCalls.indexOf(aggregateCall);
+                assert i >= 0;
+            }
+            projects.add(groupSet.cardinality() + i, aggregateCall.name);
+        }
+        aggregate_(groupSet, groupSets, r, distinctAggregateCalls, registrar.extraNodes, inFields);
+        return project(projects.transform((i, name) -> aliasMaybe(field(i), name)));
+    }
+
+    /**
+     * Returns whether an input is already unique, and therefore a Project can be created instead of
+     * an Aggregate.
+     *
+     * <p>{@link AggregateRemoveRule} does something similar, but also handles {@link
+     * org.apache.calcite.sql.SqlSingletonAggFunction} calls.
+     */
+    private boolean alreadyUnique(
+            List<AggCallPlus> aggCallList,
+            GroupKeyImpl groupKey,
+            ImmutableBitSet groupSet,
+            List<RexNode> extraNodes) {
+        final RelMetadataQuery mq = peek().getCluster().getMetadataQuery();
+        if (aggCallList.isEmpty() && groupSet.isEmpty()) {
+            final Double minRowCount = mq.getMinRowCount(peek());
+            if (minRowCount == null || minRowCount < 1d) {
+                // We can't remove "GROUP BY ()" if there's a chance the rel could be
+                // empty.
+                return false;
+            }
+        }
+
+        // If there are aggregate functions, we must be able to flatten them
+        if (!aggCallList.stream().allMatch(c -> canFlattenStatic(c.aggregateCall()))) {
+            return false;
+        }
+
+        if (extraNodes.size() == fields().size()) {
+            final Boolean unique = mq.areColumnsUnique(peek(), groupSet);
+            if (unique != null && unique && !config.aggregateUnique() && groupKey.isSimple()) {
+                // Rel is already unique.
+                return true;
+            }
+        }
+
+        // If there is at most one row, rel is already unique.
+        final Double maxRowCount = mq.getMaxRowCount(peek());
+        return maxRowCount != null
+                && maxRowCount <= 1D
+                && !config.aggregateUnique()
+                && groupKey.isSimple();
+    }
+
+    /**
+     * Finishes the implementation of {@link #aggregate} by creating an {@link Aggregate} and
+     * pushing it onto the stack.
+     */
+    private RelBuilder aggregate_(
+            ImmutableBitSet groupSet,
+            ImmutableList<ImmutableBitSet> groupSets,
+            RelNode input,
+            List<AggregateCall> aggregateCalls,
+            List<RexNode> extraNodes,
+            PairList<ImmutableSet<String>, RelDataTypeField> inFields) {
+        final RelNode aggregate =
+                struct.aggregateFactory.createAggregate(
+                        input, ImmutableList.of(), groupSet, groupSets, aggregateCalls);
+
+        // build field list
+        final PairList<ImmutableSet<String>, RelDataTypeField> fields = PairList.of();
+        final List<RelDataTypeField> aggregateFields = aggregate.getRowType().getFieldList();
+        int i = 0;
+        // first, group fields
+        for (Integer groupField : groupSet.asList()) {
+            RexNode node = extraNodes.get(groupField);
+            final SqlKind kind = node.getKind();
+            switch (kind) {
+                case INPUT_REF:
+                    fields.add(inFields.get(((RexInputRef) node).getIndex()));
+                    break;
+                default:
+                    String name = aggregateFields.get(i).getName();
+                    RelDataTypeField fieldType = new RelDataTypeFieldImpl(name, i, node.getType());
+                    fields.add(ImmutableSet.of(), fieldType);
+                    break;
+            }
+            i++;
+        }
+        // second, aggregate fields. retain `i' as field index
+        for (int j = 0; j < aggregateCalls.size(); ++j) {
+            final AggregateCall call = aggregateCalls.get(j);
+            final RelDataTypeField fieldType =
+                    new RelDataTypeFieldImpl(
+                            aggregateFields.get(i + j).getName(), i + j, call.getType());
+            fields.add(ImmutableSet.of(), fieldType);
+        }
+        stack.push(new Frame(aggregate, fields));
+        return this;
+    }
+
+    /**
+     * The {@code GROUP_ID()} function is used to distinguish duplicate groups. However, as
+     * Aggregate normalizes group sets to canonical form (i.e., flatten, sorting, redundancy
+     * removal), this information is lost in RelNode. Therefore, it is impossible to implement the
+     * function in runtime.
+     *
+     * <p>To fill this gap, an aggregation query that contains duplicate group sets is rewritten
+     * into a Union of Aggregate operators whose group sets are distinct. The number of inputs to
+     * the Union is equal to the maximum number of duplicates. In the {@code N}th input to the
+     * Union, calls to the {@code GROUP_ID} aggregate function are replaced by the integer literal
+     * {@code N}.
+     *
+     * <p>This method also handles the case where group sets are distinct but there is a call to
+     * {@code GROUP_ID}. That call is replaced by the integer literal {@code 0}.
+     *
+     * <p>Also see the discussion in <a
+     * href="https://issues.apache.org/jira/browse/CALCITE-1824">[CALCITE-1824] GROUP_ID returns
+     * wrong result</a> and <a
+     * href="https://issues.apache.org/jira/browse/CALCITE-4748">[CALCITE-4748] If there are
+     * duplicate GROUPING SETS, Calcite should return duplicate rows</a>.
+     */
+    private RelBuilder rewriteAggregateWithDuplicateGroupSets(
+            ImmutableBitSet groupSet,
+            ImmutableSortedMultiset<ImmutableBitSet> groupSets,
+            List<AggCallPlus> aggregateCalls) {
+        final List<String> fieldNamesIfNoRewrite =
+                Aggregate.deriveRowType(
+                                getTypeFactory(),
+                                peek().getRowType(),
+                                false,
+                                groupSet,
+                                groupSets.asList(),
+                                aggregateCalls.stream()
+                                        .map(AggCallPlus::aggregateCall)
+                                        .collect(toImmutableList()))
+                        .getFieldNames();
+
+        // If n duplicates exist for a particular grouping, the {@code GROUP_ID()}
+        // function produces values in the range 0 to n-1. For each value,
+        // we need to figure out the corresponding group sets.
+        //
+        // For example, "... GROUPING SETS (a, a, b, c, c, c, c)"
+        // (i) The max value of the GROUP_ID() function returns is 3
+        // (ii) GROUPING SETS (a, b, c) produces value 0,
+        //      GROUPING SETS (a, c) produces value 1,
+        //      GROUPING SETS (c) produces value 2
+        //      GROUPING SETS (c) produces value 3
+        final Map<Integer, Set<ImmutableBitSet>> groupIdToGroupSets = new HashMap<>();
+        int maxGroupId = 0;
+        for (Multiset.Entry<ImmutableBitSet> entry : groupSets.entrySet()) {
+            int groupId = entry.getCount() - 1;
+            if (groupId > maxGroupId) {
+                maxGroupId = groupId;
+            }
+            for (int i = 0; i <= groupId; i++) {
+                groupIdToGroupSets
+                        .computeIfAbsent(i, k -> Sets.newTreeSet(ImmutableBitSet.COMPARATOR))
+                        .add(entry.getElement());
+            }
+        }
+
+        // AggregateCall list without GROUP_ID function
+        final List<AggCall> aggregateCallsWithoutGroupId = new ArrayList<>(aggregateCalls);
+        aggregateCallsWithoutGroupId.removeIf(RelBuilder::isGroupId);
+
+        // For each group id value, we first construct an Aggregate without
+        // GROUP_ID() function call, and then create a Project node on top of it.
+        // The Project adds literal value for group id in right position.
+        final Frame frame = stack.pop();
+        for (int groupId = 0; groupId <= maxGroupId; groupId++) {
+            // Create the Aggregate node without GROUP_ID() call
+            stack.push(frame);
+            aggregate(
+                    groupKey(groupSet, castNonNull(groupIdToGroupSets.get(groupId))),
+                    aggregateCallsWithoutGroupId);
+
+            final List<RexNode> selectList = new ArrayList<>();
+            final int groupExprLength = groupSet.cardinality();
+            // Project fields in group by expressions
+            for (int i = 0; i < groupExprLength; i++) {
+                selectList.add(field(i));
+            }
+            // Project fields in aggregate calls
+            int groupIdCount = 0;
+            for (int i = 0; i < aggregateCalls.size(); i++) {
+                if (isGroupId(aggregateCalls.get(i))) {
+                    selectList.add(
+                            getRexBuilder()
+                                    .makeExactLiteral(
+                                            BigDecimal.valueOf(groupId),
+                                            getTypeFactory().createSqlType(SqlTypeName.BIGINT)));
+                    groupIdCount++;
+                } else {
+                    selectList.add(field(groupExprLength + i - groupIdCount));
+                }
+            }
+            project(selectList, fieldNamesIfNoRewrite);
+        }
+
+        return union(true, maxGroupId + 1);
+    }
+
+    private static boolean isGroupId(AggCall c) {
+        return ((AggCallPlus) c).op().kind == SqlKind.GROUP_ID;
+    }
+
+    private RelBuilder setOp(boolean all, SqlKind kind, int n) {
+        List<RelNode> inputs = new ArrayList<>();
+        for (int i = 0; i < n; i++) {
+            inputs.add(0, build());
+        }
+        switch (kind) {
+            case UNION:
+            case INTERSECT:
+            case EXCEPT:
+                if (n < 1) {
+                    throw new IllegalArgumentException("bad INTERSECT/UNION/EXCEPT input count");
+                }
+                break;
+            default:
+                throw new AssertionError("bad setOp " + kind);
+        }
+
+        if (n == 1) {
+            return push(inputs.get(0));
+        }
+
+        if (config.simplifyValues()
+                && kind == UNION
+                && inputs.stream().allMatch(r -> r instanceof Values)) {
+            List<RelDataType> inputTypes = Util.transform(inputs, RelNode::getRowType);
+            RelDataType rowType = getTypeFactory().leastRestrictive(inputTypes);
+            requireNonNull(rowType, () -> "leastRestrictive(" + inputTypes + ")");
+            final List<List<RexLiteral>> tuples = new ArrayList<>();
+            for (RelNode input : inputs) {
+                tuples.addAll(((Values) input).tuples);
+            }
+            final List<List<RexLiteral>> tuples2 = all ? tuples : Util.distinctList(tuples);
+            return values(tuples2, rowType);
+        }
+
+        return push(struct.setOpFactory.createSetOp(kind, inputs, all));
+    }
+
+    /**
+     * Creates a {@link Union} of the two most recent relational expressions on the stack.
+     *
+     * @param all Whether to create UNION ALL
+     */
+    public RelBuilder union(boolean all) {
+        return union(all, 2);
+    }
+
+    /**
+     * Creates a {@link Union} of the {@code n} most recent relational expressions on the stack.
+     *
+     * @param all Whether to create UNION ALL
+     * @param n Number of inputs to the UNION operator
+     */
+    public RelBuilder union(boolean all, int n) {
+        return setOp(all, UNION, n);
+    }
+
+    /**
+     * Creates an {@link Intersect} of the two most recent relational expressions on the stack.
+     *
+     * @param all Whether to create INTERSECT ALL
+     */
+    public RelBuilder intersect(boolean all) {
+        return intersect(all, 2);
+    }
+
+    /**
+     * Creates an {@link Intersect} of the {@code n} most recent relational expressions on the
+     * stack.
+     *
+     * @param all Whether to create INTERSECT ALL
+     * @param n Number of inputs to the INTERSECT operator
+     */
+    public RelBuilder intersect(boolean all, int n) {
+        return setOp(all, SqlKind.INTERSECT, n);
+    }
+
+    /**
+     * Creates a {@link Minus} of the two most recent relational expressions on the stack.
+     *
+     * @param all Whether to create EXCEPT ALL
+     */
+    public RelBuilder minus(boolean all) {
+        return minus(all, 2);
+    }
+
+    /**
+     * Creates a {@link Minus} of the {@code n} most recent relational expressions on the stack.
+     *
+     * @param all Whether to create EXCEPT ALL
+     */
+    public RelBuilder minus(boolean all, int n) {
+        return setOp(all, SqlKind.EXCEPT, n);
+    }
+
+    /**
+     * Creates a {@link TableScan} on a {@link TransientTable} with the given name, using as type
+     * the top of the stack's type.
+     *
+     * @param tableName table name
+     */
+    @Experimental
+    public RelBuilder transientScan(String tableName) {
+        return this.transientScan(tableName, this.peek().getRowType());
+    }
+
+    /**
+     * Creates a {@link TableScan} on a {@link TransientTable} with the given name and type.
+     *
+     * @param tableName table name
+     * @param rowType row type of the table
+     */
+    @Experimental
+    public RelBuilder transientScan(String tableName, RelDataType rowType) {
+        TransientTable transientTable = new ListTransientTable(tableName, rowType);
+        requireNonNull(relOptSchema, "relOptSchema");
+        RelOptTable relOptTable =
+                RelOptTableImpl.create(
+                        relOptSchema, rowType, transientTable, ImmutableList.of(tableName));
+        RelNode scan =
+                struct.scanFactory.createScan(
+                        ViewExpanders.toRelContext(viewExpander, cluster), relOptTable);
+        push(scan);
+        rename(rowType.getFieldNames());
+        return this;
+    }
+
+    /**
+     * Creates a {@link TableSpool} for the most recent relational expression.
+     *
+     * @param readType Spool's read type (as described in {@link Spool.Type})
+     * @param writeType Spool's write type (as described in {@link Spool.Type})
+     * @param table Table to write into
+     */
+    private RelBuilder tableSpool(Spool.Type readType, Spool.Type writeType, RelOptTable table) {
+        RelNode spool = struct.spoolFactory.createTableSpool(peek(), readType, writeType, table);
+        replaceTop(spool);
+        return this;
+    }
+
+    /**
+     * Creates a {@link RepeatUnion} associated to a {@link TransientTable} without a maximum number
+     * of iterations, i.e. repeatUnion(tableName, all, -1).
+     *
+     * @param tableName name of the {@link TransientTable} associated to the {@link RepeatUnion}
+     * @param all whether duplicates will be considered or not
+     */
+    @Experimental
+    public RelBuilder repeatUnion(String tableName, boolean all) {
+        return repeatUnion(tableName, all, -1);
+    }
+
+    /**
+     * Creates a {@link RepeatUnion} associated to a {@link TransientTable} of the two most recent
+     * relational expressions on the stack.
+     *
+     * <p>Warning: if these relational expressions are not correctly defined, this operation might
+     * lead to an infinite loop.
+     *
+     * <p>The generated {@link RepeatUnion} operates as follows:
+     *
+     * <ul>
+     *   <li>Evaluate its left term once, propagating the results into the {@link TransientTable};
+     *   <li>Evaluate its right term (which may contain a {@link TableScan} on the {@link
+     *       TransientTable}) over and over until it produces no more results (or until an optional
+     *       maximum number of iterations is reached). On each iteration, the results are propagated
+     *       into the {@link TransientTable}, overwriting the results from the previous one.
+     * </ul>
+     *
+     * @param tableName Name of the {@link TransientTable} associated to the {@link RepeatUnion}
+     * @param all Whether duplicates are considered
+     * @param iterationLimit Maximum number of iterations; negative value means no limit
+     */
+    @Experimental
+    public RelBuilder repeatUnion(String tableName, boolean all, int iterationLimit) {
+        RelOptTable table = null;
+        for (int i = 0; i < stack.size(); i++) { // search scan(tableName) in the stack
+            table = RelOptUtil.findTable(peek(i), tableName);
+            if (table != null) { // found
+                break;
+            }
+        }
+        if (table == null) {
+            throw RESOURCE.tableNotFound(tableName).ex();
+        }
+
+        RelNode iterative = tableSpool(Spool.Type.LAZY, Spool.Type.LAZY, table).build();
+        RelNode seed = tableSpool(Spool.Type.LAZY, Spool.Type.LAZY, table).build();
+        RelNode repeatUnion =
+                struct.repeatUnionFactory.createRepeatUnion(
+                        seed, iterative, all, iterationLimit, table);
+        return push(repeatUnion);
+    }
+
+    /** Creates a {@link Join} with an array of conditions. */
+    public RelBuilder join(JoinRelType joinType, RexNode condition0, RexNode... conditions) {
+        return join(joinType, Lists.asList(condition0, conditions));
+    }
+
+    /** Creates a {@link Join} with multiple conditions. */
+    public RelBuilder join(JoinRelType joinType, Iterable<? extends RexNode> conditions) {
+        return join(joinType, and(conditions), ImmutableSet.of());
+    }
+
+    /** Creates a {@link Join} with one condition. */
+    public RelBuilder join(JoinRelType joinType, RexNode condition) {
+        return join(joinType, condition, ImmutableSet.of());
+    }
+
+    /** Creates a {@link Join} with correlating variables. */
+    public RelBuilder join(
+            JoinRelType joinType, RexNode condition, Set<CorrelationId> variablesSet) {
+        Frame right = stack.pop();
+        final Frame left = stack.pop();
+        final RelNode join;
+        final boolean correlate = checkIfCorrelated(variablesSet, joinType, left.rel, right.rel);
+        RexNode postCondition = literal(true);
+        if (config.simplify()) {
+            // Normalize expanded versions IS NOT DISTINCT FROM so that simplifier does not
+            // transform the expression to something unrecognizable
+            if (condition instanceof RexCall) {
+                condition =
+                        RelOptUtil.collapseExpandedIsNotDistinctFromExpr(
+                                (RexCall) condition, getRexBuilder());
+            }
+            condition = simplifier.simplifyUnknownAsFalse(condition);
+        }
+        if (correlate) {
+            final CorrelationId id = Iterables.getOnlyElement(variablesSet);
+            // Correlate does not have an ON clause.
+            switch (joinType) {
+                case LEFT:
+                case SEMI:
+                case ANTI:
+                    // For a LEFT/SEMI/ANTI, predicate must be evaluated first.
+                    stack.push(right);
+                    filter(condition.accept(new Shifter(left.rel, id, right.rel)));
+                    right = stack.pop();
+                    break;
+                case INNER:
+                    // For INNER, we can defer.
+                    postCondition = condition;
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Correlated " + joinType + " join is not supported");
+            }
+            final ImmutableBitSet requiredColumns = RelOptUtil.correlationColumns(id, right.rel);
+            join =
+                    struct.correlateFactory.createCorrelate(
+                            left.rel, right.rel, ImmutableList.of(), id, requiredColumns, joinType);
+        } else {
+            RelNode join0 =
+                    struct.joinFactory.createJoin(
+                            left.rel,
+                            right.rel,
+                            ImmutableList.of(),
+                            condition,
+                            variablesSet,
+                            joinType,
+                            false);
+
+            if (join0 instanceof Join && config.pushJoinCondition()) {
+                join = RelOptUtil.pushDownJoinConditions((Join) join0, this);
+            } else {
+                join = join0;
+            }
+        }
+        final PairList<ImmutableSet<String>, RelDataTypeField> fields = PairList.of();
+        fields.addAll(left.fields);
+        fields.addAll(right.fields);
+        stack.push(new Frame(join, fields));
+        filter(postCondition);
+        return this;
+    }
+
+    /**
+     * Creates a {@link Correlate} with a {@link CorrelationId} and an array of fields that are used
+     * by correlation.
+     */
+    public RelBuilder correlate(
+            JoinRelType joinType, CorrelationId correlationId, RexNode... requiredFields) {
+        return correlate(joinType, correlationId, ImmutableList.copyOf(requiredFields));
+    }
+
+    /**
+     * Creates a {@link Correlate} with a {@link CorrelationId} and a list of fields that are used
+     * by correlation.
+     */
+    public RelBuilder correlate(
+            JoinRelType joinType,
+            CorrelationId correlationId,
+            Iterable<? extends RexNode> requiredFields) {
+        Frame right = stack.pop();
+
+        final Registrar registrar = new Registrar(fields(), peek().getRowType().getFieldNames());
+
+        List<Integer> requiredOrdinals =
+                registrar.registerExpressions(ImmutableList.copyOf(requiredFields));
+
+        project(registrar.extraNodes);
+        rename(registrar.names);
+        Frame left = stack.pop();
+
+        final RelNode correlate =
+                struct.correlateFactory.createCorrelate(
+                        left.rel,
+                        right.rel,
+                        ImmutableList.of(),
+                        correlationId,
+                        ImmutableBitSet.of(requiredOrdinals),
+                        joinType);
+
+        final PairList<ImmutableSet<String>, RelDataTypeField> fields = PairList.of();
+        fields.addAll(left.fields);
+        fields.addAll(right.fields);
+        stack.push(new Frame(correlate, fields));
+
+        return this;
+    }
+
+    /**
+     * Creates a {@link Join} using USING syntax.
+     *
+     * <p>For each of the field names, both left and right inputs must have a field of that name.
+     * Constructs a join condition that the left and right fields are equal.
+     *
+     * @param joinType Join type
+     * @param fieldNames Field names
+     */
+    public RelBuilder join(JoinRelType joinType, String... fieldNames) {
+        final List<RexNode> conditions = new ArrayList<>();
+        for (String fieldName : fieldNames) {
+            conditions.add(equals(field(2, 0, fieldName), field(2, 1, fieldName)));
+        }
+        return join(joinType, conditions);
+    }
+
+    /**
+     * Creates a {@link Join} with {@link JoinRelType#SEMI}.
+     *
+     * <p>A semi-join is a form of join that combines two relational expressions according to some
+     * condition, and outputs only rows from the left input for which at least one row from the
+     * right input matches. It only outputs columns from the left input, and ignores duplicates on
+     * the right.
+     *
+     * <p>For example, {@code EMP semi-join DEPT} finds all {@code EMP} records that do not have a
+     * corresponding {@code DEPT} record, similar to the following SQL:
+     *
+     * <blockquote>
+     *
+     * <pre>
+     * SELECT * FROM EMP
+     * WHERE EXISTS (SELECT 1 FROM DEPT
+     *     WHERE DEPT.DEPTNO = EMP.DEPTNO)</pre>
+     *
+     * </blockquote>
+     */
+    public RelBuilder semiJoin(Iterable<? extends RexNode> conditions) {
+        final Frame right = stack.pop();
+        final RelNode semiJoin =
+                struct.joinFactory.createJoin(
+                        peek(),
+                        right.rel,
+                        ImmutableList.of(),
+                        and(conditions),
+                        ImmutableSet.of(),
+                        JoinRelType.SEMI,
+                        false);
+        replaceTop(semiJoin);
+        return this;
+    }
+
+    /**
+     * Creates a {@link Join} with {@link JoinRelType#SEMI}.
+     *
+     * @see #semiJoin(Iterable)
+     */
+    public RelBuilder semiJoin(RexNode... conditions) {
+        return semiJoin(ImmutableList.copyOf(conditions));
+    }
+
+    /**
+     * Creates an anti-join.
+     *
+     * <p>An anti-join is a form of join that combines two relational expressions according to some
+     * condition, but outputs only rows from the left input for which no rows from the right input
+     * match.
+     *
+     * <p>For example, {@code EMP anti-join DEPT} finds all {@code EMP} records that do not have a
+     * corresponding {@code DEPT} record, similar to the following SQL:
+     *
+     * <blockquote>
+     *
+     * <pre>
+     * SELECT * FROM EMP
+     * WHERE NOT EXISTS (SELECT 1 FROM DEPT
+     *     WHERE DEPT.DEPTNO = EMP.DEPTNO)</pre>
+     *
+     * </blockquote>
+     */
+    public RelBuilder antiJoin(Iterable<? extends RexNode> conditions) {
+        final Frame right = stack.pop();
+        final RelNode antiJoin =
+                struct.joinFactory.createJoin(
+                        peek(),
+                        right.rel,
+                        ImmutableList.of(),
+                        and(conditions),
+                        ImmutableSet.of(),
+                        JoinRelType.ANTI,
+                        false);
+        replaceTop(antiJoin);
+        return this;
+    }
+
+    /**
+     * Creates an anti-join.
+     *
+     * @see #antiJoin(Iterable)
+     */
+    public RelBuilder antiJoin(RexNode... conditions) {
+        return antiJoin(ImmutableList.copyOf(conditions));
+    }
+
+    /** Assigns a table alias to the top entry on the stack. */
+    public RelBuilder as(final String alias) {
+        final Frame pair = stack.pop();
+        final PairList<ImmutableSet<String>, RelDataTypeField> newFields = PairList.of();
+        pair.fields.forEach(
+                (aliases, field) -> {
+                    final ImmutableSet<String> aliasList =
+                            aliases.contains(alias)
+                                    ? aliases
+                                    : ImmutableSet.<String>builder()
+                                            .addAll(aliases)
+                                            .add(alias)
+                                            .build();
+                    newFields.add(aliasList, field);
+                });
+        stack.push(new Frame(pair.rel, newFields));
+        return this;
+    }
+
+    /**
+     * Creates a {@link Values}.
+     *
+     * <p>The {@code values} array must have the same number of entries as {@code fieldNames}, or an
+     * integer multiple if you wish to create multiple rows.
+     *
+     * <p>The {@code fieldNames} array must not be null or empty, but may contain null values.
+     *
+     * <p>If there are zero rows, or if all values of any column are null, this method cannot deduce
+     * the type of columns. For these cases, call {@link #values(Iterable, RelDataType)}.
+     *
+     * @param fieldNames Field names
+     * @param values Values
+     */
+    public RelBuilder values(@Nullable String[] fieldNames, @Nullable Object... values) {
+        requireNonNull(fieldNames, "fieldNames");
+        if (fieldNames.length == 0
+                || values.length % fieldNames.length != 0
+                || values.length < fieldNames.length) {
+            throw new IllegalArgumentException(
+                    "Value count must be a positive multiple of field count");
+        }
+        final int rowCount = values.length / fieldNames.length;
+        for (Ord<@Nullable String> fieldName : Ord.zip(fieldNames)) {
+            if (allNull(values, fieldName.i, fieldNames.length)) {
+                throw new IllegalArgumentException(
+                        "All values of field '"
+                                + fieldName.e
+                                + "' (field index "
+                                + fieldName.i
+                                + ")"
+                                + " are null; cannot deduce type");
+            }
+        }
+        final ImmutableList<ImmutableList<RexLiteral>> tupleList =
+                tupleList(fieldNames.length, values);
+        assert tupleList.size() == rowCount;
+        final List<String> fieldNameList =
+                Util.transformIndexed(
+                        Arrays.asList(fieldNames),
+                        (name, i) -> name != null ? name : SqlUtil.deriveAliasFromOrdinal(i));
+        return values(tupleList, fieldNameList);
+    }
+
+    private RelBuilder values(List<? extends List<RexLiteral>> tupleList, List<String> fieldNames) {
+        final RelDataTypeFactory typeFactory = cluster.getTypeFactory();
+        final RelDataTypeFactory.Builder builder = typeFactory.builder();
+        Ord.forEach(
+                fieldNames,
+                (fieldName, i) -> {
+                    final RelDataType type =
+                            typeFactory.leastRestrictive(
+                                    new AbstractList<RelDataType>() {
+                                        @Override
+                                        public RelDataType get(int index) {
+                                            return tupleList.get(index).get(i).getType();
+                                        }
+
+                                        @Override
+                                        public int size() {
+                                            return tupleList.size();
+                                        }
+                                    });
+                    assert type != null : "can't infer type for field " + i + ", " + fieldName;
+                    builder.add(fieldName, type);
+                });
+        final RelDataType rowType = builder.build();
+        return values(tupleList, rowType);
+    }
+
+    private ImmutableList<ImmutableList<RexLiteral>> tupleList(
+            int columnCount, @Nullable Object[] values) {
+        final ImmutableList.Builder<ImmutableList<RexLiteral>> listBuilder =
+                ImmutableList.builder();
+        final List<RexLiteral> valueList = new ArrayList<>();
+        for (int i = 0; i < values.length; i++) {
+            Object value = values[i];
+            valueList.add(literal(value));
+            if ((i + 1) % columnCount == 0) {
+                listBuilder.add(ImmutableList.copyOf(valueList));
+                valueList.clear();
+            }
+        }
+        return listBuilder.build();
+    }
+
+    /** Returns whether all values for a given column are null. */
+    private static boolean allNull(@Nullable Object[] values, int column, int columnCount) {
+        for (int i = column; i < values.length; i += columnCount) {
+            if (values[i] != null) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Creates a relational expression that reads from an input and throws all of the rows away.
+     *
+     * <p>Note that this method always pops one relational expression from the stack. {@code
+     * values}, in contrast, does not pop any relational expressions, and always produces a leaf.
+     *
+     * <p>The default implementation creates a {@link Values} with the same specified row type and
+     * aliases as the input, and ignores the input entirely. But schema-on-query systems such as
+     * Drill might override this method to create a relation expression that retains the input, just
+     * to read its schema.
+     */
+    public RelBuilder empty() {
+        final Frame frame = stack.pop();
+        final RelNode values =
+                struct.valuesFactory.createValues(
+                        cluster, frame.rel.getRowType(), ImmutableList.of());
+        stack.push(new Frame(values, frame.fields));
+        return this;
+    }
+
+    /**
+     * Creates a {@link Values} with a specified row type.
+     *
+     * <p>This method can handle cases that {@link #values(String[], Object...)} cannot, such as all
+     * values of a column being null, or there being zero rows.
+     *
+     * @param rowType Row type
+     * @param columnValues Values
+     */
+    public RelBuilder values(RelDataType rowType, Object... columnValues) {
+        final ImmutableList<ImmutableList<RexLiteral>> tupleList =
+                tupleList(rowType.getFieldCount(), columnValues);
+        RelNode values =
+                struct.valuesFactory.createValues(
+                        cluster, rowType, ImmutableList.copyOf(tupleList));
+        push(values);
+        return this;
+    }
+
+    /**
+     * Creates a {@link Values} with a specified row type.
+     *
+     * <p>This method can handle cases that {@link #values(String[], Object...)} cannot, such as all
+     * values of a column being null, or there being zero rows.
+     *
+     * @param tupleList Tuple list
+     * @param rowType Row type
+     */
+    public RelBuilder values(Iterable<? extends List<RexLiteral>> tupleList, RelDataType rowType) {
+        RelNode values = struct.valuesFactory.createValues(cluster, rowType, copy(tupleList));
+        push(values);
+        return this;
+    }
+
+    /**
+     * Creates a {@link Values} with a specified row type and zero rows.
+     *
+     * @param rowType Row type
+     */
+    public RelBuilder values(RelDataType rowType) {
+        return values(ImmutableList.<ImmutableList<RexLiteral>>of(), rowType);
+    }
+
+    /**
+     * Converts an iterable of lists into an immutable list of immutable lists with the same
+     * contents. Returns the same object if possible.
+     */
+    private static <E> ImmutableList<ImmutableList<E>> copy(Iterable<? extends List<E>> tupleList) {
+        final ImmutableList.Builder<ImmutableList<E>> builder = ImmutableList.builder();
+        int changeCount = 0;
+        for (List<E> literals : tupleList) {
+            final ImmutableList<E> literals2 = ImmutableList.copyOf(literals);
+            builder.add(literals2);
+            if (literals != literals2) {
+                ++changeCount;
+            }
+        }
+        if (changeCount == 0 && tupleList instanceof ImmutableList) {
+            // don't make a copy if we don't have to
+            //noinspection unchecked
+            return (ImmutableList<ImmutableList<E>>) tupleList;
+        }
+        return builder.build();
+    }
+
+    /** Creates a limit without a sort. */
+    public RelBuilder limit(int offset, int fetch) {
+        return sortLimit(offset, fetch, ImmutableList.of());
+    }
+
+    /** Creates an Exchange by distribution. */
+    public RelBuilder exchange(RelDistribution distribution) {
+        RelNode exchange = struct.exchangeFactory.createExchange(peek(), distribution);
+        replaceTop(exchange);
+        return this;
+    }
+
+    /** Creates a SortExchange by distribution and collation. */
+    public RelBuilder sortExchange(RelDistribution distribution, RelCollation collation) {
+        RelNode exchange =
+                struct.sortExchangeFactory.createSortExchange(peek(), distribution, collation);
+        replaceTop(exchange);
+        return this;
+    }
+
+    /**
+     * Creates a {@link Sort} by field ordinals.
+     *
+     * <p>Negative fields mean descending: -1 means field(0) descending, -2 means field(1)
+     * descending, etc.
+     */
+    public RelBuilder sort(int... fields) {
+        final ImmutableList.Builder<RexNode> builder = ImmutableList.builder();
+        for (int field : fields) {
+            builder.add(field < 0 ? desc(field(-field - 1)) : field(field));
+        }
+        return sortLimit(-1, -1, builder.build());
+    }
+
+    /** Creates a {@link Sort} by expressions. */
+    public RelBuilder sort(RexNode... nodes) {
+        return sortLimit(-1, -1, ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a {@link Sort} by expressions. */
+    public RelBuilder sort(Iterable<? extends RexNode> nodes) {
+        return sortLimit(-1, -1, nodes);
+    }
+
+    /** Creates a {@link Sort} by expressions, with limit and offset. */
+    public RelBuilder sortLimit(int offset, int fetch, RexNode... nodes) {
+        return sortLimit(offset, fetch, ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a {@link Sort} by specifying collations. */
+    public RelBuilder sort(RelCollation collation) {
+        final RelNode sort = struct.sortFactory.createSort(peek(), collation, null, null);
+        replaceTop(sort);
+        return this;
+    }
+
+    /**
+     * Creates a {@link Sort} by a list of expressions, with limit and offset.
+     *
+     * @param offset Number of rows to skip; non-positive means don't skip any
+     * @param fetch Maximum number of rows to fetch; negative means no limit
+     * @param nodes Sort expressions
+     */
+    public RelBuilder sortLimit(int offset, int fetch, Iterable<? extends RexNode> nodes) {
+        final @Nullable RexNode offsetNode = offset <= 0 ? null : literal(offset);
+        final @Nullable RexNode fetchNode = fetch < 0 ? null : literal(fetch);
+        return sortLimit(offsetNode, fetchNode, nodes);
+    }
+
+    /**
+     * Creates a {@link Sort} by a list of expressions, with limitNode and offsetNode.
+     *
+     * @param offsetNode RexLiteral means number of rows to skip is deterministic, RexDynamicParam
+     *     means number of rows to skip is dynamic.
+     * @param fetchNode RexLiteral means maximum number of rows to fetch is deterministic,
+     *     RexDynamicParam mean maximum number is dynamic.
+     * @param nodes Sort expressions
+     */
+    public RelBuilder sortLimit(
+            @Nullable RexNode offsetNode,
+            @Nullable RexNode fetchNode,
+            Iterable<? extends RexNode> nodes) {
+        if (offsetNode != null) {
+            if (!(offsetNode instanceof RexLiteral || offsetNode instanceof RexDynamicParam)) {
+                throw new IllegalArgumentException(
+                        "OFFSET node must be RexLiteral or RexDynamicParam");
+            }
+        }
+        if (fetchNode != null) {
+            if (!(fetchNode instanceof RexLiteral || fetchNode instanceof RexDynamicParam)) {
+                throw new IllegalArgumentException(
+                        "FETCH node must be RexLiteral or RexDynamicParam");
+            }
+        }
+
+        final Registrar registrar = new Registrar(fields(), ImmutableList.of());
+        final List<RelFieldCollation> fieldCollations = registrar.registerFieldCollations(nodes);
+        final int fetch = fetchNode instanceof RexLiteral ? RexLiteral.intValue(fetchNode) : -1;
+        if (offsetNode == null && fetch == 0 && config.simplifyLimit()) {
+            return empty();
+        }
+        if (offsetNode == null && fetchNode == null && fieldCollations.isEmpty()) {
+            return this; // sort is trivial
+        }
+
+        if (fieldCollations.isEmpty()) {
+            assert registrar.addedFieldCount() == 0;
+            RelNode top = peek();
+            if (top instanceof Sort) {
+                final Sort sort2 = (Sort) top;
+                if (sort2.offset == null && sort2.fetch == null) {
+                    replaceTop(sort2.getInput());
+                    final RelNode sort =
+                            struct.sortFactory.createSort(
+                                    peek(), sort2.collation, offsetNode, fetchNode);
+                    replaceTop(sort);
+                    return this;
+                }
+            }
+            if (top instanceof Project) {
+                final Project project = (Project) top;
+                if (project.getInput() instanceof Sort) {
+                    final Sort sort2 = (Sort) project.getInput();
+                    if (sort2.offset == null && sort2.fetch == null) {
+                        final RelNode sort =
+                                struct.sortFactory.createSort(
+                                        sort2.getInput(), sort2.collation, offsetNode, fetchNode);
+                        replaceTop(
+                                struct.projectFactory.createProject(
+                                        sort,
+                                        project.getHints(),
+                                        project.getProjects(),
+                                        Pair.right(project.getNamedProjects()),
+                                        project.getVariablesSet()));
+                        return this;
+                    }
+                }
+            }
+        }
+        if (registrar.addedFieldCount() > 0) {
+            project(registrar.extraNodes);
+        }
+        final RelNode sort =
+                struct.sortFactory.createSort(
+                        peek(), RelCollations.of(fieldCollations), offsetNode, fetchNode);
+        replaceTop(sort);
+        if (registrar.addedFieldCount() > 0) {
+            project(registrar.originalExtraNodes);
+        }
+        return this;
+    }
+
+    private static RelFieldCollation collation(
+            RexNode node,
+            RelFieldCollation.Direction direction,
+            RelFieldCollation.NullDirection nullDirection,
+            List<RexNode> extraNodes) {
+        switch (node.getKind()) {
+            case INPUT_REF:
+                return new RelFieldCollation(
+                        ((RexInputRef) node).getIndex(),
+                        direction,
+                        Util.first(nullDirection, direction.defaultNullDirection()));
+            case DESCENDING:
+                return collation(
+                        ((RexCall) node).getOperands().get(0),
+                        RelFieldCollation.Direction.DESCENDING,
+                        nullDirection,
+                        extraNodes);
+            case NULLS_FIRST:
+                return collation(
+                        ((RexCall) node).getOperands().get(0),
+                        direction,
+                        RelFieldCollation.NullDirection.FIRST,
+                        extraNodes);
+            case NULLS_LAST:
+                return collation(
+                        ((RexCall) node).getOperands().get(0),
+                        direction,
+                        RelFieldCollation.NullDirection.LAST,
+                        extraNodes);
+            default:
+                final int fieldIndex = extraNodes.size();
+                extraNodes.add(node);
+                return new RelFieldCollation(
+                        fieldIndex,
+                        direction,
+                        Util.first(nullDirection, direction.defaultNullDirection()));
+        }
+    }
+
+    private static RexFieldCollation rexCollation(
+            RexNode node,
+            RelFieldCollation.Direction direction,
+            RelFieldCollation.NullDirection nullDirection) {
+        switch (node.getKind()) {
+            case DESCENDING:
+                return rexCollation(
+                        ((RexCall) node).operands.get(0),
+                        RelFieldCollation.Direction.DESCENDING,
+                        nullDirection);
+            case NULLS_LAST:
+                return rexCollation(
+                        ((RexCall) node).operands.get(0),
+                        direction,
+                        RelFieldCollation.NullDirection.LAST);
+            case NULLS_FIRST:
+                return rexCollation(
+                        ((RexCall) node).operands.get(0),
+                        direction,
+                        RelFieldCollation.NullDirection.FIRST);
+            default:
+                final Set<SqlKind> flags = EnumSet.noneOf(SqlKind.class);
+                if (direction == RelFieldCollation.Direction.DESCENDING) {
+                    flags.add(SqlKind.DESCENDING);
+                }
+                if (nullDirection == RelFieldCollation.NullDirection.FIRST) {
+                    flags.add(SqlKind.NULLS_FIRST);
+                }
+                if (nullDirection == RelFieldCollation.NullDirection.LAST) {
+                    flags.add(SqlKind.NULLS_LAST);
+                }
+                return new RexFieldCollation(node, flags);
+        }
+    }
+
+    /**
+     * Creates a projection that converts the current relational expression's output to a desired
+     * row type.
+     *
+     * <p>The desired row type and the row type to be converted must have the same number of fields.
+     *
+     * @param castRowType row type after cast
+     * @param rename if true, use field names from castRowType; if false, preserve field names from
+     *     rel
+     */
+    public RelBuilder convert(RelDataType castRowType, boolean rename) {
+        final RelNode r = build();
+        final RelNode r2 = RelOptUtil.createCastRel(r, castRowType, rename, struct.projectFactory);
+        push(r2);
+        return this;
+    }
+
+    // BEGIN FLINK MODIFICATION
+
+    /**
+     * Creates a projection that converts the current relational expression's output to a desired
+     * row type.
+     *
+     * <p>The desired row type and the row type to be converted must have the same number of fields.
+     *
+     * @param castRowType row type after cast
+     * @param rename if true, use field names from castRowType; if false, preserve field names from
+     * @param canBeIgnoredForMultiJoin if true, set simplify flag for project rel node
+     */
+    public RelBuilder convert(
+            RelDataType castRowType, boolean rename, boolean canBeIgnoredForMultiJoin) {
+        final RelNode r = build();
+        final RelNode r2 = RelOptUtil.createCastRel(r, castRowType, rename, struct.projectFactory);
+        if (canBeIgnoredForMultiJoin && r2 instanceof LogicalProject) {
+            ((LogicalProject) r2).setIgnoreForMultiJoin(true);
+        }
+        push(r2);
+        return this;
+    }
+
+    // END FLINK MODIFICATION

Review Comment:
   why should we have it here and do not have in Calcite as a fix?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [FLINK-39285][table] Ignore project rel node generated by FlinkFilterJoinRule in JoinToMultiJoinRule [flink]

Posted by "ldadima (via GitHub)" <gi...@apache.org>.
ldadima commented on PR #27815:
URL: https://github.com/apache/flink/pull/27815#issuecomment-4241246895

   @snuyanzin @gustavodemorais 
   Can you check the pull request, please?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [FLINK-39285][table] Ignore project rel node generated by FlinkFilterJoinRule in JoinToMultiJoinRule [flink]

Posted by "ldadima (via GitHub)" <gi...@apache.org>.
ldadima commented on code in PR #27815:
URL: https://github.com/apache/flink/pull/27815#discussion_r3584337961


##########
flink-table/flink-table-planner/src/main/java/org/apache/calcite/tools/RelBuilder.java:
##########
@@ -0,0 +1,5500 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.calcite.tools;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedMultiset;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multiset;
+import com.google.common.collect.Sets;
+import org.apache.calcite.linq4j.Ord;
+import org.apache.calcite.linq4j.function.Experimental;
+import org.apache.calcite.plan.Context;
+import org.apache.calcite.plan.Contexts;
+import org.apache.calcite.plan.Convention;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptPredicateList;
+import org.apache.calcite.plan.RelOptSamplingParameters;
+import org.apache.calcite.plan.RelOptSchema;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.plan.ViewExpanders;
+import org.apache.calcite.prepare.RelOptTableImpl;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.RelDistribution;
+import org.apache.calcite.rel.RelFieldCollation;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Aggregate;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.core.Correlate;
+import org.apache.calcite.rel.core.CorrelationId;
+import org.apache.calcite.rel.core.Filter;
+import org.apache.calcite.rel.core.Intersect;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.Match;
+import org.apache.calcite.rel.core.Minus;
+import org.apache.calcite.rel.core.Project;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.core.RepeatUnion;
+import org.apache.calcite.rel.core.Sample;
+import org.apache.calcite.rel.core.Snapshot;
+import org.apache.calcite.rel.core.Sort;
+import org.apache.calcite.rel.core.Spool;
+import org.apache.calcite.rel.core.TableFunctionScan;
+import org.apache.calcite.rel.core.TableScan;
+import org.apache.calcite.rel.core.TableSpool;
+import org.apache.calcite.rel.core.Uncollect;
+import org.apache.calcite.rel.core.Union;
+import org.apache.calcite.rel.core.Values;
+import org.apache.calcite.rel.hint.Hintable;
+import org.apache.calcite.rel.hint.RelHint;
+import org.apache.calcite.rel.logical.LogicalFilter;
+import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rel.metadata.RelColumnMapping;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.AggregateRemoveRule;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rel.type.RelDataTypeFieldImpl;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexCallBinding;
+import org.apache.calcite.rex.RexCorrelVariable;
+import org.apache.calcite.rex.RexDynamicParam;
+import org.apache.calcite.rex.RexExecutor;
+import org.apache.calcite.rex.RexFieldCollation;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.rex.RexSimplify;
+import org.apache.calcite.rex.RexSubQuery;
+import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.rex.RexWindowBound;
+import org.apache.calcite.rex.RexWindowBounds;
+import org.apache.calcite.runtime.Hook;
+import org.apache.calcite.runtime.ImmutablePairList;
+import org.apache.calcite.runtime.PairList;
+import org.apache.calcite.schema.TransientTable;
+import org.apache.calcite.schema.impl.ListTransientTable;
+import org.apache.calcite.sql.SqlAggFunction;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlStaticAggFunction;
+import org.apache.calcite.sql.SqlUtil;
+import org.apache.calcite.sql.SqlWindow;
+import org.apache.calcite.sql.fun.SqlCountAggFunction;
+import org.apache.calcite.sql.fun.SqlInternalOperators;
+import org.apache.calcite.sql.fun.SqlLikeOperator;
+import org.apache.calcite.sql.fun.SqlQuantifyOperator;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.SqlReturnTypeInference;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.type.TableFunctionReturnTypeInference;
+import org.apache.calcite.sql.validate.SqlValidatorUtil;
+import org.apache.calcite.sql2rel.SqlToRelConverter;
+import org.apache.calcite.util.DateString;
+import org.apache.calcite.util.Holder;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.ImmutableIntList;
+import org.apache.calcite.util.ImmutableNullableList;
+import org.apache.calcite.util.Litmus;
+import org.apache.calcite.util.NlsString;
+import org.apache.calcite.util.Optionality;
+import org.apache.calcite.util.Pair;
+import org.apache.calcite.util.Util;
+import org.apache.calcite.util.mapping.Mapping;
+import org.apache.calcite.util.mapping.Mappings;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.immutables.value.Value;
+
+import java.math.BigDecimal;
+import java.util.AbstractList;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static java.util.Objects.requireNonNull;
+import static org.apache.calcite.linq4j.Nullness.castNonNull;
+import static org.apache.calcite.rel.rules.AggregateRemoveRule.canFlattenStatic;
+import static org.apache.calcite.sql.SqlKind.UNION;
+import static org.apache.calcite.util.Static.RESOURCE;
+
+/**
+ * Builder for relational expressions.
+ *
+ * <p>{@code RelBuilder} does not make possible anything that you could not also accomplish by
+ * calling the factory methods of the particular relational expression. But it makes common tasks
+ * more straightforward and concise.
+ *
+ * <p>{@code RelBuilder} uses factories to create relational expressions. By default, it uses the
+ * default factories, which create logical relational expressions ({@link LogicalFilter}, {@link
+ * LogicalProject} and so forth). But you could override those factories so that, say, {@code
+ * filter} creates instead a {@code HiveFilter}.
+ *
+ * <p>It is not thread-safe.
+ */
+@Value.Enclosing
+public class RelBuilder {
+    protected final RelOptCluster cluster;
+    protected final @Nullable RelOptSchema relOptSchema;
+    private final Deque<Frame> stack = new ArrayDeque<>();
+    private RexSimplify simplifier;
+    private final Config config;
+    private final RelOptTable.ViewExpander viewExpander;
+    private RelFactories.Struct struct;
+
+    protected RelBuilder(
+            @Nullable Context context, RelOptCluster cluster, @Nullable RelOptSchema relOptSchema) {
+        this.cluster = cluster;
+        this.relOptSchema = relOptSchema;
+        if (context == null) {
+            context = Contexts.EMPTY_CONTEXT;
+        }
+        this.config = getConfig(context);
+        this.viewExpander = getViewExpander(cluster, context);
+        this.struct = requireNonNull(RelFactories.Struct.fromContext(context));
+        final RexExecutor executor =
+                context.maybeUnwrap(RexExecutor.class)
+                        .orElse(Util.first(cluster.getPlanner().getExecutor(), RexUtil.EXECUTOR));
+        final RelOptPredicateList predicates = RelOptPredicateList.EMPTY;
+        this.simplifier = new RexSimplify(cluster.getRexBuilder(), predicates, executor);
+    }
+
+    /**
+     * Derives the view expander {@link org.apache.calcite.plan.RelOptTable.ViewExpander} to be used
+     * for this RelBuilder.
+     *
+     * <p>The ViewExpander instance is used for expanding views in the default table scan factory
+     * {@code RelFactories.TableScanFactoryImpl}. You can also define a new table scan factory in
+     * the {@code struct} to override the whole table scan creation.
+     *
+     * <p>The default view expander does not support expanding views.
+     */
+    private static RelOptTable.ViewExpander getViewExpander(
+            RelOptCluster cluster, Context context) {
+        return context.maybeUnwrap(RelOptTable.ViewExpander.class)
+                .orElseGet(() -> ViewExpanders.simpleContext(cluster));
+    }
+
+    /**
+     * Derives the Config to be used for this RelBuilder.
+     *
+     * <p>Overrides {@link RelBuilder.Config#simplify} if {@link Hook#REL_BUILDER_SIMPLIFY} is set.
+     */
+    private static Config getConfig(Context context) {
+        final Config config = context.maybeUnwrap(Config.class).orElse(Config.DEFAULT);
+        boolean simplify = Hook.REL_BUILDER_SIMPLIFY.get(config.simplify());
+        return config.withSimplify(simplify);
+    }
+
+    /** Creates a RelBuilder. */
+    public static RelBuilder create(FrameworkConfig config) {
+        return Frameworks.withPrepare(
+                config,
+                (cluster, relOptSchema, rootSchema, statement) ->
+                        new RelBuilder(config.getContext(), cluster, relOptSchema));
+    }
+
+    /**
+     * Creates a copy of this RelBuilder, with the same state as this, applying a transform to the
+     * config.
+     */
+    public RelBuilder transform(UnaryOperator<Config> transform) {
+        final Context context = Contexts.of(struct, transform.apply(config));
+        return new RelBuilder(context, cluster, relOptSchema);
+    }
+
+    /**
+     * Performs an action on this RelBuilder.
+     *
+     * <p>For example, consider the following code:
+     *
+     * <blockquote>
+     *
+     * <pre>
+     *   RelNode filterAndRename(RelBuilder relBuilder, RelNode rel,
+     *       RexNode condition, List&lt;String&gt; fieldNames) {
+     *     relBuilder.push(rel)
+     *         .filter(condition);
+     *     if (fieldNames != null) {
+     *       relBuilder.rename(fieldNames);
+     *     }
+     *     return relBuilder
+     *         .build();</pre>
+     *
+     * </blockquote>
+     *
+     * <p>The pipeline is disrupted by the 'if'. The {@code let} method allows you to perform the
+     * flow as a single pipeline:
+     *
+     * <blockquote>
+     *
+     * <pre>
+     *   RelNode filterAndRename(RelBuilder relBuilder, RelNode rel,
+     *       RexNode condition, List&lt;String&gt; fieldNames) {
+     *     return relBuilder.push(rel)
+     *         .filter(condition)
+     *         .let(r -&gt; fieldNames == null ? r : r.rename(fieldNames))
+     *         .build();</pre>
+     *
+     * </blockquote>
+     *
+     * <p>In pipelined cases such as this one, the lambda must return this RelBuilder. But {@code
+     * let} return values of other types.
+     */
+    public <R> R let(Function<RelBuilder, R> consumer) {
+        return consumer.apply(this);
+    }
+
+    /**
+     * Converts this RelBuilder to a string. The string is the string representation of all of the
+     * RelNodes on the stack.
+     */
+    @Override
+    public String toString() {
+        return stack.stream()
+                .map(frame -> RelOptUtil.toString(frame.rel))
+                .collect(Collectors.joining(""));
+    }
+
+    /** Returns the type factory. */
+    public RelDataTypeFactory getTypeFactory() {
+        return cluster.getTypeFactory();
+    }
+
+    /**
+     * Returns new RelBuilder that adopts the convention provided. RelNode will be created with such
+     * convention if corresponding factory is provided.
+     */
+    public RelBuilder adoptConvention(Convention convention) {
+        this.struct = convention.getRelFactories();
+        return this;
+    }
+
+    /** Returns the builder for {@link RexNode} expressions. */
+    public RexBuilder getRexBuilder() {
+        return cluster.getRexBuilder();
+    }
+
+    /**
+     * Creates a {@link RelBuilderFactory}, a partially-created RelBuilder. Just add a {@link
+     * RelOptCluster} and a {@link RelOptSchema}
+     */
+    public static RelBuilderFactory proto(final Context context) {
+        return (cluster, schema) -> new RelBuilder(context, cluster, schema);
+    }
+
+    /** Creates a {@link RelBuilderFactory} that uses a given set of factories. */
+    public static RelBuilderFactory proto(Object... factories) {
+        return proto(Contexts.of(factories));
+    }
+
+    public RelOptCluster getCluster() {
+        return cluster;
+    }
+
+    public @Nullable RelOptSchema getRelOptSchema() {
+        return relOptSchema;
+    }
+
+    public RelFactories.TableScanFactory getScanFactory() {
+        return struct.scanFactory;
+    }
+
+    // Methods for manipulating the stack
+
+    /**
+     * Adds a relational expression to be the input to the next relational expression constructed.
+     *
+     * <p>This method is usual when you want to weave in relational expressions that are not
+     * supported by the builder. If, while creating such expressions, you need to use previously
+     * built expressions as inputs, call {@link #build()} to pop those inputs.
+     */
+    public RelBuilder push(RelNode node) {
+        stack.push(new Frame(node));
+        return this;
+    }
+
+    /** Adds a rel node to the top of the stack while preserving the field names and aliases. */
+    private void replaceTop(RelNode node) {
+        final Frame frame = stack.pop();
+        stack.push(new Frame(node, frame.fields));
+    }
+
+    /** Pushes a collection of relational expressions. */
+    public RelBuilder pushAll(Iterable<? extends RelNode> nodes) {
+        for (RelNode node : nodes) {
+            push(node);
+        }
+        return this;
+    }
+
+    /** Returns the size of the stack. */
+    public int size() {
+        return stack.size();
+    }
+
+    /**
+     * Returns the final relational expression.
+     *
+     * <p>Throws if the stack is empty.
+     */
+    public RelNode build() {
+        return stack.pop().rel;
+    }
+
+    /** Returns the relational expression at the top of the stack, but does not remove it. */
+    public RelNode peek() {
+        return castNonNull(peek_()).rel;
+    }
+
+    private @Nullable Frame peek_() {
+        return stack.peek();
+    }
+
+    /**
+     * Returns the relational expression {@code n} positions from the top of the stack, but does not
+     * remove it.
+     */
+    public RelNode peek(int n) {
+        return peek_(n).rel;
+    }
+
+    private Frame peek_(int n) {
+        if (n == 0) {
+            // more efficient than starting an iterator
+            return Objects.requireNonNull(stack.peek(), "stack.peek");
+        }
+        return Iterables.get(stack, n);
+    }
+
+    /**
+     * Returns the relational expression {@code n} positions from the top of the stack, but does not
+     * remove it.
+     */
+    public RelNode peek(int inputCount, int inputOrdinal) {
+        return peek_(inputCount, inputOrdinal).rel;
+    }
+
+    private Frame peek_(int inputCount, int inputOrdinal) {
+        return peek_(inputCount - 1 - inputOrdinal);
+    }
+
+    /**
+     * Returns the number of fields in all inputs before (to the left of) the given input.
+     *
+     * @param inputCount Number of inputs
+     * @param inputOrdinal Input ordinal
+     */
+    private int inputOffset(int inputCount, int inputOrdinal) {
+        int offset = 0;
+        for (int i = 0; i < inputOrdinal; i++) {
+            offset += peek(inputCount, i).getRowType().getFieldCount();
+        }
+        return offset;
+    }
+
+    /** Evaluates an expression with a relational expression temporarily on the stack. */
+    public <E> E with(RelNode r, Function<RelBuilder, E> fn) {
+        try {
+            push(r);
+            return fn.apply(this);
+        } finally {
+            stack.pop();
+        }
+    }
+
+    /** Performs an action with a temporary simplifier. */
+    public <E> E withSimplifier(
+            BiFunction<RelBuilder, RexSimplify, RexSimplify> simplifierTransform,
+            Function<RelBuilder, E> fn) {
+        final RexSimplify previousSimplifier = this.simplifier;
+        try {
+            this.simplifier = simplifierTransform.apply(this, previousSimplifier);
+            return fn.apply(this);
+        } finally {
+            this.simplifier = previousSimplifier;
+        }
+    }
+
+    /** Performs an action using predicates of the {@link #peek() current node} to simplify. */
+    public <E> E withPredicates(RelMetadataQuery mq, Function<RelBuilder, E> fn) {
+        final RelOptPredicateList predicates = mq.getPulledUpPredicates(peek());
+        return withSimplifier((r, s) -> s.withPredicates(predicates), fn);
+    }
+
+    // Methods that return scalar expressions
+
+    /** Creates a literal (constant expression). */
+    public RexLiteral literal(@Nullable Object value) {
+        final RexBuilder rexBuilder = cluster.getRexBuilder();
+        if (value == null) {
+            final RelDataType type = getTypeFactory().createSqlType(SqlTypeName.NULL);
+            return rexBuilder.makeNullLiteral(type);
+        } else if (value instanceof Boolean) {
+            return rexBuilder.makeLiteral((Boolean) value);
+        } else if (value instanceof BigDecimal) {
+            return rexBuilder.makeExactLiteral((BigDecimal) value);
+        } else if (value instanceof Float || value instanceof Double) {
+            return rexBuilder.makeApproxLiteral(BigDecimal.valueOf(((Number) value).doubleValue()));
+        } else if (value instanceof Number) {
+            return rexBuilder.makeExactLiteral(BigDecimal.valueOf(((Number) value).longValue()));
+        } else if (value instanceof String) {
+            return rexBuilder.makeLiteral((String) value);
+        } else if (value instanceof Enum) {
+            return rexBuilder.makeLiteral(
+                    value, getTypeFactory().createSqlType(SqlTypeName.SYMBOL));
+        } else if (value instanceof DateString) {
+            return rexBuilder.makeDateLiteral((DateString) value);
+        } else {
+            throw new IllegalArgumentException(
+                    "cannot convert " + value + " (" + value.getClass() + ") to a constant");
+        }
+    }
+
+    @Deprecated // to be removed before 2.0
+    public RelBuilder variable(Holder<RexCorrelVariable> v) {
+        return variable(v::set);
+    }
+
+    /**
+     * Creates a correlation variable for the current input, and writes it into a Consumer.
+     *
+     * <p>Often the Consumer will write to a {@link Holder}, as follows:
+     *
+     * <blockquote>
+     *
+     * {@code RelBuilder builder; builder.scan("EMP") .variable(v::set)
+     * .filter(builder.equals(builder.field(0), v.get())) }
+     *
+     * </blockquote>
+     */
+    public RelBuilder variable(Consumer<RexCorrelVariable> consumer) {
+        consumer.accept(
+                (RexCorrelVariable)
+                        getRexBuilder().makeCorrel(peek().getRowType(), cluster.createCorrel()));
+        return this;
+    }
+
+    /**
+     * Creates a reference to a field by name.
+     *
+     * <p>Equivalent to {@code field(1, 0, fieldName)}.
+     *
+     * @param fieldName Field name
+     */
+    public RexInputRef field(String fieldName) {
+        return field(1, 0, fieldName);
+    }
+
+    /**
+     * Creates a reference to a field of given input relational expression by name.
+     *
+     * @param inputCount Number of inputs
+     * @param inputOrdinal Input ordinal
+     * @param fieldName Field name
+     */
+    public RexInputRef field(int inputCount, int inputOrdinal, String fieldName) {
+        final Frame frame = peek_(inputCount, inputOrdinal);
+        final List<String> fieldNames = Pair.left(frame.fields());
+        int i = fieldNames.indexOf(fieldName);
+        if (i >= 0) {
+            return field(inputCount, inputOrdinal, i);
+        } else {
+            throw new IllegalArgumentException(
+                    "field [" + fieldName + "] not found; input fields are: " + fieldNames);
+        }
+    }
+
+    /**
+     * Creates a reference to an input field by ordinal.
+     *
+     * <p>Equivalent to {@code field(1, 0, ordinal)}.
+     *
+     * @param fieldOrdinal Field ordinal
+     */
+    public RexInputRef field(int fieldOrdinal) {
+        return (RexInputRef) field(1, 0, fieldOrdinal, false);
+    }
+
+    /**
+     * Creates a reference to a field of a given input relational expression by ordinal.
+     *
+     * @param inputCount Number of inputs
+     * @param inputOrdinal Input ordinal
+     * @param fieldOrdinal Field ordinal within input
+     */
+    public RexInputRef field(int inputCount, int inputOrdinal, int fieldOrdinal) {
+        return (RexInputRef) field(inputCount, inputOrdinal, fieldOrdinal, false);
+    }
+
+    /**
+     * As {@link #field(int, int, int)}, but if {@code alias} is true, the method may apply an alias
+     * to make sure that the field has the same name as in the input frame. If no alias is applied
+     * the expression is definitely a {@link RexInputRef}.
+     */
+    private RexNode field(int inputCount, int inputOrdinal, int fieldOrdinal, boolean alias) {
+        final Frame frame = peek_(inputCount, inputOrdinal);
+        final RelNode input = frame.rel;
+        final RelDataType rowType = input.getRowType();
+        if (fieldOrdinal < 0 || fieldOrdinal > rowType.getFieldCount()) {
+            throw new IllegalArgumentException(
+                    "field ordinal ["
+                            + fieldOrdinal
+                            + "] out of range; input fields are: "
+                            + rowType.getFieldNames());
+        }
+        final RelDataTypeField field = rowType.getFieldList().get(fieldOrdinal);
+        final int offset = inputOffset(inputCount, inputOrdinal);
+        final RexInputRef ref =
+                cluster.getRexBuilder().makeInputRef(field.getType(), offset + fieldOrdinal);
+        final RelDataTypeField aliasField = frame.fields().get(fieldOrdinal);
+        if (!alias || field.getName().equals(aliasField.getName())) {
+            return ref;
+        } else {
+            return alias(ref, aliasField.getName());
+        }
+    }
+
+    /**
+     * Creates a reference to a field of the current record which originated in a relation with a
+     * given alias.
+     */
+    public RexNode field(String alias, String fieldName) {
+        return field(1, alias, fieldName);
+    }
+
+    /**
+     * Creates a reference to a field which originated in a relation with the given alias. Searches
+     * for the relation starting at the top of the stack.
+     */
+    public RexNode field(int inputCount, String alias, String fieldName) {
+        requireNonNull(alias, "alias");
+        requireNonNull(fieldName, "fieldName");
+        for (int inputOrdinal = 0; inputOrdinal < inputCount; ++inputOrdinal) {
+            final Frame frame = peek_(inputOrdinal);
+            final List<ImmutableSet<String>> aliasSets = frame.fields.leftList();
+            final List<RelDataTypeField> fields = frame.fields.rightList();
+            for (int i = 0, n = frame.fields.size(); i < n; i++) {
+                // If alias and field name match, reference that field.
+                if (aliasSets.get(i).contains(alias) && fields.get(i).getName().equals(fieldName)) {
+                    return field(inputCount, inputCount - 1 - inputOrdinal, i);
+                }
+            }
+        }
+
+        // Not found. Build a message.
+        StringBuilder b =
+                new StringBuilder()
+                        .append("{alias=")
+                        .append(alias)
+                        .append(",fieldName=")
+                        .append(fieldName)
+                        .append("} field not found; fields are: ");
+        for (int inputOrdinal = 0; inputOrdinal < inputCount; ++inputOrdinal) {
+            final Frame frame = peek_(inputOrdinal);
+            frame.fields.forEach(
+                    (aliasSet, field) ->
+                            b.append(
+                                    String.format(
+                                            Locale.ROOT,
+                                            "{aliases=%s,fieldName=%s}",
+                                            aliasSet,
+                                            field.getName())));
+        }
+        throw new IllegalArgumentException(b.toString());
+    }
+
+    /** Returns a reference to a given field of a record-valued expression. */
+    public RexNode field(RexNode e, String name) {
+        return getRexBuilder().makeFieldAccess(e, name, false);
+    }
+
+    /** Returns references to the fields of the top input. */
+    public ImmutableList<RexNode> fields() {
+        return fields(1, 0);
+    }
+
+    /** Returns references to the fields of a given input. */
+    public ImmutableList<RexNode> fields(int inputCount, int inputOrdinal) {
+        final RelNode input = peek(inputCount, inputOrdinal);
+        final RelDataType rowType = input.getRowType();
+        final ImmutableList.Builder<RexNode> nodes = ImmutableList.builder();
+        for (int fieldOrdinal : Util.range(rowType.getFieldCount())) {
+            nodes.add(field(inputCount, inputOrdinal, fieldOrdinal));
+        }
+        return nodes.build();
+    }
+
+    /** Returns references to fields for a given collation. */
+    public ImmutableList<RexNode> fields(RelCollation collation) {
+        final ImmutableList.Builder<RexNode> nodes = ImmutableList.builder();
+        for (RelFieldCollation fieldCollation : collation.getFieldCollations()) {
+            RexNode node = field(fieldCollation.getFieldIndex());
+            switch (fieldCollation.direction) {
+                case DESCENDING:
+                    node = desc(node);
+                    break;
+                default:
+                    break;
+            }
+            switch (fieldCollation.nullDirection) {
+                case FIRST:
+                    node = nullsFirst(node);
+                    break;
+                case LAST:
+                    node = nullsLast(node);
+                    break;
+                default:
+                    break;
+            }
+            nodes.add(node);
+        }
+        return nodes.build();
+    }
+
+    /** Returns references to fields for a given list of input ordinals. */
+    public ImmutableList<RexNode> fields(List<? extends Number> ordinals) {
+        final ImmutableList.Builder<RexNode> nodes = ImmutableList.builder();
+        for (Number ordinal : ordinals) {
+            RexNode node = field(1, 0, ordinal.intValue(), false);
+            nodes.add(node);
+        }
+        return nodes.build();
+    }
+
+    /** Returns references to fields for a given bit set of input ordinals. */
+    public ImmutableList<RexNode> fields(ImmutableBitSet ordinals) {
+        return fields(ordinals.asList());
+    }
+
+    /** Returns references to fields identified by name. */
+    public ImmutableList<RexNode> fields(Iterable<String> fieldNames) {
+        final ImmutableList.Builder<RexNode> builder = ImmutableList.builder();
+        for (String fieldName : fieldNames) {
+            builder.add(field(fieldName));
+        }
+        return builder.build();
+    }
+
+    /** Returns references to fields identified by a mapping. */
+    public ImmutableList<RexNode> fields(Mappings.TargetMapping mapping) {
+        return fields(Mappings.asListNonNull(mapping));
+    }
+
+    /** Creates an access to a field by name. */
+    public RexNode dot(RexNode node, String fieldName) {
+        final RexBuilder builder = cluster.getRexBuilder();
+        return builder.makeFieldAccess(node, fieldName, true);
+    }
+
+    /** Creates an access to a field by ordinal. */
+    public RexNode dot(RexNode node, int fieldOrdinal) {
+        final RexBuilder builder = cluster.getRexBuilder();
+        return builder.makeFieldAccess(node, fieldOrdinal);
+    }
+
+    /** Creates a call to a scalar operator. */
+    public RexNode call(SqlOperator operator, RexNode... operands) {
+        return call(operator, ImmutableList.copyOf(operands));
+    }
+
+    /** Creates a call to a scalar operator. */
+    private RexCall call(SqlOperator operator, List<RexNode> operandList) {
+        switch (operator.getKind()) {
+            case LIKE:
+            case SIMILAR:
+                final SqlLikeOperator likeOperator = (SqlLikeOperator) operator;
+                if (likeOperator.isNegated()) {
+                    final SqlOperator notLikeOperator = likeOperator.not();
+                    return (RexCall) not(call(notLikeOperator, operandList));
+                }
+                break;
+            case BETWEEN:
+                assert operandList.size() == 3;
+                return (RexCall)
+                        between(operandList.get(0), operandList.get(1), operandList.get(2));
+            default:
+                break;
+        }
+        final RexBuilder builder = cluster.getRexBuilder();
+        final RelDataType type = builder.deriveReturnType(operator, operandList);
+        return (RexCall) builder.makeCall(type, operator, operandList);
+    }
+
+    /** Creates a call to a scalar operator. */
+    public RexNode call(SqlOperator operator, Iterable<? extends RexNode> operands) {
+        return call(operator, ImmutableList.copyOf(operands));
+    }
+
+    /**
+     * Creates an IN predicate with a list of values.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Emp")
+     *     .filter(b.in(b.field("deptno"), b.literal(10), b.literal(20)))
+     * }</pre>
+     *
+     * is equivalent to SQL
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Emp
+     * WHERE deptno IN (10, 20)
+     * }</pre>
+     */
+    public RexNode in(RexNode arg, RexNode... ranges) {
+        return in(arg, ImmutableList.copyOf(ranges));
+    }
+
+    /**
+     * Creates an IN predicate with a list of values.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Emps")
+     *     .filter(
+     *         b.in(b.field("deptno"),
+     *             Arrays.asList(b.literal(10), b.literal(20))))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Emps
+     * WHERE deptno IN (10, 20)
+     * }</pre>
+     */
+    public RexNode in(RexNode arg, Iterable<? extends RexNode> ranges) {
+        return getRexBuilder().makeIn(arg, ImmutableList.copyOf(ranges));
+    }
+
+    /** Creates an IN predicate with a sub-query. */
+    @Experimental
+    public RexSubQuery in(RelNode rel, Iterable<? extends RexNode> nodes) {
+        return RexSubQuery.in(rel, ImmutableList.copyOf(nodes));
+    }
+
+    /**
+     * Creates an IN predicate with a sub-query.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Emps")
+     *     .filter(
+     *         b.in(b.field("deptno"),
+     *             b2 -> b2.scan("Depts")
+     *                 .filter(
+     *                     b2.eq(b2.field("location"), b2.literal("Boston")))
+     *                 .project(b.field("deptno"))
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Emps
+     * WHERE deptno IN (SELECT deptno FROM Dept WHERE location = 'Boston')
+     * }</pre>
+     */
+    @Experimental
+    public RexNode in(RexNode arg, Function<RelBuilder, RelNode> f) {
+        final RelNode rel = f.apply(this);
+        return RexSubQuery.in(rel, ImmutableList.of(arg));
+    }
+
+    /**
+     * Creates a SOME (or ANY) predicate.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Emps")
+     *     .filter(
+     *         b.some(b.field("commission"),
+     *             SqlStdOperatorTable.GREATER_THAN,
+     *             b2 -> b2.scan("Emps")
+     *                 .filter(
+     *                     b2.eq(b2.field("job"), b2.literal("Manager")))
+     *                 .project(b2.field("sal"))
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Emps
+     * WHERE commission > SOME (SELECT sal FROM Emps WHERE job = 'Manager')
+     * }</pre>
+     *
+     * <p>or (since {@code SOME} and {@code ANY} are synonyms) the SQL
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Emps
+     * WHERE commission > ANY (SELECT sal FROM Emps WHERE job = 'Manager')
+     * }</pre>
+     */
+    @Experimental
+    public RexSubQuery some(RexNode node, SqlOperator op, Function<RelBuilder, RelNode> f) {
+        return some_(node, op.kind, f);
+    }
+
+    private RexSubQuery some_(RexNode node, SqlKind kind, Function<RelBuilder, RelNode> f) {
+        final RelNode rel = f.apply(this);
+        final SqlQuantifyOperator quantifyOperator = SqlStdOperatorTable.some(kind);
+        return RexSubQuery.some(rel, ImmutableList.of(node), quantifyOperator);
+    }
+
+    /**
+     * Creates an ALL predicate.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Emps")
+     *     .filter(
+     *         b.all(b.field("commission"),
+     *             SqlStdOperatorTable.GREATER_THAN,
+     *             b2 -> b2.scan("Emps")
+     *                 .filter(
+     *                     b2.eq(b2.field("job"), b2.literal("Manager")))
+     *                 .project(b2.field("sal"))
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Emps
+     * WHERE commission > ALL (SELECT sal FROM Emps WHERE job = 'Manager')
+     * }</pre>
+     *
+     * <p>Calcite translates {@code ALL} predicates to {@code NOT SOME}. The following SQL is
+     * equivalent to the previous:
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Emps
+     * WHERE NOT (commission <= SOME (SELECT sal FROM Emps WHERE job = 'Manager'))
+     * }</pre>
+     */
+    @Experimental
+    public RexNode all(RexNode node, SqlOperator op, Function<RelBuilder, RelNode> f) {
+        return not(some_(node, op.kind.negateNullSafe(), f));
+    }
+
+    /**
+     * Creates an EXISTS predicate.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Depts")
+     *     .filter(
+     *         b.exists(b2 ->
+     *             b2.scan("Emps")
+     *                 .filter(
+     *                     b2.eq(b2.field("job"), b2.literal("Manager")))
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Depts
+     * WHERE EXISTS (SELECT 1 FROM Emps WHERE job = 'Manager')
+     * }</pre>
+     */
+    @Experimental
+    public RexSubQuery exists(Function<RelBuilder, RelNode> f) {
+        final RelNode rel = f.apply(this);
+        return RexSubQuery.exists(rel);
+    }
+
+    /**
+     * Creates a UNIQUE predicate.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Depts")
+     *     .filter(
+     *         b.exists(b2 ->
+     *             b2.scan("Emps")
+     *                 .filter(
+     *                     b2.eq(b2.field("job"), b2.literal("Manager")))
+     *                 .project(b2.field("deptno")
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT *
+     * FROM Depts
+     * WHERE UNIQUE (SELECT deptno FROM Emps WHERE job = 'Manager')
+     * }</pre>
+     */
+    @Experimental
+    public RexSubQuery unique(Function<RelBuilder, RelNode> f) {
+        final RelNode rel = f.apply(this);
+        return RexSubQuery.unique(rel);
+    }
+
+    /**
+     * Creates a scalar sub-query.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Depts")
+     *     .project(
+     *         b.field("deptno")
+     *         b.scalarQuery(b2 ->
+     *             b2.scan("Emps")
+     *                 .aggregate(
+     *                     b2.eq(b2.field("job"), b2.literal("Manager")))
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT deptno, (SELECT MAX(sal) FROM Emps)
+     * FROM Depts
+     * }</pre>
+     */
+    @Experimental
+    public RexSubQuery scalarQuery(Function<RelBuilder, RelNode> f) {
+        return RexSubQuery.scalar(f.apply(this));
+    }
+
+    /**
+     * Creates an ARRAY sub-query.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Depts")
+     *     .project(
+     *         b.field("deptno")
+     *         b.arrayQuery(b2 ->
+     *             b2.scan("Emps")
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT deptno, ARRAY (SELECT * FROM Emps)
+     * FROM Depts
+     * }</pre>
+     */
+    @Experimental
+    public RexSubQuery arrayQuery(Function<RelBuilder, RelNode> f) {
+        return RexSubQuery.array(f.apply(this));
+    }
+
+    /**
+     * Creates a MULTISET sub-query.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Depts")
+     *     .project(
+     *         b.field("deptno")
+     *         b.multisetQuery(b2 ->
+     *             b2.scan("Emps")
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT deptno, MULTISET (SELECT * FROM Emps)
+     * FROM Depts
+     * }</pre>
+     */
+    @Experimental
+    public RexSubQuery multisetQuery(Function<RelBuilder, RelNode> f) {
+        return RexSubQuery.multiset(f.apply(this));
+    }
+
+    /**
+     * Creates a MAP sub-query.
+     *
+     * <p>For example,
+     *
+     * <pre>{@code
+     * b.scan("Depts")
+     *     .project(
+     *         b.field("deptno")
+     *         b.multisetQuery(b2 ->
+     *             b2.scan("Emps")
+     *                 .project(b2.field("empno"), b2.field("job"))
+     *                 .build()))
+     * }</pre>
+     *
+     * <p>is equivalent to the SQL
+     *
+     * <pre>{@code
+     * SELECT deptno, MAP (SELECT empno, job FROM Emps)
+     * FROM Depts
+     * }</pre>
+     */
+    @Experimental
+    public RexSubQuery mapQuery(Function<RelBuilder, RelNode> f) {
+        return RexSubQuery.map(f.apply(this));
+    }
+
+    /** Creates an AND. */
+    public RexNode and(RexNode... operands) {
+        return and(ImmutableList.copyOf(operands));
+    }
+
+    /**
+     * Creates an AND.
+     *
+     * <p>Simplifies the expression a little: {@code e AND TRUE} becomes {@code e}; {@code e AND e2
+     * AND NOT e} becomes {@code e2}.
+     */
+    public RexNode and(Iterable<? extends RexNode> operands) {
+        return RexUtil.composeConjunction(getRexBuilder(), operands);
+    }
+
+    /** Creates an OR. */
+    public RexNode or(RexNode... operands) {
+        return or(ImmutableList.copyOf(operands));
+    }
+
+    /** Creates an OR. */
+    public RexNode or(Iterable<? extends RexNode> operands) {
+        return RexUtil.composeDisjunction(cluster.getRexBuilder(), operands);
+    }
+
+    /** Creates a NOT. */
+    public RexNode not(RexNode operand) {
+        return call(SqlStdOperatorTable.NOT, operand);
+    }
+
+    /** Creates an {@code =}. */
+    public RexNode equals(RexNode operand0, RexNode operand1) {
+        return call(SqlStdOperatorTable.EQUALS, operand0, operand1);
+    }
+
+    /** Creates a {@code >}. */
+    public RexNode greaterThan(RexNode operand0, RexNode operand1) {
+        return call(SqlStdOperatorTable.GREATER_THAN, operand0, operand1);
+    }
+
+    /** Creates a {@code >=}. */
+    public RexNode greaterThanOrEqual(RexNode operand0, RexNode operand1) {
+        return call(SqlStdOperatorTable.GREATER_THAN_OR_EQUAL, operand0, operand1);
+    }
+
+    /** Creates a {@code <}. */
+    public RexNode lessThan(RexNode operand0, RexNode operand1) {
+        return call(SqlStdOperatorTable.LESS_THAN, operand0, operand1);
+    }
+
+    /** Creates a {@code <=}. */
+    public RexNode lessThanOrEqual(RexNode operand0, RexNode operand1) {
+        return call(SqlStdOperatorTable.LESS_THAN_OR_EQUAL, operand0, operand1);
+    }
+
+    /** Creates a {@code <>}. */
+    public RexNode notEquals(RexNode operand0, RexNode operand1) {
+        return call(SqlStdOperatorTable.NOT_EQUALS, operand0, operand1);
+    }
+
+    /**
+     * Creates an expression equivalent to "{@code o0 IS NOT DISTINCT FROM o1}". It is also
+     * equivalent to "{@code o0 = o1 OR (o0 IS NULL AND o1 IS NULL)}".
+     */
+    public RexNode isNotDistinctFrom(RexNode operand0, RexNode operand1) {
+        return RelOptUtil.isDistinctFrom(getRexBuilder(), operand0, operand1, true);
+    }
+
+    /**
+     * Creates an expression equivalent to {@code o0 IS DISTINCT FROM o1}. It is also equivalent to
+     * "{@code NOT (o0 = o1 OR (o0 IS NULL AND o1 IS NULL))}.
+     */
+    public RexNode isDistinctFrom(RexNode operand0, RexNode operand1) {
+        return RelOptUtil.isDistinctFrom(getRexBuilder(), operand0, operand1, false);
+    }
+
+    /** Creates a {@code BETWEEN}. */
+    public RexNode between(RexNode arg, RexNode lower, RexNode upper) {
+        return getRexBuilder().makeBetween(arg, lower, upper);
+    }
+
+    /** Creates ab {@code IS NULL}. */
+    public RexNode isNull(RexNode operand) {
+        return call(SqlStdOperatorTable.IS_NULL, operand);
+    }
+
+    /** Creates an {@code IS NOT NULL}. */
+    public RexNode isNotNull(RexNode operand) {
+        return call(SqlStdOperatorTable.IS_NOT_NULL, operand);
+    }
+
+    /** Creates an expression that casts an expression to a given type. */
+    public RexNode cast(RexNode expr, SqlTypeName typeName) {
+        final RelDataType type = cluster.getTypeFactory().createSqlType(typeName);
+        return cluster.getRexBuilder().makeCast(type, expr);
+    }
+
+    /**
+     * Creates an expression that casts an expression to a type with a given name and precision or
+     * length.
+     */
+    public RexNode cast(RexNode expr, SqlTypeName typeName, int precision) {
+        final RelDataType type = cluster.getTypeFactory().createSqlType(typeName, precision);
+        return cluster.getRexBuilder().makeCast(type, expr);
+    }
+
+    /**
+     * Creates an expression that casts an expression to a type with a given name, precision and
+     * scale.
+     */
+    public RexNode cast(RexNode expr, SqlTypeName typeName, int precision, int scale) {
+        final RelDataType type = cluster.getTypeFactory().createSqlType(typeName, precision, scale);
+        return cluster.getRexBuilder().makeCast(type, expr);
+    }
+
+    /**
+     * Returns an expression wrapped in an alias.
+     *
+     * <p>This method is idempotent: If the expression is already wrapped in the correct alias, does
+     * nothing; if wrapped in an incorrect alias, removes the incorrect alias and applies the
+     * correct alias.
+     *
+     * @see #project
+     */
+    public RexNode alias(RexNode expr, String alias) {
+        final RexNode aliasLiteral = literal(alias);
+        switch (expr.getKind()) {
+            case AS:
+                final RexCall call = (RexCall) expr;
+                if (call.operands.get(1).equals(aliasLiteral)) {
+                    // current alias is correct
+                    return expr;
+                }
+                expr = call.operands.get(0);
+            // strip current (incorrect) alias, and fall through
+            default:
+                return call(SqlStdOperatorTable.AS, expr, aliasLiteral);
+        }
+    }
+
+    private RexNode aliasMaybe(RexNode node, @Nullable String name) {
+        return name == null ? node : alias(node, name);
+    }
+
+    /** Converts a sort expression to descending. */
+    public RexNode desc(RexNode node) {
+        return call(SqlStdOperatorTable.DESC, node);
+    }
+
+    /** Converts a sort expression to nulls last. */
+    public RexNode nullsLast(RexNode node) {
+        return call(SqlStdOperatorTable.NULLS_LAST, node);
+    }
+
+    /** Converts a sort expression to nulls first. */
+    public RexNode nullsFirst(RexNode node) {
+        return call(SqlStdOperatorTable.NULLS_FIRST, node);
+    }
+
+    // Methods that create window bounds
+
+    /**
+     * Creates an {@code UNBOUNDED PRECEDING} window bound, for use in methods such as {@link
+     * OverCall#rowsFrom(RexWindowBound)} and {@link OverCall#rangeBetween(RexWindowBound,
+     * RexWindowBound)}.
+     */
+    public RexWindowBound unboundedPreceding() {
+        return RexWindowBounds.UNBOUNDED_PRECEDING;
+    }
+
+    /**
+     * Creates a {@code bound PRECEDING} window bound, for use in methods such as {@link
+     * OverCall#rowsFrom(RexWindowBound)} and {@link OverCall#rangeBetween(RexWindowBound,
+     * RexWindowBound)}.
+     */
+    public RexWindowBound preceding(RexNode bound) {
+        return RexWindowBounds.preceding(bound);
+    }
+
+    /**
+     * Creates a {@code CURRENT ROW} window bound, for use in methods such as {@link
+     * OverCall#rowsFrom(RexWindowBound)} and {@link OverCall#rangeBetween(RexWindowBound,
+     * RexWindowBound)}.
+     */
+    public RexWindowBound currentRow() {
+        return RexWindowBounds.CURRENT_ROW;
+    }
+
+    /**
+     * Creates a {@code bound FOLLOWING} window bound, for use in methods such as {@link
+     * OverCall#rowsFrom(RexWindowBound)} and {@link OverCall#rangeBetween(RexWindowBound,
+     * RexWindowBound)}.
+     */
+    public RexWindowBound following(RexNode bound) {
+        return RexWindowBounds.following(bound);
+    }
+
+    /**
+     * Creates an {@code UNBOUNDED FOLLOWING} window bound, for use in methods such as {@link
+     * OverCall#rowsFrom(RexWindowBound)} and {@link OverCall#rangeBetween(RexWindowBound,
+     * RexWindowBound)}.
+     */
+    public RexWindowBound unboundedFollowing() {
+        return RexWindowBounds.UNBOUNDED_FOLLOWING;
+    }
+
+    // Methods that create group keys and aggregate calls
+
+    /** Creates an empty group key. */
+    public GroupKey groupKey() {
+        return groupKey(ImmutableList.of());
+    }
+
+    /** Creates a group key. */
+    public GroupKey groupKey(RexNode... nodes) {
+        return groupKey(ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a group key. */
+    public GroupKey groupKey(Iterable<? extends RexNode> nodes) {
+        return new GroupKeyImpl(ImmutableList.copyOf(nodes), null, null);
+    }
+
+    /** Creates a group key with grouping sets. */
+    public GroupKey groupKey(
+            Iterable<? extends RexNode> nodes,
+            Iterable<? extends Iterable<? extends RexNode>> nodeLists) {
+        return groupKey_(nodes, nodeLists);
+    }
+
+    // CHECKSTYLE: IGNORE 1
+    /**
+     * @deprecated Now that indicator is deprecated, use {@link #groupKey(Iterable, Iterable)},
+     *     which has the same behavior as calling this method with {@code indicator = false}.
+     */
+    @Deprecated // to be removed before 2.0
+    public GroupKey groupKey(
+            Iterable<? extends RexNode> nodes,
+            boolean indicator,
+            Iterable<? extends Iterable<? extends RexNode>> nodeLists) {
+        Aggregate.checkIndicator(indicator);
+        return groupKey_(nodes, nodeLists);
+    }
+
+    private static GroupKey groupKey_(
+            Iterable<? extends RexNode> nodes,
+            Iterable<? extends Iterable<? extends RexNode>> nodeLists) {
+        final ImmutableList.Builder<ImmutableList<RexNode>> builder = ImmutableList.builder();
+        for (Iterable<? extends RexNode> nodeList : nodeLists) {
+            builder.add(ImmutableList.copyOf(nodeList));
+        }
+        return new GroupKeyImpl(ImmutableList.copyOf(nodes), builder.build(), null);
+    }
+
+    /** Creates a group key of fields identified by ordinal. */
+    public GroupKey groupKey(int... fieldOrdinals) {
+        return groupKey(fields(ImmutableIntList.of(fieldOrdinals)));
+    }
+
+    /** Creates a group key of fields identified by name. */
+    public GroupKey groupKey(String... fieldNames) {
+        return groupKey(fields(ImmutableList.copyOf(fieldNames)));
+    }
+
+    /**
+     * Creates a group key, identified by field positions in the underlying relational expression.
+     *
+     * <p>This method of creating a group key does not allow you to group on new expressions, only
+     * column projections, but is efficient, especially when you are coming from an existing {@link
+     * Aggregate}.
+     */
+    public GroupKey groupKey(ImmutableBitSet groupSet) {
+        return groupKey_(groupSet, ImmutableList.of(groupSet));
+    }
+
+    /**
+     * Creates a group key with grouping sets, both identified by field positions in the underlying
+     * relational expression.
+     *
+     * <p>This method of creating a group key does not allow you to group on new expressions, only
+     * column projections, but is efficient, especially when you are coming from an existing {@link
+     * Aggregate}.
+     *
+     * <p>It is possible for {@code groupSet} to be strict superset of all {@code groupSets}. For
+     * example, in the pseudo SQL
+     *
+     * <pre>{@code
+     * GROUP BY 0, 1, 2
+     * GROUPING SETS ((0, 1), 0)
+     * }</pre>
+     *
+     * <p>column 2 does not appear in either grouping set. This is not valid SQL. We can approximate
+     * in actual SQL by adding an extra grouping set and filtering out using {@code HAVING}, as
+     * follows:
+     *
+     * <pre>{@code
+     * GROUP BY GROUPING SETS ((0, 1, 2), (0, 1), 0)
+     * HAVING GROUPING_ID(0, 1, 2) <> 0
+     * }</pre>
+     */
+    public GroupKey groupKey(
+            ImmutableBitSet groupSet, Iterable<? extends ImmutableBitSet> groupSets) {
+        return groupKey_(groupSet, ImmutableList.copyOf(groupSets));
+    }
+
+    // CHECKSTYLE: IGNORE 1
+    /**
+     * @deprecated Use {@link #groupKey(ImmutableBitSet, Iterable)}.
+     */
+    @Deprecated // to be removed before 2.0
+    public GroupKey groupKey(
+            ImmutableBitSet groupSet,
+            boolean indicator,
+            @Nullable ImmutableList<ImmutableBitSet> groupSets) {
+        Aggregate.checkIndicator(indicator);
+        return groupKey_(
+                groupSet,
+                groupSets == null ? ImmutableList.of(groupSet) : ImmutableList.copyOf(groupSets));
+    }
+
+    private GroupKey groupKey_(ImmutableBitSet groupSet, ImmutableList<ImmutableBitSet> groupSets) {
+        if (groupSet.length() > peek().getRowType().getFieldCount()) {
+            throw new IllegalArgumentException("out of bounds: " + groupSet);
+        }
+        requireNonNull(groupSets, "groupSets");
+        final ImmutableList<RexNode> nodes = fields(groupSet);
+        return groupKey_(nodes, Util.transform(groupSets, this::fields));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public AggCall aggregateCall(
+            SqlAggFunction aggFunction,
+            boolean distinct,
+            RexNode filter,
+            @Nullable String alias,
+            RexNode... operands) {
+        return aggregateCall(
+                aggFunction,
+                distinct,
+                false,
+                false,
+                filter,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.copyOf(operands));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public AggCall aggregateCall(
+            SqlAggFunction aggFunction,
+            boolean distinct,
+            boolean approximate,
+            RexNode filter,
+            @Nullable String alias,
+            RexNode... operands) {
+        return aggregateCall(
+                aggFunction,
+                distinct,
+                approximate,
+                false,
+                filter,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.copyOf(operands));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public AggCall aggregateCall(
+            SqlAggFunction aggFunction,
+            boolean distinct,
+            RexNode filter,
+            @Nullable String alias,
+            Iterable<? extends RexNode> operands) {
+        return aggregateCall(
+                aggFunction,
+                distinct,
+                false,
+                false,
+                filter,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.copyOf(operands));
+    }
+
+    @Deprecated // to be removed before 2.0
+    public AggCall aggregateCall(
+            SqlAggFunction aggFunction,
+            boolean distinct,
+            boolean approximate,
+            RexNode filter,
+            @Nullable String alias,
+            Iterable<? extends RexNode> operands) {
+        return aggregateCall(
+                aggFunction,
+                distinct,
+                approximate,
+                false,
+                filter,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.copyOf(operands));
+    }
+
+    /**
+     * Creates a call to an aggregate function.
+     *
+     * <p>To add other operands, apply {@link AggCall#distinct()}, {@link
+     * AggCall#approximate(boolean)}, {@link AggCall#filter(RexNode...)}, {@link AggCall#sort},
+     * {@link AggCall#as} to the result.
+     */
+    public AggCall aggregateCall(SqlAggFunction aggFunction, Iterable<? extends RexNode> operands) {
+        return aggregateCall(
+                aggFunction,
+                false,
+                false,
+                false,
+                null,
+                null,
+                ImmutableList.of(),
+                null,
+                ImmutableList.of(),
+                ImmutableList.copyOf(operands));
+    }
+
+    /**
+     * Creates a call to an aggregate function.
+     *
+     * <p>To add other operands, apply {@link AggCall#distinct()}, {@link
+     * AggCall#approximate(boolean)}, {@link AggCall#filter(RexNode...)}, {@link AggCall#sort},
+     * {@link AggCall#as} to the result.
+     */
+    public AggCall aggregateCall(SqlAggFunction aggFunction, RexNode... operands) {
+        return aggregateCall(
+                aggFunction,
+                false,
+                false,
+                false,
+                null,
+                null,
+                ImmutableList.of(),
+                null,
+                ImmutableList.of(),
+                ImmutableList.copyOf(operands));
+    }
+
+    /** Creates a call to an aggregate function as a copy of an {@link AggregateCall}. */
+    public AggCall aggregateCall(AggregateCall a) {
+        return aggregateCall(
+                a.getAggregation(),
+                a.isDistinct(),
+                a.isApproximate(),
+                a.ignoreNulls(),
+                a.filterArg < 0 ? null : field(a.filterArg),
+                a.distinctKeys == null ? null : fields(a.distinctKeys),
+                fields(a.collation),
+                a.name,
+                ImmutableList.copyOf(a.rexList),
+                fields(a.getArgList()));
+    }
+
+    /**
+     * Creates a call to an aggregate function as a copy of an {@link AggregateCall}, applying a
+     * mapping.
+     */
+    public AggCall aggregateCall(AggregateCall a, Mapping mapping) {
+        return aggregateCall(
+                a.getAggregation(),
+                a.isDistinct(),
+                a.isApproximate(),
+                a.ignoreNulls(),
+                a.filterArg < 0 ? null : field(Mappings.apply(mapping, a.filterArg)),
+                a.distinctKeys == null ? null : fields(Mappings.apply(mapping, a.distinctKeys)),
+                fields(RexUtil.apply(mapping, a.collation)),
+                a.name,
+                ImmutableList.copyOf(a.rexList),
+                fields(Mappings.apply2(mapping, a.getArgList())));
+    }
+
+    /** Creates a call to an aggregate function with all applicable operands. */
+    protected AggCall aggregateCall(
+            SqlAggFunction aggFunction,
+            boolean distinct,
+            boolean approximate,
+            boolean ignoreNulls,
+            @Nullable RexNode filter,
+            @Nullable ImmutableList<RexNode> distinctKeys,
+            ImmutableList<RexNode> orderKeys,
+            @Nullable String alias,
+            ImmutableList<RexNode> preOperands,
+            ImmutableList<RexNode> operands) {
+        return new AggCallImpl(
+                aggFunction,
+                distinct,
+                approximate,
+                ignoreNulls,
+                filter,
+                alias,
+                preOperands,
+                operands,
+                distinctKeys,
+                orderKeys);
+    }
+
+    /** Creates a call to the {@code COUNT} aggregate function. */
+    public AggCall count(RexNode... operands) {
+        return count(false, null, operands);
+    }
+
+    /** Creates a call to the {@code COUNT} aggregate function. */
+    public AggCall count(Iterable<? extends RexNode> operands) {
+        return count(false, null, operands);
+    }
+
+    /**
+     * Creates a call to the {@code COUNT} aggregate function, optionally distinct and with an
+     * alias.
+     */
+    public AggCall count(boolean distinct, @Nullable String alias, RexNode... operands) {
+        return aggregateCall(
+                SqlStdOperatorTable.COUNT,
+                distinct,
+                false,
+                false,
+                null,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.copyOf(operands));
+    }
+
+    /**
+     * Creates a call to the {@code COUNT} aggregate function, optionally distinct and with an
+     * alias.
+     */
+    public AggCall count(
+            boolean distinct, @Nullable String alias, Iterable<? extends RexNode> operands) {
+        return aggregateCall(
+                SqlStdOperatorTable.COUNT,
+                distinct,
+                false,
+                false,
+                null,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.copyOf(operands));
+    }
+
+    /** Creates a call to the {@code COUNT(*)} aggregate function. */
+    public AggCall countStar(@Nullable String alias) {
+        return count(false, alias);
+    }
+
+    /** Creates a call to the {@code SUM} aggregate function. */
+    public AggCall sum(RexNode operand) {
+        return sum(false, null, operand);
+    }
+
+    /**
+     * Creates a call to the {@code SUM} aggregate function, optionally distinct and with an alias.
+     */
+    public AggCall sum(boolean distinct, @Nullable String alias, RexNode operand) {
+        return aggregateCall(
+                SqlStdOperatorTable.SUM,
+                distinct,
+                false,
+                false,
+                null,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.of(operand));
+    }
+
+    /** Creates a call to the {@code AVG} aggregate function. */
+    public AggCall avg(RexNode operand) {
+        return avg(false, null, operand);
+    }
+
+    /**
+     * Creates a call to the {@code AVG} aggregate function, optionally distinct and with an alias.
+     */
+    public AggCall avg(boolean distinct, @Nullable String alias, RexNode operand) {
+        return aggregateCall(
+                SqlStdOperatorTable.AVG,
+                distinct,
+                false,
+                false,
+                null,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.of(operand));
+    }
+
+    /** Creates a call to the {@code MIN} aggregate function. */
+    public AggCall min(RexNode operand) {
+        return min(null, operand);
+    }
+
+    /** Creates a call to the {@code MIN} aggregate function, optionally with an alias. */
+    public AggCall min(@Nullable String alias, RexNode operand) {
+        return aggregateCall(
+                SqlStdOperatorTable.MIN,
+                false,
+                false,
+                false,
+                null,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.of(operand));
+    }
+
+    /** Creates a call to the {@code MAX} aggregate function, optionally with an alias. */
+    public AggCall max(RexNode operand) {
+        return max(null, operand);
+    }
+
+    /** Creates a call to the {@code MAX} aggregate function. */
+    public AggCall max(@Nullable String alias, RexNode operand) {
+        return aggregateCall(
+                SqlStdOperatorTable.MAX,
+                false,
+                false,
+                false,
+                null,
+                null,
+                ImmutableList.of(),
+                alias,
+                ImmutableList.of(),
+                ImmutableList.of(operand));
+    }
+
+    /** Creates a call to the {@code LITERAL_AGG} aggregate function. */
+    public AggCall literalAgg(@Nullable Object value) {
+        return aggregateCall(SqlInternalOperators.LITERAL_AGG).preOperands(literal(value));
+    }
+
+    // Methods for patterns
+
+    /**
+     * Creates a reference to a given field of the pattern.
+     *
+     * @param alpha the pattern name
+     * @param type Type of field
+     * @param i Ordinal of field
+     * @return Reference to field of pattern
+     */
+    public RexNode patternField(String alpha, RelDataType type, int i) {
+        return getRexBuilder().makePatternFieldRef(alpha, type, i);
+    }
+
+    /** Creates a call that concatenates patterns; for use in {@link #match}. */
+    public RexNode patternConcat(Iterable<? extends RexNode> nodes) {
+        final ImmutableList<RexNode> list = ImmutableList.copyOf(nodes);
+        if (list.size() > 2) {
+            // Convert into binary calls
+            return patternConcat(patternConcat(Util.skipLast(list)), Util.last(list));
+        }
+        final RelDataType t = getTypeFactory().createSqlType(SqlTypeName.NULL);
+        return getRexBuilder().makeCall(t, SqlStdOperatorTable.PATTERN_CONCAT, list);
+    }
+
+    /** Creates a call that concatenates patterns; for use in {@link #match}. */
+    public RexNode patternConcat(RexNode... nodes) {
+        return patternConcat(ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a call that creates alternate patterns; for use in {@link #match}. */
+    public RexNode patternAlter(Iterable<? extends RexNode> nodes) {
+        final RelDataType t = getTypeFactory().createSqlType(SqlTypeName.NULL);
+        return getRexBuilder()
+                .makeCall(t, SqlStdOperatorTable.PATTERN_ALTER, ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a call that creates alternate patterns; for use in {@link #match}. */
+    public RexNode patternAlter(RexNode... nodes) {
+        return patternAlter(ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a call that creates quantify patterns; for use in {@link #match}. */
+    public RexNode patternQuantify(Iterable<? extends RexNode> nodes) {
+        final RelDataType t = getTypeFactory().createSqlType(SqlTypeName.NULL);
+        return getRexBuilder()
+                .makeCall(t, SqlStdOperatorTable.PATTERN_QUANTIFIER, ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a call that creates quantify patterns; for use in {@link #match}. */
+    public RexNode patternQuantify(RexNode... nodes) {
+        return patternQuantify(ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a call that creates permute patterns; for use in {@link #match}. */
+    public RexNode patternPermute(Iterable<? extends RexNode> nodes) {
+        final RelDataType t = getTypeFactory().createSqlType(SqlTypeName.NULL);
+        return getRexBuilder()
+                .makeCall(t, SqlStdOperatorTable.PATTERN_PERMUTE, ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a call that creates permute patterns; for use in {@link #match}. */
+    public RexNode patternPermute(RexNode... nodes) {
+        return patternPermute(ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a call that creates an exclude pattern; for use in {@link #match}. */
+    public RexNode patternExclude(RexNode node) {
+        final RelDataType t = getTypeFactory().createSqlType(SqlTypeName.NULL);
+        return getRexBuilder()
+                .makeCall(t, SqlStdOperatorTable.PATTERN_EXCLUDE, ImmutableList.of(node));
+    }
+
+    // Methods that create relational expressions
+
+    /**
+     * Creates a {@link TableScan} of the table with a given name.
+     *
+     * <p>Throws if the table does not exist.
+     *
+     * <p>Returns this builder.
+     *
+     * @param tableNames Name of table (can optionally be qualified)
+     */
+    public RelBuilder scan(Iterable<String> tableNames) {
+        final List<String> names = ImmutableList.copyOf(tableNames);
+        requireNonNull(relOptSchema, "relOptSchema");
+        final RelOptTable relOptTable = relOptSchema.getTableForMember(names);
+        if (relOptTable == null) {
+            throw RESOURCE.tableNotFound(String.join(".", names)).ex();
+        }
+        final RelNode scan =
+                struct.scanFactory.createScan(
+                        ViewExpanders.toRelContext(viewExpander, cluster), relOptTable);
+        push(scan);
+        rename(relOptTable.getRowType().getFieldNames());
+
+        // When the node is not a TableScan but from expansion,
+        // we need to explicitly add the alias.
+        if (!(scan instanceof TableScan)) {
+            as(Util.last(ImmutableList.copyOf(tableNames)));
+        }
+        return this;
+    }
+
+    /**
+     * Creates a {@link TableScan} of the table with a given name.
+     *
+     * <p>Throws if the table does not exist.
+     *
+     * <p>Returns this builder.
+     *
+     * @param tableNames Name of table (can optionally be qualified)
+     */
+    public RelBuilder scan(String... tableNames) {
+        return scan(ImmutableList.copyOf(tableNames));
+    }
+
+    /**
+     * Creates a {@link Snapshot} of a given snapshot period.
+     *
+     * <p>Returns this builder.
+     *
+     * @param period Name of table (can optionally be qualified)
+     */
+    public RelBuilder snapshot(RexNode period) {
+        final Frame frame = stack.pop();
+        final RelNode snapshot = struct.snapshotFactory.createSnapshot(frame.rel, period);
+        stack.push(new Frame(snapshot, frame.fields));
+        return this;
+    }
+
+    /**
+     * Gets column mappings of the operator.
+     *
+     * @param op operator instance
+     * @return column mappings associated with this function
+     */
+    private static @Nullable Set<RelColumnMapping> getColumnMappings(SqlOperator op) {
+        SqlReturnTypeInference inference = op.getReturnTypeInference();
+        if (inference instanceof TableFunctionReturnTypeInference) {
+            return ((TableFunctionReturnTypeInference) inference).getColumnMappings();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Creates a RexCall to the {@code CURSOR} function by ordinal.
+     *
+     * @param inputCount Number of inputs
+     * @param ordinal The reference to the relational input
+     * @return RexCall to CURSOR function
+     */
+    public RexNode cursor(int inputCount, int ordinal) {
+        if (inputCount <= ordinal || ordinal < 0) {
+            throw new IllegalArgumentException("bad input count or ordinal");
+        }
+        // Refer to the "ordinal"th input as if it were a field
+        // (because that's how things are laid out inside a TableFunctionScan)
+        final RelNode input = peek(inputCount, ordinal);
+        return call(
+                SqlStdOperatorTable.CURSOR,
+                getRexBuilder().makeInputRef(input.getRowType(), ordinal));
+    }
+
+    /** Creates a {@link TableFunctionScan}. */
+    public RelBuilder functionScan(SqlOperator operator, int inputCount, RexNode... operands) {
+        return functionScan(operator, inputCount, ImmutableList.copyOf(operands));
+    }
+
+    /** Creates a {@link TableFunctionScan}. */
+    public RelBuilder functionScan(
+            SqlOperator operator, int inputCount, Iterable<? extends RexNode> operands) {
+        if (inputCount < 0 || inputCount > stack.size()) {
+            throw new IllegalArgumentException("bad input count");
+        }
+
+        // Gets inputs.
+        final List<RelNode> inputs = new ArrayList<>();
+        for (int i = 0; i < inputCount; i++) {
+            inputs.add(0, build());
+        }
+
+        final RexCall call = call(operator, ImmutableList.copyOf(operands));
+        final RelNode functionScan =
+                struct.tableFunctionScanFactory.createTableFunctionScan(
+                        cluster, inputs, call, null, getColumnMappings(operator));
+        push(functionScan);
+        return this;
+    }
+
+    /**
+     * Creates a {@link Filter} of an array of predicates.
+     *
+     * <p>The predicates are combined using AND, and optimized in a similar way to the {@link #and}
+     * method. If the result is TRUE no filter is created.
+     */
+    public RelBuilder filter(RexNode... predicates) {
+        return filter(ImmutableSet.of(), ImmutableList.copyOf(predicates));
+    }
+
+    /**
+     * Creates a {@link Filter} of a list of predicates.
+     *
+     * <p>The predicates are combined using AND, and optimized in a similar way to the {@link #and}
+     * method. If the result is TRUE no filter is created.
+     */
+    public RelBuilder filter(Iterable<? extends RexNode> predicates) {
+        return filter(ImmutableSet.of(), predicates);
+    }
+
+    /**
+     * Creates a {@link Filter} of a list of correlation variables and an array of predicates.
+     *
+     * <p>The predicates are combined using AND, and optimized in a similar way to the {@link #and}
+     * method. If the result is TRUE no filter is created.
+     */
+    public RelBuilder filter(Iterable<CorrelationId> variablesSet, RexNode... predicates) {
+        return filter(variablesSet, ImmutableList.copyOf(predicates));
+    }
+
+    /**
+     * Creates a {@link Filter} of a list of correlation variables and a list of predicates.
+     *
+     * <p>The predicates are combined using AND, and optimized in a similar way to the {@link #and}
+     * method. If simplification is on and the result is TRUE, no filter is created.
+     */
+    public RelBuilder filter(
+            Iterable<CorrelationId> variablesSet, Iterable<? extends RexNode> predicates) {
+        final RexNode conjunctionPredicates;
+        if (config.simplify()) {
+            conjunctionPredicates = simplifier.simplifyFilterPredicates(predicates);
+        } else {
+            conjunctionPredicates = RexUtil.composeConjunction(simplifier.rexBuilder, predicates);
+        }
+
+        if (conjunctionPredicates == null || conjunctionPredicates.isAlwaysFalse()) {
+            return empty();
+        }
+        if (conjunctionPredicates.isAlwaysTrue()) {
+            return this;
+        }
+
+        final Frame frame = stack.pop();
+        final RelNode filter =
+                struct.filterFactory.createFilter(
+                        frame.rel, conjunctionPredicates, ImmutableSet.copyOf(variablesSet));
+        stack.push(new Frame(filter, frame.fields));
+        return this;
+    }
+
+    /** Creates a {@link Project} of the given expressions. */
+    public RelBuilder project(RexNode... nodes) {
+        return project(ImmutableList.copyOf(nodes));
+    }
+
+    /**
+     * Creates a {@link Project} of the given list of expressions.
+     *
+     * <p>Infers names as would {@link #project(Iterable, Iterable)} if all suggested names were
+     * null.
+     *
+     * @param nodes Expressions
+     */
+    public RelBuilder project(Iterable<? extends RexNode> nodes) {
+        return project(nodes, ImmutableList.of());
+    }
+
+    /**
+     * Creates a {@link Project} of the given list of expressions and field names.
+     *
+     * @param nodes Expressions
+     * @param fieldNames field names for expressions
+     */
+    public RelBuilder project(
+            Iterable<? extends RexNode> nodes, Iterable<? extends @Nullable String> fieldNames) {
+        return project(nodes, fieldNames, false);
+    }
+
+    /**
+     * Creates a {@link Project} of the given list of expressions, using the given names.
+     *
+     * <p>Names are deduced as follows:
+     *
+     * <ul>
+     *   <li>If the length of {@code fieldNames} is greater than the index of the current entry in
+     *       {@code nodes}, and the entry in {@code fieldNames} is not null, uses it; otherwise
+     *   <li>If an expression projects an input field, or is a cast an input field, uses the input
+     *       field name; otherwise
+     *   <li>If an expression is a call to {@link SqlStdOperatorTable#AS} (see {@link #alias}),
+     *       removes the call but uses the intended alias.
+     * </ul>
+     *
+     * <p>After the field names have been inferred, makes the field names unique by appending
+     * numeric suffixes.
+     *
+     * @param nodes Expressions
+     * @param fieldNames Suggested field names
+     * @param force create project even if it is identity
+     */
+    public RelBuilder project(
+            Iterable<? extends RexNode> nodes,
+            Iterable<? extends @Nullable String> fieldNames,
+            boolean 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 expressions. */
+    public RelBuilder projectPlus(RexNode... nodes) {
+        return projectPlus(ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a {@link Project} of all original fields, plus the given list of expressions. */
+    public RelBuilder projectPlus(Iterable<? extends RexNode> nodes) {
+        return project(Iterables.concat(fields(), nodes));
+    }
+
+    /**
+     * Creates a {@link Project} of all original fields, except the given expressions.
+     *
+     * @throws IllegalArgumentException if the given expressions contain duplicates or there is an
+     *     expression that does not match an existing field
+     */
+    public RelBuilder projectExcept(RexNode... expressions) {
+        return projectExcept(ImmutableList.copyOf(expressions));
+    }
+
+    /**
+     * Creates a {@link Project} of all original fields, except the given list of expressions.
+     *
+     * @throws IllegalArgumentException if the given expressions contain duplicates or there is an
+     *     expression that does not match an existing field
+     */
+    public RelBuilder projectExcept(Iterable<RexNode> expressions) {
+        List<RexNode> allExpressions = new ArrayList<>(fields());
+        Set<RexNode> excludeExpressions = new HashSet<>();
+        for (RexNode excludeExp : expressions) {
+            if (!excludeExpressions.add(excludeExp)) {
+                throw new IllegalArgumentException(
+                        "Input list contains duplicates. Expression "
+                                + excludeExp
+                                + " exists multiple times.");
+            }
+            if (!allExpressions.remove(excludeExp)) {
+                throw new IllegalArgumentException(
+                        "Expression " + excludeExp.toString() + " not found.");
+            }
+        }
+        return this.project(allExpressions);
+    }
+
+    /**
+     * Creates a {@link Project} of the given list of expressions, using the given names.
+     *
+     * <p>Names are deduced as follows:
+     *
+     * <ul>
+     *   <li>If the length of {@code fieldNames} is greater than the index of the current entry in
+     *       {@code nodes}, and the entry in {@code fieldNames} is not null, uses it; otherwise
+     *   <li>If an expression projects an input field, or is a cast an input field, uses the input
+     *       field name; otherwise
+     *   <li>If an expression is a call to {@link SqlStdOperatorTable#AS} (see {@link #alias}),
+     *       removes the call but uses the intended alias.
+     * </ul>
+     *
+     * <p>After the field names have been inferred, makes the field names unique by appending
+     * numeric suffixes.
+     *
+     * @param nodes Expressions
+     * @param fieldNames Suggested field names
+     * @param hints Hints
+     * @param force create project even if it is identity
+     */
+    private RelBuilder project_(
+            Iterable<? extends RexNode> nodes,
+            Iterable<? extends @Nullable String> fieldNames,
+            Iterable<RelHint> hints,
+            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.
+        if (!force && Iterables.isEmpty(fieldNames) && RexUtil.isIdentity(nodeList, inputRowType)) {
+            return this;
+        }
+
+        final List<@Nullable String> fieldNameList = Lists.newArrayList(fieldNames);
+        while (fieldNameList.size() < nodeList.size()) {
+            fieldNameList.add(null);
+        }
+
+        // Do not merge projection when top projection has correlation variables
+        bloat:
+        if (frame.rel instanceof Project && 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.
+            for (int i = 0; i < fieldNameList.size(); i++) {
+                if (fieldNameList.get(i) == null) {
+                    final RexNode node = nodeList.get(i);
+                    if (node instanceof RexInputRef) {
+                        final RexInputRef ref = (RexInputRef) node;
+                        fieldNameList.set(
+                                i, project.getRowType().getFieldNames().get(ref.getIndex()));
+                    }
+                }
+            }
+            final List<RexNode> newNodes =
+                    RelOptUtil.pushPastProjectUnlessBloat(nodeList, project, config.bloat());
+            if (newNodes == null) {
+                // The merged expression is more complex than the input expressions.
+                // Do not merge.
+                break bloat;
+            }
+
+            // Carefully build a list of fields, so that table aliases from the input
+            // can be seen for fields that are based on a RexInputRef.
+            final Frame frame1 = stack.pop();
+            final PairList<ImmutableSet<String>, RelDataTypeField> fields = PairList.of();
+            project.getInput()
+                    .getRowType()
+                    .getFieldList()
+                    .forEach(f -> fields.add(ImmutableSet.of(), f));
+            for (Pair<RexNode, ImmutableSet<String>> pair :
+                    Pair.zip(project.getProjects(), frame1.fields.leftList())) {
+                switch (pair.left.getKind()) {
+                    case INPUT_REF:
+                        final int i = ((RexInputRef) pair.left).getIndex();
+                        fields.set(i, pair.right, fields.rightList().get(i));
+                        break;
+                    default:
+                        break;
+                }
+            }
+            stack.push(new Frame(project.getInput(), fields));
+            final ImmutableSet.Builder<RelHint> mergedHints = ImmutableSet.builder();
+            mergedHints.addAll(project.getHints());
+            mergedHints.addAll(hints);
+            // Keep bottom projection's variablesSet.
+            return project_(
+                    newNodes,
+                    fieldNameList,
+                    mergedHints.build(),
+                    force,
+                    ImmutableSet.copyOf(project.getVariablesSet()));
+        }
+
+        // Simplify expressions.
+        if (config.simplify()) {
+            nodeList.replaceAll(e -> simplifier.simplifyPreservingType(e));
+        }
+
+        // Replace null names with generated aliases.
+        for (int i = 0; i < fieldNameList.size(); i++) {
+            if (fieldNameList.get(i) == null) {
+                fieldNameList.set(i, inferAlias(nodeList, nodeList.get(i), i));
+            }
+        }
+
+        final PairList<ImmutableSet<String>, RelDataTypeField> fields = PairList.of();
+        final Set<String> uniqueNameList =
+                getTypeFactory().getTypeSystem().isSchemaCaseSensitive()
+                        ? new HashSet<>()
+                        : new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+        // calculate final names and build field list
+        for (int i = 0; i < fieldNameList.size(); ++i) {
+            final RexNode node = nodeList.get(i);
+            String name = fieldNameList.get(i);
+            String originalName = name;
+            if (name == null || uniqueNameList.contains(name)) {
+                int j = 0;
+                if (name == null) {
+                    j = i;
+                }
+                do {
+                    name = SqlValidatorUtil.F_SUGGESTER.apply(originalName, j, j++);
+                } while (uniqueNameList.contains(name));
+                fieldNameList.set(i, name);
+            }
+            RelDataTypeField fieldType = new RelDataTypeFieldImpl(name, i, node.getType());
+            switch (node.getKind()) {
+                case INPUT_REF:
+                    // preserve rel aliases for INPUT_REF fields
+                    final int index = ((RexInputRef) node).getIndex();
+                    fields.add(frame.fields.leftList().get(index), fieldType);
+                    break;
+                default:
+                    fields.add(ImmutableSet.of(), fieldType);
+                    break;
+            }
+            uniqueNameList.add(name);
+        }
+        if (!force && RexUtil.isIdentity(nodeList, inputRowType)) {
+            if (fieldNameList.equals(inputRowType.getFieldNames())) {
+                // Do not create an identity project if it does not rename any fields
+                return this;
+            } else {
+                // create "virtual" row type for project only rename fields
+                stack.pop();
+                // Ignore the hints.
+                stack.push(new Frame(frame.rel, fields));
+            }
+            return this;
+        }
+
+        // If the expressions are all literals, and the input is a Values with N
+        // rows (or an Aggregate with 1 row), replace with a Values with same tuple
+        // N times.
+        final int rowCount;
+        if (config.simplifyValues()
+                && nodeList.stream().allMatch(e -> e instanceof RexLiteral)
+                && (rowCount = fixedRowCount(frame)) >= 0) {
+            RelNode unused = build();
+            final RelDataTypeFactory.Builder typeBuilder = getTypeFactory().builder();
+            Pair.forEach(
+                    fieldNameList,
+                    nodeList,
+                    (name, expr) -> typeBuilder.add(requireNonNull(name, "name"), expr.getType()));
+            @SuppressWarnings({"unchecked", "rawtypes"})
+            final List<RexLiteral> tuple = (List<RexLiteral>) (List) nodeList;
+            return values(Collections.nCopies(rowCount, tuple), typeBuilder.build());
+        }
+
+        final RelNode project =
+                struct.projectFactory.createProject(
+                        frame.rel,
+                        ImmutableList.copyOf(hints),
+                        ImmutableList.copyOf(nodeList),
+                        fieldNameList,
+                        variables);
+        stack.pop();
+        stack.push(new Frame(project, fields));
+        return this;
+    }
+
+    /**
+     * If current frame will return a known, constant number of rows, returns that number; otherwise
+     * returns -1.
+     */
+    private static int fixedRowCount(Frame frame) {
+        if (frame.rel instanceof Values) {
+            return ((Values) frame.rel).tuples.size();
+        }
+        if (frame.rel instanceof Aggregate) {
+            final Aggregate aggregate = (Aggregate) frame.rel;
+            if (aggregate.getGroupSet().isEmpty()
+                    && aggregate.getGroupType() == Aggregate.Group.SIMPLE) {
+                return 1;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * 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
+     */
+    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", "rawtypes"})
+        final List<? extends RexNode> nodeList =
+                nodes instanceof List ? (List) nodes : ImmutableList.copyOf(nodes);
+        final List<@Nullable String> fieldNameList =
+                fieldNames == null
+                        ? null
+                        : fieldNames instanceof List
+                                ? (List<@Nullable String>) fieldNames
+                                : ImmutableNullableList.copyOf(fieldNames);
+        final RelNode input = peek();
+        final RelDataType rowType =
+                RexUtil.createStructType(
+                        cluster.getTypeFactory(),
+                        nodeList,
+                        fieldNameList,
+                        SqlValidatorUtil.F_SUGGESTER);
+        if (!force && RexUtil.isIdentity(nodeList, input.getRowType())) {
+            if (input instanceof Project && fieldNames != null) {
+                // Rename columns of child projection if desired field names are given.
+                final Frame frame = stack.pop();
+                final Project childProject = (Project) frame.rel;
+                final Project newInput =
+                        childProject.copy(
+                                childProject.getTraitSet(),
+                                childProject.getInput(),
+                                childProject.getProjects(),
+                                rowType);
+                stack.push(new Frame(newInput.attachHints(childProject.getHints()), frame.fields));
+            }
+            if (input instanceof Values && fieldNameList != null) {
+                // Rename columns of child values if desired field names are given.
+                final Frame frame = stack.pop();
+                final Values values = (Values) frame.rel;
+                final RelDataTypeFactory.Builder typeBuilder = getTypeFactory().builder();
+                Pair.forEach(
+                        fieldNameList,
+                        rowType.getFieldList(),
+                        (name, field) ->
+                                typeBuilder.add(requireNonNull(name, "name"), field.getType()));
+                final RelDataType newRowType = typeBuilder.build();
+                final RelNode newValues =
+                        struct.valuesFactory.createValues(cluster, newRowType, values.tuples);
+                stack.push(new Frame(newValues, frame.fields));
+            }
+        } else {
+            project(nodeList, rowType.getFieldNames(), force, variablesSet);
+        }
+        return this;
+    }
+
+    /**
+     * Creates an {@link Uncollect} with given item aliases.
+     *
+     * @param itemAliases Operand item aliases, never null
+     * @param withOrdinality If {@code withOrdinality}, the output contains an extra {@code
+     *     ORDINALITY} column
+     */
+    public RelBuilder uncollect(List<String> itemAliases, boolean withOrdinality) {
+        Frame frame = stack.pop();
+        stack.push(
+                new Frame(
+                        new Uncollect(
+                                cluster,
+                                cluster.traitSetOf(Convention.NONE),
+                                frame.rel,
+                                withOrdinality,
+                                requireNonNull(itemAliases, "itemAliases"))));
+        return this;
+    }
+
+    /**
+     * Ensures that the field names match those given.
+     *
+     * <p>If all fields have the same name, adds nothing; if any fields do not have the same name,
+     * adds a {@link Project}.
+     *
+     * <p>Note that the names can be short-lived. Other {@code RelBuilder} operations make no
+     * guarantees about the field names of the rows they produce.
+     *
+     * @param fieldNames List of desired field names; may contain null values or have fewer fields
+     *     than the current row type
+     */
+    public RelBuilder rename(List<? extends @Nullable String> fieldNames) {
+        final List<String> oldFieldNames = peek().getRowType().getFieldNames();
+        Preconditions.checkArgument(
+                fieldNames.size() <= oldFieldNames.size(), "More names than fields");
+        final List<String> newFieldNames = new ArrayList<>(oldFieldNames);
+        for (int i = 0; i < fieldNames.size(); i++) {
+            final String s = fieldNames.get(i);
+            if (s != null) {
+                newFieldNames.set(i, s);
+            }
+        }
+        if (oldFieldNames.equals(newFieldNames)) {
+            return this;
+        }
+        if (peek() instanceof Values) {
+            // Special treatment for VALUES. Re-build it rather than add a project.
+            final Values v = (Values) build();
+            final RelDataTypeFactory.Builder b = getTypeFactory().builder();
+            for (Pair<String, RelDataTypeField> p :
+                    Pair.zip(newFieldNames, v.getRowType().getFieldList())) {
+                b.add(p.left, p.right.getType());
+            }
+            return values(v.tuples, b.build());
+        }
+
+        return project(fields(), newFieldNames, true);
+    }
+
+    /**
+     * Infers the alias of an expression.
+     *
+     * <p>If the expression was created by {@link #alias}, replaces the expression in the project
+     * list.
+     */
+    private @Nullable String inferAlias(List<RexNode> exprList, RexNode expr, int i) {
+        switch (expr.getKind()) {
+            case INPUT_REF:
+                final RexInputRef ref = (RexInputRef) expr;
+                return requireNonNull(stack.peek(), "empty frame stack")
+                        .fields
+                        .get(ref.getIndex())
+                        .getValue()
+                        .getName();
+            case CAST:
+                return inferAlias(exprList, ((RexCall) expr).getOperands().get(0), -1);
+            case AS:
+                final RexCall call = (RexCall) expr;
+                if (i >= 0) {
+                    exprList.set(i, call.getOperands().get(0));
+                }
+                NlsString value = (NlsString) ((RexLiteral) call.getOperands().get(1)).getValue();
+                return castNonNull(value).getValue();
+            default:
+                return null;
+        }
+    }
+
+    /** Creates an {@link Aggregate} that makes the relational expression distinct on all fields. */
+    public RelBuilder distinct() {
+        return aggregate_((GroupKeyImpl) groupKey(fields()), ImmutableList.of());
+    }
+
+    /** Creates an {@link Aggregate} with an array of calls. */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public RelBuilder aggregate(GroupKey groupKey, AggCall... aggCalls) {
+        return aggregate_((GroupKeyImpl) groupKey, (ImmutableList) ImmutableList.copyOf(aggCalls));
+    }
+
+    /** Creates an {@link Aggregate} with an array of {@link AggregateCall}s. */
+    public RelBuilder aggregate(GroupKey groupKey, List<AggregateCall> aggregateCalls) {
+        return aggregate_(
+                (GroupKeyImpl) groupKey,
+                aggregateCalls.stream()
+                        .map(
+                                aggregateCall ->
+                                        new AggCallImpl2(
+                                                aggregateCall,
+                                                aggregateCall.getArgList().stream()
+                                                        .map(this::field)
+                                                        .collect(toImmutableList())))
+                        .collect(toImmutableList()));
+    }
+
+    /** Creates an {@link Aggregate} with multiple calls. */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public RelBuilder aggregate(GroupKey groupKey, Iterable<? extends AggCall> aggCalls) {
+        return aggregate_(
+                (GroupKeyImpl) groupKey, ImmutableList.<AggCallPlus>copyOf((Iterable) aggCalls));
+    }
+
+    /** Creates an {@link Aggregate} with multiple calls. */
+    private RelBuilder aggregate_(
+            GroupKeyImpl groupKey, final ImmutableList<AggCallPlus> aggCalls) {
+        if (groupKey.nodes.isEmpty() && aggCalls.isEmpty() && config.pruneInputOfAggregate()) {
+            // Query is "SELECT /* no fields */ FROM t GROUP BY ()", which always
+            // returns one row with zero columns.
+            if (config.preventEmptyFieldList()) {
+                // Convert to "VALUES ROW(true)".
+                return values(new String[] {"dummy"}, true);
+            } else {
+                // Convert to "VALUES ROW()".
+                return values(
+                        ImmutableList.of(ImmutableList.of()), getTypeFactory().builder().build());
+            }
+        }
+        final Registrar registrar = new Registrar(fields(), peek().getRowType().getFieldNames());
+        final ImmutableBitSet groupSet =
+                ImmutableBitSet.of(registrar.registerExpressions(groupKey.nodes));
+        if (alreadyUnique(aggCalls, groupKey, groupSet, registrar.extraNodes)) {
+            final List<RexNode> nodes = new ArrayList<>(fields(groupSet));
+            aggCalls.forEach(
+                    c -> {
+                        final AggregateCall call = c.aggregateCall();
+                        final SqlStaticAggFunction staticFun =
+                                call.getAggregation().unwrapOrThrow(SqlStaticAggFunction.class);
+                        final RexNode node =
+                                staticFun.constant(
+                                        getRexBuilder(), groupSet, ImmutableList.of(), call);
+                        nodes.add(aliasMaybe(requireNonNull(node, "node"), call.getName()));
+                    });
+            return project(nodes);
+        }
+
+        ImmutableList<ImmutableBitSet> groupSets;
+        if (groupKey.nodeLists != null) {
+            final int sizeBefore = registrar.extraNodes.size();
+            final List<ImmutableBitSet> groupSetList = new ArrayList<>();
+            for (ImmutableList<RexNode> nodeList : groupKey.nodeLists) {
+                final ImmutableBitSet groupSet2 =
+                        ImmutableBitSet.of(registrar.registerExpressions(nodeList));
+                if (!groupSet.contains(groupSet2)) {
+                    throw new IllegalArgumentException(
+                            "group set element " + nodeList + " must be a subset of group key");
+                }
+                groupSetList.add(groupSet2);
+            }
+            final ImmutableSortedMultiset<ImmutableBitSet> groupSetMultiset =
+                    ImmutableSortedMultiset.copyOf(ImmutableBitSet.COMPARATOR, groupSetList);
+            if (aggCalls.stream().anyMatch(RelBuilder::isGroupId)
+                    || !ImmutableBitSet.ORDERING.isStrictlyOrdered(groupSetMultiset)) {
+                return rewriteAggregateWithDuplicateGroupSets(groupSet, groupSetMultiset, aggCalls);
+            }
+            groupSets = ImmutableList.copyOf(groupSetMultiset.elementSet());
+            if (registrar.extraNodes.size() > sizeBefore) {
+                throw new IllegalArgumentException(
+                        "group sets contained expressions "
+                                + "not in group key: "
+                                + Util.skip(registrar.extraNodes, sizeBefore));
+            }
+        } else {
+            groupSets = ImmutableList.of(groupSet);
+        }
+
+        aggCalls.forEach(aggCall -> aggCall.register(registrar));
+        project(registrar.extraNodes);
+        rename(registrar.names);
+        final Frame frame = stack.pop();
+        RelNode r = frame.rel;
+        final List<AggregateCall> aggregateCalls = new ArrayList<>();
+        for (AggCallPlus aggCall : aggCalls) {
+            aggregateCalls.add(aggCall.aggregateCall(registrar, groupSet, r));
+        }
+
+        assert ImmutableBitSet.ORDERING.isStrictlyOrdered(groupSets) : groupSets;
+        for (ImmutableBitSet set : groupSets) {
+            assert groupSet.contains(set);
+        }
+
+        PairList<ImmutableSet<String>, RelDataTypeField> inFields = frame.fields;
+        final ImmutableBitSet groupSet2;
+        final ImmutableList<ImmutableBitSet> groupSets2;
+        if (config.pruneInputOfAggregate() && r instanceof Project) {
+            final Set<Integer> fieldsUsed = RelOptUtil.getAllFields2(groupSet, aggregateCalls);
+            // Some parts of the system can't handle rows with zero fields, so
+            // pretend that one field is used.
+            if (fieldsUsed.isEmpty()) {
+                r = ((Project) r).getInput();
+                groupSet2 = groupSet;
+                groupSets2 = groupSets;
+            } else if (fieldsUsed.size() < r.getRowType().getFieldCount()) {
+                // Some fields are computed but not used. Prune them.
+                final Map<Integer, Integer> map = new HashMap<>();
+                for (int source : fieldsUsed) {
+                    map.put(source, map.size());
+                }
+
+                groupSet2 = groupSet.permute(map);
+                groupSets2 =
+                        ImmutableBitSet.ORDERING.immutableSortedCopy(
+                                ImmutableBitSet.permute(groupSets, map));
+
+                final Mappings.TargetMapping targetMapping =
+                        Mappings.target(map, r.getRowType().getFieldCount(), fieldsUsed.size());
+                final List<AggregateCall> oldAggregateCalls = new ArrayList<>(aggregateCalls);
+                aggregateCalls.clear();
+                for (AggregateCall aggregateCall : oldAggregateCalls) {
+                    aggregateCalls.add(aggregateCall.transform(targetMapping));
+                }
+                final PairList<ImmutableSet<String>, RelDataTypeField> newInFields = PairList.of();
+                newInFields.addAll(Mappings.permute(inFields, targetMapping.inverse()));
+                inFields = newInFields;
+
+                final Project project = (Project) r;
+                final List<RexNode> newProjects = new ArrayList<>();
+                final RelDataTypeFactory.Builder builder = cluster.getTypeFactory().builder();
+                for (int i : fieldsUsed) {
+                    newProjects.add(project.getProjects().get(i));
+                    builder.add(project.getRowType().getFieldList().get(i));
+                }
+                r =
+                        project.copy(
+                                cluster.traitSet(),
+                                project.getInput(),
+                                newProjects,
+                                builder.build());
+            } else {
+                groupSet2 = groupSet;
+                groupSets2 = groupSets;
+            }
+        } else {
+            groupSet2 = groupSet;
+            groupSets2 = groupSets;
+        }
+
+        if (!config.dedupAggregateCalls() || Util.isDistinct(aggregateCalls)) {
+            return aggregate_(
+                    groupSet2, groupSets2, r, aggregateCalls, registrar.extraNodes, inFields);
+        }
+
+        // There are duplicate aggregate calls. Rebuild the list to eliminate
+        // duplicates, then add a Project.
+        final Set<AggregateCall> callSet = new HashSet<>();
+        final PairList<Integer, @Nullable String> projects = PairList.of();
+        Util.range(groupSet.cardinality()).forEach(i -> projects.add(i, null));
+        final List<AggregateCall> distinctAggregateCalls = new ArrayList<>();
+        for (AggregateCall aggregateCall : aggregateCalls) {
+            final int i;
+            if (callSet.add(aggregateCall)) {
+                i = distinctAggregateCalls.size();
+                distinctAggregateCalls.add(aggregateCall);
+            } else {
+                i = distinctAggregateCalls.indexOf(aggregateCall);
+                assert i >= 0;
+            }
+            projects.add(groupSet.cardinality() + i, aggregateCall.name);
+        }
+        aggregate_(groupSet, groupSets, r, distinctAggregateCalls, registrar.extraNodes, inFields);
+        return project(projects.transform((i, name) -> aliasMaybe(field(i), name)));
+    }
+
+    /**
+     * Returns whether an input is already unique, and therefore a Project can be created instead of
+     * an Aggregate.
+     *
+     * <p>{@link AggregateRemoveRule} does something similar, but also handles {@link
+     * org.apache.calcite.sql.SqlSingletonAggFunction} calls.
+     */
+    private boolean alreadyUnique(
+            List<AggCallPlus> aggCallList,
+            GroupKeyImpl groupKey,
+            ImmutableBitSet groupSet,
+            List<RexNode> extraNodes) {
+        final RelMetadataQuery mq = peek().getCluster().getMetadataQuery();
+        if (aggCallList.isEmpty() && groupSet.isEmpty()) {
+            final Double minRowCount = mq.getMinRowCount(peek());
+            if (minRowCount == null || minRowCount < 1d) {
+                // We can't remove "GROUP BY ()" if there's a chance the rel could be
+                // empty.
+                return false;
+            }
+        }
+
+        // If there are aggregate functions, we must be able to flatten them
+        if (!aggCallList.stream().allMatch(c -> canFlattenStatic(c.aggregateCall()))) {
+            return false;
+        }
+
+        if (extraNodes.size() == fields().size()) {
+            final Boolean unique = mq.areColumnsUnique(peek(), groupSet);
+            if (unique != null && unique && !config.aggregateUnique() && groupKey.isSimple()) {
+                // Rel is already unique.
+                return true;
+            }
+        }
+
+        // If there is at most one row, rel is already unique.
+        final Double maxRowCount = mq.getMaxRowCount(peek());
+        return maxRowCount != null
+                && maxRowCount <= 1D
+                && !config.aggregateUnique()
+                && groupKey.isSimple();
+    }
+
+    /**
+     * Finishes the implementation of {@link #aggregate} by creating an {@link Aggregate} and
+     * pushing it onto the stack.
+     */
+    private RelBuilder aggregate_(
+            ImmutableBitSet groupSet,
+            ImmutableList<ImmutableBitSet> groupSets,
+            RelNode input,
+            List<AggregateCall> aggregateCalls,
+            List<RexNode> extraNodes,
+            PairList<ImmutableSet<String>, RelDataTypeField> inFields) {
+        final RelNode aggregate =
+                struct.aggregateFactory.createAggregate(
+                        input, ImmutableList.of(), groupSet, groupSets, aggregateCalls);
+
+        // build field list
+        final PairList<ImmutableSet<String>, RelDataTypeField> fields = PairList.of();
+        final List<RelDataTypeField> aggregateFields = aggregate.getRowType().getFieldList();
+        int i = 0;
+        // first, group fields
+        for (Integer groupField : groupSet.asList()) {
+            RexNode node = extraNodes.get(groupField);
+            final SqlKind kind = node.getKind();
+            switch (kind) {
+                case INPUT_REF:
+                    fields.add(inFields.get(((RexInputRef) node).getIndex()));
+                    break;
+                default:
+                    String name = aggregateFields.get(i).getName();
+                    RelDataTypeField fieldType = new RelDataTypeFieldImpl(name, i, node.getType());
+                    fields.add(ImmutableSet.of(), fieldType);
+                    break;
+            }
+            i++;
+        }
+        // second, aggregate fields. retain `i' as field index
+        for (int j = 0; j < aggregateCalls.size(); ++j) {
+            final AggregateCall call = aggregateCalls.get(j);
+            final RelDataTypeField fieldType =
+                    new RelDataTypeFieldImpl(
+                            aggregateFields.get(i + j).getName(), i + j, call.getType());
+            fields.add(ImmutableSet.of(), fieldType);
+        }
+        stack.push(new Frame(aggregate, fields));
+        return this;
+    }
+
+    /**
+     * The {@code GROUP_ID()} function is used to distinguish duplicate groups. However, as
+     * Aggregate normalizes group sets to canonical form (i.e., flatten, sorting, redundancy
+     * removal), this information is lost in RelNode. Therefore, it is impossible to implement the
+     * function in runtime.
+     *
+     * <p>To fill this gap, an aggregation query that contains duplicate group sets is rewritten
+     * into a Union of Aggregate operators whose group sets are distinct. The number of inputs to
+     * the Union is equal to the maximum number of duplicates. In the {@code N}th input to the
+     * Union, calls to the {@code GROUP_ID} aggregate function are replaced by the integer literal
+     * {@code N}.
+     *
+     * <p>This method also handles the case where group sets are distinct but there is a call to
+     * {@code GROUP_ID}. That call is replaced by the integer literal {@code 0}.
+     *
+     * <p>Also see the discussion in <a
+     * href="https://issues.apache.org/jira/browse/CALCITE-1824">[CALCITE-1824] GROUP_ID returns
+     * wrong result</a> and <a
+     * href="https://issues.apache.org/jira/browse/CALCITE-4748">[CALCITE-4748] If there are
+     * duplicate GROUPING SETS, Calcite should return duplicate rows</a>.
+     */
+    private RelBuilder rewriteAggregateWithDuplicateGroupSets(
+            ImmutableBitSet groupSet,
+            ImmutableSortedMultiset<ImmutableBitSet> groupSets,
+            List<AggCallPlus> aggregateCalls) {
+        final List<String> fieldNamesIfNoRewrite =
+                Aggregate.deriveRowType(
+                                getTypeFactory(),
+                                peek().getRowType(),
+                                false,
+                                groupSet,
+                                groupSets.asList(),
+                                aggregateCalls.stream()
+                                        .map(AggCallPlus::aggregateCall)
+                                        .collect(toImmutableList()))
+                        .getFieldNames();
+
+        // If n duplicates exist for a particular grouping, the {@code GROUP_ID()}
+        // function produces values in the range 0 to n-1. For each value,
+        // we need to figure out the corresponding group sets.
+        //
+        // For example, "... GROUPING SETS (a, a, b, c, c, c, c)"
+        // (i) The max value of the GROUP_ID() function returns is 3
+        // (ii) GROUPING SETS (a, b, c) produces value 0,
+        //      GROUPING SETS (a, c) produces value 1,
+        //      GROUPING SETS (c) produces value 2
+        //      GROUPING SETS (c) produces value 3
+        final Map<Integer, Set<ImmutableBitSet>> groupIdToGroupSets = new HashMap<>();
+        int maxGroupId = 0;
+        for (Multiset.Entry<ImmutableBitSet> entry : groupSets.entrySet()) {
+            int groupId = entry.getCount() - 1;
+            if (groupId > maxGroupId) {
+                maxGroupId = groupId;
+            }
+            for (int i = 0; i <= groupId; i++) {
+                groupIdToGroupSets
+                        .computeIfAbsent(i, k -> Sets.newTreeSet(ImmutableBitSet.COMPARATOR))
+                        .add(entry.getElement());
+            }
+        }
+
+        // AggregateCall list without GROUP_ID function
+        final List<AggCall> aggregateCallsWithoutGroupId = new ArrayList<>(aggregateCalls);
+        aggregateCallsWithoutGroupId.removeIf(RelBuilder::isGroupId);
+
+        // For each group id value, we first construct an Aggregate without
+        // GROUP_ID() function call, and then create a Project node on top of it.
+        // The Project adds literal value for group id in right position.
+        final Frame frame = stack.pop();
+        for (int groupId = 0; groupId <= maxGroupId; groupId++) {
+            // Create the Aggregate node without GROUP_ID() call
+            stack.push(frame);
+            aggregate(
+                    groupKey(groupSet, castNonNull(groupIdToGroupSets.get(groupId))),
+                    aggregateCallsWithoutGroupId);
+
+            final List<RexNode> selectList = new ArrayList<>();
+            final int groupExprLength = groupSet.cardinality();
+            // Project fields in group by expressions
+            for (int i = 0; i < groupExprLength; i++) {
+                selectList.add(field(i));
+            }
+            // Project fields in aggregate calls
+            int groupIdCount = 0;
+            for (int i = 0; i < aggregateCalls.size(); i++) {
+                if (isGroupId(aggregateCalls.get(i))) {
+                    selectList.add(
+                            getRexBuilder()
+                                    .makeExactLiteral(
+                                            BigDecimal.valueOf(groupId),
+                                            getTypeFactory().createSqlType(SqlTypeName.BIGINT)));
+                    groupIdCount++;
+                } else {
+                    selectList.add(field(groupExprLength + i - groupIdCount));
+                }
+            }
+            project(selectList, fieldNamesIfNoRewrite);
+        }
+
+        return union(true, maxGroupId + 1);
+    }
+
+    private static boolean isGroupId(AggCall c) {
+        return ((AggCallPlus) c).op().kind == SqlKind.GROUP_ID;
+    }
+
+    private RelBuilder setOp(boolean all, SqlKind kind, int n) {
+        List<RelNode> inputs = new ArrayList<>();
+        for (int i = 0; i < n; i++) {
+            inputs.add(0, build());
+        }
+        switch (kind) {
+            case UNION:
+            case INTERSECT:
+            case EXCEPT:
+                if (n < 1) {
+                    throw new IllegalArgumentException("bad INTERSECT/UNION/EXCEPT input count");
+                }
+                break;
+            default:
+                throw new AssertionError("bad setOp " + kind);
+        }
+
+        if (n == 1) {
+            return push(inputs.get(0));
+        }
+
+        if (config.simplifyValues()
+                && kind == UNION
+                && inputs.stream().allMatch(r -> r instanceof Values)) {
+            List<RelDataType> inputTypes = Util.transform(inputs, RelNode::getRowType);
+            RelDataType rowType = getTypeFactory().leastRestrictive(inputTypes);
+            requireNonNull(rowType, () -> "leastRestrictive(" + inputTypes + ")");
+            final List<List<RexLiteral>> tuples = new ArrayList<>();
+            for (RelNode input : inputs) {
+                tuples.addAll(((Values) input).tuples);
+            }
+            final List<List<RexLiteral>> tuples2 = all ? tuples : Util.distinctList(tuples);
+            return values(tuples2, rowType);
+        }
+
+        return push(struct.setOpFactory.createSetOp(kind, inputs, all));
+    }
+
+    /**
+     * Creates a {@link Union} of the two most recent relational expressions on the stack.
+     *
+     * @param all Whether to create UNION ALL
+     */
+    public RelBuilder union(boolean all) {
+        return union(all, 2);
+    }
+
+    /**
+     * Creates a {@link Union} of the {@code n} most recent relational expressions on the stack.
+     *
+     * @param all Whether to create UNION ALL
+     * @param n Number of inputs to the UNION operator
+     */
+    public RelBuilder union(boolean all, int n) {
+        return setOp(all, UNION, n);
+    }
+
+    /**
+     * Creates an {@link Intersect} of the two most recent relational expressions on the stack.
+     *
+     * @param all Whether to create INTERSECT ALL
+     */
+    public RelBuilder intersect(boolean all) {
+        return intersect(all, 2);
+    }
+
+    /**
+     * Creates an {@link Intersect} of the {@code n} most recent relational expressions on the
+     * stack.
+     *
+     * @param all Whether to create INTERSECT ALL
+     * @param n Number of inputs to the INTERSECT operator
+     */
+    public RelBuilder intersect(boolean all, int n) {
+        return setOp(all, SqlKind.INTERSECT, n);
+    }
+
+    /**
+     * Creates a {@link Minus} of the two most recent relational expressions on the stack.
+     *
+     * @param all Whether to create EXCEPT ALL
+     */
+    public RelBuilder minus(boolean all) {
+        return minus(all, 2);
+    }
+
+    /**
+     * Creates a {@link Minus} of the {@code n} most recent relational expressions on the stack.
+     *
+     * @param all Whether to create EXCEPT ALL
+     */
+    public RelBuilder minus(boolean all, int n) {
+        return setOp(all, SqlKind.EXCEPT, n);
+    }
+
+    /**
+     * Creates a {@link TableScan} on a {@link TransientTable} with the given name, using as type
+     * the top of the stack's type.
+     *
+     * @param tableName table name
+     */
+    @Experimental
+    public RelBuilder transientScan(String tableName) {
+        return this.transientScan(tableName, this.peek().getRowType());
+    }
+
+    /**
+     * Creates a {@link TableScan} on a {@link TransientTable} with the given name and type.
+     *
+     * @param tableName table name
+     * @param rowType row type of the table
+     */
+    @Experimental
+    public RelBuilder transientScan(String tableName, RelDataType rowType) {
+        TransientTable transientTable = new ListTransientTable(tableName, rowType);
+        requireNonNull(relOptSchema, "relOptSchema");
+        RelOptTable relOptTable =
+                RelOptTableImpl.create(
+                        relOptSchema, rowType, transientTable, ImmutableList.of(tableName));
+        RelNode scan =
+                struct.scanFactory.createScan(
+                        ViewExpanders.toRelContext(viewExpander, cluster), relOptTable);
+        push(scan);
+        rename(rowType.getFieldNames());
+        return this;
+    }
+
+    /**
+     * Creates a {@link TableSpool} for the most recent relational expression.
+     *
+     * @param readType Spool's read type (as described in {@link Spool.Type})
+     * @param writeType Spool's write type (as described in {@link Spool.Type})
+     * @param table Table to write into
+     */
+    private RelBuilder tableSpool(Spool.Type readType, Spool.Type writeType, RelOptTable table) {
+        RelNode spool = struct.spoolFactory.createTableSpool(peek(), readType, writeType, table);
+        replaceTop(spool);
+        return this;
+    }
+
+    /**
+     * Creates a {@link RepeatUnion} associated to a {@link TransientTable} without a maximum number
+     * of iterations, i.e. repeatUnion(tableName, all, -1).
+     *
+     * @param tableName name of the {@link TransientTable} associated to the {@link RepeatUnion}
+     * @param all whether duplicates will be considered or not
+     */
+    @Experimental
+    public RelBuilder repeatUnion(String tableName, boolean all) {
+        return repeatUnion(tableName, all, -1);
+    }
+
+    /**
+     * Creates a {@link RepeatUnion} associated to a {@link TransientTable} of the two most recent
+     * relational expressions on the stack.
+     *
+     * <p>Warning: if these relational expressions are not correctly defined, this operation might
+     * lead to an infinite loop.
+     *
+     * <p>The generated {@link RepeatUnion} operates as follows:
+     *
+     * <ul>
+     *   <li>Evaluate its left term once, propagating the results into the {@link TransientTable};
+     *   <li>Evaluate its right term (which may contain a {@link TableScan} on the {@link
+     *       TransientTable}) over and over until it produces no more results (or until an optional
+     *       maximum number of iterations is reached). On each iteration, the results are propagated
+     *       into the {@link TransientTable}, overwriting the results from the previous one.
+     * </ul>
+     *
+     * @param tableName Name of the {@link TransientTable} associated to the {@link RepeatUnion}
+     * @param all Whether duplicates are considered
+     * @param iterationLimit Maximum number of iterations; negative value means no limit
+     */
+    @Experimental
+    public RelBuilder repeatUnion(String tableName, boolean all, int iterationLimit) {
+        RelOptTable table = null;
+        for (int i = 0; i < stack.size(); i++) { // search scan(tableName) in the stack
+            table = RelOptUtil.findTable(peek(i), tableName);
+            if (table != null) { // found
+                break;
+            }
+        }
+        if (table == null) {
+            throw RESOURCE.tableNotFound(tableName).ex();
+        }
+
+        RelNode iterative = tableSpool(Spool.Type.LAZY, Spool.Type.LAZY, table).build();
+        RelNode seed = tableSpool(Spool.Type.LAZY, Spool.Type.LAZY, table).build();
+        RelNode repeatUnion =
+                struct.repeatUnionFactory.createRepeatUnion(
+                        seed, iterative, all, iterationLimit, table);
+        return push(repeatUnion);
+    }
+
+    /** Creates a {@link Join} with an array of conditions. */
+    public RelBuilder join(JoinRelType joinType, RexNode condition0, RexNode... conditions) {
+        return join(joinType, Lists.asList(condition0, conditions));
+    }
+
+    /** Creates a {@link Join} with multiple conditions. */
+    public RelBuilder join(JoinRelType joinType, Iterable<? extends RexNode> conditions) {
+        return join(joinType, and(conditions), ImmutableSet.of());
+    }
+
+    /** Creates a {@link Join} with one condition. */
+    public RelBuilder join(JoinRelType joinType, RexNode condition) {
+        return join(joinType, condition, ImmutableSet.of());
+    }
+
+    /** Creates a {@link Join} with correlating variables. */
+    public RelBuilder join(
+            JoinRelType joinType, RexNode condition, Set<CorrelationId> variablesSet) {
+        Frame right = stack.pop();
+        final Frame left = stack.pop();
+        final RelNode join;
+        final boolean correlate = checkIfCorrelated(variablesSet, joinType, left.rel, right.rel);
+        RexNode postCondition = literal(true);
+        if (config.simplify()) {
+            // Normalize expanded versions IS NOT DISTINCT FROM so that simplifier does not
+            // transform the expression to something unrecognizable
+            if (condition instanceof RexCall) {
+                condition =
+                        RelOptUtil.collapseExpandedIsNotDistinctFromExpr(
+                                (RexCall) condition, getRexBuilder());
+            }
+            condition = simplifier.simplifyUnknownAsFalse(condition);
+        }
+        if (correlate) {
+            final CorrelationId id = Iterables.getOnlyElement(variablesSet);
+            // Correlate does not have an ON clause.
+            switch (joinType) {
+                case LEFT:
+                case SEMI:
+                case ANTI:
+                    // For a LEFT/SEMI/ANTI, predicate must be evaluated first.
+                    stack.push(right);
+                    filter(condition.accept(new Shifter(left.rel, id, right.rel)));
+                    right = stack.pop();
+                    break;
+                case INNER:
+                    // For INNER, we can defer.
+                    postCondition = condition;
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Correlated " + joinType + " join is not supported");
+            }
+            final ImmutableBitSet requiredColumns = RelOptUtil.correlationColumns(id, right.rel);
+            join =
+                    struct.correlateFactory.createCorrelate(
+                            left.rel, right.rel, ImmutableList.of(), id, requiredColumns, joinType);
+        } else {
+            RelNode join0 =
+                    struct.joinFactory.createJoin(
+                            left.rel,
+                            right.rel,
+                            ImmutableList.of(),
+                            condition,
+                            variablesSet,
+                            joinType,
+                            false);
+
+            if (join0 instanceof Join && config.pushJoinCondition()) {
+                join = RelOptUtil.pushDownJoinConditions((Join) join0, this);
+            } else {
+                join = join0;
+            }
+        }
+        final PairList<ImmutableSet<String>, RelDataTypeField> fields = PairList.of();
+        fields.addAll(left.fields);
+        fields.addAll(right.fields);
+        stack.push(new Frame(join, fields));
+        filter(postCondition);
+        return this;
+    }
+
+    /**
+     * Creates a {@link Correlate} with a {@link CorrelationId} and an array of fields that are used
+     * by correlation.
+     */
+    public RelBuilder correlate(
+            JoinRelType joinType, CorrelationId correlationId, RexNode... requiredFields) {
+        return correlate(joinType, correlationId, ImmutableList.copyOf(requiredFields));
+    }
+
+    /**
+     * Creates a {@link Correlate} with a {@link CorrelationId} and a list of fields that are used
+     * by correlation.
+     */
+    public RelBuilder correlate(
+            JoinRelType joinType,
+            CorrelationId correlationId,
+            Iterable<? extends RexNode> requiredFields) {
+        Frame right = stack.pop();
+
+        final Registrar registrar = new Registrar(fields(), peek().getRowType().getFieldNames());
+
+        List<Integer> requiredOrdinals =
+                registrar.registerExpressions(ImmutableList.copyOf(requiredFields));
+
+        project(registrar.extraNodes);
+        rename(registrar.names);
+        Frame left = stack.pop();
+
+        final RelNode correlate =
+                struct.correlateFactory.createCorrelate(
+                        left.rel,
+                        right.rel,
+                        ImmutableList.of(),
+                        correlationId,
+                        ImmutableBitSet.of(requiredOrdinals),
+                        joinType);
+
+        final PairList<ImmutableSet<String>, RelDataTypeField> fields = PairList.of();
+        fields.addAll(left.fields);
+        fields.addAll(right.fields);
+        stack.push(new Frame(correlate, fields));
+
+        return this;
+    }
+
+    /**
+     * Creates a {@link Join} using USING syntax.
+     *
+     * <p>For each of the field names, both left and right inputs must have a field of that name.
+     * Constructs a join condition that the left and right fields are equal.
+     *
+     * @param joinType Join type
+     * @param fieldNames Field names
+     */
+    public RelBuilder join(JoinRelType joinType, String... fieldNames) {
+        final List<RexNode> conditions = new ArrayList<>();
+        for (String fieldName : fieldNames) {
+            conditions.add(equals(field(2, 0, fieldName), field(2, 1, fieldName)));
+        }
+        return join(joinType, conditions);
+    }
+
+    /**
+     * Creates a {@link Join} with {@link JoinRelType#SEMI}.
+     *
+     * <p>A semi-join is a form of join that combines two relational expressions according to some
+     * condition, and outputs only rows from the left input for which at least one row from the
+     * right input matches. It only outputs columns from the left input, and ignores duplicates on
+     * the right.
+     *
+     * <p>For example, {@code EMP semi-join DEPT} finds all {@code EMP} records that do not have a
+     * corresponding {@code DEPT} record, similar to the following SQL:
+     *
+     * <blockquote>
+     *
+     * <pre>
+     * SELECT * FROM EMP
+     * WHERE EXISTS (SELECT 1 FROM DEPT
+     *     WHERE DEPT.DEPTNO = EMP.DEPTNO)</pre>
+     *
+     * </blockquote>
+     */
+    public RelBuilder semiJoin(Iterable<? extends RexNode> conditions) {
+        final Frame right = stack.pop();
+        final RelNode semiJoin =
+                struct.joinFactory.createJoin(
+                        peek(),
+                        right.rel,
+                        ImmutableList.of(),
+                        and(conditions),
+                        ImmutableSet.of(),
+                        JoinRelType.SEMI,
+                        false);
+        replaceTop(semiJoin);
+        return this;
+    }
+
+    /**
+     * Creates a {@link Join} with {@link JoinRelType#SEMI}.
+     *
+     * @see #semiJoin(Iterable)
+     */
+    public RelBuilder semiJoin(RexNode... conditions) {
+        return semiJoin(ImmutableList.copyOf(conditions));
+    }
+
+    /**
+     * Creates an anti-join.
+     *
+     * <p>An anti-join is a form of join that combines two relational expressions according to some
+     * condition, but outputs only rows from the left input for which no rows from the right input
+     * match.
+     *
+     * <p>For example, {@code EMP anti-join DEPT} finds all {@code EMP} records that do not have a
+     * corresponding {@code DEPT} record, similar to the following SQL:
+     *
+     * <blockquote>
+     *
+     * <pre>
+     * SELECT * FROM EMP
+     * WHERE NOT EXISTS (SELECT 1 FROM DEPT
+     *     WHERE DEPT.DEPTNO = EMP.DEPTNO)</pre>
+     *
+     * </blockquote>
+     */
+    public RelBuilder antiJoin(Iterable<? extends RexNode> conditions) {
+        final Frame right = stack.pop();
+        final RelNode antiJoin =
+                struct.joinFactory.createJoin(
+                        peek(),
+                        right.rel,
+                        ImmutableList.of(),
+                        and(conditions),
+                        ImmutableSet.of(),
+                        JoinRelType.ANTI,
+                        false);
+        replaceTop(antiJoin);
+        return this;
+    }
+
+    /**
+     * Creates an anti-join.
+     *
+     * @see #antiJoin(Iterable)
+     */
+    public RelBuilder antiJoin(RexNode... conditions) {
+        return antiJoin(ImmutableList.copyOf(conditions));
+    }
+
+    /** Assigns a table alias to the top entry on the stack. */
+    public RelBuilder as(final String alias) {
+        final Frame pair = stack.pop();
+        final PairList<ImmutableSet<String>, RelDataTypeField> newFields = PairList.of();
+        pair.fields.forEach(
+                (aliases, field) -> {
+                    final ImmutableSet<String> aliasList =
+                            aliases.contains(alias)
+                                    ? aliases
+                                    : ImmutableSet.<String>builder()
+                                            .addAll(aliases)
+                                            .add(alias)
+                                            .build();
+                    newFields.add(aliasList, field);
+                });
+        stack.push(new Frame(pair.rel, newFields));
+        return this;
+    }
+
+    /**
+     * Creates a {@link Values}.
+     *
+     * <p>The {@code values} array must have the same number of entries as {@code fieldNames}, or an
+     * integer multiple if you wish to create multiple rows.
+     *
+     * <p>The {@code fieldNames} array must not be null or empty, but may contain null values.
+     *
+     * <p>If there are zero rows, or if all values of any column are null, this method cannot deduce
+     * the type of columns. For these cases, call {@link #values(Iterable, RelDataType)}.
+     *
+     * @param fieldNames Field names
+     * @param values Values
+     */
+    public RelBuilder values(@Nullable String[] fieldNames, @Nullable Object... values) {
+        requireNonNull(fieldNames, "fieldNames");
+        if (fieldNames.length == 0
+                || values.length % fieldNames.length != 0
+                || values.length < fieldNames.length) {
+            throw new IllegalArgumentException(
+                    "Value count must be a positive multiple of field count");
+        }
+        final int rowCount = values.length / fieldNames.length;
+        for (Ord<@Nullable String> fieldName : Ord.zip(fieldNames)) {
+            if (allNull(values, fieldName.i, fieldNames.length)) {
+                throw new IllegalArgumentException(
+                        "All values of field '"
+                                + fieldName.e
+                                + "' (field index "
+                                + fieldName.i
+                                + ")"
+                                + " are null; cannot deduce type");
+            }
+        }
+        final ImmutableList<ImmutableList<RexLiteral>> tupleList =
+                tupleList(fieldNames.length, values);
+        assert tupleList.size() == rowCount;
+        final List<String> fieldNameList =
+                Util.transformIndexed(
+                        Arrays.asList(fieldNames),
+                        (name, i) -> name != null ? name : SqlUtil.deriveAliasFromOrdinal(i));
+        return values(tupleList, fieldNameList);
+    }
+
+    private RelBuilder values(List<? extends List<RexLiteral>> tupleList, List<String> fieldNames) {
+        final RelDataTypeFactory typeFactory = cluster.getTypeFactory();
+        final RelDataTypeFactory.Builder builder = typeFactory.builder();
+        Ord.forEach(
+                fieldNames,
+                (fieldName, i) -> {
+                    final RelDataType type =
+                            typeFactory.leastRestrictive(
+                                    new AbstractList<RelDataType>() {
+                                        @Override
+                                        public RelDataType get(int index) {
+                                            return tupleList.get(index).get(i).getType();
+                                        }
+
+                                        @Override
+                                        public int size() {
+                                            return tupleList.size();
+                                        }
+                                    });
+                    assert type != null : "can't infer type for field " + i + ", " + fieldName;
+                    builder.add(fieldName, type);
+                });
+        final RelDataType rowType = builder.build();
+        return values(tupleList, rowType);
+    }
+
+    private ImmutableList<ImmutableList<RexLiteral>> tupleList(
+            int columnCount, @Nullable Object[] values) {
+        final ImmutableList.Builder<ImmutableList<RexLiteral>> listBuilder =
+                ImmutableList.builder();
+        final List<RexLiteral> valueList = new ArrayList<>();
+        for (int i = 0; i < values.length; i++) {
+            Object value = values[i];
+            valueList.add(literal(value));
+            if ((i + 1) % columnCount == 0) {
+                listBuilder.add(ImmutableList.copyOf(valueList));
+                valueList.clear();
+            }
+        }
+        return listBuilder.build();
+    }
+
+    /** Returns whether all values for a given column are null. */
+    private static boolean allNull(@Nullable Object[] values, int column, int columnCount) {
+        for (int i = column; i < values.length; i += columnCount) {
+            if (values[i] != null) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Creates a relational expression that reads from an input and throws all of the rows away.
+     *
+     * <p>Note that this method always pops one relational expression from the stack. {@code
+     * values}, in contrast, does not pop any relational expressions, and always produces a leaf.
+     *
+     * <p>The default implementation creates a {@link Values} with the same specified row type and
+     * aliases as the input, and ignores the input entirely. But schema-on-query systems such as
+     * Drill might override this method to create a relation expression that retains the input, just
+     * to read its schema.
+     */
+    public RelBuilder empty() {
+        final Frame frame = stack.pop();
+        final RelNode values =
+                struct.valuesFactory.createValues(
+                        cluster, frame.rel.getRowType(), ImmutableList.of());
+        stack.push(new Frame(values, frame.fields));
+        return this;
+    }
+
+    /**
+     * Creates a {@link Values} with a specified row type.
+     *
+     * <p>This method can handle cases that {@link #values(String[], Object...)} cannot, such as all
+     * values of a column being null, or there being zero rows.
+     *
+     * @param rowType Row type
+     * @param columnValues Values
+     */
+    public RelBuilder values(RelDataType rowType, Object... columnValues) {
+        final ImmutableList<ImmutableList<RexLiteral>> tupleList =
+                tupleList(rowType.getFieldCount(), columnValues);
+        RelNode values =
+                struct.valuesFactory.createValues(
+                        cluster, rowType, ImmutableList.copyOf(tupleList));
+        push(values);
+        return this;
+    }
+
+    /**
+     * Creates a {@link Values} with a specified row type.
+     *
+     * <p>This method can handle cases that {@link #values(String[], Object...)} cannot, such as all
+     * values of a column being null, or there being zero rows.
+     *
+     * @param tupleList Tuple list
+     * @param rowType Row type
+     */
+    public RelBuilder values(Iterable<? extends List<RexLiteral>> tupleList, RelDataType rowType) {
+        RelNode values = struct.valuesFactory.createValues(cluster, rowType, copy(tupleList));
+        push(values);
+        return this;
+    }
+
+    /**
+     * Creates a {@link Values} with a specified row type and zero rows.
+     *
+     * @param rowType Row type
+     */
+    public RelBuilder values(RelDataType rowType) {
+        return values(ImmutableList.<ImmutableList<RexLiteral>>of(), rowType);
+    }
+
+    /**
+     * Converts an iterable of lists into an immutable list of immutable lists with the same
+     * contents. Returns the same object if possible.
+     */
+    private static <E> ImmutableList<ImmutableList<E>> copy(Iterable<? extends List<E>> tupleList) {
+        final ImmutableList.Builder<ImmutableList<E>> builder = ImmutableList.builder();
+        int changeCount = 0;
+        for (List<E> literals : tupleList) {
+            final ImmutableList<E> literals2 = ImmutableList.copyOf(literals);
+            builder.add(literals2);
+            if (literals != literals2) {
+                ++changeCount;
+            }
+        }
+        if (changeCount == 0 && tupleList instanceof ImmutableList) {
+            // don't make a copy if we don't have to
+            //noinspection unchecked
+            return (ImmutableList<ImmutableList<E>>) tupleList;
+        }
+        return builder.build();
+    }
+
+    /** Creates a limit without a sort. */
+    public RelBuilder limit(int offset, int fetch) {
+        return sortLimit(offset, fetch, ImmutableList.of());
+    }
+
+    /** Creates an Exchange by distribution. */
+    public RelBuilder exchange(RelDistribution distribution) {
+        RelNode exchange = struct.exchangeFactory.createExchange(peek(), distribution);
+        replaceTop(exchange);
+        return this;
+    }
+
+    /** Creates a SortExchange by distribution and collation. */
+    public RelBuilder sortExchange(RelDistribution distribution, RelCollation collation) {
+        RelNode exchange =
+                struct.sortExchangeFactory.createSortExchange(peek(), distribution, collation);
+        replaceTop(exchange);
+        return this;
+    }
+
+    /**
+     * Creates a {@link Sort} by field ordinals.
+     *
+     * <p>Negative fields mean descending: -1 means field(0) descending, -2 means field(1)
+     * descending, etc.
+     */
+    public RelBuilder sort(int... fields) {
+        final ImmutableList.Builder<RexNode> builder = ImmutableList.builder();
+        for (int field : fields) {
+            builder.add(field < 0 ? desc(field(-field - 1)) : field(field));
+        }
+        return sortLimit(-1, -1, builder.build());
+    }
+
+    /** Creates a {@link Sort} by expressions. */
+    public RelBuilder sort(RexNode... nodes) {
+        return sortLimit(-1, -1, ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a {@link Sort} by expressions. */
+    public RelBuilder sort(Iterable<? extends RexNode> nodes) {
+        return sortLimit(-1, -1, nodes);
+    }
+
+    /** Creates a {@link Sort} by expressions, with limit and offset. */
+    public RelBuilder sortLimit(int offset, int fetch, RexNode... nodes) {
+        return sortLimit(offset, fetch, ImmutableList.copyOf(nodes));
+    }
+
+    /** Creates a {@link Sort} by specifying collations. */
+    public RelBuilder sort(RelCollation collation) {
+        final RelNode sort = struct.sortFactory.createSort(peek(), collation, null, null);
+        replaceTop(sort);
+        return this;
+    }
+
+    /**
+     * Creates a {@link Sort} by a list of expressions, with limit and offset.
+     *
+     * @param offset Number of rows to skip; non-positive means don't skip any
+     * @param fetch Maximum number of rows to fetch; negative means no limit
+     * @param nodes Sort expressions
+     */
+    public RelBuilder sortLimit(int offset, int fetch, Iterable<? extends RexNode> nodes) {
+        final @Nullable RexNode offsetNode = offset <= 0 ? null : literal(offset);
+        final @Nullable RexNode fetchNode = fetch < 0 ? null : literal(fetch);
+        return sortLimit(offsetNode, fetchNode, nodes);
+    }
+
+    /**
+     * Creates a {@link Sort} by a list of expressions, with limitNode and offsetNode.
+     *
+     * @param offsetNode RexLiteral means number of rows to skip is deterministic, RexDynamicParam
+     *     means number of rows to skip is dynamic.
+     * @param fetchNode RexLiteral means maximum number of rows to fetch is deterministic,
+     *     RexDynamicParam mean maximum number is dynamic.
+     * @param nodes Sort expressions
+     */
+    public RelBuilder sortLimit(
+            @Nullable RexNode offsetNode,
+            @Nullable RexNode fetchNode,
+            Iterable<? extends RexNode> nodes) {
+        if (offsetNode != null) {
+            if (!(offsetNode instanceof RexLiteral || offsetNode instanceof RexDynamicParam)) {
+                throw new IllegalArgumentException(
+                        "OFFSET node must be RexLiteral or RexDynamicParam");
+            }
+        }
+        if (fetchNode != null) {
+            if (!(fetchNode instanceof RexLiteral || fetchNode instanceof RexDynamicParam)) {
+                throw new IllegalArgumentException(
+                        "FETCH node must be RexLiteral or RexDynamicParam");
+            }
+        }
+
+        final Registrar registrar = new Registrar(fields(), ImmutableList.of());
+        final List<RelFieldCollation> fieldCollations = registrar.registerFieldCollations(nodes);
+        final int fetch = fetchNode instanceof RexLiteral ? RexLiteral.intValue(fetchNode) : -1;
+        if (offsetNode == null && fetch == 0 && config.simplifyLimit()) {
+            return empty();
+        }
+        if (offsetNode == null && fetchNode == null && fieldCollations.isEmpty()) {
+            return this; // sort is trivial
+        }
+
+        if (fieldCollations.isEmpty()) {
+            assert registrar.addedFieldCount() == 0;
+            RelNode top = peek();
+            if (top instanceof Sort) {
+                final Sort sort2 = (Sort) top;
+                if (sort2.offset == null && sort2.fetch == null) {
+                    replaceTop(sort2.getInput());
+                    final RelNode sort =
+                            struct.sortFactory.createSort(
+                                    peek(), sort2.collation, offsetNode, fetchNode);
+                    replaceTop(sort);
+                    return this;
+                }
+            }
+            if (top instanceof Project) {
+                final Project project = (Project) top;
+                if (project.getInput() instanceof Sort) {
+                    final Sort sort2 = (Sort) project.getInput();
+                    if (sort2.offset == null && sort2.fetch == null) {
+                        final RelNode sort =
+                                struct.sortFactory.createSort(
+                                        sort2.getInput(), sort2.collation, offsetNode, fetchNode);
+                        replaceTop(
+                                struct.projectFactory.createProject(
+                                        sort,
+                                        project.getHints(),
+                                        project.getProjects(),
+                                        Pair.right(project.getNamedProjects()),
+                                        project.getVariablesSet()));
+                        return this;
+                    }
+                }
+            }
+        }
+        if (registrar.addedFieldCount() > 0) {
+            project(registrar.extraNodes);
+        }
+        final RelNode sort =
+                struct.sortFactory.createSort(
+                        peek(), RelCollations.of(fieldCollations), offsetNode, fetchNode);
+        replaceTop(sort);
+        if (registrar.addedFieldCount() > 0) {
+            project(registrar.originalExtraNodes);
+        }
+        return this;
+    }
+
+    private static RelFieldCollation collation(
+            RexNode node,
+            RelFieldCollation.Direction direction,
+            RelFieldCollation.NullDirection nullDirection,
+            List<RexNode> extraNodes) {
+        switch (node.getKind()) {
+            case INPUT_REF:
+                return new RelFieldCollation(
+                        ((RexInputRef) node).getIndex(),
+                        direction,
+                        Util.first(nullDirection, direction.defaultNullDirection()));
+            case DESCENDING:
+                return collation(
+                        ((RexCall) node).getOperands().get(0),
+                        RelFieldCollation.Direction.DESCENDING,
+                        nullDirection,
+                        extraNodes);
+            case NULLS_FIRST:
+                return collation(
+                        ((RexCall) node).getOperands().get(0),
+                        direction,
+                        RelFieldCollation.NullDirection.FIRST,
+                        extraNodes);
+            case NULLS_LAST:
+                return collation(
+                        ((RexCall) node).getOperands().get(0),
+                        direction,
+                        RelFieldCollation.NullDirection.LAST,
+                        extraNodes);
+            default:
+                final int fieldIndex = extraNodes.size();
+                extraNodes.add(node);
+                return new RelFieldCollation(
+                        fieldIndex,
+                        direction,
+                        Util.first(nullDirection, direction.defaultNullDirection()));
+        }
+    }
+
+    private static RexFieldCollation rexCollation(
+            RexNode node,
+            RelFieldCollation.Direction direction,
+            RelFieldCollation.NullDirection nullDirection) {
+        switch (node.getKind()) {
+            case DESCENDING:
+                return rexCollation(
+                        ((RexCall) node).operands.get(0),
+                        RelFieldCollation.Direction.DESCENDING,
+                        nullDirection);
+            case NULLS_LAST:
+                return rexCollation(
+                        ((RexCall) node).operands.get(0),
+                        direction,
+                        RelFieldCollation.NullDirection.LAST);
+            case NULLS_FIRST:
+                return rexCollation(
+                        ((RexCall) node).operands.get(0),
+                        direction,
+                        RelFieldCollation.NullDirection.FIRST);
+            default:
+                final Set<SqlKind> flags = EnumSet.noneOf(SqlKind.class);
+                if (direction == RelFieldCollation.Direction.DESCENDING) {
+                    flags.add(SqlKind.DESCENDING);
+                }
+                if (nullDirection == RelFieldCollation.NullDirection.FIRST) {
+                    flags.add(SqlKind.NULLS_FIRST);
+                }
+                if (nullDirection == RelFieldCollation.NullDirection.LAST) {
+                    flags.add(SqlKind.NULLS_LAST);
+                }
+                return new RexFieldCollation(node, flags);
+        }
+    }
+
+    /**
+     * Creates a projection that converts the current relational expression's output to a desired
+     * row type.
+     *
+     * <p>The desired row type and the row type to be converted must have the same number of fields.
+     *
+     * @param castRowType row type after cast
+     * @param rename if true, use field names from castRowType; if false, preserve field names from
+     *     rel
+     */
+    public RelBuilder convert(RelDataType castRowType, boolean rename) {
+        final RelNode r = build();
+        final RelNode r2 = RelOptUtil.createCastRel(r, castRowType, rename, struct.projectFactory);
+        push(r2);
+        return this;
+    }
+
+    // BEGIN FLINK MODIFICATION
+
+    /**
+     * Creates a projection that converts the current relational expression's output to a desired
+     * row type.
+     *
+     * <p>The desired row type and the row type to be converted must have the same number of fields.
+     *
+     * @param castRowType row type after cast
+     * @param rename if true, use field names from castRowType; if false, preserve field names from
+     * @param canBeIgnoredForMultiJoin if true, set simplify flag for project rel node
+     */
+    public RelBuilder convert(
+            RelDataType castRowType, boolean rename, boolean canBeIgnoredForMultiJoin) {
+        final RelNode r = build();
+        final RelNode r2 = RelOptUtil.createCastRel(r, castRowType, rename, struct.projectFactory);
+        if (canBeIgnoredForMultiJoin && r2 instanceof LogicalProject) {
+            ((LogicalProject) r2).setIgnoreForMultiJoin(true);
+        }
+        push(r2);
+        return this;
+    }
+
+    // END FLINK MODIFICATION

Review Comment:
   As I understand it, usually, the necessary changes to Calcite classes for Flink are made within the Flink project.



##########
flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalProject.java:
##########
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.calcite.rel.logical;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.apache.calcite.plan.Convention;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelCollationTraitDef;
+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;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.sql.validate.SqlValidatorUtil;
+import org.apache.calcite.util.Util;
+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 targeted at any particular engine or
+ * calling convention.
+ */
+public final class LogicalProject extends Project {
+    // ~ Constructors -----------------------------------------------------------
+
+    /**
+     * Creates a LogicalProject.
+     *
+     * <p>Use {@link #create} unless you know what you're doing.
+     *
+     * @param cluster Cluster this relational expression belongs to
+     * @param traitSet Traits of this relational expression
+     * @param hints Hints of this relational expression
+     * @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,
+            RelTraitSet traitSet,
+            List<RelHint> hints,
+            RelNode input,
+            List<? extends RexNode> projects,
+            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, 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, ImmutableSet.of());
+        Util.discard(flags);
+    }
+
+    @Deprecated // to be removed before 2.0
+    public LogicalProject(
+            RelOptCluster cluster,
+            RelNode input,
+            List<RexNode> projects,
+            @Nullable List<? extends @Nullable String> fieldNames,
+            int flags) {
+        this(
+                cluster,
+                cluster.traitSetOf(RelCollations.EMPTY),
+                ImmutableList.of(),
+                input,
+                projects,
+                RexUtil.createStructType(cluster.getTypeFactory(), projects, fieldNames, null),
+                ImmutableSet.of());
+        Util.discard(flags);
+    }
+
+    /** Creates a LogicalProject by parsing serialized output. */
+    public LogicalProject(RelInput input) {
+        super(input);
+    }
+
+    // ~ Methods ----------------------------------------------------------------
+
+    /**
+     * 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, variablesSet);
+    }
+
+    /**
+     * 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, variablesSet);
+    }
+
+    @Override
+    public LogicalProject copy(
+            RelTraitSet traitSet, RelNode input, List<RexNode> projects, RelDataType rowType) {
+        LogicalProject project =
+                new LogicalProject(
+                        getCluster(), traitSet, hints, input, projects, rowType, variablesSet);
+        project.setIgnoreForMultiJoin(this.canBeIgnoredForMultiJoin);
+        return project;
+    }
+
+    @Override
+    public RelNode accept(RelShuttle shuttle) {
+        return shuttle.visit(this);
+    }
+
+    @Override
+    public RelNode withHints(List<RelHint> hintList) {
+        return new LogicalProject(
+                getCluster(), traitSet, hintList, input, getProjects(), getRowType(), variablesSet);
+    }
+
+    @Override
+    public boolean deepEquals(@Nullable Object obj) {
+        return deepEquals0(obj);
+    }
+
+    @Override
+    public int deepHashCode() {
+        return deepHashCode0();
+    }
+
+    // BEGIN FLINK MODIFICATION
+
+    private boolean canBeIgnoredForMultiJoin = false;
+
+    public void setIgnoreForMultiJoin(boolean canBeIgnoredForMultiJoin) {
+        this.canBeIgnoredForMultiJoin = canBeIgnoredForMultiJoin;
+    }
+
+    public boolean canBeIgnoredForMultiJoin() {
+        return canBeIgnoredForMultiJoin;
+    }
+
+    // END FLINK MODIFICATION

Review Comment:
   As I understand it, usually, the necessary changes to Calcite classes for Flink are made within the Flink project.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [FLINK-39285][table] Ignore project rel node generated by FlinkFilterJoinRule in JoinToMultiJoinRule [flink]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #27815:
URL: https://github.com/apache/flink/pull/27815#issuecomment-6139976938

   This PR is being marked as stale since it has not had any activity in the last 90 days. 
   If you would like to keep this PR alive, please leave a comment asking for a review. 
   If the PR has merge conflicts, update it with the latest from the base branch.
   
   If you are having difficulty finding a reviewer, please reach out to the 
   community, contact details can be found here: https://flink.apache.org/what-is-flink/community/
   
   If this PR is no longer valid or desired, please feel free to close it. 
   If no activity occurs in the next 30 days, it will be automatically closed.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@flink.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org