You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by tw...@apache.org on 2022/10/21 07:48:02 UTC

[flink] 02/02: [FLINK-20873][table] Upgrade Calcite to version 1.27

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

twalthr pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git

commit 600dfc77a301601f997fdf2c4f479a41b5e0ef2f
Author: Sergey Nuyanzin <sn...@gmail.com>
AuthorDate: Fri Oct 7 15:42:33 2022 +0200

    [FLINK-20873][table] Upgrade Calcite to version 1.27
    
    This closes #20850.
---
 .../delegation/hive/HiveParserCalcitePlanner.java  |    1 +
 flink-table/flink-sql-client/pom.xml               |    6 +
 flink-table/flink-sql-parser-hive/pom.xml          |   10 +
 .../parser/hive/FlinkHiveSqlParserImplTest.java    |   24 +
 flink-table/flink-sql-parser/pom.xml               |   22 +-
 .../sql/parser/validate/FlinkSqlConformance.java   |    7 +
 .../flink/sql/parser/FlinkSqlParserImplTest.java   |   41 +-
 flink-table/flink-table-planner/pom.xml            |   50 +-
 .../org/apache/calcite/rel/core/Correlate.java     |   35 +-
 .../java/org/apache/calcite/rel/core/Filter.java   |   29 +-
 .../org/apache/calcite/rel/core/Intersect.java     |    9 +-
 .../java/org/apache/calcite/rel/core/Minus.java    |    2 +-
 .../java/org/apache/calcite/rel/core/SetOp.java    |   11 +-
 .../java/org/apache/calcite/rel/core/Sort.java     |   97 +-
 .../java/org/apache/calcite/rel/core/Values.java   |   33 +-
 .../java/org/apache/calcite/rel/core/Window.java   |   45 +-
 .../calcite/rel/logical/LogicalCorrelate.java      |    7 +-
 .../apache/calcite/rel/logical/LogicalFilter.java  |   36 +-
 .../apache/calcite/rel/logical/LogicalSort.java    |   14 +-
 .../apache/calcite/rel/logical/LogicalUnion.java   |    1 +
 .../apache/calcite/rel/logical/LogicalValues.java  |   35 +-
 .../apache/calcite/rel/logical/LogicalWindow.java  |   21 +-
 .../java/org/apache/calcite/rex/RexLiteral.java    | 1179 ----------------
 .../java/org/apache/calcite/rex/RexSimplify.java   |  637 ++++++---
 .../org/apache/calcite/sql/SqlMatchRecognize.java  |  428 ------
 .../java/org/apache/calcite/sql/SqlTableRef.java   |   80 --
 .../calcite/sql/validate/SqlValidatorImpl.java     | 1338 ++++++++++++------
 .../apache/calcite/sql2rel/RelDecorrelator.java    |  373 +++--
 .../apache/calcite/sql2rel/SqlToRelConverter.java  | 1452 ++++++++++++--------
 .../table/planner/calcite/FlinkRelBuilder.java     |    6 +-
 .../table/planner/calcite/FlinkRexBuilder.java     |    4 +-
 .../table/planner/delegation/PlannerContext.java   |    6 +-
 .../functions/sql/SqlJsonObjectFunction.java       |  158 ---
 .../sql/SqlJsonObjectFunctionWrapper.java          |    1 +
 ...FlinkAggregateExpandDistinctAggregatesRule.java |    6 +
 .../plan/rules/logical/SubQueryDecorrelator.java   |    5 +-
 .../src/main/resources/META-INF/NOTICE             |   10 +-
 .../planner/calcite/FlinkLogicalRelFactories.scala |    2 +-
 .../logical/LogicalWindowAggregateRuleBase.scala   |    1 +
 .../planner/plan/schema/FlinkTableFunction.scala   |    7 +-
 .../table/planner/catalog/JavaCatalogTableTest.xml |   31 +-
 .../table/planner/plan/batch/sql/CalcTest.xml      |    8 +-
 .../plan/batch/sql/LegacyTableSourceTest.xml       |    2 +-
 .../batch/sql/join/NestedLoopSemiAntiJoinTest.xml  |    8 +-
 .../plan/batch/sql/join/SemiAntiJoinTest.xml       |    8 +-
 .../planner/plan/batch/table/SetOperatorsTest.xml  |    4 +-
 .../rules/logical/ConvertToNotInOrInRuleTest.xml   |    2 +-
 .../PushFilterInCalcIntoTableSourceRuleTest.xml    |    4 +-
 ...PushFilterIntoLegacyTableSourceScanRuleTest.xml |    4 +-
 .../PushFilterIntoTableSourceScanRuleTest.xml      |    4 +-
 .../logical/SimplifyFilterConditionRuleTest.xml    |    8 +-
 .../subquery/FlinkRewriteSubQueryRuleTest.xml      |    2 +-
 .../logical/subquery/SubQueryAntiJoinTest.xml      |    8 +-
 .../logical/subquery/SubQuerySemiJoinTest.xml      |    8 +-
 .../table/planner/plan/stream/sql/CalcTest.xml     |    8 +-
 .../plan/stream/sql/LegacyTableSourceTest.xml      |    2 +-
 .../planner/plan/stream/sql/join/JoinTest.xml      |    6 +-
 .../plan/stream/sql/join/SemiAntiJoinTest.xml      |   12 +-
 .../table/planner/plan/stream/table/CalcTest.xml   |    4 +-
 .../table/planner/plan/stream/table/JoinTest.xml   |    4 +-
 .../plan/metadata/FlinkRelMdHandlerTestBase.scala  |    1 +
 .../planner/plan/utils/FlinkRexUtilTest.scala      |   13 +-
 flink-table/pom.xml                                |    2 +-
 63 files changed, 2981 insertions(+), 3401 deletions(-)

diff --git a/flink-connectors/flink-connector-hive/src/main/java/org/apache/flink/table/planner/delegation/hive/HiveParserCalcitePlanner.java b/flink-connectors/flink-connector-hive/src/main/java/org/apache/flink/table/planner/delegation/hive/HiveParserCalcitePlanner.java
index 992d5af27d7..274813eaa21 100644
--- a/flink-connectors/flink-connector-hive/src/main/java/org/apache/flink/table/planner/delegation/hive/HiveParserCalcitePlanner.java
+++ b/flink-connectors/flink-connector-hive/src/main/java/org/apache/flink/table/planner/delegation/hive/HiveParserCalcitePlanner.java
@@ -1201,6 +1201,7 @@ public class HiveParserCalcitePlanner {
                             false,
                             gbKeyIndices,
                             -1,
+                            null,
                             RelCollations.EMPTY,
                             groupSet.cardinality(),
                             gbInputRel,
diff --git a/flink-table/flink-sql-client/pom.xml b/flink-table/flink-sql-client/pom.xml
index 84a1577769b..6145e3950d3 100644
--- a/flink-table/flink-sql-client/pom.xml
+++ b/flink-table/flink-sql-client/pom.xml
@@ -164,6 +164,12 @@ under the License.
 			<groupId>org.apache.flink</groupId>
 			<artifactId>flink-sql-parser</artifactId>
 			<version>${project.version}</version>
+			<exclusions>
+				<exclusion>
+					<groupId>org.apache.calcite</groupId>
+					<artifactId>calcite-core</artifactId>
+				</exclusion>
+			</exclusions>
 		</dependency>
 
 		<!-- test dependencies for HiveCatalog. The exclusions should be in sync with those flink-connector-hive -->
diff --git a/flink-table/flink-sql-parser-hive/pom.xml b/flink-table/flink-sql-parser-hive/pom.xml
index b2082468c1d..85ee6cb01b2 100644
--- a/flink-table/flink-sql-parser-hive/pom.xml
+++ b/flink-table/flink-sql-parser-hive/pom.xml
@@ -56,6 +56,12 @@ under the License.
 			<artifactId>guava</artifactId>
 			<version>${guava.version}</version>
 			<scope>provided</scope>
+			<exclusions>
+				<exclusion>
+					<groupId>org.checkerframework</groupId>
+					<artifactId>checker-qual</artifactId>
+				</exclusion>
+			</exclusions>
 		</dependency>
 		<dependency>
 			<groupId>org.apache.calcite</groupId>
@@ -87,6 +93,10 @@ under the License.
 					<groupId>org.apiguardian</groupId>
 					<artifactId>apiguardian-api</artifactId>
 				</exclusion>
+				<exclusion>
+					<groupId>com.google.errorprone</groupId>
+					<artifactId>error_prone_annotations</artifactId>
+				</exclusion>
 			</exclusions>
 			<scope>test</scope>
 			<type>test-jar</type>
diff --git a/flink-table/flink-sql-parser-hive/src/test/java/org/apache/flink/sql/parser/hive/FlinkHiveSqlParserImplTest.java b/flink-table/flink-sql-parser-hive/src/test/java/org/apache/flink/sql/parser/hive/FlinkHiveSqlParserImplTest.java
index 6ff6a16cc2a..1ca19985dd7 100644
--- a/flink-table/flink-sql-parser-hive/src/test/java/org/apache/flink/sql/parser/hive/FlinkHiveSqlParserImplTest.java
+++ b/flink-table/flink-sql-parser-hive/src/test/java/org/apache/flink/sql/parser/hive/FlinkHiveSqlParserImplTest.java
@@ -34,6 +34,8 @@ class FlinkHiveSqlParserImplTest extends SqlParserTest {
     }
 
     // ignore test methods that we don't support
+    // BEGIN
+    // DESCRIBE STATEMENT
     @Disabled
     @Test
     void testDescribeStatement() {}
@@ -42,10 +44,32 @@ class FlinkHiveSqlParserImplTest extends SqlParserTest {
     @Test
     void testTableHintsInInsert() {}
 
+    // ARRAY_AGG
+    @Disabled
+    @Test
+    void testArrayAgg() {}
+
+    // DESCRIBE SCHEMA
     @Disabled
     @Test
     void testDescribeSchema() {}
 
+    // EXPLAIN AS DOT
+    @Disabled
+    @Test
+    void testExplainAsDot() {}
+
+    // GROUP CONCAT
+    @Disabled
+    @Test
+    void testGroupConcat() {}
+
+    // STRING_AGG
+    @Disabled
+    @Test
+    void testStringAgg() {}
+    // END
+
     @Test
     void testShowDatabases() {
         sql("show databases").ok("SHOW DATABASES");
diff --git a/flink-table/flink-sql-parser/pom.xml b/flink-table/flink-sql-parser/pom.xml
index bf1faef7a4a..5b1f80c6976 100644
--- a/flink-table/flink-sql-parser/pom.xml
+++ b/flink-table/flink-sql-parser/pom.xml
@@ -48,6 +48,12 @@ under the License.
 			<artifactId>guava</artifactId>
 			<version>${guava.version}</version>
 			<scope>provided</scope>
+			<exclusions>
+				<exclusion>
+					<groupId>org.checkerframework</groupId>
+					<artifactId>checker-qual</artifactId>
+				</exclusion>
+			</exclusions>
 		</dependency>
 		<dependency>
 			<groupId>org.apache.calcite</groupId>
@@ -56,11 +62,11 @@ under the License.
 			<version>${calcite.version}</version>
 			<exclusions>
 				<!--
-				"mvn dependency:tree" as of Calcite 1.26.0:
+				"mvn dependency:tree" as of Calcite 1.27.0:
 
-				[INFO] +- org.apache.calcite:calcite-core:jar:1.26.0:compile
-				[INFO] |  +- org.apache.calcite.avatica:avatica-core:jar:1.17.0:compile
-				[INFO] |  +- org.apiguardian:apiguardian-api:jar:1.1.0:compile
+				[INFO] +- org.apache.calcite:calcite-core:jar:1.27.0:compile
+				[INFO] |  +- org.apache.calcite.avatica:avatica-core:jar:1.18.0:compile
+				[INFO] |  \- org.checkerframework:checker-qual:jar:3.10.0:compile
 
 				Dependencies that are not needed for how we use Calcite right now.
 				-->
@@ -161,6 +167,10 @@ under the License.
 					<groupId>org.apiguardian</groupId>
 					<artifactId>apiguardian-api</artifactId>
 				</exclusion>
+				<exclusion>
+					<groupId>com.google.errorprone</groupId>
+					<artifactId>error_prone_annotations</artifactId>
+				</exclusion>
 			</exclusions>
 		</dependency>
 		<dependency>
@@ -193,6 +203,10 @@ under the License.
 					<groupId>org.apiguardian</groupId>
 					<artifactId>apiguardian-api</artifactId>
 				</exclusion>
+				<exclusion>
+					<groupId>com.google.errorprone</groupId>
+					<artifactId>error_prone_annotations</artifactId>
+				</exclusion>
 			</exclusions>
 			<scope>test</scope>
 			<type>test-jar</type>
diff --git a/flink-table/flink-sql-parser/src/main/java/org/apache/flink/sql/parser/validate/FlinkSqlConformance.java b/flink-table/flink-sql-parser/src/main/java/org/apache/flink/sql/parser/validate/FlinkSqlConformance.java
index 88ba81531ff..f2c449d2627 100644
--- a/flink-table/flink-sql-parser/src/main/java/org/apache/flink/sql/parser/validate/FlinkSqlConformance.java
+++ b/flink-table/flink-sql-parser/src/main/java/org/apache/flink/sql/parser/validate/FlinkSqlConformance.java
@@ -18,7 +18,9 @@
 
 package org.apache.flink.sql.parser.validate;
 
+import org.apache.calcite.sql.fun.SqlLibrary;
 import org.apache.calcite.sql.validate.SqlConformance;
+import org.apache.calcite.sql.validate.SqlConformanceEnum;
 
 /** Sql conformance used for flink to set specific sql dialect parser. * */
 public enum FlinkSqlConformance implements SqlConformance {
@@ -176,4 +178,9 @@ public enum FlinkSqlConformance implements SqlConformance {
     public boolean allowQualifyingCommonColumn() {
         return true;
     }
+
+    @Override
+    public SqlLibrary semantics() {
+        return SqlConformanceEnum.DEFAULT.semantics();
+    }
 }
diff --git a/flink-table/flink-sql-parser/src/test/java/org/apache/flink/sql/parser/FlinkSqlParserImplTest.java b/flink-table/flink-sql-parser/src/test/java/org/apache/flink/sql/parser/FlinkSqlParserImplTest.java
index 27d57acc88f..741afa6d010 100644
--- a/flink-table/flink-sql-parser/src/test/java/org/apache/flink/sql/parser/FlinkSqlParserImplTest.java
+++ b/flink-table/flink-sql-parser/src/test/java/org/apache/flink/sql/parser/FlinkSqlParserImplTest.java
@@ -64,14 +64,39 @@ class FlinkSqlParserImplTest extends SqlParserTest {
         sql("desc catalog a").ok("DESCRIBE CATALOG `A`");
     }
 
-    /**
-     * Here we override the super method to avoid test error from `describe schema` supported in
-     * original calcite.
-     */
+    // ignore test methods that we don't support
+    // BEGIN
+    // ARRAY_AGG
+    @Disabled
+    @Test
+    void testArrayAgg() {}
+
+    // DESCRIBE SCHEMA
     @Disabled
     @Test
     void testDescribeSchema() {}
 
+    // DESCRIBE STATEMENT
+    @Disabled
+    @Test
+    void testDescribeStatement() {}
+
+    // GROUP CONCAT
+    @Disabled
+    @Test
+    void testGroupConcat() {}
+
+    // EXPLAIN AS DOT
+    @Disabled
+    @Test
+    void testExplainAsDot() {}
+
+    // STRING_AGG
+    @Disabled
+    @Test
+    void testStringAgg() {}
+    // END
+
     @Test
     void testUseCatalog() {
         sql("use catalog a").ok("USE CATALOG `A`");
@@ -286,14 +311,6 @@ class FlinkSqlParserImplTest extends SqlParserTest {
                 .ok("SHOW COLUMNS IN `CATALOG1`.`DB1`.`TBL` NOT LIKE '%'");
     }
 
-    /**
-     * Here we override the super method to avoid test error from `describe statement` supported in
-     * original calcite.
-     */
-    @Disabled
-    @Test
-    void testDescribeStatement() {}
-
     @Test
     void testAlterTable() {
         sql("alter table t1 rename to t2").ok("ALTER TABLE `T1` RENAME TO `T2`");
diff --git a/flink-table/flink-table-planner/pom.xml b/flink-table/flink-table-planner/pom.xml
index 7bc30283358..e1fd5ac359d 100644
--- a/flink-table/flink-table-planner/pom.xml
+++ b/flink-table/flink-table-planner/pom.xml
@@ -42,6 +42,12 @@ under the License.
 		<dependency>
 			<groupId>com.google.guava</groupId>
 			<artifactId>guava</artifactId>
+			<exclusions>
+				<exclusion>
+					<groupId>org.checkerframework</groupId>
+					<artifactId>checker-qual</artifactId>
+				</exclusion>
+			</exclusions>
 		</dependency>
 		<dependency>
 			<groupId>org.codehaus.janino</groupId>
@@ -81,6 +87,17 @@ under the License.
 				</exclusion>
 			</exclusions>
 		</dependency>
+		<!-- Use newer version v1.1.2 as in Calcite because org.junit.jupiter:junit-jupiter contains it -->
+		<dependency>
+			<artifactId>apiguardian-api</artifactId>
+			<groupId>org.apiguardian</groupId>
+			<version>1.1.2</version>
+		</dependency>
+		<dependency>
+			<groupId>org.checkerframework</groupId>
+			<artifactId>checker-qual</artifactId>
+			<version>3.10.0</version>
+		</dependency>
 		<dependency>
 			<groupId>org.apache.flink</groupId>
 			<artifactId>flink-sql-parser-hive</artifactId>
@@ -111,22 +128,22 @@ under the License.
 			<version>${calcite.version}</version>
 			<exclusions>
 				<!--
-				"mvn dependency:tree" as of Calcite 1.26.0:
-
-				[INFO] +- org.apache.calcite:calcite-core:jar:1.26.0:compile
-				[INFO] |  +- org.apache.calcite:calcite-linq4j:jar:1.26.0:compile
-				[INFO] |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.12.1:compile
-				[INFO] |  +- org.apiguardian:apiguardian-api:jar:1.1.0:compile
-				[INFO] |  +- com.esri.geometry:esri-geometry-api:jar:2.2.0:runtime
-				[INFO] |  +- com.fasterxml.jackson.core:jackson-core:jar:2.12.1:runtime
-				[INFO] |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.12.1:runtime
+				"mvn dependency:tree" as of Calcite 1.27.0:
+
+				[INFO] +- org.apache.calcite:calcite-core:jar:1.27.0:compile
+				[INFO] |  +- org.apache.calcite:calcite-linq4j:jar:1.27.0:compile
+				[INFO] |  +- com.esri.geometry:esri-geometry-api:jar:2.2.0:compile
+				[INFO] |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.13.2:compile
+				[INFO] |  +- org.apache.calcite.avatica:avatica-core:jar:1.18.0:compile
+				[INFO] |  +- com.fasterxml.jackson.core:jackson-core:jar:2.13.2:compile
+				[INFO] |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.13.2.2:compile
 				[INFO] |  +- com.jayway.jsonpath:json-path:jar:2.4.0:runtime
 				[INFO] |  |  \- net.minidev:json-smart:jar:2.3:runtime
 				[INFO] |  |     \- net.minidev:accessors-smart:jar:1.2:runtime
 				[INFO] |  |        \- org.ow2.asm:asm:jar:5.0.4:runtime
-				[INFO] |  +- commons-codec:commons-codec:jar:1.10:runtime
-				[INFO] |  +- org.apache.commons:commons-lang3:jar:3.3.2:compile
-				[INFO] |  \- commons-io:commons-io:jar:2.4:compile
+				[INFO] |  +- commons-codec:commons-codec:jar:1.15:runtime
+				[INFO] |  \- commons-io:commons-io:jar:2.11.0:compile
+
 
 				Dependencies that are not needed for how we use Calcite right now.
 				-->
@@ -179,6 +196,10 @@ under the License.
 					<groupId>org.apiguardian</groupId>
 					<artifactId>apiguardian-api</artifactId>
 				</exclusion>
+				<exclusion>
+					<groupId>com.google.errorprone</groupId>
+					<artifactId>error_prone_annotations</artifactId>
+				</exclusion>
 			</exclusions>
 		</dependency>
 
@@ -347,6 +368,7 @@ under the License.
 							<include>com.google.guava:failureaccess</include>
 							<include>commons-codec:commons-codec</include>
 							<include>commons-io:commons-io</include>
+							<include>org.checkerframework:checker-qual</include>
 
 							<!-- flink-table-planner dependencies -->
 							<include>org.apache.flink:flink-sql-parser</include>
@@ -373,6 +395,10 @@ under the License.
 							<pattern>org.apache.commons.io</pattern>
 							<shadedPattern>org.apache.flink.calcite.shaded.org.apache.commons.io</shadedPattern>
 						</relocation>
+						<relocation>
+							<pattern>org.checkerframework</pattern>
+							<shadedPattern>org.apache.flink.calcite.shaded.org.checkerframework</shadedPattern>
+						</relocation>
 
 						<!-- These are relocated to match package names, but not included in this jar -->
 						<relocation>
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Correlate.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Correlate.java
index 8df4da8b823..62d507eb125 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Correlate.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Correlate.java
@@ -18,6 +18,8 @@
 
 package org.apache.calcite.rel.core;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptPlanner;
@@ -34,12 +36,14 @@ import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.sql.validate.SqlValidatorUtil;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.Litmus;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
 import java.util.Set;
 
+import static java.util.Objects.requireNonNull;
+
 /**
  * A relational operator that performs nested-loop joins.
  *
@@ -75,7 +79,7 @@ public abstract class Correlate extends BiRel implements Hintable {
     protected final CorrelationId correlationId;
     protected final ImmutableBitSet requiredColumns;
     protected final JoinRelType joinType;
-    protected final com.google.common.collect.ImmutableList<RelHint> hints;
+    protected final ImmutableList<RelHint> hints;
 
     // ~ Constructors -----------------------------------------------------------
 
@@ -101,10 +105,10 @@ public abstract class Correlate extends BiRel implements Hintable {
             JoinRelType joinType) {
         super(cluster, traitSet, left, right);
         assert !joinType.generatesNullsOnLeft() : "Correlate has invalid join type " + joinType;
-        this.joinType = Objects.requireNonNull(joinType);
-        this.correlationId = Objects.requireNonNull(correlationId);
-        this.requiredColumns = Objects.requireNonNull(requiredColumns);
-        this.hints = com.google.common.collect.ImmutableList.copyOf(hints);
+        this.joinType = requireNonNull(joinType, "joinType");
+        this.correlationId = requireNonNull(correlationId, "correlationId");
+        this.requiredColumns = requireNonNull(requiredColumns, "requiredColumns");
+        this.hints = ImmutableList.copyOf(hints);
     }
 
     /**
@@ -141,15 +145,16 @@ public abstract class Correlate extends BiRel implements Hintable {
      *
      * @param input Input representation
      */
-    public Correlate(RelInput input) {
+    protected Correlate(RelInput input) {
         this(
                 input.getCluster(),
                 input.getTraitSet(),
                 input.getInputs().get(0),
                 input.getInputs().get(1),
-                new CorrelationId((Integer) input.get("correlation")),
+                new CorrelationId(
+                        requireNonNull((Integer) input.get("correlation"), "correlation")),
                 input.getBitSet("requiredColumns"),
-                input.getEnum("joinType", JoinRelType.class));
+                requireNonNull(input.getEnum("joinType", JoinRelType.class), "joinType"));
     }
 
     // ~ Methods ----------------------------------------------------------------
@@ -190,7 +195,7 @@ public abstract class Correlate extends BiRel implements Hintable {
                         joinType,
                         getCluster().getTypeFactory(),
                         null,
-                        com.google.common.collect.ImmutableList.of());
+                        ImmutableList.of());
             case ANTI:
             case SEMI:
                 return left.getRowType();
@@ -232,7 +237,7 @@ public abstract class Correlate extends BiRel implements Hintable {
 
     @Override
     public Set<CorrelationId> getVariablesSet() {
-        return com.google.common.collect.ImmutableSet.of(correlationId);
+        return ImmutableSet.of(correlationId);
     }
 
     @Override
@@ -248,7 +253,7 @@ public abstract class Correlate extends BiRel implements Hintable {
     }
 
     @Override
-    public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
+    public @Nullable RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
         double rowCount = mq.getRowCount(this);
 
         final double rightRowCount = right.estimateRowCount(mq);
@@ -258,9 +263,15 @@ public abstract class Correlate extends BiRel implements Hintable {
         }
 
         Double restartCount = mq.getRowCount(getLeft());
+        if (restartCount == null) {
+            return planner.getCostFactory().makeInfiniteCost();
+        }
         // RelMetadataQuery.getCumulativeCost(getRight()); does not work for
         // RelSubset, so we ask planner to cost-estimate right relation
         RelOptCost rightCost = planner.getCost(getRight(), mq);
+        if (rightCost == null) {
+            return planner.getCostFactory().makeInfiniteCost();
+        }
         RelOptCost rescanCost = rightCost.multiplyBy(Math.max(1.0, restartCount - 1));
 
         return planner.getCostFactory()
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Filter.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Filter.java
index 34a28def16b..b28ca203207 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Filter.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Filter.java
@@ -18,7 +18,6 @@
 
 package org.apache.calcite.rel.core;
 
-import org.apache.calcite.config.CalciteSystemProperty;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptPlanner;
@@ -38,10 +37,15 @@ import org.apache.calcite.rex.RexProgram;
 import org.apache.calcite.rex.RexShuttle;
 import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.util.Litmus;
+import org.apiguardian.api.API;
+import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.util.List;
 import java.util.Objects;
 
+import static java.util.Objects.requireNonNull;
+
 /**
  * Relational expression that iterates over its input and returns elements for which <code>condition
  * </code> evaluates to <code>true</code>.
@@ -71,6 +75,7 @@ public abstract class Filter extends SingleRel implements Hintable {
      * @param child input relational expression
      * @param condition boolean expression which determines whether a row is allowed to pass
      */
+    @SuppressWarnings("method.invocation.invalid")
     protected Filter(
             RelOptCluster cluster,
             RelTraitSet traits,
@@ -78,11 +83,10 @@ public abstract class Filter extends SingleRel implements Hintable {
             RelNode child,
             RexNode condition) {
         super(cluster, traits, child);
-        assert condition != null;
-        assert RexUtil.isFlat(condition) : condition;
-        this.condition = condition;
-        // Too expensive for everyday use:
-        assert !CalciteSystemProperty.DEBUG.value() || isValid(Litmus.THROW, null);
+        this.condition = requireNonNull(condition, "condition");
+        assert RexUtil.isFlat(condition)
+                : "RexUtil.isFlat should be true for condition " + condition;
+        assert isValid(Litmus.THROW, null);
         this.hints = com.google.common.collect.ImmutableList.copyOf(hints);
     }
 
@@ -104,7 +108,7 @@ public abstract class Filter extends SingleRel implements Hintable {
                 input.getCluster(),
                 input.getTraitSet(),
                 input.getInput(),
-                input.getExpression("condition"));
+                requireNonNull(input.getExpression("condition"), "condition"));
     }
 
     // ~ Methods ----------------------------------------------------------------
@@ -116,6 +120,7 @@ public abstract class Filter extends SingleRel implements Hintable {
 
     public abstract Filter copy(RelTraitSet traitSet, RelNode input, RexNode condition);
 
+    @Override
     public RelNode accept(RexShuttle shuttle) {
         RexNode condition = shuttle.apply(this.condition);
         if (this.condition == condition) {
@@ -134,7 +139,7 @@ public abstract class Filter extends SingleRel implements Hintable {
     }
 
     @Override
-    public boolean isValid(Litmus litmus, Context context) {
+    public boolean isValid(Litmus litmus, @Nullable Context context) {
         if (RexUtil.isNullabilityCast(getCluster().getTypeFactory(), condition)) {
             return litmus.fail("Cast for just nullability not allowed");
         }
@@ -147,7 +152,7 @@ public abstract class Filter extends SingleRel implements Hintable {
     }
 
     @Override
-    public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
+    public @Nullable RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
         double dRows = mq.getRowCount(this);
         double dCpu = mq.getRowCount(getInput());
         double dIo = 0;
@@ -171,11 +176,14 @@ public abstract class Filter extends SingleRel implements Hintable {
         return RelMdUtil.estimateFilteredRows(child, condition, mq);
     }
 
+    @Override
     public RelWriter explainTerms(RelWriter pw) {
         return super.explainTerms(pw).item("condition", condition);
     }
 
-    protected boolean deepEquals0(Object obj) {
+    @API(since = "1.24", status = API.Status.INTERNAL)
+    @EnsuresNonNullIf(expression = "#1", result = true)
+    protected boolean deepEquals0(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -190,6 +198,7 @@ public abstract class Filter extends SingleRel implements Hintable {
                 && getRowType().equalsSansFieldNames(o.getRowType());
     }
 
+    @API(since = "1.24", status = API.Status.INTERNAL)
     protected int deepHashCode0() {
         return Objects.hash(traitSet, hints, input.deepHashCode(), condition);
     }
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Intersect.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Intersect.java
index ad20e66fe37..9029903f50a 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Intersect.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Intersect.java
@@ -40,7 +40,7 @@ import java.util.List;
  */
 public abstract class Intersect extends SetOp {
     /** Creates an Intersect. */
-    public Intersect(
+    protected Intersect(
             RelOptCluster cluster,
             RelTraitSet traits,
             List<RelHint> hints,
@@ -64,7 +64,12 @@ public abstract class Intersect extends SetOp {
         // REVIEW jvs 30-May-2005:  I just pulled this out of a hat.
         double dRows = Double.MAX_VALUE;
         for (RelNode input : inputs) {
-            dRows = Math.min(dRows, mq.getRowCount(input));
+            Double rowCount = mq.getRowCount(input);
+            if (rowCount == null) {
+                // Assume this input does not reduce row count
+                continue;
+            }
+            dRows = Math.min(dRows, rowCount);
         }
         dRows *= 0.25;
         return dRows;
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Minus.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Minus.java
index ae3cb8c6d23..15196253055 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Minus.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Minus.java
@@ -44,7 +44,7 @@ import java.util.List;
  */
 public abstract class Minus extends SetOp {
 
-    public Minus(
+    protected Minus(
             RelOptCluster cluster,
             RelTraitSet traits,
             List<RelHint> hints,
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/SetOp.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/SetOp.java
index 710fcaed874..93fe1131168 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/SetOp.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/SetOp.java
@@ -18,6 +18,7 @@
 
 package org.apache.calcite.rel.core;
 
+import com.google.common.collect.ImmutableList;
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptUtil;
@@ -46,10 +47,10 @@ import java.util.List;
 public abstract class SetOp extends AbstractRelNode implements Hintable {
     // ~ Instance fields --------------------------------------------------------
 
-    protected com.google.common.collect.ImmutableList<RelNode> inputs;
+    protected ImmutableList<RelNode> inputs;
     public final SqlKind kind;
     public final boolean all;
-    protected final com.google.common.collect.ImmutableList<RelHint> hints;
+    protected final ImmutableList<RelHint> hints;
 
     // ~ Constructors -----------------------------------------------------------
 
@@ -65,9 +66,9 @@ public abstract class SetOp extends AbstractRelNode implements Hintable {
         com.google.common.base.Preconditions.checkArgument(
                 kind == SqlKind.UNION || kind == SqlKind.INTERSECT || kind == SqlKind.EXCEPT);
         this.kind = kind;
-        this.inputs = com.google.common.collect.ImmutableList.copyOf(inputs);
+        this.inputs = ImmutableList.copyOf(inputs);
         this.all = all;
-        this.hints = com.google.common.collect.ImmutableList.copyOf(hints);
+        this.hints = ImmutableList.copyOf(hints);
     }
 
     /** Creates a SetOp. */
@@ -104,7 +105,7 @@ public abstract class SetOp extends AbstractRelNode implements Hintable {
     public void replaceInput(int ordinalInParent, RelNode p) {
         final List<RelNode> newInputs = new ArrayList<>(inputs);
         newInputs.set(ordinalInParent, p);
-        inputs = com.google.common.collect.ImmutableList.copyOf(newInputs);
+        inputs = ImmutableList.copyOf(newInputs);
         recomputeDigest();
     }
 
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Sort.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Sort.java
index 93c38fbdd44..f8408b91f94 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Sort.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Sort.java
@@ -18,6 +18,7 @@
 
 package org.apache.calcite.rel.core;
 
+import com.google.common.collect.ImmutableList;
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptCost;
@@ -33,9 +34,11 @@ import org.apache.calcite.rel.SingleRel;
 import org.apache.calcite.rel.hint.Hintable;
 import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexShuttle;
 import org.apache.calcite.util.Util;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.util.Collections;
 import java.util.List;
@@ -52,9 +55,9 @@ public abstract class Sort extends SingleRel implements Hintable {
     // ~ Instance fields --------------------------------------------------------
 
     public final RelCollation collation;
-    public final RexNode offset;
-    public final RexNode fetch;
-    protected final com.google.common.collect.ImmutableList<RelHint> hints;
+    public final @Nullable RexNode offset;
+    public final @Nullable RexNode fetch;
+    protected final ImmutableList<RelHint> hints;
 
     // ~ Constructors -----------------------------------------------------------
 
@@ -66,7 +69,8 @@ public abstract class Sort extends SingleRel implements Hintable {
      * @param child input relational expression
      * @param collation array of sort specifications
      */
-    public Sort(RelOptCluster cluster, RelTraitSet traits, RelNode child, RelCollation collation) {
+    protected Sort(
+            RelOptCluster cluster, RelTraitSet traits, RelNode child, RelCollation collation) {
         this(cluster, traits, Collections.emptyList(), child, collation, null, null);
     }
 
@@ -101,19 +105,19 @@ public abstract class Sort extends SingleRel implements Hintable {
      * @param offset Expression for number of rows to discard before returning first row
      * @param fetch Expression for number of rows to fetch
      */
-    public Sort(
+    protected Sort(
             RelOptCluster cluster,
             RelTraitSet traits,
             List<RelHint> hints,
             RelNode child,
             RelCollation collation,
-            RexNode offset,
-            RexNode fetch) {
+            @Nullable RexNode offset,
+            @Nullable RexNode fetch) {
         super(cluster, traits, child);
         this.collation = collation;
         this.offset = offset;
         this.fetch = fetch;
-        this.hints = com.google.common.collect.ImmutableList.copyOf(hints);
+        this.hints = ImmutableList.copyOf(hints);
 
         assert traits.containsIfApplicable(collation)
                 : "traits=" + traits + ", collation=" + collation;
@@ -122,7 +126,7 @@ public abstract class Sort extends SingleRel implements Hintable {
     }
 
     /** Creates a Sort by parsing serialized output. */
-    public Sort(RelInput input) {
+    protected Sort(RelInput input) {
         this(
                 input.getCluster(),
                 input.getTraitSet().plus(input.getCollation()),
@@ -147,19 +151,68 @@ public abstract class Sort extends SingleRel implements Hintable {
             RelTraitSet traitSet,
             RelNode newInput,
             RelCollation newCollation,
-            RexNode offset,
-            RexNode fetch);
+            @Nullable RexNode offset,
+            @Nullable RexNode fetch);
 
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The CPU cost of a Sort has three main cases:
+     *
+     * <ul>
+     *   <li>If {@code fetch} is zero, CPU cost is zero; otherwise,
+     *   <li>if the sort keys are empty, we don't need to sort, only step over the rows, and
+     *       therefore the CPU cost is {@code min(fetch + offset, inputRowCount) * bytesPerRow};
+     *       otherwise
+     *   <li>we need to read and sort {@code inputRowCount} rows, with at most {@code min(fetch +
+     *       offset, inputRowCount)} of them in the sort data structure at a time, giving a CPU cost
+     *       of {@code inputRowCount * log(min(fetch + offset, inputRowCount)) * bytesPerRow}.
+     * </ul>
+     *
+     * <p>The cost model factors in row width via {@code bytesPerRow}, because sorts need to move
+     * rows around, not just compare them; by making the cost higher if rows are wider, we
+     * discourage pushing a Project through a Sort. We assume that each field is 4 bytes, and we add
+     * 3 'virtual fields' to represent the per-row overhead. Thus a 1-field row is (3 + 1) * 4 = 16
+     * bytes; a 5-field row is (3 + 5) * 4 = 32 bytes.
+     *
+     * <p>The cost model does not consider a 5-field sort to be more expensive than, say, a 2-field
+     * sort, because both sorts will compare just one field most of the time.
+     */
     @Override
-    public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
-        // Higher cost if rows are wider discourages pushing a project through a
-        // sort.
-        final double rowCount = mq.getRowCount(this);
-        final double bytesPerRow = getRowType().getFieldCount() * 4;
-        final double cpu = Util.nLogN(rowCount) * bytesPerRow;
-        return planner.getCostFactory().makeCost(rowCount, cpu, 0);
+    public @Nullable RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
+        final double offsetValue = Util.first(doubleValue(offset), 0d);
+        assert offsetValue >= 0 : "offset should not be negative:" + offsetValue;
+
+        final double inCount = mq.getRowCount(input);
+        @Nullable Double fetchValue = doubleValue(fetch);
+        final double readCount;
+        if (fetchValue == null) {
+            readCount = inCount;
+        } else if (fetchValue <= 0) {
+            // Case 1. Read zero rows from input, therefore CPU cost is zero.
+            return planner.getCostFactory().makeCost(inCount, 0, 0);
+        } else {
+            readCount = Math.min(inCount, offsetValue + fetchValue);
+        }
+
+        final double bytesPerRow = (3 + getRowType().getFieldCount()) * 4;
+
+        final double cpu;
+        if (collation.getFieldCollations().isEmpty()) {
+            // Case 2. If sort keys are empty, CPU cost is cheaper because we are just
+            // stepping over the first "readCount" rows, rather than sorting all
+            // "inCount" them. (Presumably we are applying FETCH and/or OFFSET,
+            // otherwise this Sort is a no-op.)
+            cpu = readCount * bytesPerRow;
+        } else {
+            // Case 3. Read and sort all "inCount" rows, keeping "readCount" in the
+            // sort data structure at a time.
+            cpu = Util.nLogM(inCount, readCount) * bytesPerRow;
+        }
+        return planner.getCostFactory().makeCost(readCount, cpu, 0);
     }
 
+    @Override
     public RelNode accept(RexShuttle shuttle) {
         RexNode offset = shuttle.apply(this.offset);
         RexNode fetch = shuttle.apply(this.fetch);
@@ -208,6 +261,7 @@ public abstract class Sort extends SingleRel implements Hintable {
                                         input, Objects.requireNonNull(field).getFieldIndex()));
     }
 
+    @Override
     public RelWriter explainTerms(RelWriter pw) {
         super.explainTerms(pw);
         if (pw.nest()) {
@@ -226,7 +280,12 @@ public abstract class Sort extends SingleRel implements Hintable {
     }
 
     @Override
-    public com.google.common.collect.ImmutableList<RelHint> getHints() {
+    public ImmutableList<RelHint> getHints() {
         return hints;
     }
+
+    /** Returns the double value of a node if it is a literal, otherwise null. */
+    private static @Nullable Double doubleValue(@Nullable RexNode r) {
+        return r instanceof RexLiteral ? ((RexLiteral) r).getValueAs(Double.class) : null;
+    }
 }
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Values.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Values.java
index e4a6eba4785..8032241426d 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Values.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Values.java
@@ -18,6 +18,7 @@
 
 package org.apache.calcite.rel.core;
 
+import com.google.common.collect.ImmutableList;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptPlanner;
@@ -36,6 +37,7 @@ import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.sql.SqlExplainLevel;
 import org.apache.calcite.sql.type.SqlTypeUtil;
 import org.apache.calcite.util.Pair;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.util.Collections;
 import java.util.List;
@@ -52,7 +54,7 @@ public abstract class Values extends AbstractRelNode implements Hintable {
 
     public static final Predicate<? super Values> IS_EMPTY_J = Values::isEmpty;
 
-    protected final com.google.common.collect.ImmutableList<RelHint> hints;
+    protected final ImmutableList<RelHint> hints;
 
     @SuppressWarnings("Guava")
     @Deprecated // to be removed before 2.0
@@ -65,9 +67,7 @@ public abstract class Values extends AbstractRelNode implements Hintable {
 
     // ~ Instance fields --------------------------------------------------------
 
-    public final com.google.common.collect.ImmutableList<
-                    com.google.common.collect.ImmutableList<RexLiteral>>
-            tuples;
+    public final ImmutableList<ImmutableList<RexLiteral>> tuples;
 
     // ~ Constructors -----------------------------------------------------------
 
@@ -87,14 +87,12 @@ public abstract class Values extends AbstractRelNode implements Hintable {
             RelOptCluster cluster,
             List<RelHint> hints,
             RelDataType rowType,
-            com.google.common.collect.ImmutableList<
-                            com.google.common.collect.ImmutableList<RexLiteral>>
-                    tuples,
+            ImmutableList<ImmutableList<RexLiteral>> tuples,
             RelTraitSet traits) {
         super(cluster, traits);
         this.rowType = rowType;
         this.tuples = tuples;
-        this.hints = com.google.common.collect.ImmutableList.copyOf(hints);
+        this.hints = ImmutableList.copyOf(hints);
         assert assertRowType();
     }
 
@@ -120,7 +118,7 @@ public abstract class Values extends AbstractRelNode implements Hintable {
     }
 
     /** Creates a Values by parsing serialized output. */
-    public Values(RelInput input) {
+    protected Values(RelInput input) {
         this(
                 input.getCluster(),
                 input.getRowType("type"),
@@ -154,21 +152,18 @@ public abstract class Values extends AbstractRelNode implements Hintable {
         return !isEmpty(values);
     }
 
-    public com.google.common.collect.ImmutableList<
-                    com.google.common.collect.ImmutableList<RexLiteral>>
-            getTuples(RelInput input) {
+    public ImmutableList<ImmutableList<RexLiteral>> getTuples(RelInput input) {
         return input.getTuples("tuples");
     }
 
     /** Returns the rows of literals represented by this Values relational expression. */
-    public com.google.common.collect.ImmutableList<
-                    com.google.common.collect.ImmutableList<RexLiteral>>
-            getTuples() {
+    public ImmutableList<ImmutableList<RexLiteral>> getTuples() {
         return tuples;
     }
 
     /** Returns true if all tuples match rowType; otherwise, assert on mismatch. */
     private boolean assertRowType() {
+        RelDataType rowType = getRowType();
         for (List<RexLiteral> tuple : tuples) {
             assert tuple.size() == rowType.getFieldCount();
             for (Pair<RexLiteral, RelDataTypeField> pair :
@@ -190,11 +185,12 @@ public abstract class Values extends AbstractRelNode implements Hintable {
 
     @Override
     protected RelDataType deriveRowType() {
+        assert rowType != null : "rowType must not be null for " + this;
         return rowType;
     }
 
     @Override
-    public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
+    public @Nullable RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
         double dRows = mq.getRowCount(this);
 
         // Assume CPU is negligible since values are precomputed.
@@ -204,15 +200,18 @@ public abstract class Values extends AbstractRelNode implements Hintable {
     }
 
     // implement RelNode
+    @Override
     public double estimateRowCount(RelMetadataQuery mq) {
         return tuples.size();
     }
 
     // implement RelNode
+    @Override
     public RelWriter explainTerms(RelWriter pw) {
         // A little adapter just to get the tuples to come out
         // with curly brackets instead of square brackets.  Plus
         // more whitespace for readability.
+        RelDataType rowType = getRowType();
         RelWriter relWriter =
                 super.explainTerms(pw)
                         // For rel digest, include the row type since a rendered
@@ -243,7 +242,7 @@ public abstract class Values extends AbstractRelNode implements Hintable {
     }
 
     @Override
-    public com.google.common.collect.ImmutableList<RelHint> getHints() {
+    public ImmutableList<RelHint> getHints() {
         return hints;
     }
 }
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Window.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Window.java
index 7784dd8364c..5b797d42186 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Window.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/core/Window.java
@@ -18,6 +18,7 @@
 
 package org.apache.calcite.rel.core;
 
+import com.google.common.collect.ImmutableList;
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptCost;
@@ -46,6 +47,8 @@ import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.ImmutableIntList;
 import org.apache.calcite.util.Litmus;
 import org.apache.calcite.util.Util;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
 
 import java.util.AbstractList;
 import java.util.Collections;
@@ -68,9 +71,9 @@ import java.util.Objects;
  * <p>Created by {@link org.apache.calcite.rel.rules.ProjectToWindowRule}.
  */
 public abstract class Window extends SingleRel implements Hintable {
-    public final com.google.common.collect.ImmutableList<Group> groups;
-    public final com.google.common.collect.ImmutableList<RexLiteral> constants;
-    protected final com.google.common.collect.ImmutableList<RelHint> hints;
+    public final ImmutableList<Group> groups;
+    public final ImmutableList<RexLiteral> constants;
+    protected final ImmutableList<RelHint> hints;
 
     /**
      * Creates a window relational expression.
@@ -83,7 +86,7 @@ public abstract class Window extends SingleRel implements Hintable {
      * @param rowType Output row type
      * @param groups Windows
      */
-    public Window(
+    protected Window(
             RelOptCluster cluster,
             RelTraitSet traitSet,
             List<RelHint> hints,
@@ -92,11 +95,11 @@ public abstract class Window extends SingleRel implements Hintable {
             RelDataType rowType,
             List<Group> groups) {
         super(cluster, traitSet, input);
-        this.constants = com.google.common.collect.ImmutableList.copyOf(constants);
+        this.constants = ImmutableList.copyOf(constants);
         assert rowType != null;
         this.rowType = rowType;
-        this.groups = com.google.common.collect.ImmutableList.copyOf(groups);
-        this.hints = com.google.common.collect.ImmutableList.copyOf(hints);
+        this.groups = ImmutableList.copyOf(groups);
+        this.hints = ImmutableList.copyOf(hints);
     }
 
     /**
@@ -159,6 +162,7 @@ public abstract class Window extends SingleRel implements Hintable {
         return litmus.succeed();
     }
 
+    @Override
     public RelWriter explainTerms(RelWriter pw) {
         super.explainTerms(pw);
         for (Ord<Group> window : Ord.zip(groups)) {
@@ -170,10 +174,12 @@ public abstract class Window extends SingleRel implements Hintable {
     public static ImmutableIntList getProjectOrdinals(final List<RexNode> exprs) {
         return ImmutableIntList.copyOf(
                 new AbstractList<Integer>() {
+                    @Override
                     public Integer get(int index) {
                         return ((RexSlot) exprs.get(index)).getIndex();
                     }
 
+                    @Override
                     public int size() {
                         return exprs.size();
                     }
@@ -183,6 +189,7 @@ public abstract class Window extends SingleRel implements Hintable {
     public static RelCollation getCollation(final List<RexFieldCollation> collations) {
         return RelCollations.of(
                 new AbstractList<RelFieldCollation>() {
+                    @Override
                     public RelFieldCollation get(int index) {
                         final RexFieldCollation collation = collations.get(index);
                         return new RelFieldCollation(
@@ -191,6 +198,7 @@ public abstract class Window extends SingleRel implements Hintable {
                                 collation.getNullDirection());
                     }
 
+                    @Override
                     public int size() {
                         return collations.size();
                     }
@@ -207,7 +215,7 @@ public abstract class Window extends SingleRel implements Hintable {
     }
 
     @Override
-    public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
+    public @Nullable RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
         // Cost is proportional to the number of rows and the number of
         // components (groups and aggregate functions). There is
         // no I/O cost.
@@ -256,7 +264,7 @@ public abstract class Window extends SingleRel implements Hintable {
          * List of {@link Window.RexWinAggCall} objects, each of which is a call to a {@link
          * org.apache.calcite.sql.SqlAggFunction}.
          */
-        public final com.google.common.collect.ImmutableList<RexWinAggCall> aggCalls;
+        public final ImmutableList<RexWinAggCall> aggCalls;
 
         public Group(
                 ImmutableBitSet keys,
@@ -265,19 +273,21 @@ public abstract class Window extends SingleRel implements Hintable {
                 RexWindowBound upperBound,
                 RelCollation orderKeys,
                 List<RexWinAggCall> aggCalls) {
-            this.keys = Objects.requireNonNull(keys);
+            this.keys = Objects.requireNonNull(keys, "keys");
             this.isRows = isRows;
-            this.lowerBound = Objects.requireNonNull(lowerBound);
-            this.upperBound = Objects.requireNonNull(upperBound);
-            this.orderKeys = Objects.requireNonNull(orderKeys);
-            this.aggCalls = com.google.common.collect.ImmutableList.copyOf(aggCalls);
+            this.lowerBound = Objects.requireNonNull(lowerBound, "lowerBound");
+            this.upperBound = Objects.requireNonNull(upperBound, "upperBound");
+            this.orderKeys = Objects.requireNonNull(orderKeys, "orderKeys");
+            this.aggCalls = ImmutableList.copyOf(aggCalls);
             this.digest = computeString();
         }
 
+        @Override
         public String toString() {
             return digest;
         }
 
+        @RequiresNonNull({"keys", "orderKeys", "lowerBound", "upperBound", "aggCalls"})
         private String computeString() {
             final StringBuilder buf = new StringBuilder("window(");
             final int i = buf.length();
@@ -323,7 +333,7 @@ public abstract class Window extends SingleRel implements Hintable {
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             return this == obj || obj instanceof Group && this.digest.equals(((Group) obj).digest);
         }
 
@@ -360,10 +370,12 @@ public abstract class Window extends SingleRel implements Hintable {
                             windowRel.getRowType().getFieldNames(),
                             windowRel.getInput().getRowType().getFieldCount());
             return new AbstractList<AggregateCall>() {
+                @Override
                 public int size() {
                     return aggCalls.size();
                 }
 
+                @Override
                 public AggregateCall get(int index) {
                     final RexWinAggCall aggCall = aggCalls.get(index);
                     final SqlAggFunction op = (SqlAggFunction) aggCall.getOperator();
@@ -374,6 +386,7 @@ public abstract class Window extends SingleRel implements Hintable {
                             aggCall.ignoreNulls,
                             getProjectOrdinals(aggCall.getOperands()),
                             -1,
+                            null,
                             RelCollations.EMPTY,
                             aggCall.getType(),
                             fieldNames.get(aggCall.ordinal));
@@ -434,7 +447,7 @@ public abstract class Window extends SingleRel implements Hintable {
         }
 
         @Override
-        public boolean equals(Object o) {
+        public boolean equals(@Nullable Object o) {
             if (this == o) {
                 return true;
             }
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalCorrelate.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalCorrelate.java
index 2c0703c5487..2147454271b 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalCorrelate.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalCorrelate.java
@@ -35,6 +35,8 @@ import org.apache.calcite.util.Litmus;
 import java.util.Collections;
 import java.util.List;
 
+import static java.util.Objects.requireNonNull;
+
 /**
  * A relational operator that performs nested-loop joins.
  *
@@ -114,9 +116,10 @@ public final class LogicalCorrelate extends Correlate {
                 input.getTraitSet(),
                 input.getInputs().get(0),
                 input.getInputs().get(1),
-                new CorrelationId((Integer) input.get("correlation")),
+                new CorrelationId(
+                        (Integer) requireNonNull(input.get("correlation"), "correlation")),
                 input.getBitSet("requiredColumns"),
-                input.getEnum("joinType", JoinRelType.class));
+                requireNonNull(input.getEnum("joinType", JoinRelType.class), "joinType"));
     }
 
     /** Creates a LogicalCorrelate. */
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java
index 05852df6926..54eab93d6e4 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java
@@ -18,6 +18,8 @@
 
 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;
@@ -34,7 +36,7 @@ import org.apache.calcite.rel.metadata.RelMdCollation;
 import org.apache.calcite.rel.metadata.RelMdDistribution;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.util.Litmus;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.util.List;
 import java.util.Objects;
@@ -72,23 +74,17 @@ public final class LogicalFilter extends Filter {
             RexNode condition,
             com.google.common.collect.ImmutableSet<CorrelationId> variablesSet) {
         super(cluster, traitSet, hints, child, condition);
-        this.variablesSet = Objects.requireNonNull(variablesSet);
-        assert isValid(Litmus.THROW, null);
+        this.variablesSet = Objects.requireNonNull(variablesSet, "variablesSet");
     }
 
+    @Deprecated // to be removed before 2.0
     public LogicalFilter(
             RelOptCluster cluster,
             RelTraitSet traitSet,
             RelNode child,
             RexNode condition,
-            com.google.common.collect.ImmutableSet<CorrelationId> variablesSet) {
-        this(
-                cluster,
-                traitSet,
-                com.google.common.collect.ImmutableList.of(),
-                child,
-                condition,
-                variablesSet);
+            ImmutableSet<CorrelationId> variablesSet) {
+        this(cluster, traitSet, ImmutableList.of(), child, condition, variablesSet);
     }
 
     @Deprecated // to be removed before 2.0
@@ -99,30 +95,23 @@ public final class LogicalFilter extends Filter {
 
     @Deprecated // to be removed before 2.0
     public LogicalFilter(RelOptCluster cluster, RelNode child, RexNode condition) {
-        this(
-                cluster,
-                cluster.traitSetOf(Convention.NONE),
-                child,
-                condition,
-                com.google.common.collect.ImmutableSet.of());
+        this(cluster, cluster.traitSetOf(Convention.NONE), child, condition, ImmutableSet.of());
     }
 
     /** Creates a LogicalFilter by parsing serialized output. */
     public LogicalFilter(RelInput input) {
         super(input);
-        this.variablesSet = com.google.common.collect.ImmutableSet.of();
+        this.variablesSet = ImmutableSet.of();
     }
 
     /** Creates a LogicalFilter. */
     public static LogicalFilter create(final RelNode input, RexNode condition) {
-        return create(input, condition, com.google.common.collect.ImmutableSet.of());
+        return create(input, condition, ImmutableSet.of());
     }
 
     /** Creates a LogicalFilter. */
     public static LogicalFilter create(
-            final RelNode input,
-            RexNode condition,
-            com.google.common.collect.ImmutableSet<CorrelationId> variablesSet) {
+            final RelNode input, RexNode condition, ImmutableSet<CorrelationId> variablesSet) {
         final RelOptCluster cluster = input.getCluster();
         final RelMetadataQuery mq = cluster.getMetadataQuery();
         final RelTraitSet traitSet =
@@ -143,6 +132,7 @@ public final class LogicalFilter extends Filter {
         return variablesSet;
     }
 
+    @Override
     public LogicalFilter copy(RelTraitSet traitSet, RelNode input, RexNode condition) {
         assert traitSet.containsIfApplicable(Convention.NONE);
         return new LogicalFilter(getCluster(), traitSet, hints, input, condition, variablesSet);
@@ -159,7 +149,7 @@ public final class LogicalFilter extends Filter {
     }
 
     @Override
-    public boolean deepEquals(Object obj) {
+    public boolean deepEquals(@Nullable Object obj) {
         return deepEquals0(obj) && variablesSet.equals(((LogicalFilter) obj).variablesSet);
     }
 
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalSort.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalSort.java
index e0eda26b83a..66e037b4523 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalSort.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalSort.java
@@ -29,6 +29,7 @@ import org.apache.calcite.rel.RelShuttle;
 import org.apache.calcite.rel.core.Sort;
 import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rex.RexNode;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.util.Collections;
 import java.util.List;
@@ -57,8 +58,8 @@ public final class LogicalSort extends Sort {
             List<RelHint> hints,
             RelNode input,
             RelCollation collation,
-            RexNode offset,
-            RexNode fetch) {
+            @Nullable RexNode offset,
+            @Nullable RexNode fetch) {
         super(cluster, traitSet, hints, input, collation, offset, fetch);
         assert traitSet.containsIfApplicable(Convention.NONE);
     }
@@ -77,7 +78,10 @@ public final class LogicalSort extends Sort {
      * @param fetch Expression for number of rows to fetch
      */
     public static LogicalSort create(
-            RelNode input, RelCollation collation, RexNode offset, RexNode fetch) {
+            RelNode input,
+            RelCollation collation,
+            @Nullable RexNode offset,
+            @Nullable RexNode fetch) {
         RelOptCluster cluster = input.getCluster();
         collation = RelCollationTraitDef.INSTANCE.canonize(collation);
         RelTraitSet traitSet = input.getTraitSet().replace(Convention.NONE).replace(collation);
@@ -91,8 +95,8 @@ public final class LogicalSort extends Sort {
             RelTraitSet traitSet,
             RelNode newInput,
             RelCollation newCollation,
-            RexNode offset,
-            RexNode fetch) {
+            @Nullable RexNode offset,
+            @Nullable RexNode fetch) {
         return new LogicalSort(
                 getCluster(), traitSet, hints, newInput, newCollation, offset, fetch);
     }
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalUnion.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalUnion.java
index 24de518359b..2e1992f6f5a 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalUnion.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalUnion.java
@@ -83,6 +83,7 @@ public final class LogicalUnion extends Union {
 
     // ~ Methods ----------------------------------------------------------------
 
+    @Override
     public LogicalUnion copy(RelTraitSet traitSet, List<RelNode> inputs, boolean all) {
         assert traitSet.containsIfApplicable(Convention.NONE);
         return new LogicalUnion(getCluster(), traitSet, hints, inputs, all);
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalValues.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalValues.java
index 7dd2c6b66c4..e76a9ecf192 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalValues.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalValues.java
@@ -18,6 +18,7 @@
 
 package org.apache.calcite.rel.logical;
 
+import com.google.common.collect.ImmutableList;
 import org.apache.calcite.plan.Convention;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
@@ -65,9 +66,7 @@ public class LogicalValues extends Values {
             RelTraitSet traitSet,
             List<RelHint> hints,
             RelDataType rowType,
-            com.google.common.collect.ImmutableList<
-                            com.google.common.collect.ImmutableList<RexLiteral>>
-                    tuples) {
+            ImmutableList<ImmutableList<RexLiteral>> tuples) {
         super(cluster, hints, rowType, tuples, traitSet);
     }
 
@@ -85,9 +84,7 @@ public class LogicalValues extends Values {
             RelOptCluster cluster,
             RelTraitSet traitSet,
             RelDataType rowType,
-            com.google.common.collect.ImmutableList<
-                            com.google.common.collect.ImmutableList<RexLiteral>>
-                    tuples) {
+            ImmutableList<ImmutableList<RexLiteral>> tuples) {
         this(cluster, traitSet, Collections.emptyList(), rowType, tuples);
     }
 
@@ -95,9 +92,7 @@ public class LogicalValues extends Values {
     public LogicalValues(
             RelOptCluster cluster,
             RelDataType rowType,
-            com.google.common.collect.ImmutableList<
-                            com.google.common.collect.ImmutableList<RexLiteral>>
-                    tuples) {
+            ImmutableList<ImmutableList<RexLiteral>> tuples) {
         this(cluster, cluster.traitSetOf(Convention.NONE), rowType, tuples);
     }
 
@@ -110,9 +105,7 @@ public class LogicalValues extends Values {
     public static LogicalValues create(
             RelOptCluster cluster,
             final RelDataType rowType,
-            final com.google.common.collect.ImmutableList<
-                            com.google.common.collect.ImmutableList<RexLiteral>>
-                    tuples) {
+            final ImmutableList<ImmutableList<RexLiteral>> tuples) {
         final RelMetadataQuery mq = cluster.getMetadataQuery();
         final RelTraitSet traitSet =
                 cluster.traitSetOf(Convention.NONE)
@@ -134,7 +127,7 @@ public class LogicalValues extends Values {
 
     /** Creates a LogicalValues that outputs no rows of a given row type. */
     public static LogicalValues createEmpty(RelOptCluster cluster, RelDataType rowType) {
-        return create(cluster, rowType, com.google.common.collect.ImmutableList.of());
+        return create(cluster, rowType, ImmutableList.of());
     }
 
     /** Creates a LogicalValues that outputs one row and one column. */
@@ -145,15 +138,13 @@ public class LogicalValues extends Values {
                         .add("ZERO", SqlTypeName.INTEGER)
                         .nullable(false)
                         .build();
-        final com.google.common.collect.ImmutableList<
-                        com.google.common.collect.ImmutableList<RexLiteral>>
-                tuples =
-                        com.google.common.collect.ImmutableList.of(
-                                com.google.common.collect.ImmutableList.of(
-                                        cluster.getRexBuilder()
-                                                .makeExactLiteral(
-                                                        BigDecimal.ZERO,
-                                                        rowType.getFieldList().get(0).getType())));
+        final ImmutableList<ImmutableList<RexLiteral>> tuples =
+                ImmutableList.of(
+                        ImmutableList.of(
+                                cluster.getRexBuilder()
+                                        .makeExactLiteral(
+                                                BigDecimal.ZERO,
+                                                rowType.getFieldList().get(0).getType())));
         return create(cluster, rowType, tuples);
     }
 
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalWindow.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalWindow.java
index 6c9cc5a1c5e..3208f4205d8 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalWindow.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalWindow.java
@@ -18,6 +18,9 @@
 
 package org.apache.calcite.rel.logical;
 
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptUtil;
@@ -41,6 +44,7 @@ import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.Litmus;
 import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.util.AbstractList;
 import java.util.ArrayList;
@@ -133,8 +137,7 @@ public final class LogicalWindow extends Window {
         final RelDataType outRowType = program.getOutputRowType();
         // Build a list of distinct groups, partitions and aggregate
         // functions.
-        final com.google.common.collect.Multimap<WindowKey, RexOver> windowMap =
-                com.google.common.collect.LinkedListMultimap.create();
+        final Multimap<WindowKey, RexOver> windowMap = LinkedListMultimap.create();
 
         final int inputFieldCount = child.getRowType().getFieldCount();
 
@@ -163,7 +166,7 @@ public final class LogicalWindow extends Window {
         // Build a list of groups, partitions, and aggregate functions. Each
         // aggregate function will add its arguments as outputs of the input
         // program.
-        final Map<RexOver, RexOver> origToNewOver = new IdentityHashMap<>();
+        final IdentityHashMap<RexOver, RexOver> origToNewOver = new IdentityHashMap<>();
         for (RexNode agg : program.getExprList()) {
             if (agg instanceof RexOver) {
                 final RexOver origOver = (RexOver) agg;
@@ -243,6 +246,7 @@ public final class LogicalWindow extends Window {
         // the output calc (if it exists).
         RexShuttle shuttle =
                 new RexShuttle() {
+                    @Override
                     public RexNode visitOver(RexOver over) {
                         // Look up the aggCall which this expr was translated to.
                         final Window.RexWinAggCall aggCall = aggMap.get(origToNewOver.get(over));
@@ -266,6 +270,7 @@ public final class LogicalWindow extends Window {
                         return new RexInputRef(index, over.getType());
                     }
 
+                    @Override
                     public RexNode visitLocalRef(RexLocalRef localRef) {
                         final int index = localRef.getIndex();
                         if (index < inputFieldCount) {
@@ -298,10 +303,12 @@ public final class LogicalWindow extends Window {
 
     private static List<RexNode> toInputRefs(final List<? extends RexNode> operands) {
         return new AbstractList<RexNode>() {
+            @Override
             public int size() {
                 return operands.size();
             }
 
+            @Override
             public RexNode get(int index) {
                 final RexNode operand = operands.get(index);
                 if (operand instanceof RexInputRef) {
@@ -345,7 +352,7 @@ public final class LogicalWindow extends Window {
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             return obj == this
                     || obj instanceof WindowKey
                             && groupSet.equals(((WindowKey) obj).groupSet)
@@ -357,15 +364,13 @@ public final class LogicalWindow extends Window {
     }
 
     private static void addWindows(
-            com.google.common.collect.Multimap<WindowKey, RexOver> windowMap,
-            RexOver over,
-            final int inputFieldCount) {
+            Multimap<WindowKey, RexOver> windowMap, RexOver over, final int inputFieldCount) {
         final RexWindow aggWindow = over.getWindow();
 
         // Look up or create a window.
         RelCollation orderKeys =
                 getCollation(
-                        com.google.common.collect.Lists.newArrayList(
+                        Lists.newArrayList(
                                 Util.filter(
                                         aggWindow.orderKeys,
                                         rexFieldCollation ->
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rex/RexLiteral.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rex/RexLiteral.java
deleted file mode 100644
index dfb68088c9f..00000000000
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rex/RexLiteral.java
+++ /dev/null
@@ -1,1179 +0,0 @@
-/*
- * 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.rex;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import org.apache.calcite.avatica.util.ByteString;
-import org.apache.calcite.avatica.util.DateTimeUtils;
-import org.apache.calcite.avatica.util.TimeUnit;
-import org.apache.calcite.config.CalciteSystemProperty;
-import org.apache.calcite.linq4j.function.Functions;
-import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.rel.type.RelDataTypeField;
-import org.apache.calcite.runtime.FlatLists;
-import org.apache.calcite.runtime.GeoFunctions;
-import org.apache.calcite.runtime.Geometries;
-import org.apache.calcite.sql.SqlCollation;
-import org.apache.calcite.sql.SqlKind;
-import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.fun.SqlStdOperatorTable;
-import org.apache.calcite.sql.parser.SqlParserUtil;
-import org.apache.calcite.sql.type.SqlTypeName;
-import org.apache.calcite.util.CompositeList;
-import org.apache.calcite.util.ConversionUtil;
-import org.apache.calcite.util.DateString;
-import org.apache.calcite.util.Litmus;
-import org.apache.calcite.util.NlsString;
-import org.apache.calcite.util.Sarg;
-import org.apache.calcite.util.TimeString;
-import org.apache.calcite.util.TimestampString;
-import org.apache.calcite.util.Util;
-
-import java.io.PrintWriter;
-import java.math.BigDecimal;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.TimeZone;
-
-/**
- * Constant value in a row-expression.
- *
- * <p>There are several methods for creating literals in {@link RexBuilder}: {@link
- * RexBuilder#makeLiteral(boolean)} and so forth.
- *
- * <p>How is the value stored? In that respect, the class is somewhat of a black box. There is a
- * {@link #getValue} method which returns the value as an object, but the type of that value is
- * implementation detail, and it is best that your code does not depend upon that knowledge. It is
- * better to use task-oriented methods such as {@link #getValue2} and {@link #toJavaString}.
- *
- * <p>The allowable types and combinations are:
- *
- * <table>
- * <caption>Allowable types for RexLiteral instances</caption>
- * <tr>
- * <th>TypeName</th>
- * <th>Meaning</th>
- * <th>Value type</th>
- * </tr>
- * <tr>
- * <td>{@link SqlTypeName#NULL}</td>
- * <td>The null value. It has its own special type.</td>
- * <td>null</td>
- * </tr>
- * <tr>
- * <td>{@link SqlTypeName#BOOLEAN}</td>
- * <td>Boolean, namely <code>TRUE</code>, <code>FALSE</code> or <code>
- * UNKNOWN</code>.</td>
- * <td>{@link Boolean}, or null represents the UNKNOWN value</td>
- * </tr>
- * <tr>
- * <td>{@link SqlTypeName#DECIMAL}</td>
- * <td>Exact number, for example <code>0</code>, <code>-.5</code>, <code>
- * 12345</code>.</td>
- * <td>{@link BigDecimal}</td>
- * </tr>
- * <tr>
- * <td>{@link SqlTypeName#DOUBLE}</td>
- * <td>Approximate number, for example <code>6.023E-23</code>.</td>
- * <td>{@link BigDecimal}</td>
- * </tr>
- * <tr>
- * <td>{@link SqlTypeName#DATE}</td>
- * <td>Date, for example <code>DATE '1969-04'29'</code></td>
- * <td>{@link Calendar};
- *     also {@link Calendar} (UTC time zone)
- *     and {@link Integer} (days since POSIX epoch)</td>
- * </tr>
- * <tr>
- * <td>{@link SqlTypeName#TIME}</td>
- * <td>Time, for example <code>TIME '18:37:42.567'</code></td>
- * <td>{@link Calendar};
- *     also {@link Calendar} (UTC time zone)
- *     and {@link Integer} (milliseconds since midnight)</td>
- * </tr>
- * <tr>
- * <td>{@link SqlTypeName#TIMESTAMP}</td>
- * <td>Timestamp, for example <code>TIMESTAMP '1969-04-29
- * 18:37:42.567'</code></td>
- * <td>{@link TimestampString};
- *     also {@link Calendar} (UTC time zone)
- *     and {@link Long} (milliseconds since POSIX epoch)</td>
- * </tr>
- * <tr>
- * <td>{@link SqlTypeName#INTERVAL_DAY},
- *     {@link SqlTypeName#INTERVAL_DAY_HOUR},
- *     {@link SqlTypeName#INTERVAL_DAY_MINUTE},
- *     {@link SqlTypeName#INTERVAL_DAY_SECOND},
- *     {@link SqlTypeName#INTERVAL_HOUR},
- *     {@link SqlTypeName#INTERVAL_HOUR_MINUTE},
- *     {@link SqlTypeName#INTERVAL_HOUR_SECOND},
- *     {@link SqlTypeName#INTERVAL_MINUTE},
- *     {@link SqlTypeName#INTERVAL_MINUTE_SECOND},
- *     {@link SqlTypeName#INTERVAL_SECOND}</td>
- * <td>Interval, for example <code>INTERVAL '4:3:2' HOUR TO SECOND</code></td>
- * <td>{@link BigDecimal};
- *     also {@link Long} (milliseconds)</td>
- * </tr>
- * <tr>
- * <td>{@link SqlTypeName#INTERVAL_YEAR},
- *     {@link SqlTypeName#INTERVAL_YEAR_MONTH},
- *     {@link SqlTypeName#INTERVAL_MONTH}</td>
- * <td>Interval, for example <code>INTERVAL '2-3' YEAR TO MONTH</code></td>
- * <td>{@link BigDecimal};
- *     also {@link Integer} (months)</td>
- * </tr>
- * <tr>
- * <td>{@link SqlTypeName#CHAR}</td>
- * <td>Character constant, for example <code>'Hello, world!'</code>, <code>
- * ''</code>, <code>_N'Bonjour'</code>, <code>_ISO-8859-1'It''s superman!'
- * COLLATE SHIFT_JIS$ja_JP$2</code>. These are always CHAR, never VARCHAR.</td>
- * <td>{@link NlsString};
- *     also {@link String}</td>
- * </tr>
- * <tr>
- * <td>{@link SqlTypeName#BINARY}</td>
- * <td>Binary constant, for example <code>X'7F34'</code>. (The number of hexits
- * must be even; see above.) These constants are always BINARY, never
- * VARBINARY.</td>
- * <td>{@link ByteBuffer};
- *     also {@code byte[]}</td>
- * </tr>
- * <tr>
- * <td>{@link SqlTypeName#SYMBOL}</td>
- * <td>A symbol is a special type used to make parsing easier; it is not part of
- * the SQL standard, and is not exposed to end-users. It is used to hold a flag,
- * such as the LEADING flag in a call to the function <code>
- * TRIM([LEADING|TRAILING|BOTH] chars FROM string)</code>.</td>
- * <td>An enum class</td>
- * </tr>
- * </table>
- *
- * <p>Line 374 ~ 376: fix FLINK-20942, the file should be removed when upgrade to CALCITE 1.27.0
- * release.
- */
-public class RexLiteral extends RexNode {
-    // ~ Instance fields --------------------------------------------------------
-
-    /**
-     * The value of this literal. Must be consistent with its type, as per {@link
-     * #valueMatchesType}. For example, you can't store an {@link Integer} value here just because
-     * you feel like it -- all numbers are represented by a {@link BigDecimal}. But since this field
-     * is private, it doesn't really matter how the values are stored.
-     */
-    private final Comparable value;
-
-    /** The real type of this literal, as reported by {@link #getType}. */
-    private final RelDataType type;
-
-    // TODO jvs 26-May-2006:  Use SqlTypeFamily instead; it exists
-    // for exactly this purpose (to avoid the confusion which results
-    // from overloading SqlTypeName).
-    /**
-     * An indication of the broad type of this literal -- even if its type isn't a SQL type.
-     * Sometimes this will be different than the SQL type; for example, all exact numbers, including
-     * integers have typeName {@link SqlTypeName#DECIMAL}. See {@link #valueMatchesType} for the
-     * definitive story.
-     */
-    private final SqlTypeName typeName;
-
-    private static final ImmutableList<TimeUnit> TIME_UNITS =
-            ImmutableList.copyOf(TimeUnit.values());
-
-    // ~ Constructors -----------------------------------------------------------
-
-    /** Creates a <code>RexLiteral</code>. */
-    RexLiteral(Comparable value, RelDataType type, SqlTypeName typeName) {
-        this.value = value;
-        this.type = Objects.requireNonNull(type);
-        this.typeName = Objects.requireNonNull(typeName);
-        Preconditions.checkArgument(valueMatchesType(value, typeName, true));
-        Preconditions.checkArgument((value == null) == type.isNullable());
-        Preconditions.checkArgument(typeName != SqlTypeName.ANY);
-        this.digest = computeDigest(RexDigestIncludeType.OPTIONAL);
-    }
-
-    // ~ Methods ----------------------------------------------------------------
-
-    /**
-     * Returns a string which concisely describes the definition of this rex literal. Two literals
-     * are equivalent if and only if their digests are the same.
-     *
-     * <p>The digest does not contain the expression's identity, but does include the identity of
-     * children.
-     *
-     * <p>Technically speaking 1:INT differs from 1:FLOAT, so we need data type in the literal's
-     * digest, however we want to avoid extra verbosity of the {@link RelNode#getDigest()} for
-     * readability purposes, so we omit type info in certain cases. For instance, 1:INT becomes 1
-     * (INT is implied by default), however 1:BIGINT always holds the type
-     *
-     * <p>Here's a non-exhaustive list of the "well known cases":
-     *
-     * <ul>
-     *   <li>Hide "NOT NULL" for not null literals
-     *   <li>Hide INTEGER, BOOLEAN, SYMBOL, TIME(0), TIMESTAMP(0), DATE(0) types
-     *   <li>Hide collation when it matches IMPLICIT/COERCIBLE
-     *   <li>Hide charset when it matches default
-     *   <li>Hide CHAR(xx) when literal length is equal to the precision of the type. In other
-     *       words, use 'Bob' instead of 'Bob':CHAR(3)
-     *   <li>Hide BOOL for AND/OR arguments. In other words, AND(true, null) means null is BOOL.
-     *   <li>Hide types for literals in simple binary operations (e.g. +, -, *, /, comparison) when
-     *       type of the other argument is clear. See {@link RexCall#computeDigest(boolean)} For
-     *       instance: =(true. null) means null is BOOL. =($0, null) means the type of null matches
-     *       the type of $0.
-     * </ul>
-     *
-     * @param includeType whether the digest should include type or not
-     * @return digest
-     */
-    public final String computeDigest(RexDigestIncludeType includeType) {
-        if (includeType == RexDigestIncludeType.OPTIONAL) {
-            if (digest != null) {
-                // digest is initialized with OPTIONAL, so cached value matches for
-                // includeType=OPTIONAL as well
-                return digest;
-            }
-            // Compute we should include the type or not
-            includeType = digestIncludesType();
-        } else if (digest != null && includeType == digestIncludesType()) {
-            // The digest is always computed with includeType=OPTIONAL
-            // If it happened to omit the type, we want to optimize computeDigest(NO_TYPE) as well
-            // If the digest includes the type, we want to optimize computeDigest(ALWAYS)
-            return digest;
-        }
-
-        return toJavaString(value, typeName, type, includeType);
-    }
-
-    /**
-     * Returns true if {@link RexDigestIncludeType#OPTIONAL} digest would include data type.
-     *
-     * @see RexCall#computeDigest(boolean)
-     * @return true if {@link RexDigestIncludeType#OPTIONAL} digest would include data type
-     */
-    RexDigestIncludeType digestIncludesType() {
-        return shouldIncludeType(value, type);
-    }
-
-    /** Returns whether a value is appropriate for its type. (We have rules about these things!) */
-    public static boolean valueMatchesType(Comparable value, SqlTypeName typeName, boolean strict) {
-        if (value == null) {
-            return true;
-        }
-        switch (typeName) {
-            case BOOLEAN:
-                // Unlike SqlLiteral, we do not allow boolean null.
-                return value instanceof Boolean;
-            case NULL:
-                return false; // value should have been null
-            case INTEGER: // not allowed -- use Decimal
-            case TINYINT:
-            case SMALLINT:
-                if (strict) {
-                    throw Util.unexpected(typeName);
-                }
-                // fall through
-            case DECIMAL:
-            case DOUBLE:
-            case FLOAT:
-            case REAL:
-            case BIGINT:
-                return value instanceof BigDecimal;
-            case DATE:
-                return value instanceof DateString;
-            case TIME:
-                return value instanceof TimeString;
-            case TIME_WITH_LOCAL_TIME_ZONE:
-                return value instanceof TimeString;
-            case TIMESTAMP:
-                return value instanceof TimestampString;
-            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
-                return value instanceof TimestampString;
-            case INTERVAL_YEAR:
-            case INTERVAL_YEAR_MONTH:
-            case INTERVAL_MONTH:
-            case INTERVAL_DAY:
-            case INTERVAL_DAY_HOUR:
-            case INTERVAL_DAY_MINUTE:
-            case INTERVAL_DAY_SECOND:
-            case INTERVAL_HOUR:
-            case INTERVAL_HOUR_MINUTE:
-            case INTERVAL_HOUR_SECOND:
-            case INTERVAL_MINUTE:
-            case INTERVAL_MINUTE_SECOND:
-            case INTERVAL_SECOND:
-                // The value of a DAY-TIME interval (whatever the start and end units,
-                // even say HOUR TO MINUTE) is in milliseconds (perhaps fractional
-                // milliseconds). The value of a YEAR-MONTH interval is in months.
-                return value instanceof BigDecimal;
-            case VARBINARY: // not allowed -- use Binary
-                if (strict) {
-                    throw Util.unexpected(typeName);
-                }
-                // fall through
-            case BINARY:
-                return value instanceof ByteString;
-            case VARCHAR: // not allowed -- use Char
-                if (strict) {
-                    throw Util.unexpected(typeName);
-                }
-                // fall through
-            case CHAR:
-                // A SqlLiteral's charset and collation are optional; not so a
-                // RexLiteral.
-                return (value instanceof NlsString)
-                        && (((NlsString) value).getCharset() != null)
-                        && (((NlsString) value).getCollation() != null);
-            case SARG:
-                return value instanceof Sarg;
-            case SYMBOL:
-                return value instanceof Enum;
-            case ROW:
-            case MULTISET:
-                return value instanceof List;
-            case GEOMETRY:
-                return value instanceof Geometries.Geom;
-            case ANY:
-                // Literal of type ANY is not legal. "CAST(2 AS ANY)" remains
-                // an integer literal surrounded by a cast function.
-                return false;
-            default:
-                throw Util.unexpected(typeName);
-        }
-    }
-
-    /** Returns the strict literal type for a given type. */
-    public static SqlTypeName strictTypeName(RelDataType type) {
-        final SqlTypeName typeName = type.getSqlTypeName();
-        switch (typeName) {
-            case INTEGER:
-            case TINYINT:
-            case SMALLINT:
-                return SqlTypeName.DECIMAL;
-            case REAL:
-            case FLOAT:
-                return SqlTypeName.DOUBLE;
-            case VARBINARY:
-                return SqlTypeName.BINARY;
-            case VARCHAR:
-                return SqlTypeName.CHAR;
-            default:
-                return typeName;
-        }
-    }
-
-    private static String toJavaString(
-            Comparable value,
-            SqlTypeName typeName,
-            RelDataType type,
-            RexDigestIncludeType includeType) {
-        assert includeType != RexDigestIncludeType.OPTIONAL
-                : "toJavaString must not be called with includeType=OPTIONAL";
-        if (value == null) {
-            return includeType == RexDigestIncludeType.NO_TYPE
-                    ? "null"
-                    : "null:" + type.getFullTypeString();
-        }
-        StringBuilder sb = new StringBuilder();
-        appendAsJava(value, sb, typeName, type, false, includeType);
-
-        if (includeType != RexDigestIncludeType.NO_TYPE) {
-            sb.append(':');
-            final String fullTypeString = type.getFullTypeString();
-            if (!fullTypeString.endsWith("NOT NULL")) {
-                sb.append(fullTypeString);
-            } else {
-                // Trim " NOT NULL". Apparently, the literal is not null, so we just print the data
-                // type.
-                sb.append(fullTypeString, 0, fullTypeString.length() - 9);
-            }
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Computes if data type can be omitted from the digset.
-     *
-     * <p>For instance, {@code 1:BIGINT} has to keep data type while {@code 1:INT} should be
-     * represented as just {@code 1}.
-     *
-     * <p>Implementation assumption: this method should be fast. In fact might call {@link
-     * NlsString#getValue()} which could decode the string, however we rely on the cache there.
-     *
-     * @see RexLiteral#computeDigest(RexDigestIncludeType)
-     * @param value value of the literal
-     * @param type type of the literal
-     * @return NO_TYPE when type can be omitted, ALWAYS otherwise
-     */
-    private static RexDigestIncludeType shouldIncludeType(Comparable value, RelDataType type) {
-        if (type.isNullable()) {
-            // This means "null literal", so we require a type for it
-            // There might be exceptions like AND(null, true) which are handled by
-            // RexCall#computeDigest
-            return RexDigestIncludeType.ALWAYS;
-        }
-        // The variable here simplifies debugging (one can set a breakpoint at return)
-        // final ensures we set the value in all the branches, and it ensures the value is set just
-        // once
-        final RexDigestIncludeType includeType;
-        if (type.getSqlTypeName() == SqlTypeName.BOOLEAN
-                || type.getSqlTypeName() == SqlTypeName.INTEGER
-                || type.getSqlTypeName() == SqlTypeName.SYMBOL) {
-            // We don't want false:BOOLEAN NOT NULL, so we don't print type information for
-            // non-nullable BOOLEAN and INTEGER
-            includeType = RexDigestIncludeType.NO_TYPE;
-        } else if (type.getSqlTypeName() == SqlTypeName.CHAR && value instanceof NlsString) {
-            NlsString nlsString = (NlsString) value;
-
-            // Ignore type information for 'Bar':CHAR(3)
-            if (((nlsString.getCharset() != null
-                                    && type.getCharset().equals(nlsString.getCharset()))
-                            || (nlsString.getCharset() == null
-                                    && SqlCollation.IMPLICIT
-                                            .getCharset()
-                                            .equals(type.getCharset())))
-                    && nlsString.getCollation().equals(type.getCollation())
-                    && ((NlsString) value).getValue().length() == type.getPrecision()) {
-                includeType = RexDigestIncludeType.NO_TYPE;
-            } else {
-                includeType = RexDigestIncludeType.ALWAYS;
-            }
-        } else if (type.getPrecision() == 0
-                && (type.getSqlTypeName() == SqlTypeName.TIME
-                        || type.getSqlTypeName() == SqlTypeName.TIMESTAMP
-                        || type.getSqlTypeName() == SqlTypeName.DATE)) {
-            // Ignore type information for '12:23:20':TIME(0)
-            // Note that '12:23:20':TIME WITH LOCAL TIME ZONE
-            includeType = RexDigestIncludeType.NO_TYPE;
-        } else {
-            includeType = RexDigestIncludeType.ALWAYS;
-        }
-        return includeType;
-    }
-
-    /**
-     * Returns whether a value is valid as a constant value, using the same criteria as {@link
-     * #valueMatchesType}.
-     */
-    public static boolean validConstant(Object o, Litmus litmus) {
-        if (o == null
-                || o instanceof BigDecimal
-                || o instanceof NlsString
-                || o instanceof ByteString
-                || o instanceof Boolean) {
-            return litmus.succeed();
-        } else if (o instanceof List) {
-            List list = (List) o;
-            for (Object o1 : list) {
-                if (!validConstant(o1, litmus)) {
-                    return litmus.fail("not a constant: {}", o1);
-                }
-            }
-            return litmus.succeed();
-        } else if (o instanceof Map) {
-            @SuppressWarnings("unchecked")
-            final Map<Object, Object> map = (Map) o;
-            for (Map.Entry entry : map.entrySet()) {
-                if (!validConstant(entry.getKey(), litmus)) {
-                    return litmus.fail("not a constant: {}", entry.getKey());
-                }
-                if (!validConstant(entry.getValue(), litmus)) {
-                    return litmus.fail("not a constant: {}", entry.getValue());
-                }
-            }
-            return litmus.succeed();
-        } else {
-            return litmus.fail("not a constant: {}", o);
-        }
-    }
-
-    /**
-     * Returns a list of the time units covered by an interval type such as HOUR TO SECOND. Adds
-     * MILLISECOND if the end is SECOND, to deal with fractional seconds.
-     */
-    private static List<TimeUnit> getTimeUnits(SqlTypeName typeName) {
-        final TimeUnit start = typeName.getStartUnit();
-        final TimeUnit end = typeName.getEndUnit();
-        final ImmutableList<TimeUnit> list = TIME_UNITS.subList(start.ordinal(), end.ordinal() + 1);
-        if (end == TimeUnit.SECOND) {
-            return CompositeList.of(list, ImmutableList.of(TimeUnit.MILLISECOND));
-        }
-        return list;
-    }
-
-    private String intervalString(BigDecimal v) {
-        final List<TimeUnit> timeUnits = getTimeUnits(type.getSqlTypeName());
-        final StringBuilder b = new StringBuilder();
-        for (TimeUnit timeUnit : timeUnits) {
-            final BigDecimal[] result = v.divideAndRemainder(timeUnit.multiplier);
-            if (b.length() > 0) {
-                b.append(timeUnit.separator);
-            }
-            final int width = b.length() == 0 ? -1 : width(timeUnit); // don't pad 1st
-            pad(b, result[0].toString(), width);
-            v = result[1];
-        }
-        if (Util.last(timeUnits) == TimeUnit.MILLISECOND) {
-            while (b.toString().matches(".*\\.[0-9]*0")) {
-                if (b.toString().endsWith(".0")) {
-                    b.setLength(b.length() - 2); // remove ".0"
-                } else {
-                    b.setLength(b.length() - 1); // remove "0"
-                }
-            }
-        }
-        return b.toString();
-    }
-
-    private static void pad(StringBuilder b, String s, int width) {
-        if (width >= 0) {
-            for (int i = s.length(); i < width; i++) {
-                b.append('0');
-            }
-        }
-        b.append(s);
-    }
-
-    private static int width(TimeUnit timeUnit) {
-        switch (timeUnit) {
-            case MILLISECOND:
-                return 3;
-            case HOUR:
-            case MINUTE:
-            case SECOND:
-                return 2;
-            default:
-                return -1;
-        }
-    }
-
-    /** Prints the value this literal as a Java string constant. */
-    public void printAsJava(PrintWriter pw) {
-        Util.asStringBuilder(
-                pw,
-                sb -> appendAsJava(value, sb, typeName, type, true, RexDigestIncludeType.NO_TYPE));
-    }
-
-    /**
-     * Appends the specified value in the provided destination as a Java string. The value must be
-     * consistent with the type, as per {@link #valueMatchesType}.
-     *
-     * <p>Typical return values:
-     *
-     * <ul>
-     *   <li>true
-     *   <li>null
-     *   <li>"Hello, world!"
-     *   <li>1.25
-     *   <li>1234ABCD
-     * </ul>
-     *
-     * @param value Value to be appended to the provided destination as a Java string
-     * @param sb Destination to which to append the specified value
-     * @param typeName Type name to be used for the transformation of the value to a Java string
-     * @param type Type to be used for the transformation of the value to a Java string
-     * @param includeType Whether to include the data type in the Java representation
-     */
-    private static void appendAsJava(
-            Comparable value,
-            StringBuilder sb,
-            SqlTypeName typeName,
-            RelDataType type,
-            boolean java,
-            RexDigestIncludeType includeType) {
-        switch (typeName) {
-            case CHAR:
-                NlsString nlsString = (NlsString) value;
-                if (java) {
-                    Util.printJavaString(sb, nlsString.getValue(), true);
-                } else {
-                    boolean includeCharset =
-                            (nlsString.getCharsetName() != null)
-                                    && !nlsString
-                                            .getCharsetName()
-                                            .equals(CalciteSystemProperty.DEFAULT_CHARSET.value());
-                    sb.append(nlsString.asSql(includeCharset, false));
-                }
-                break;
-            case BOOLEAN:
-                assert value instanceof Boolean;
-                sb.append(value.toString());
-                break;
-            case DECIMAL:
-                assert value instanceof BigDecimal;
-                sb.append(value.toString());
-                break;
-            case DOUBLE:
-                assert value instanceof BigDecimal;
-                sb.append(Util.toScientificNotation((BigDecimal) value));
-                break;
-            case BIGINT:
-                assert value instanceof BigDecimal;
-                long narrowLong = ((BigDecimal) value).longValue();
-                sb.append(String.valueOf(narrowLong));
-                sb.append('L');
-                break;
-            case BINARY:
-                assert value instanceof ByteString;
-                sb.append("X'");
-                sb.append(((ByteString) value).toString(16));
-                sb.append("'");
-                break;
-            case NULL:
-                assert value == null;
-                sb.append("null");
-                break;
-            case SARG:
-                assert value instanceof Sarg;
-                //noinspection unchecked,rawtypes
-                Util.asStringBuilder(sb, sb2 -> printSarg(sb2, (Sarg) value, type));
-                break;
-            case SYMBOL:
-                assert value instanceof Enum;
-                sb.append("FLAG(");
-                sb.append(value.toString());
-                sb.append(")");
-                break;
-            case DATE:
-                assert value instanceof DateString;
-                sb.append(value.toString());
-                break;
-            case TIME:
-            case TIME_WITH_LOCAL_TIME_ZONE:
-                assert value instanceof TimeString;
-                sb.append(value.toString());
-                break;
-            case TIMESTAMP:
-            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
-                assert value instanceof TimestampString;
-                sb.append(value.toString());
-                break;
-            case INTERVAL_YEAR:
-            case INTERVAL_YEAR_MONTH:
-            case INTERVAL_MONTH:
-            case INTERVAL_DAY:
-            case INTERVAL_DAY_HOUR:
-            case INTERVAL_DAY_MINUTE:
-            case INTERVAL_DAY_SECOND:
-            case INTERVAL_HOUR:
-            case INTERVAL_HOUR_MINUTE:
-            case INTERVAL_HOUR_SECOND:
-            case INTERVAL_MINUTE:
-            case INTERVAL_MINUTE_SECOND:
-            case INTERVAL_SECOND:
-                assert value instanceof BigDecimal;
-                sb.append(value.toString());
-                break;
-            case MULTISET:
-            case ROW:
-                final List<RexLiteral> list = (List) value;
-                Util.asStringBuilder(
-                        sb,
-                        sb2 ->
-                                Util.printList(
-                                        sb,
-                                        list.size(),
-                                        (sb3, i) ->
-                                                sb3.append(
-                                                        list.get(i).computeDigest(includeType))));
-                break;
-            case GEOMETRY:
-                final String wkt = GeoFunctions.ST_AsWKT((Geometries.Geom) value);
-                sb.append(wkt);
-                break;
-            default:
-                assert valueMatchesType(value, typeName, true);
-                throw Util.needToImplement(typeName);
-        }
-    }
-
-    private static <C extends Comparable<C>> void printSarg(
-            StringBuilder sb, Sarg<C> sarg, RelDataType type) {
-        sarg.printTo(sb, (sb2, value) -> sb2.append(toLiteral(type, value)));
-    }
-
-    /**
-     * Converts a value to a temporary literal, for the purposes of generating a digest. Literals of
-     * type ROW and MULTISET require that their components are also literals.
-     */
-    private static RexLiteral toLiteral(RelDataType type, Comparable<?> value) {
-        final SqlTypeName typeName = strictTypeName(type);
-        switch (typeName) {
-            case ROW:
-                final List<Comparable<?>> fieldValues = (List) value;
-                final List<RelDataTypeField> fields = type.getFieldList();
-                final List<RexLiteral> fieldLiterals =
-                        FlatLists.of(
-                                Functions.generate(
-                                        fieldValues.size(),
-                                        i ->
-                                                toLiteral(
-                                                        fields.get(i).getType(),
-                                                        fieldValues.get(i))));
-                return new RexLiteral((Comparable) fieldLiterals, type, typeName);
-
-            case MULTISET:
-                final List<Comparable<?>> elementValues = (List) value;
-                final List<RexLiteral> elementLiterals =
-                        FlatLists.of(
-                                Functions.generate(
-                                        elementValues.size(),
-                                        i ->
-                                                toLiteral(
-                                                        type.getComponentType(),
-                                                        elementValues.get(i))));
-                return new RexLiteral((Comparable) elementLiterals, type, typeName);
-
-            default:
-                return new RexLiteral(value, type, typeName);
-        }
-    }
-
-    /**
-     * Converts a Jdbc string into a RexLiteral. This method accepts a string, as returned by the
-     * Jdbc method ResultSet.getString(), and restores the string into an equivalent RexLiteral. It
-     * allows one to use Jdbc strings as a common format for data.
-     *
-     * <p>If a null literal is provided, then a null pointer will be returned.
-     *
-     * @param type data type of literal to be read
-     * @param typeName type family of literal
-     * @param literal the (non-SQL encoded) string representation, as returned by the Jdbc call to
-     *     return a column as a string
-     * @return a typed RexLiteral, or null
-     */
-    public static RexLiteral fromJdbcString(
-            RelDataType type, SqlTypeName typeName, String literal) {
-        if (literal == null) {
-            return null;
-        }
-
-        switch (typeName) {
-            case CHAR:
-                Charset charset = type.getCharset();
-                SqlCollation collation = type.getCollation();
-                NlsString str = new NlsString(literal, charset.name(), collation);
-                return new RexLiteral(str, type, typeName);
-            case BOOLEAN:
-                boolean b = ConversionUtil.toBoolean(literal);
-                return new RexLiteral(b, type, typeName);
-            case DECIMAL:
-            case DOUBLE:
-                BigDecimal d = new BigDecimal(literal);
-                return new RexLiteral(d, type, typeName);
-            case BINARY:
-                byte[] bytes = ConversionUtil.toByteArrayFromString(literal, 16);
-                return new RexLiteral(new ByteString(bytes), type, typeName);
-            case NULL:
-                return new RexLiteral(null, type, typeName);
-            case INTERVAL_DAY:
-            case INTERVAL_DAY_HOUR:
-            case INTERVAL_DAY_MINUTE:
-            case INTERVAL_DAY_SECOND:
-            case INTERVAL_HOUR:
-            case INTERVAL_HOUR_MINUTE:
-            case INTERVAL_HOUR_SECOND:
-            case INTERVAL_MINUTE:
-            case INTERVAL_MINUTE_SECOND:
-            case INTERVAL_SECOND:
-                long millis = SqlParserUtil.intervalToMillis(literal, type.getIntervalQualifier());
-                return new RexLiteral(BigDecimal.valueOf(millis), type, typeName);
-            case INTERVAL_YEAR:
-            case INTERVAL_YEAR_MONTH:
-            case INTERVAL_MONTH:
-                long months = SqlParserUtil.intervalToMonths(literal, type.getIntervalQualifier());
-                return new RexLiteral(BigDecimal.valueOf(months), type, typeName);
-            case DATE:
-            case TIME:
-            case TIMESTAMP:
-                String format = getCalendarFormat(typeName);
-                TimeZone tz = DateTimeUtils.UTC_ZONE;
-                final Comparable v;
-                switch (typeName) {
-                    case DATE:
-                        final Calendar cal =
-                                DateTimeUtils.parseDateFormat(
-                                        literal, new SimpleDateFormat(format, Locale.ROOT), tz);
-                        if (cal == null) {
-                            throw new AssertionError(
-                                    "fromJdbcString: invalid date/time value '" + literal + "'");
-                        }
-                        v = DateString.fromCalendarFields(cal);
-                        break;
-                    default:
-                        // Allow fractional seconds for times and timestamps
-                        assert format != null;
-                        final DateTimeUtils.PrecisionTime ts =
-                                DateTimeUtils.parsePrecisionDateTimeLiteral(
-                                        literal, new SimpleDateFormat(format, Locale.ROOT), tz, -1);
-                        if (ts == null) {
-                            throw new AssertionError(
-                                    "fromJdbcString: invalid date/time value '" + literal + "'");
-                        }
-                        switch (typeName) {
-                            case TIMESTAMP:
-                                v =
-                                        TimestampString.fromCalendarFields(ts.getCalendar())
-                                                .withFraction(ts.getFraction());
-                                break;
-                            case TIME:
-                                v =
-                                        TimeString.fromCalendarFields(ts.getCalendar())
-                                                .withFraction(ts.getFraction());
-                                break;
-                            default:
-                                throw new AssertionError();
-                        }
-                }
-                return new RexLiteral(v, type, typeName);
-
-            case SYMBOL:
-                // Symbols are for internal use
-            default:
-                throw new AssertionError("fromJdbcString: unsupported type");
-        }
-    }
-
-    private static String getCalendarFormat(SqlTypeName typeName) {
-        switch (typeName) {
-            case DATE:
-                return DateTimeUtils.DATE_FORMAT_STRING;
-            case TIME:
-                return DateTimeUtils.TIME_FORMAT_STRING;
-            case TIMESTAMP:
-                return DateTimeUtils.TIMESTAMP_FORMAT_STRING;
-            default:
-                throw new AssertionError("getCalendarFormat: unknown type");
-        }
-    }
-
-    public SqlTypeName getTypeName() {
-        return typeName;
-    }
-
-    public RelDataType getType() {
-        return type;
-    }
-
-    @Override
-    public SqlKind getKind() {
-        return SqlKind.LITERAL;
-    }
-
-    /** Returns whether this literal's value is null. */
-    public boolean isNull() {
-        return value == null;
-    }
-
-    /**
-     * Returns the value of this literal.
-     *
-     * <p>For backwards compatibility, returns DATE. TIME and TIMESTAMP as a {@link Calendar} value
-     * in UTC time zone.
-     */
-    public Comparable getValue() {
-        assert valueMatchesType(value, typeName, true) : value;
-        if (value == null) {
-            return null;
-        }
-        switch (typeName) {
-            case TIME:
-            case DATE:
-            case TIMESTAMP:
-                return getValueAs(Calendar.class);
-            default:
-                return value;
-        }
-    }
-
-    /**
-     * Returns the value of this literal, in the form that the calculator program builder wants it.
-     */
-    public Object getValue2() {
-        if (value == null) {
-            return null;
-        }
-        switch (typeName) {
-            case CHAR:
-                return getValueAs(String.class);
-            case DECIMAL:
-            case TIMESTAMP:
-            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
-                return getValueAs(Long.class);
-            case DATE:
-            case TIME:
-            case TIME_WITH_LOCAL_TIME_ZONE:
-                return getValueAs(Integer.class);
-            default:
-                return value;
-        }
-    }
-
-    /** Returns the value of this literal, in the form that the rex-to-lix translator wants it. */
-    public Object getValue3() {
-        if (value == null) {
-            return null;
-        }
-        switch (typeName) {
-            case DECIMAL:
-                assert value instanceof BigDecimal;
-                return value;
-            default:
-                return getValue2();
-        }
-    }
-
-    /** Returns the value of this literal, in the form that {@link RexInterpreter} wants it. */
-    public Comparable getValue4() {
-        if (value == null) {
-            return null;
-        }
-        switch (typeName) {
-            case TIMESTAMP:
-            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
-                return getValueAs(Long.class);
-            case DATE:
-            case TIME:
-            case TIME_WITH_LOCAL_TIME_ZONE:
-                return getValueAs(Integer.class);
-            default:
-                return value;
-        }
-    }
-
-    /**
-     * Returns the value of this literal as an instance of the specified class.
-     *
-     * <p>The following SQL types allow more than one form:
-     *
-     * <ul>
-     *   <li>CHAR as {@link NlsString} or {@link String}
-     *   <li>TIME as {@link TimeString}, {@link Integer} (milliseconds since midnight), {@link
-     *       Calendar} (in UTC)
-     *   <li>DATE as {@link DateString}, {@link Integer} (days since 1970-01-01), {@link Calendar}
-     *   <li>TIMESTAMP as {@link TimestampString}, {@link Long} (milliseconds since 1970-01-01
-     *       00:00:00), {@link Calendar}
-     *   <li>DECIMAL as {@link BigDecimal} or {@link Long}
-     * </ul>
-     *
-     * <p>Called with {@code clazz} = {@link Comparable}, returns the value in its native form.
-     *
-     * @param clazz Desired return type
-     * @param <T> Return type
-     * @return Value of this literal in the desired type
-     */
-    public <T> T getValueAs(Class<T> clazz) {
-        if (value == null || clazz.isInstance(value)) {
-            return clazz.cast(value);
-        }
-        switch (typeName) {
-            case BINARY:
-                if (clazz == byte[].class) {
-                    return clazz.cast(((ByteString) value).getBytes());
-                }
-                break;
-            case CHAR:
-                if (clazz == String.class) {
-                    return clazz.cast(((NlsString) value).getValue());
-                } else if (clazz == Character.class) {
-                    return clazz.cast(((NlsString) value).getValue().charAt(0));
-                }
-                break;
-            case VARCHAR:
-                if (clazz == String.class) {
-                    return clazz.cast(((NlsString) value).getValue());
-                }
-                break;
-            case DECIMAL:
-                if (clazz == Long.class) {
-                    return clazz.cast(((BigDecimal) value).unscaledValue().longValue());
-                }
-                // fall through
-            case BIGINT:
-            case INTEGER:
-            case SMALLINT:
-            case TINYINT:
-            case DOUBLE:
-            case REAL:
-            case FLOAT:
-                if (clazz == Long.class) {
-                    return clazz.cast(((BigDecimal) value).longValue());
-                } else if (clazz == Integer.class) {
-                    return clazz.cast(((BigDecimal) value).intValue());
-                } else if (clazz == Short.class) {
-                    return clazz.cast(((BigDecimal) value).shortValue());
-                } else if (clazz == Byte.class) {
-                    return clazz.cast(((BigDecimal) value).byteValue());
-                } else if (clazz == Double.class) {
-                    return clazz.cast(((BigDecimal) value).doubleValue());
-                } else if (clazz == Float.class) {
-                    return clazz.cast(((BigDecimal) value).floatValue());
-                }
-                break;
-            case DATE:
-                if (clazz == Integer.class) {
-                    return clazz.cast(((DateString) value).getDaysSinceEpoch());
-                } else if (clazz == Calendar.class) {
-                    return clazz.cast(((DateString) value).toCalendar());
-                }
-                break;
-            case TIME:
-                if (clazz == Integer.class) {
-                    return clazz.cast(((TimeString) value).getMillisOfDay());
-                } else if (clazz == Calendar.class) {
-                    // Note: Nanos are ignored
-                    return clazz.cast(((TimeString) value).toCalendar());
-                }
-                break;
-            case TIME_WITH_LOCAL_TIME_ZONE:
-                if (clazz == Integer.class) {
-                    // Milliseconds since 1970-01-01 00:00:00
-                    return clazz.cast(((TimeString) value).getMillisOfDay());
-                }
-                break;
-            case TIMESTAMP:
-                if (clazz == Long.class) {
-                    // Milliseconds since 1970-01-01 00:00:00
-                    return clazz.cast(((TimestampString) value).getMillisSinceEpoch());
-                } else if (clazz == Calendar.class) {
-                    // Note: Nanos are ignored
-                    return clazz.cast(((TimestampString) value).toCalendar());
-                }
-                break;
-            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
-                if (clazz == Long.class) {
-                    // Milliseconds since 1970-01-01 00:00:00
-                    return clazz.cast(((TimestampString) value).getMillisSinceEpoch());
-                } else if (clazz == Calendar.class) {
-                    // Note: Nanos are ignored
-                    return clazz.cast(((TimestampString) value).toCalendar());
-                }
-                break;
-            case INTERVAL_YEAR:
-            case INTERVAL_YEAR_MONTH:
-            case INTERVAL_MONTH:
-            case INTERVAL_DAY:
-            case INTERVAL_DAY_HOUR:
-            case INTERVAL_DAY_MINUTE:
-            case INTERVAL_DAY_SECOND:
-            case INTERVAL_HOUR:
-            case INTERVAL_HOUR_MINUTE:
-            case INTERVAL_HOUR_SECOND:
-            case INTERVAL_MINUTE:
-            case INTERVAL_MINUTE_SECOND:
-            case INTERVAL_SECOND:
-                if (clazz == Integer.class) {
-                    return clazz.cast(((BigDecimal) value).intValue());
-                } else if (clazz == Long.class) {
-                    return clazz.cast(((BigDecimal) value).longValue());
-                } else if (clazz == String.class) {
-                    return clazz.cast(intervalString(getValueAs(BigDecimal.class).abs()));
-                } else if (clazz == Boolean.class) {
-                    // return whether negative
-                    return clazz.cast(getValueAs(BigDecimal.class).signum() < 0);
-                }
-                break;
-        }
-        throw new AssertionError("cannot convert " + typeName + " literal to " + clazz);
-    }
-
-    public static boolean booleanValue(RexNode node) {
-        return (Boolean) ((RexLiteral) node).value;
-    }
-
-    public boolean isAlwaysTrue() {
-        if (typeName != SqlTypeName.BOOLEAN) {
-            return false;
-        }
-        return booleanValue(this);
-    }
-
-    public boolean isAlwaysFalse() {
-        if (typeName != SqlTypeName.BOOLEAN) {
-            return false;
-        }
-        return !booleanValue(this);
-    }
-
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        return (obj instanceof RexLiteral)
-                && equals(((RexLiteral) obj).value, value)
-                && equals(((RexLiteral) obj).type, type);
-    }
-
-    public int hashCode() {
-        return Objects.hash(value, type);
-    }
-
-    public static Comparable value(RexNode node) {
-        return findValue(node);
-    }
-
-    public static int intValue(RexNode node) {
-        final Comparable value = findValue(node);
-        return ((Number) value).intValue();
-    }
-
-    public static String stringValue(RexNode node) {
-        final Comparable value = findValue(node);
-        return (value == null) ? null : ((NlsString) value).getValue();
-    }
-
-    private static Comparable findValue(RexNode node) {
-        if (node instanceof RexLiteral) {
-            return ((RexLiteral) node).value;
-        }
-        if (node instanceof RexCall) {
-            final RexCall call = (RexCall) node;
-            final SqlOperator operator = call.getOperator();
-            if (operator == SqlStdOperatorTable.CAST) {
-                return findValue(call.getOperands().get(0));
-            }
-            if (operator == SqlStdOperatorTable.UNARY_MINUS) {
-                final BigDecimal value = (BigDecimal) findValue(call.getOperands().get(0));
-                return value.negate();
-            }
-        }
-        throw new AssertionError("not a literal: " + node);
-    }
-
-    public static boolean isNullLiteral(RexNode node) {
-        return (node instanceof RexLiteral) && (((RexLiteral) node).value == null);
-    }
-
-    private static boolean equals(Object o1, Object o2) {
-        return Objects.equals(o1, o2);
-    }
-
-    public <R> R accept(RexVisitor<R> visitor) {
-        return visitor.visitLiteral(this);
-    }
-
-    public <R, P> R accept(RexBiVisitor<R, P> visitor, P arg) {
-        return visitor.visitLiteral(this, arg);
-    }
-}
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rex/RexSimplify.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rex/RexSimplify.java
index ea59497adfd..66bf009ea47 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rex/RexSimplify.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rex/RexSimplify.java
@@ -45,8 +45,7 @@ import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.RangeSets;
 import org.apache.calcite.util.Sarg;
 import org.apache.calcite.util.Util;
-
-import javax.annotation.Nonnull;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.util.ArrayList;
 import java.util.BitSet;
@@ -58,9 +57,10 @@ import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 
+import static java.util.Objects.requireNonNull;
+import static org.apache.calcite.linq4j.Nullness.castNonNull;
 import static org.apache.calcite.rex.RexUnknownAs.FALSE;
 import static org.apache.calcite.rex.RexUnknownAs.TRUE;
 import static org.apache.calcite.rex.RexUnknownAs.UNKNOWN;
@@ -68,14 +68,8 @@ import static org.apache.calcite.rex.RexUnknownAs.UNKNOWN;
 /**
  * Context required to simplify a row-expression.
  *
- * <p>Copied to fix Calcite 1.26 bugs, should be removed for the next Calcite upgrade.
- *
- * <p>Changes (line numbers are from the original RexSimplify file):
- *
- * <ol>
- *   <li>CALCITE-4364 & FLINK-19811: Line 1307, Line 1764, Line 2638 ~ Line 2656.
- *   <li>CALCITE-4446 & FLINK-22015: Line 2542 ~ Line 2548, Line 2614 ~ Line 2619.
- * </ol>
+ * <p>Currently, is a workaround for <a href="https://issues.apache.org/jira/browse/FLINK-29237">
+ * Line 2822 ~ Line 2827. After fixing should be removed
  */
 public class RexSimplify {
     private final boolean paranoid;
@@ -111,12 +105,12 @@ public class RexSimplify {
             boolean predicateElimination,
             boolean paranoid,
             RexExecutor executor) {
-        this.rexBuilder = Objects.requireNonNull(rexBuilder);
-        this.predicates = Objects.requireNonNull(predicates);
-        this.defaultUnknownAs = Objects.requireNonNull(defaultUnknownAs);
+        this.rexBuilder = requireNonNull(rexBuilder, "rexBuilder");
+        this.predicates = requireNonNull(predicates, "predicates");
+        this.defaultUnknownAs = requireNonNull(defaultUnknownAs, "defaultUnknownAs");
         this.predicateElimination = predicateElimination;
         this.paranoid = paranoid;
-        this.executor = Objects.requireNonNull(executor);
+        this.executor = requireNonNull(executor, "executor");
         this.strong = new Strong();
     }
 
@@ -307,6 +301,8 @@ public class RexSimplify {
                     case FALSE:
                     case TRUE:
                         return rexBuilder.makeLiteral(unknownAs.toBoolean());
+                    default:
+                        break;
                 }
             }
             return rexBuilder.makeNullLiteral(e.getType());
@@ -345,7 +341,7 @@ public class RexSimplify {
             case SEARCH:
                 return simplifySearch((RexCall) e, unknownAs);
             case LIKE:
-                return simplifyLike((RexCall) e);
+                return simplifyLike((RexCall) e, unknownAs);
             case MINUS_PREFIX:
                 return simplifyUnaryMinus((RexCall) e, unknownAs);
             case PLUS_PREFIX:
@@ -359,6 +355,63 @@ public class RexSimplify {
         }
     }
 
+    /** Applies NOT to an expression. */
+    RexNode not(RexNode e) {
+        return RexUtil.not(rexBuilder, e);
+    }
+
+    /** Applies IS NOT FALSE to an expression. */
+    RexNode isNotFalse(RexNode e) {
+        return e.isAlwaysTrue()
+                ? rexBuilder.makeLiteral(true)
+                : e.isAlwaysFalse()
+                        ? rexBuilder.makeLiteral(false)
+                        : e.getKind() == SqlKind.NOT
+                                ? isNotTrue(((RexCall) e).operands.get(0))
+                                : predicates.isEffectivelyNotNull(e)
+                                        ? e // would "CAST(e AS BOOLEAN NOT NULL)" better?
+                                        : rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_FALSE, e);
+    }
+
+    /** Applies IS NOT TRUE to an expression. */
+    RexNode isNotTrue(RexNode e) {
+        return e.isAlwaysTrue()
+                ? rexBuilder.makeLiteral(false)
+                : e.isAlwaysFalse()
+                        ? rexBuilder.makeLiteral(true)
+                        : e.getKind() == SqlKind.NOT
+                                ? isNotFalse(((RexCall) e).operands.get(0))
+                                : predicates.isEffectivelyNotNull(e)
+                                        ? not(e)
+                                        : rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_TRUE, e);
+    }
+
+    /** Applies IS TRUE to an expression. */
+    RexNode isTrue(RexNode e) {
+        return e.isAlwaysTrue()
+                ? rexBuilder.makeLiteral(true)
+                : e.isAlwaysFalse()
+                        ? rexBuilder.makeLiteral(false)
+                        : e.getKind() == SqlKind.NOT
+                                ? isFalse(((RexCall) e).operands.get(0))
+                                : predicates.isEffectivelyNotNull(e)
+                                        ? e // would "CAST(e AS BOOLEAN NOT NULL)" better?
+                                        : rexBuilder.makeCall(SqlStdOperatorTable.IS_TRUE, e);
+    }
+
+    /** Applies IS FALSE to an expression. */
+    RexNode isFalse(RexNode e) {
+        return e.isAlwaysTrue()
+                ? rexBuilder.makeLiteral(false)
+                : e.isAlwaysFalse()
+                        ? rexBuilder.makeLiteral(true)
+                        : e.getKind() == SqlKind.NOT
+                                ? isTrue(((RexCall) e).operands.get(0))
+                                : predicates.isEffectivelyNotNull(e)
+                                        ? not(e)
+                                        : rexBuilder.makeCall(SqlStdOperatorTable.IS_FALSE, e);
+    }
+
     /** Runs simplification inside a non-specialized node. */
     private RexNode simplifyGenericNode(RexCall e) {
         final List<RexNode> operands = new ArrayList<>(e.operands);
@@ -369,11 +422,13 @@ public class RexSimplify {
         return rexBuilder.makeCall(e.getType(), e.getOperator(), operands);
     }
 
-    private RexNode simplifyLike(RexCall e) {
+    private RexNode simplifyLike(RexCall e, RexUnknownAs unknownAs) {
         if (e.operands.get(1) instanceof RexLiteral) {
             final RexLiteral literal = (RexLiteral) e.operands.get(1);
-            if (literal.getValueAs(String.class).equals("%")) {
-                return rexBuilder.makeLiteral(true);
+            if ("%".equals(literal.getValueAs(String.class))) {
+                // "x LIKE '%'" simplifies to "x = x"
+                final RexNode x = e.operands.get(0);
+                return simplify(rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, x, x), unknownAs);
             }
         }
         return simplifyGenericNode(e);
@@ -434,9 +489,7 @@ public class RexSimplify {
                             return cmp.ref;
                         case LESS_THAN:
                         case NOT_EQUALS: // x!=true
-                            return simplify(
-                                    rexBuilder.makeCall(SqlStdOperatorTable.NOT, cmp.ref),
-                                    unknownAs);
+                            return simplify(not(cmp.ref), unknownAs);
                         case GREATER_THAN:
                             /* this is false, but could be null if x is null */
                             if (!cmp.ref.getType().isNullable()) {
@@ -449,15 +502,15 @@ public class RexSimplify {
                                 return rexBuilder.makeLiteral(true);
                             }
                             break;
+                        default:
+                            break;
                     }
                 }
                 if (cmp.literal.isAlwaysFalse()) {
                     switch (cmp.kind) {
                         case EQUALS:
                         case LESS_THAN_OR_EQUAL:
-                            return simplify(
-                                    rexBuilder.makeCall(SqlStdOperatorTable.NOT, cmp.ref),
-                                    unknownAs);
+                            return simplify(not(cmp.ref), unknownAs);
                         case NOT_EQUALS:
                         case GREATER_THAN:
                             return cmp.ref;
@@ -473,6 +526,8 @@ public class RexSimplify {
                                 return rexBuilder.makeLiteral(false);
                             }
                             break;
+                        default:
+                            break;
                     }
                 }
             }
@@ -595,10 +650,7 @@ public class RexSimplify {
             doneTerms.set(i);
             final RexNode t2 = simplify.simplify(t, unknownAs);
             terms.set(i, t2);
-            final RexNode inverse =
-                    simplify.simplify(
-                            rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_TRUE, t2),
-                            RexUnknownAs.UNKNOWN);
+            final RexNode inverse = simplify.simplify(isNotTrue(t2), RexUnknownAs.UNKNOWN);
             final RelOptPredicateList newPredicates =
                     simplify.predicates.union(
                             rexBuilder,
@@ -625,71 +677,95 @@ public class RexSimplify {
 
     private RexNode simplifyNot(RexCall call, RexUnknownAs unknownAs) {
         final RexNode a = call.getOperands().get(0);
+        final List<RexNode> newOperands;
         switch (a.getKind()) {
             case NOT:
                 // NOT NOT x ==> x
                 return simplify(((RexCall) a).getOperands().get(0), unknownAs);
+
+            case SEARCH:
+                // NOT SEARCH(x, Sarg[(-inf, 10) OR NULL) ==> SEARCH(x, Sarg[[10, +inf)])
+                final RexCall call2 = (RexCall) a;
+                final RexNode ref = call2.operands.get(0);
+                final RexLiteral literal = (RexLiteral) call2.operands.get(1);
+                final Sarg sarg = literal.getValueAs(Sarg.class);
+                return simplifySearch(
+                        call2.clone(
+                                call2.type,
+                                ImmutableList.of(
+                                        ref,
+                                        rexBuilder.makeLiteral(
+                                                requireNonNull(sarg, "sarg").negate(),
+                                                literal.getType(),
+                                                literal.getTypeName()))),
+                        unknownAs.negate());
+
             case LITERAL:
                 if (a.getType().getSqlTypeName() == SqlTypeName.BOOLEAN
                         && !RexLiteral.isNullLiteral(a)) {
                     return rexBuilder.makeLiteral(!RexLiteral.booleanValue(a));
                 }
-        }
-        final SqlKind negateKind = a.getKind().negate();
-        if (a.getKind() != negateKind) {
-            return simplify(
-                    rexBuilder.makeCall(RexUtil.op(negateKind), ((RexCall) a).getOperands()),
-                    unknownAs);
-        }
-        final SqlKind negateKind2 = a.getKind().negateNullSafe();
-        if (a.getKind() != negateKind2) {
-            return simplify(
-                    rexBuilder.makeCall(RexUtil.op(negateKind2), ((RexCall) a).getOperands()),
-                    unknownAs);
-        }
-        if (a.getKind() == SqlKind.AND) {
-            // NOT distributivity for AND
-            final List<RexNode> newOperands = new ArrayList<>();
-            for (RexNode operand : ((RexCall) a).getOperands()) {
-                newOperands.add(
-                        simplify(rexBuilder.makeCall(SqlStdOperatorTable.NOT, operand), unknownAs));
-            }
-            return simplify(rexBuilder.makeCall(SqlStdOperatorTable.OR, newOperands), unknownAs);
-        }
-        if (a.getKind() == SqlKind.OR) {
-            // NOT distributivity for OR
-            final List<RexNode> newOperands = new ArrayList<>();
-            for (RexNode operand : ((RexCall) a).getOperands()) {
-                newOperands.add(
-                        simplify(rexBuilder.makeCall(SqlStdOperatorTable.NOT, operand), unknownAs));
-            }
-            return simplify(rexBuilder.makeCall(SqlStdOperatorTable.AND, newOperands), unknownAs);
-        }
-        if (a.getKind() == SqlKind.CASE) {
-            final List<RexNode> newOperands = new ArrayList<>();
-            List<RexNode> operands = ((RexCall) a).getOperands();
-            for (int i = 0; i < operands.size(); i += 2) {
-                if (i + 1 == operands.size()) {
-                    newOperands.add(rexBuilder.makeCall(SqlStdOperatorTable.NOT, operands.get(i)));
-                } else {
-                    newOperands.add(operands.get(i));
-                    newOperands.add(
-                            rexBuilder.makeCall(SqlStdOperatorTable.NOT, operands.get(i + 1)));
+                break;
+
+            case AND:
+                // NOT distributivity for AND
+                newOperands = new ArrayList<>();
+                for (RexNode operand : ((RexCall) a).getOperands()) {
+                    newOperands.add(simplify(not(operand), unknownAs));
+                }
+                return simplify(
+                        rexBuilder.makeCall(SqlStdOperatorTable.OR, newOperands), unknownAs);
+
+            case OR:
+                // NOT distributivity for OR
+                newOperands = new ArrayList<>();
+                for (RexNode operand : ((RexCall) a).getOperands()) {
+                    newOperands.add(simplify(not(operand), unknownAs));
+                }
+                return simplify(
+                        rexBuilder.makeCall(SqlStdOperatorTable.AND, newOperands), unknownAs);
+
+            case CASE:
+                newOperands = new ArrayList<>();
+                List<RexNode> operands = ((RexCall) a).getOperands();
+                for (int i = 0; i < operands.size(); i += 2) {
+                    if (i + 1 == operands.size()) {
+                        newOperands.add(not(operands.get(i)));
+                    } else {
+                        newOperands.add(operands.get(i));
+                        newOperands.add(not(operands.get(i + 1)));
+                    }
+                }
+                return simplify(
+                        rexBuilder.makeCall(SqlStdOperatorTable.CASE, newOperands), unknownAs);
+
+            case IN:
+            case NOT_IN:
+                // do not try to negate
+                break;
+
+            default:
+                final SqlKind negateKind = a.getKind().negate();
+                if (a.getKind() != negateKind) {
+                    return simplify(
+                            rexBuilder.makeCall(
+                                    RexUtil.op(negateKind), ((RexCall) a).getOperands()),
+                            unknownAs);
+                }
+                final SqlKind negateKind2 = a.getKind().negateNullSafe();
+                if (a.getKind() != negateKind2) {
+                    return simplify(
+                            rexBuilder.makeCall(
+                                    RexUtil.op(negateKind2), ((RexCall) a).getOperands()),
+                            unknownAs);
                 }
-            }
-            return simplify(rexBuilder.makeCall(SqlStdOperatorTable.CASE, newOperands), unknownAs);
         }
+
         RexNode a2 = simplify(a, unknownAs.negate());
         if (a == a2) {
             return call;
         }
-        if (a2.isAlwaysTrue()) {
-            return rexBuilder.makeLiteral(false);
-        }
-        if (a2.isAlwaysFalse()) {
-            return rexBuilder.makeLiteral(true);
-        }
-        return rexBuilder.makeCall(SqlStdOperatorTable.NOT, a2);
+        return not(a2);
     }
 
     private RexNode simplifyUnaryMinus(RexCall call, RexUnknownAs unknownAs) {
@@ -705,14 +781,14 @@ public class RexSimplify {
         return simplify(call.getOperands().get(0), unknownAs);
     }
 
-    private @Nonnull RexNode simplifyIs(RexCall call, RexUnknownAs unknownAs) {
+    private RexNode simplifyIs(RexCall call, RexUnknownAs unknownAs) {
         final SqlKind kind = call.getKind();
         final RexNode a = call.getOperands().get(0);
         final RexNode simplified = simplifyIs1(kind, a, unknownAs);
         return simplified == null ? call : simplified;
     }
 
-    private RexNode simplifyIs1(SqlKind kind, RexNode a, RexUnknownAs unknownAs) {
+    private @Nullable RexNode simplifyIs1(SqlKind kind, RexNode a, RexUnknownAs unknownAs) {
         // UnknownAs.FALSE corresponds to x IS TRUE evaluation
         // UnknownAs.TRUE to x IS NOT FALSE
         // Note that both UnknownAs.TRUE and UnknownAs.FALSE only changes the meaning of Unknown
@@ -725,13 +801,13 @@ public class RexSimplify {
             return simplify(a, unknownAs);
         }
         if (kind == SqlKind.IS_FALSE && unknownAs == RexUnknownAs.FALSE) {
-            return simplify(rexBuilder.makeCall(SqlStdOperatorTable.NOT, a), unknownAs);
+            return simplify(not(a), unknownAs);
         }
         if (kind == SqlKind.IS_NOT_FALSE && unknownAs == RexUnknownAs.TRUE) {
             return simplify(a, unknownAs);
         }
         if (kind == SqlKind.IS_NOT_TRUE && unknownAs == RexUnknownAs.TRUE) {
-            return simplify(rexBuilder.makeCall(SqlStdOperatorTable.NOT, a), unknownAs);
+            return simplify(not(a), unknownAs);
         }
         final RexNode pred = simplifyIsPredicate(kind, a);
         if (pred != null) {
@@ -741,7 +817,7 @@ public class RexSimplify {
         return simplifyIs2(kind, a, unknownAs);
     }
 
-    private RexNode simplifyIsPredicate(SqlKind kind, RexNode a) {
+    private @Nullable RexNode simplifyIsPredicate(SqlKind kind, RexNode a) {
         if (!RexUtil.isReferenceOrAccess(a, true)) {
             return null;
         }
@@ -758,7 +834,7 @@ public class RexSimplify {
         return null;
     }
 
-    private RexNode simplifyIs2(SqlKind kind, RexNode a, RexUnknownAs unknownAs) {
+    private @Nullable RexNode simplifyIs2(SqlKind kind, RexNode a, RexUnknownAs unknownAs) {
         final RexNode simplified;
         switch (kind) {
             case IS_NULL:
@@ -777,32 +853,40 @@ public class RexSimplify {
                     return simplified;
                 }
                 break;
+
             case IS_TRUE:
-            case IS_NOT_FALSE:
                 // x IS TRUE ==> x (if x is not nullable)
+                if (predicates.isEffectivelyNotNull(a)) {
+                    return simplify(a, unknownAs);
+                }
+                simplified = simplify(a, RexUnknownAs.FALSE);
+                if (simplified == a) {
+                    return null;
+                }
+                return isTrue(simplified);
+
+            case IS_NOT_FALSE:
                 // x IS NOT FALSE ==> x (if x is not nullable)
                 if (predicates.isEffectivelyNotNull(a)) {
                     return simplify(a, unknownAs);
-                } else {
-                    RexNode newSub =
-                            simplify(
-                                    a,
-                                    kind == SqlKind.IS_TRUE
-                                            ? RexUnknownAs.FALSE
-                                            : RexUnknownAs.TRUE);
-                    if (newSub == a) {
-                        return null;
-                    }
-                    return rexBuilder.makeCall(RexUtil.op(kind), ImmutableList.of(newSub));
                 }
+                simplified = simplify(a, RexUnknownAs.TRUE);
+                if (simplified == a) {
+                    return null;
+                }
+                return isNotFalse(simplified);
+
             case IS_FALSE:
             case IS_NOT_TRUE:
                 // x IS NOT TRUE ==> NOT x (if x is not nullable)
                 // x IS FALSE ==> NOT x (if x is not nullable)
                 if (predicates.isEffectivelyNotNull(a)) {
-                    return simplify(rexBuilder.makeCall(SqlStdOperatorTable.NOT, a), unknownAs);
+                    return simplify(not(a), unknownAs);
                 }
                 break;
+
+            default:
+                break;
         }
         switch (a.getKind()) {
             case NOT:
@@ -815,6 +899,8 @@ public class RexSimplify {
                 final SqlOperator notKind = RexUtil.op(kind.negateNullSafe());
                 final RexNode arg = ((RexCall) a).operands.get(0);
                 return simplify(rexBuilder.makeCall(notKind, arg), UNKNOWN);
+            default:
+                break;
         }
         final RexNode a2 = simplify(a, UNKNOWN);
         if (a != a2) {
@@ -823,7 +909,7 @@ public class RexSimplify {
         return null; // cannot be simplified
     }
 
-    private RexNode simplifyIsNotNull(RexNode a) {
+    private @Nullable RexNode simplifyIsNotNull(RexNode a) {
         // Simplify the argument first,
         // call ourselves recursively to see whether we can make more progress.
         // For example, given
@@ -871,7 +957,7 @@ public class RexSimplify {
         }
     }
 
-    private RexNode simplifyIsNull(RexNode a) {
+    private @Nullable RexNode simplifyIsNull(RexNode a) {
         // Simplify the argument first,
         // call ourselves recursively to see whether we can make more progress.
         // For example, given
@@ -915,7 +1001,7 @@ public class RexSimplify {
      * @param rexNode Rex node to validate the strong policy
      * @throws AssertionError If the validation fails
      */
-    private void validateStrongPolicy(RexNode rexNode) {
+    private static void validateStrongPolicy(RexNode rexNode) {
         if (hasCustomNullabilityRules(rexNode.getKind())) {
             return;
         }
@@ -934,6 +1020,9 @@ public class RexSimplify {
                             .map(RexNode::getType)
                             .noneMatch(RelDataType::isNullable);
                 }
+                break;
+            default:
+                break;
         }
     }
 
@@ -947,7 +1036,7 @@ public class RexSimplify {
      * @param sqlKind Sql kind to check
      * @return {@code true} if specified {@link SqlKind} has custom nullability rules
      */
-    private boolean hasCustomNullabilityRules(SqlKind sqlKind) {
+    private static boolean hasCustomNullabilityRules(SqlKind sqlKind) {
         switch (sqlKind) {
             case CAST:
             case ITEM:
@@ -1085,7 +1174,7 @@ public class RexSimplify {
      * If boolean is true, simplify cond in input branch and return new branch. Otherwise, simply
      * return input branch.
      */
-    private CaseBranch generateBranch(
+    private static CaseBranch generateBranch(
             boolean simplifyCond, RexSimplify simplifier, CaseBranch branch) {
         if (simplifyCond) {
             // the previous branch was merged, time to simplify it and
@@ -1148,6 +1237,7 @@ public class RexSimplify {
     enum SafeRexVisitor implements RexVisitor<Boolean> {
         INSTANCE;
 
+        @SuppressWarnings("ImmutableEnumChecker")
         private final Set<SqlKind> safeOps;
 
         SafeRexVisitor() {
@@ -1264,29 +1354,24 @@ public class RexSimplify {
         return r.accept(SafeRexVisitor.INSTANCE);
     }
 
-    private static RexNode simplifyBooleanCase(
+    private @Nullable RexNode simplifyBooleanCase(
             RexBuilder rexBuilder,
             List<CaseBranch> inputBranches,
-            RexUnknownAs unknownAs,
+            @SuppressWarnings("unused") RexUnknownAs unknownAs,
             RelDataType branchType) {
         RexNode result;
 
         // prepare all condition/branches for boolean interpretation
         // It's done here make these interpretation changes available to case2or simplifications
-        // but not interfere with the normal simplifcation recursion
+        // but not interfere with the normal simplification recursion
         List<CaseBranch> branches = new ArrayList<>();
         for (CaseBranch branch : inputBranches) {
             if ((branches.size() > 0 && !isSafeExpression(branch.cond))
                     || !isSafeExpression(branch.value)) {
                 return null;
             }
-            RexNode cond;
-            RexNode value;
-            if (branch.cond.getType().isNullable()) {
-                cond = rexBuilder.makeCall(SqlStdOperatorTable.IS_TRUE, branch.cond);
-            } else {
-                cond = branch.cond;
-            }
+            final RexNode cond = isTrue(branch.cond);
+            final RexNode value;
             if (!branchType.equals(branch.value.getType())) {
                 value = rexBuilder.makeAbstractCast(branchType, branch.value);
             } else {
@@ -1371,7 +1456,7 @@ public class RexSimplify {
         operands.forEach(t -> sargCollector.accept(t, terms));
         if (sargCollector.needToFix()) {
             operands.clear();
-            terms.forEach(t -> operands.add(sargCollector.fix(rexBuilder, t)));
+            terms.forEach(t -> operands.add(SargCollector.fix(rexBuilder, t, unknownAs)));
         }
         terms.clear();
 
@@ -1382,6 +1467,8 @@ public class RexSimplify {
         switch (unknownAs) {
             case FALSE:
                 return simplifyAnd2ForUnknownAsFalse(terms, notTerms, Comparable.class);
+            default:
+                break;
         }
         return simplifyAnd2(terms, notTerms);
     }
@@ -1442,9 +1529,7 @@ public class RexSimplify {
         }
         // Add the NOT disjunctions back in.
         for (RexNode notDisjunction : notTerms) {
-            terms.add(
-                    simplify(
-                            rexBuilder.makeCall(SqlStdOperatorTable.NOT, notDisjunction), UNKNOWN));
+            terms.add(simplify(not(notDisjunction), UNKNOWN));
         }
         return RexUtil.composeConjunction(rexBuilder, terms);
     }
@@ -1592,6 +1677,9 @@ public class RexSimplify {
                     if (comparison != null
                             && comparison.kind != SqlKind.NOT_EQUALS) { // not supported yet
                         final C constant = comparison.literal.getValueAs(clazz);
+                        if (constant == null) {
+                            break;
+                        }
                         final RexNode result =
                                 processRange(
                                         rexBuilder,
@@ -1620,6 +1708,9 @@ public class RexSimplify {
                     break;
                 case IS_NULL:
                     nullOperands.add(((RexCall) term).getOperands().get(0));
+                    break;
+                default:
+                    break;
             }
         }
         // If one column should be null and is in a comparison predicate,
@@ -1679,7 +1770,7 @@ public class RexSimplify {
         }
         // Add the NOT disjunctions back in.
         for (RexNode notDisjunction : notTerms) {
-            terms.add(rexBuilder.makeCall(SqlStdOperatorTable.NOT, notDisjunction));
+            terms.add(not(notDisjunction));
         }
         // The negated terms: only deterministic expressions
         for (RexNode negatedTerm : negatedTerms) {
@@ -1690,6 +1781,7 @@ public class RexSimplify {
         return RexUtil.composeConjunction(rexBuilder, terms);
     }
 
+    @SuppressWarnings("BetaApi")
     private <C extends Comparable<C>> RexNode simplifyUsingPredicates(RexNode e, Class<C> clazz) {
         if (predicates.pulledUpPredicates.isEmpty()) {
             return e;
@@ -1702,6 +1794,9 @@ public class RexSimplify {
         }
 
         final C v0 = comparison.literal.getValueAs(clazz);
+        if (v0 == null) {
+            return e;
+        }
         final RangeSet<C> rangeSet = rangeSet(comparison.kind, v0);
         final RangeSet<C> rangeSet2 =
                 residue(comparison.ref, rangeSet, predicates.pulledUpPredicates, clazz);
@@ -1750,7 +1845,8 @@ public class RexSimplify {
      *   <li>{@code residue($0 < 10, [$0 < 20, $0 > 0])} returns {@code $0 < 10}
      * </ul>
      */
-    private <C extends Comparable<C>> RangeSet<C> residue(
+    @SuppressWarnings("BetaApi")
+    private static <C extends Comparable<C>> RangeSet<C> residue(
             RexNode ref, RangeSet<C> r0, List<RexNode> predicates, Class<C> clazz) {
         RangeSet<C> result = r0;
         for (RexNode predicate : predicates) {
@@ -1766,6 +1862,7 @@ public class RexSimplify {
                             && call.operands.get(1) instanceof RexLiteral) {
                         final RexLiteral literal = (RexLiteral) call.operands.get(1);
                         final C c1 = literal.getValueAs(clazz);
+                        assert c1 != null : "value must not be null in " + literal;
                         switch (predicate.getKind()) {
                             case NOT_EQUALS:
                                 // We want to intersect result with the range set of everything but
@@ -1793,6 +1890,9 @@ public class RexSimplify {
                             break; // short-cut
                         }
                     }
+                    break;
+                default:
+                    break;
             }
         }
         return result;
@@ -1845,7 +1945,7 @@ public class RexSimplify {
         terms.forEach(t -> sargCollector.accept(t, newTerms));
         if (sargCollector.needToFix()) {
             terms.clear();
-            newTerms.forEach(t -> terms.add(sargCollector.fix(rexBuilder, t)));
+            newTerms.forEach(t -> terms.add(SargCollector.fix(rexBuilder, t, unknownAs)));
         }
 
         // CALCITE-3198 Auxiliary map to simplify cases like:
@@ -1896,9 +1996,13 @@ public class RexSimplify {
                                 final Comparable comparable1 =
                                         notEqualsComparison.literal.getValue();
                                 final Comparable comparable2 =
-                                        Comparison.of(prevNotEquals).literal.getValue();
+                                        castNonNull(Comparison.of(prevNotEquals))
+                                                .literal
+                                                .getValue();
                                 //noinspection unchecked
-                                if (comparable1.compareTo(comparable2) != 0) {
+                                if (comparable1 != null
+                                        && comparable2 != null
+                                        && comparable1.compareTo(comparable2) != 0) {
                                     // X <> A OR X <> B => X IS NOT NULL OR NULL
                                     final RexNode isNotNull =
                                             rexBuilder.makeCall(
@@ -1927,6 +2031,8 @@ public class RexSimplify {
                         }
                     }
                     break;
+                default:
+                    break;
             }
         }
         return RexUtil.composeDisjunction(rexBuilder, terms);
@@ -1962,7 +2068,7 @@ public class RexSimplify {
         for (Map<RexNode, Comparable> map : foo0.assignments()) {
             for (RexNode predicate : predicates.pulledUpPredicates) {
                 final Comparable v = RexInterpreter.evaluate(predicate, map);
-                if (!v.equals(true)) {
+                if (!Boolean.TRUE.equals(v)) {
                     continue assignment_loop;
                 }
             }
@@ -1984,17 +2090,22 @@ public class RexSimplify {
                         if (v1 == NullSentinel.INSTANCE) {
                             v1 = unknownAs.toBoolean();
                         }
+                        break;
+                    default:
+                        break;
                 }
             }
             if (!v0.equals(v1)) {
                 throw new AssertionError(
-                        "result mismatch: when applied to "
+                        "result mismatch (unknown as "
+                                + unknownAs
+                                + "): when applied to "
                                 + map
-                                + ", "
+                                + ",\n"
                                 + before
                                 + " yielded "
                                 + v0
-                                + ", and "
+                                + ";\n"
                                 + simplified
                                 + " yielded "
                                 + v1);
@@ -2007,15 +2118,25 @@ public class RexSimplify {
         final RexNode a = call.getOperands().get(0);
         if (call.getOperands().get(1) instanceof RexLiteral) {
             RexLiteral literal = (RexLiteral) call.getOperands().get(1);
-            final Sarg sarg = literal.getValueAs(Sarg.class);
-            if (sarg.containsNull) {
+            final Sarg sarg = castNonNull(literal.getValueAs(Sarg.class));
+            if (sarg.isAll() || sarg.isNone()) {
+                return RexUtil.simpleSarg(rexBuilder, a, sarg, unknownAs);
+            }
+            // Remove null from sarg if the left-hand side is never null
+            if (sarg.nullAs != UNKNOWN) {
                 final RexNode simplified = simplifyIs1(SqlKind.IS_NULL, a, unknownAs);
                 if (simplified != null && simplified.isAlwaysFalse()) {
-                    final Sarg sarg2 = Sarg.of(false, sarg.rangeSet);
+                    final Sarg sarg2 = Sarg.of(UNKNOWN, sarg.rangeSet);
                     final RexLiteral literal2 =
                             rexBuilder.makeLiteral(sarg2, literal.getType(), literal.getTypeName());
-                    return call.clone(call.type, ImmutableList.of(a, literal2));
+                    // Now we've strengthened the Sarg, try to simplify again
+                    return simplifySearch(
+                            call.clone(call.type, ImmutableList.of(a, literal2)), unknownAs);
                 }
+            } else if (sarg.isPoints() && sarg.pointCount <= 1) {
+                // Expand "SEARCH(x, Sarg([point])" to "x = point"
+                // and "SEARCH(x, Sarg([])" to "false"
+                return RexUtil.expandSearch(rexBuilder, null, call);
             }
         }
         return call;
@@ -2093,13 +2214,17 @@ public class RexSimplify {
                         switch (e.getType().getSqlTypeName()) {
                             case TIMESTAMP:
                                 return e;
+                            default:
+                                break;
                         }
                         break;
+                    default:
+                        break;
                 }
                 final List<RexNode> reducedValues = new ArrayList<>();
                 final RexNode simplifiedExpr = rexBuilder.makeCast(e.getType(), operand);
                 executor.reduce(rexBuilder, ImmutableList.of(simplifiedExpr), reducedValues);
-                return Objects.requireNonNull(Iterables.getOnlyElement(reducedValues));
+                return requireNonNull(Iterables.getOnlyElement(reducedValues));
             default:
                 if (operand == e.getOperands().get(0)) {
                     return e;
@@ -2175,6 +2300,8 @@ public class RexSimplify {
                             return outer == TimeUnit.YEAR;
                         }
                         return outer.ordinal() <= inner.ordinal();
+                    default:
+                        break;
                 }
                 break;
             case QUARTER:
@@ -2188,7 +2315,12 @@ public class RexSimplify {
                     case MILLISECOND:
                     case MICROSECOND:
                         return true;
+                    default:
+                        break;
                 }
+                break;
+            default:
+                break;
         }
         return false;
     }
@@ -2202,7 +2334,7 @@ public class RexSimplify {
         return RexUtil.removeNullabilityCast(rexBuilder.getTypeFactory(), e);
     }
 
-    private static <C extends Comparable<C>> RexNode processRange(
+    private static <C extends Comparable<C>> @Nullable RexNode processRange(
             RexBuilder rexBuilder,
             List<RexNode> terms,
             Map<RexNode, Pair<Range<C>, List<RexNode>>> rangeTerms,
@@ -2437,6 +2569,7 @@ public class RexSimplify {
         }
     }
 
+    @SuppressWarnings("BetaApi")
     private static <C extends Comparable<C>> RangeSet<C> rangeSet(SqlKind comparison, C c) {
         switch (comparison) {
             case EQUALS:
@@ -2458,7 +2591,7 @@ public class RexSimplify {
     /** Marker interface for predicates (expressions that evaluate to BOOLEAN). */
     private interface Predicate {
         /** Wraps an expression in a Predicate or returns null. */
-        static Predicate of(RexNode t) {
+        static @Nullable Predicate of(RexNode t) {
             final Predicate p = Comparison.of(t);
             if (p != null) {
                 return p;
@@ -2483,16 +2616,16 @@ public class RexSimplify {
         final RexLiteral literal;
 
         private Comparison(RexNode ref, SqlKind kind, RexLiteral literal) {
-            this.ref = Objects.requireNonNull(ref);
-            this.kind = Objects.requireNonNull(kind);
-            this.literal = Objects.requireNonNull(literal);
+            this.ref = requireNonNull(ref, "ref");
+            this.kind = requireNonNull(kind, "kind");
+            this.literal = requireNonNull(literal, "literal");
         }
 
         /**
          * Creates a comparison, between a {@link RexInputRef} or {@link RexFieldAccess} or
          * deterministic {@link RexCall} and a literal.
          */
-        static Comparison of(RexNode e) {
+        static @Nullable Comparison of(RexNode e) {
             return of(
                     e,
                     node ->
@@ -2501,7 +2634,8 @@ public class RexSimplify {
         }
 
         /** Creates a comparison, or returns null. */
-        static Comparison of(RexNode e, java.util.function.Predicate<RexNode> nodePredicate) {
+        static @Nullable Comparison of(
+                RexNode e, java.util.function.Predicate<RexNode> nodePredicate) {
             switch (e.getKind()) {
                 case EQUALS:
                 case NOT_EQUALS:
@@ -2517,6 +2651,9 @@ public class RexSimplify {
                             if (nodePredicate.test(left)) {
                                 return new Comparison(left, e.getKind(), (RexLiteral) right);
                             }
+                            break;
+                        default:
+                            break;
                     }
                     switch (left.getKind()) {
                         case LITERAL:
@@ -2524,7 +2661,13 @@ public class RexSimplify {
                                 return new Comparison(
                                         right, e.getKind().reverse(), (RexLiteral) left);
                             }
+                            break;
+                        default:
+                            break;
                     }
+                    break;
+                default:
+                    break;
             }
             return null;
         }
@@ -2541,12 +2684,12 @@ public class RexSimplify {
         final SqlKind kind;
 
         private IsPredicate(RexNode ref, SqlKind kind) {
-            this.ref = Objects.requireNonNull(ref);
-            this.kind = Objects.requireNonNull(kind);
+            this.ref = requireNonNull(ref, "ref");
+            this.kind = requireNonNull(kind, "kind");
         }
 
         /** Creates an IS predicate, or returns null. */
-        static IsPredicate of(RexNode e) {
+        static @Nullable IsPredicate of(RexNode e) {
             switch (e.getKind()) {
                 case IS_NULL:
                 case IS_NOT_NULL:
@@ -2555,6 +2698,8 @@ public class RexSimplify {
                         return null;
                     }
                     return new IsPredicate(pA, e.getKind());
+                default:
+                    break;
             }
             return null;
         }
@@ -2606,7 +2751,7 @@ public class RexSimplify {
      * @param predicates Filter condition predicates
      * @return simplified conjunction of predicates for the filter, null if always false
      */
-    public RexNode simplifyFilterPredicates(Iterable<? extends RexNode> predicates) {
+    public @Nullable RexNode simplifyFilterPredicates(Iterable<? extends RexNode> predicates) {
         final RexNode simplifiedAnds =
                 withPredicateElimination(Bug.CALCITE_2401_FIXED)
                         .simplifyUnknownAsFalse(RexUtil.composeConjunction(rexBuilder, predicates));
@@ -2628,6 +2773,7 @@ public class RexSimplify {
      * <p>Returns whether the value was found.
      */
     private static <E> boolean replaceLast(List<E> list, E oldVal, E newVal) {
+        @SuppressWarnings("argument.type.incompatible")
         final int index = list.lastIndexOf(oldVal);
         if (index < 0) {
             return false;
@@ -2641,7 +2787,6 @@ public class RexSimplify {
         final Map<RexNode, RexSargBuilder> map = new HashMap<>();
         private final RexBuilder rexBuilder;
         private final boolean negate;
-
         /**
          * Count of the new terms after converting all the operands to {@code SEARCH} on a {@link
          * Sarg}. It is used to decide whether the new terms are simpler.
@@ -2674,9 +2819,12 @@ public class RexSimplify {
                             ((RexCall) e).operands.get(1),
                             e.getKind(),
                             newTerms);
-                    // CHANGED: we remove IS_NULL here
-                    // because SEARCH operator in Calcite 1.26 handles UNKNOWNs incorrectly
-                    // see CALCITE-4446
+                    /* ----- FLINK MODIFICATION BEGIN -----
+                    case IS_NULL:
+                    case IS_NOT_NULL:
+                        final RexNode arg = ((RexCall) e).operands.get(0);
+                        return accept1(arg, e.getKind(), newTerms);
+                          ----- FLINK MODIFICATION END ----- */
                 default:
                     return false;
             }
@@ -2688,16 +2836,22 @@ public class RexSimplify {
                 case FIELD_ACCESS:
                     switch (right.getKind()) {
                         case LITERAL:
-                            return accept1(left, kind, (RexLiteral) right, newTerms);
+                            return accept2b(left, kind, (RexLiteral) right, newTerms);
+                        default:
+                            break;
                     }
                     return false;
                 case LITERAL:
                     switch (right.getKind()) {
                         case INPUT_REF:
                         case FIELD_ACCESS:
-                            return accept1(right, kind.reverse(), (RexLiteral) left, newTerms);
+                            return accept2b(right, kind.reverse(), (RexLiteral) left, newTerms);
+                        default:
+                            break;
                     }
                     return false;
+                default:
+                    break;
             }
             return false;
         }
@@ -2708,8 +2862,32 @@ public class RexSimplify {
         }
 
         // always returns true
-        private boolean accept1(
-                RexNode e, SqlKind kind, @Nonnull RexLiteral literal, List<RexNode> newTerms) {
+        private boolean accept1(RexNode e, SqlKind kind, List<RexNode> newTerms) {
+            final RexSargBuilder b =
+                    map.computeIfAbsent(
+                            e,
+                            e2 -> addFluent(newTerms, new RexSargBuilder(e2, rexBuilder, negate)));
+            switch (negate ? kind.negate() : kind) {
+                case IS_NULL:
+                    b.nullAs = b.nullAs.or(TRUE);
+                    return true;
+                case IS_NOT_NULL:
+                    b.nullAs = b.nullAs.or(FALSE);
+                    b.addAll();
+                    return true;
+                default:
+                    throw new AssertionError("unexpected " + kind);
+            }
+        }
+
+        private boolean accept2b(
+                RexNode e, SqlKind kind, RexLiteral literal, List<RexNode> newTerms) {
+            if (literal.getValue() == null) {
+                // Cannot include expressions 'x > NULL' in a Sarg. Comparing to a NULL
+                // literal is a daft thing to do, because it always returns UNKNOWN. It
+                // is better handled by other simplifications.
+                return false;
+            }
             final RexSargBuilder b =
                     map.computeIfAbsent(
                             e,
@@ -2717,7 +2895,7 @@ public class RexSimplify {
             if (negate) {
                 kind = kind.negateNullSafe();
             }
-            final Comparable value = literal.getValueAs(Comparable.class);
+            final Comparable value = requireNonNull(literal.getValueAs(Comparable.class), "value");
             switch (kind) {
                 case LESS_THAN:
                     b.addRange(Range.lessThan(value), literal.getType());
@@ -2739,12 +2917,9 @@ public class RexSimplify {
                     b.addRange(Range.greaterThan(value), literal.getType());
                     return true;
                 case SEARCH:
-                    final Sarg sarg = literal.getValueAs(Sarg.class);
+                    final Sarg sarg = (Sarg) value;
                     b.addSarg(sarg, negate, literal.getType());
                     return true;
-                    // CHANGED: we remove IS_NULL here
-                    // because SEARCH operator in Calcite 1.26 handles UNKNOWNs incorrectly
-                    // see CALCITE-4446
                 default:
                     throw new AssertionError("unexpected " + kind);
             }
@@ -2754,35 +2929,43 @@ public class RexSimplify {
         boolean needToFix() {
             // Fix and converts to SEARCH if:
             // 1. A Sarg has complexity greater than 1;
-            // 2. The terms are reduced as simpler Sarg points.
+            // 2. The terms are reduced as simpler Sarg points;
+            // 3. The terms are reduced as simpler Sarg comparison.
 
-            // Initialize "allPoints" with "newTermsCount == 1"
-            // because "newTermsCount == 1" is a precondition.
-            boolean allPoints = newTermsCount == 1;
-            for (RexSargBuilder builder : map.values()) {
-                if (builder.complexity() > 1) {
-                    return true;
-                }
-                if (allPoints) {
-                    Sarg sarg = builder.build(negate);
-                    allPoints = sarg.isPoints();
-                }
-            }
-            return allPoints;
+            // Ignore 'negate' just to be compatible with previous versions of this
+            // method. "build().complexity()" would be a better estimate, if we could
+            // switch to it breaking lots of plans.
+            final Collection<RexSargBuilder> builders = map.values();
+            return builders.stream().anyMatch(b -> b.build(false).complexity() > 1)
+                    || newTermsCount == 1 && builders.stream().allMatch(b -> simpleSarg(b.build()));
+        }
+
+        /**
+         * Returns whether this Sarg can be expanded to more simple form, e.g. the IN call or single
+         * comparison.
+         */
+        private static boolean simpleSarg(Sarg sarg) {
+            return sarg.isPoints() || RangeSets.isOpenInterval(sarg.rangeSet);
         }
 
         /**
          * If a term is a call to {@code SEARCH} on a {@link RexSargBuilder}, converts it to a
          * {@code SEARCH} on a {@link Sarg}.
          */
-        RexNode fix(RexBuilder rexBuilder, RexNode term) {
+        static RexNode fix(RexBuilder rexBuilder, RexNode term, RexUnknownAs unknownAs) {
             if (term instanceof RexSargBuilder) {
-                RexSargBuilder sargBuilder = (RexSargBuilder) term;
+                final RexSargBuilder sargBuilder = (RexSargBuilder) term;
+                final Sarg sarg = sargBuilder.build();
+                if (sarg.complexity() <= 1 && simpleSarg(sarg)) {
+                    // Expand small sargs into comparisons in order to avoid plan changes
+                    // and better readability.
+                    return RexUtil.sargRef(
+                            rexBuilder, sargBuilder.ref, sarg, term.getType(), unknownAs);
+                }
                 return rexBuilder.makeCall(
                         SqlStdOperatorTable.SEARCH,
                         sargBuilder.ref,
-                        rexBuilder.makeSearchArgumentLiteral(
-                                sargBuilder.build(negate), term.getType()));
+                        rexBuilder.makeSearchArgumentLiteral(sarg, term.getType()));
             }
             return term;
         }
@@ -2793,55 +2976,60 @@ public class RexSimplify {
      * Sarg can be expanded as {@link SargCollector} traverses a list of OR or AND terms.
      *
      * <p>The {@link SargCollector#fix} method converts it to an immutable literal.
+     *
+     * <p>The {@link #nullAs} field will become {@link Sarg#nullAs}, as follows:
+     *
+     * <ul>
+     *   <li>If there is at least one term that returns TRUE when the argument is NULL, then the
+     *       overall value will be TRUE; failing that,
+     *   <li>if there is at least one term that returns UNKNOWN when the argument is NULL, then the
+     *       overall value will be UNKNOWN; failing that,
+     *   <li>the value will be FALSE.
+     * </ul>
+     *
+     * <p>This is analogous to the behavior of OR in three-valued logic: {@code TRUE OR UNKNOWN OR
+     * FALSE} returns {@code TRUE}; {@code UNKNOWN OR FALSE OR UNKNOWN} returns {@code UNKNOWN};
+     * {@code FALSE OR FALSE} returns {@code FALSE}.
      */
-    static class RexSargBuilder extends RexNode {
+    @SuppressWarnings("BetaApi")
+    private static class RexSargBuilder extends RexNode {
         final RexNode ref;
-        private final RexBuilder rexBuilder;
-        private List<RelDataType> types = new ArrayList<>();
+        final RexBuilder rexBuilder;
+        final boolean negate;
+        final List<RelDataType> types = new ArrayList<>();
         final RangeSet<Comparable> rangeSet = TreeRangeSet.create();
-        boolean containsNull;
+        RexUnknownAs nullAs = FALSE;
 
         RexSargBuilder(RexNode ref, RexBuilder rexBuilder, boolean negate) {
-            this.ref = Objects.requireNonNull(ref);
-            this.rexBuilder = Objects.requireNonNull(rexBuilder);
-            this.containsNull = false;
+            this.ref = requireNonNull(ref, "ref");
+            this.rexBuilder = requireNonNull(rexBuilder, "rexBuilder");
+            this.negate = negate;
         }
 
         @Override
         public String toString() {
-            return "SEARCH(" + ref + ", " + rangeSet + (containsNull ? " + null)" : ")");
+            return "SEARCH("
+                    + ref
+                    + ", "
+                    + (negate ? "NOT " : "")
+                    + rangeSet
+                    + "; NULL AS "
+                    + nullAs
+                    + ")";
         }
 
-        /**
-         * Returns a rough estimate of whether it is worth converting to a Sarg.
-         *
-         * <p>Examples:
-         *
-         * <ul>
-         *   <li>{@code x = 1}, {@code x <> 1}, {@code x > 1} have complexity 1
-         *   <li>{@code x > 1 or x is null} has complexity 2
-         *   <li>{@code x in (2, 4, 6) or x > 20} has complexity 4
-         * </ul>
-         */
-        int complexity() {
-            int complexity = 0;
-            if (rangeSet.asRanges().size() == 2
-                    && rangeSet.complement().asRanges().size() == 1
-                    && RangeSets.isPoint(
-                            Iterables.getOnlyElement(rangeSet.complement().asRanges()))) {
-                complexity++;
-            } else {
-                complexity += rangeSet.asRanges().size();
-            }
-            if (containsNull) {
-                ++complexity;
-            }
-            return complexity;
+        <C extends Comparable<C>> Sarg<C> build() {
+            return build(negate);
         }
 
+        @SuppressWarnings({"rawtypes", "unchecked", "UnstableApiUsage"})
         <C extends Comparable<C>> Sarg<C> build(boolean negate) {
-            final RangeSet rangeSet = negate ? this.rangeSet.complement() : this.rangeSet;
-            return Sarg.of(containsNull, rangeSet);
+            final RangeSet<C> r = (RangeSet) this.rangeSet;
+            if (negate) {
+                return Sarg.of(nullAs.negate(), r.complement());
+            } else {
+                return Sarg.of(nullAs, r);
+            }
         }
 
         @Override
@@ -2851,7 +3039,7 @@ public class RexSimplify {
                 return ref.getType();
             }
             final List<RelDataType> distinctTypes = Util.distinctList(this.types);
-            return Objects.requireNonNull(
+            return requireNonNull(
                     rexBuilder.typeFactory.leastRestrictive(distinctTypes),
                     () -> "Can't find leastRestrictive type among " + distinctTypes);
         }
@@ -2867,7 +3055,7 @@ public class RexSimplify {
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             throw new UnsupportedOperationException();
         }
 
@@ -2876,15 +3064,40 @@ public class RexSimplify {
             throw new UnsupportedOperationException();
         }
 
+        void addAll() {
+            rangeSet.add(Range.all());
+        }
+
         void addRange(Range<Comparable> range, RelDataType type) {
             types.add(type);
             rangeSet.add(range);
+            nullAs = nullAs.or(UNKNOWN);
         }
 
+        @SuppressWarnings({"UnstableApiUsage", "rawtypes", "unchecked"})
         void addSarg(Sarg sarg, boolean negate, RelDataType type) {
+            final RangeSet r;
+            final RexUnknownAs nullAs;
+            if (negate) {
+                r = sarg.rangeSet.complement();
+                nullAs = sarg.nullAs.negate();
+            } else {
+                r = sarg.rangeSet;
+                nullAs = sarg.nullAs;
+            }
             types.add(type);
-            rangeSet.addAll(negate ? sarg.rangeSet.complement() : sarg.rangeSet);
-            containsNull |= sarg.containsNull;
+            rangeSet.addAll(r);
+            switch (nullAs) {
+                case TRUE:
+                    this.nullAs = this.nullAs.or(TRUE);
+                    break;
+                case FALSE:
+                    this.nullAs = this.nullAs.or(FALSE);
+                    break;
+                case UNKNOWN:
+                    this.nullAs = this.nullAs.or(UNKNOWN);
+                    break;
+            }
         }
     }
 }
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/SqlMatchRecognize.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/SqlMatchRecognize.java
deleted file mode 100644
index e20675b0d93..00000000000
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/SqlMatchRecognize.java
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * 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.sql;
-
-import com.google.common.base.Preconditions;
-import org.apache.calcite.sql.parser.SqlParserPos;
-import org.apache.calcite.sql.util.SqlBasicVisitor;
-import org.apache.calcite.sql.util.SqlVisitor;
-import org.apache.calcite.sql.validate.SqlValidator;
-import org.apache.calcite.sql.validate.SqlValidatorScope;
-import org.apache.calcite.util.ImmutableNullableList;
-
-import javax.annotation.Nonnull;
-
-import java.util.List;
-import java.util.Objects;
-
-/**
- * SqlNode for MATCH_RECOGNIZE clause.
- *
- * <p>The class was copied over because of CALCITE-4390. The changed lines are: 110-112
- */
-public class SqlMatchRecognize extends SqlCall {
-    public static final int OPERAND_TABLE_REF = 0;
-    public static final int OPERAND_PATTERN = 1;
-    public static final int OPERAND_STRICT_START = 2;
-    public static final int OPERAND_STRICT_END = 3;
-    public static final int OPERAND_PATTERN_DEFINES = 4;
-    public static final int OPERAND_MEASURES = 5;
-    public static final int OPERAND_AFTER = 6;
-    public static final int OPERAND_SUBSET = 7;
-    public static final int OPERAND_ROWS_PER_MATCH = 8;
-    public static final int OPERAND_PARTITION_BY = 9;
-    public static final int OPERAND_ORDER_BY = 10;
-    public static final int OPERAND_INTERVAL = 11;
-
-    public static final SqlPrefixOperator SKIP_TO_FIRST =
-            new SqlPrefixOperator("SKIP TO FIRST", SqlKind.SKIP_TO_FIRST, 20, null, null, null);
-
-    public static final SqlPrefixOperator SKIP_TO_LAST =
-            new SqlPrefixOperator("SKIP TO LAST", SqlKind.SKIP_TO_LAST, 20, null, null, null);
-
-    // ~ Instance fields -------------------------------------------
-
-    private SqlNode tableRef;
-    private SqlNode pattern;
-    private SqlLiteral strictStart;
-    private SqlLiteral strictEnd;
-    private SqlNodeList patternDefList;
-    private SqlNodeList measureList;
-    private SqlNode after;
-    private SqlNodeList subsetList;
-    private SqlLiteral rowsPerMatch;
-    private SqlNodeList partitionList;
-    private SqlNodeList orderList;
-    private SqlLiteral interval;
-
-    /** Creates a SqlMatchRecognize. */
-    public SqlMatchRecognize(
-            SqlParserPos pos,
-            SqlNode tableRef,
-            SqlNode pattern,
-            SqlLiteral strictStart,
-            SqlLiteral strictEnd,
-            SqlNodeList patternDefList,
-            SqlNodeList measureList,
-            SqlNode after,
-            SqlNodeList subsetList,
-            SqlLiteral rowsPerMatch,
-            SqlNodeList partitionList,
-            SqlNodeList orderList,
-            SqlLiteral interval) {
-        super(pos);
-        this.tableRef = Objects.requireNonNull(tableRef);
-        this.pattern = Objects.requireNonNull(pattern);
-        this.strictStart = strictStart;
-        this.strictEnd = strictEnd;
-        this.patternDefList = Objects.requireNonNull(patternDefList);
-        Preconditions.checkArgument(patternDefList.size() > 0);
-        this.measureList = Objects.requireNonNull(measureList);
-        this.after = after;
-        this.subsetList = subsetList;
-        Preconditions.checkArgument(
-                rowsPerMatch == null || rowsPerMatch.value instanceof RowsPerMatchOption);
-        this.rowsPerMatch = rowsPerMatch;
-        this.partitionList = Objects.requireNonNull(partitionList);
-        this.orderList = Objects.requireNonNull(orderList);
-        this.interval = interval;
-    }
-
-    // ~ Methods
-
-    @Override
-    public SqlOperator getOperator() {
-        return SqlMatchRecognizeOperator.INSTANCE;
-    }
-
-    @Override
-    public SqlKind getKind() {
-        return SqlKind.MATCH_RECOGNIZE;
-    }
-
-    @Override
-    public List<SqlNode> getOperandList() {
-        // This method was changed to fix CALCITE-4390
-        return ImmutableNullableList.of(
-                tableRef,
-                pattern,
-                strictStart,
-                strictEnd,
-                patternDefList,
-                measureList,
-                after,
-                subsetList,
-                rowsPerMatch,
-                partitionList,
-                orderList,
-                interval);
-    }
-
-    @Override
-    public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
-        getOperator().unparse(writer, this, 0, 0);
-    }
-
-    @Override
-    public void validate(SqlValidator validator, SqlValidatorScope scope) {
-        validator.validateMatchRecognize(this);
-    }
-
-    @Override
-    public void setOperand(int i, SqlNode operand) {
-        switch (i) {
-            case OPERAND_TABLE_REF:
-                tableRef = Objects.requireNonNull(operand);
-                break;
-            case OPERAND_PATTERN:
-                pattern = operand;
-                break;
-            case OPERAND_STRICT_START:
-                strictStart = (SqlLiteral) operand;
-                break;
-            case OPERAND_STRICT_END:
-                strictEnd = (SqlLiteral) operand;
-                break;
-            case OPERAND_PATTERN_DEFINES:
-                patternDefList = Objects.requireNonNull((SqlNodeList) operand);
-                Preconditions.checkArgument(patternDefList.size() > 0);
-                break;
-            case OPERAND_MEASURES:
-                measureList = Objects.requireNonNull((SqlNodeList) operand);
-                break;
-            case OPERAND_AFTER:
-                after = operand;
-                break;
-            case OPERAND_SUBSET:
-                subsetList = (SqlNodeList) operand;
-                break;
-            case OPERAND_ROWS_PER_MATCH:
-                rowsPerMatch = (SqlLiteral) operand;
-                Preconditions.checkArgument(
-                        rowsPerMatch == null || rowsPerMatch.value instanceof RowsPerMatchOption);
-                break;
-            case OPERAND_PARTITION_BY:
-                partitionList = (SqlNodeList) operand;
-                break;
-            case OPERAND_ORDER_BY:
-                orderList = (SqlNodeList) operand;
-                break;
-            case OPERAND_INTERVAL:
-                interval = (SqlLiteral) operand;
-                break;
-            default:
-                throw new AssertionError(i);
-        }
-    }
-
-    @Nonnull
-    public SqlNode getTableRef() {
-        return tableRef;
-    }
-
-    public SqlNode getPattern() {
-        return pattern;
-    }
-
-    public SqlLiteral getStrictStart() {
-        return strictStart;
-    }
-
-    public SqlLiteral getStrictEnd() {
-        return strictEnd;
-    }
-
-    @Nonnull
-    public SqlNodeList getPatternDefList() {
-        return patternDefList;
-    }
-
-    @Nonnull
-    public SqlNodeList getMeasureList() {
-        return measureList;
-    }
-
-    public SqlNode getAfter() {
-        return after;
-    }
-
-    public SqlNodeList getSubsetList() {
-        return subsetList;
-    }
-
-    public SqlLiteral getRowsPerMatch() {
-        return rowsPerMatch;
-    }
-
-    public SqlNodeList getPartitionList() {
-        return partitionList;
-    }
-
-    public SqlNodeList getOrderList() {
-        return orderList;
-    }
-
-    public SqlLiteral getInterval() {
-        return interval;
-    }
-
-    /** Options for {@code ROWS PER MATCH}. */
-    public enum RowsPerMatchOption {
-        ONE_ROW("ONE ROW PER MATCH"),
-        ALL_ROWS("ALL ROWS PER MATCH");
-
-        private final String sql;
-
-        RowsPerMatchOption(String sql) {
-            this.sql = sql;
-        }
-
-        @Override
-        public String toString() {
-            return sql;
-        }
-
-        public SqlLiteral symbol(SqlParserPos pos) {
-            return SqlLiteral.createSymbol(this, pos);
-        }
-    }
-
-    /** Options for {@code AFTER MATCH} clause. */
-    public enum AfterOption implements Symbolizable {
-        SKIP_TO_NEXT_ROW("SKIP TO NEXT ROW"),
-        SKIP_PAST_LAST_ROW("SKIP PAST LAST ROW");
-
-        private final String sql;
-
-        AfterOption(String sql) {
-            this.sql = sql;
-        }
-
-        @Override
-        public String toString() {
-            return sql;
-        }
-    }
-
-    /** An operator describing a MATCH_RECOGNIZE specification. */
-    public static class SqlMatchRecognizeOperator extends SqlOperator {
-        public static final SqlMatchRecognizeOperator INSTANCE = new SqlMatchRecognizeOperator();
-
-        private SqlMatchRecognizeOperator() {
-            super("MATCH_RECOGNIZE", SqlKind.MATCH_RECOGNIZE, 2, true, null, null, null);
-        }
-
-        @Override
-        public SqlSyntax getSyntax() {
-            return SqlSyntax.SPECIAL;
-        }
-
-        @Override
-        public SqlCall createCall(
-                SqlLiteral functionQualifier, SqlParserPos pos, SqlNode... operands) {
-            assert functionQualifier == null;
-            assert operands.length == 12;
-
-            return new SqlMatchRecognize(
-                    pos,
-                    operands[0],
-                    operands[1],
-                    (SqlLiteral) operands[2],
-                    (SqlLiteral) operands[3],
-                    (SqlNodeList) operands[4],
-                    (SqlNodeList) operands[5],
-                    operands[6],
-                    (SqlNodeList) operands[7],
-                    (SqlLiteral) operands[8],
-                    (SqlNodeList) operands[9],
-                    (SqlNodeList) operands[10],
-                    (SqlLiteral) operands[11]);
-        }
-
-        @Override
-        public <R> void acceptCall(
-                SqlVisitor<R> visitor,
-                SqlCall call,
-                boolean onlyExpressions,
-                SqlBasicVisitor.ArgHandler<R> argHandler) {
-            if (onlyExpressions) {
-                List<SqlNode> operands = call.getOperandList();
-                for (int i = 0; i < operands.size(); i++) {
-                    SqlNode operand = operands.get(i);
-                    if (operand == null) {
-                        continue;
-                    }
-                    argHandler.visitChild(visitor, call, i, operand);
-                }
-            } else {
-                super.acceptCall(visitor, call, onlyExpressions, argHandler);
-            }
-        }
-
-        @Override
-        public void validateCall(
-                SqlCall call,
-                SqlValidator validator,
-                SqlValidatorScope scope,
-                SqlValidatorScope operandScope) {
-            validator.validateMatchRecognize(call);
-        }
-
-        @Override
-        public void unparse(SqlWriter writer, SqlCall call, int leftPrec, int rightPrec) {
-            final SqlMatchRecognize pattern = (SqlMatchRecognize) call;
-
-            pattern.tableRef.unparse(writer, 0, 0);
-            final SqlWriter.Frame mrFrame = writer.startFunCall("MATCH_RECOGNIZE");
-
-            if (pattern.partitionList != null && pattern.partitionList.size() > 0) {
-                writer.newlineAndIndent();
-                writer.sep("PARTITION BY");
-                final SqlWriter.Frame partitionFrame = writer.startList("", "");
-                pattern.partitionList.unparse(writer, 0, 0);
-                writer.endList(partitionFrame);
-            }
-
-            if (pattern.orderList != null && pattern.orderList.size() > 0) {
-                writer.newlineAndIndent();
-                writer.sep("ORDER BY");
-                writer.list(
-                        SqlWriter.FrameTypeEnum.ORDER_BY_LIST, SqlWriter.COMMA, pattern.orderList);
-            }
-
-            if (pattern.measureList != null && pattern.measureList.size() > 0) {
-                writer.newlineAndIndent();
-                writer.sep("MEASURES");
-                final SqlWriter.Frame measureFrame = writer.startList("", "");
-                pattern.measureList.unparse(writer, 0, 0);
-                writer.endList(measureFrame);
-            }
-
-            if (pattern.rowsPerMatch != null) {
-                writer.newlineAndIndent();
-                pattern.rowsPerMatch.unparse(writer, 0, 0);
-            }
-
-            if (pattern.after != null) {
-                writer.newlineAndIndent();
-                writer.sep("AFTER MATCH");
-                pattern.after.unparse(writer, 0, 0);
-            }
-
-            writer.newlineAndIndent();
-            writer.sep("PATTERN");
-
-            SqlWriter.Frame patternFrame = writer.startList("(", ")");
-            if (pattern.strictStart.booleanValue()) {
-                writer.sep("^");
-            }
-            pattern.pattern.unparse(writer, 0, 0);
-            if (pattern.strictEnd.booleanValue()) {
-                writer.sep("$");
-            }
-            writer.endList(patternFrame);
-            if (pattern.interval != null) {
-                writer.sep("WITHIN");
-                pattern.interval.unparse(writer, 0, 0);
-            }
-
-            if (pattern.subsetList != null && pattern.subsetList.size() > 0) {
-                writer.newlineAndIndent();
-                writer.sep("SUBSET");
-                SqlWriter.Frame subsetFrame = writer.startList("", "");
-                pattern.subsetList.unparse(writer, 0, 0);
-                writer.endList(subsetFrame);
-            }
-
-            writer.newlineAndIndent();
-            writer.sep("DEFINE");
-
-            final SqlWriter.Frame patternDefFrame = writer.startList("", "");
-            final SqlNodeList newDefineList = new SqlNodeList(SqlParserPos.ZERO);
-            for (SqlNode node : pattern.getPatternDefList()) {
-                final SqlCall call2 = (SqlCall) node;
-                // swap the position of alias position in AS operator
-                newDefineList.add(
-                        call2.getOperator()
-                                .createCall(SqlParserPos.ZERO, call2.operand(1), call2.operand(0)));
-            }
-            newDefineList.unparse(writer, 0, 0);
-            writer.endList(patternDefFrame);
-            writer.endList(mrFrame);
-        }
-    }
-}
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/SqlTableRef.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/SqlTableRef.java
deleted file mode 100644
index a9cb3af1302..00000000000
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/SqlTableRef.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.sql;
-
-import com.google.common.collect.ImmutableList;
-import org.apache.calcite.sql.parser.SqlParserPos;
-
-import java.util.List;
-
-/**
- * A <code>SqlTableRef</code> is a node of a parse tree which represents a table reference.
- *
- * <p>It can be attached with a sql hint statement, see {@link SqlHint} for details.
- *
- * <p>The class was copied over because of CALCITE-4406. The changed lines are: 43-49
- */
-public class SqlTableRef extends SqlCall {
-
-    // ~ Instance fields --------------------------------------------------------
-
-    private final SqlIdentifier tableName;
-    private final SqlNodeList hints;
-
-    // ~ Static fields/initializers ---------------------------------------------
-
-    private static final SqlOperator OPERATOR =
-            new SqlSpecialOperator("TABLE_REF", SqlKind.TABLE_REF) {
-                @Override
-                public SqlCall createCall(
-                        SqlLiteral functionQualifier, SqlParserPos pos, SqlNode... operands) {
-                    return new SqlTableRef(
-                            pos, (SqlIdentifier) operands[0], (SqlNodeList) operands[1]);
-                }
-            };
-
-    // ~ Constructors -----------------------------------------------------------
-
-    public SqlTableRef(SqlParserPos pos, SqlIdentifier tableName, SqlNodeList hints) {
-        super(pos);
-        this.tableName = tableName;
-        this.hints = hints;
-    }
-
-    // ~ Methods ----------------------------------------------------------------
-
-    @Override
-    public SqlOperator getOperator() {
-        return OPERATOR;
-    }
-
-    @Override
-    public List<SqlNode> getOperandList() {
-        return ImmutableList.of(tableName, hints);
-    }
-
-    @Override
-    public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
-        tableName.unparse(writer, leftPrec, rightPrec);
-        if (this.hints != null && this.hints.size() > 0) {
-            writer.newlineAndIndent();
-            writer.keyword("/*+");
-            this.hints.unparse(writer, 0, 0);
-            writer.keyword("*/");
-        }
-    }
-}
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index ed88a182990..fcf03ea12ab 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -1,13 +1,12 @@
 /*
- * 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
+ * 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
+ * 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,
@@ -15,7 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.calcite.sql.validate;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -24,7 +22,6 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import org.apache.calcite.linq4j.Ord;
-import org.apache.calcite.linq4j.function.Function2;
 import org.apache.calcite.linq4j.function.Functions;
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelOptUtil;
@@ -80,6 +77,7 @@ import org.apache.calcite.sql.SqlSelectKeyword;
 import org.apache.calcite.sql.SqlSnapshot;
 import org.apache.calcite.sql.SqlSyntax;
 import org.apache.calcite.sql.SqlTableFunction;
+import org.apache.calcite.sql.SqlUnpivot;
 import org.apache.calcite.sql.SqlUnresolvedFunction;
 import org.apache.calcite.sql.SqlUpdate;
 import org.apache.calcite.sql.SqlUtil;
@@ -113,11 +111,13 @@ import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Static;
 import org.apache.calcite.util.Util;
 import org.apache.calcite.util.trace.CalciteTrace;
+import org.apiguardian.api.API;
+import org.checkerframework.checker.nullness.qual.KeyFor;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.checker.nullness.qual.PolyNull;
+import org.checkerframework.dataflow.qual.Pure;
 import org.slf4j.Logger;
 
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.util.AbstractList;
@@ -137,22 +137,29 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.BiFunction;
 import java.util.function.Supplier;
 import java.util.function.UnaryOperator;
 import java.util.stream.Collectors;
 
+import static java.util.Objects.requireNonNull;
+import static org.apache.calcite.linq4j.Nullness.castNonNull;
 import static org.apache.calcite.sql.SqlUtil.stripAs;
+import static org.apache.calcite.sql.type.NonNullableAccessors.getCharset;
+import static org.apache.calcite.sql.type.NonNullableAccessors.getCollation;
+import static org.apache.calcite.sql.validate.SqlNonNullableAccessors.getCondition;
+import static org.apache.calcite.sql.validate.SqlNonNullableAccessors.getTable;
 import static org.apache.calcite.util.Static.RESOURCE;
 
 /**
  * Default implementation of {@link SqlValidator}, the class was copied over because of
  * CALCITE-4554.
  *
- * <p>Lines 4697 ~ 4716, Flink enables TIMESTAMP and TIMESTAMP_LTZ for system time period
- * specification type.
+ * <p>Lines 5009 ~ 5022, Flink enables TIMESTAMP and TIMESTAMP_LTZ for system time period
+ * specification type at {@link org.apache.calcite.sql.validate.SqlValidatorImpl#validateSnapshot}
  *
- * <p>Lines 5034 ~ 5038, Flink enables TIMESTAMP and TIMESTAMP_LTZ for first orderBy column in
- * matchRecognize.
+ * <p>Lines 5366 ~ 5372, Flink enables TIMESTAMP and TIMESTAMP_LTZ for first orderBy column in
+ * matchRecognize at {@link SqlValidatorImpl#validateMatchRecognize}.
  */
 public class SqlValidatorImpl implements SqlValidatorWithHints {
     // ~ Static fields/initializers ---------------------------------------------
@@ -186,19 +193,20 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      * Maps {@link SqlNode query node} objects to the {@link SqlValidatorScope} scope created from
      * them.
      */
-    protected final Map<SqlNode, SqlValidatorScope> scopes = new IdentityHashMap<>();
+    protected final IdentityHashMap<SqlNode, SqlValidatorScope> scopes = new IdentityHashMap<>();
 
     /** Maps a {@link SqlSelect} and a clause to the scope used by that clause. */
     private final Map<IdPair<SqlSelect, Clause>, SqlValidatorScope> clauseScopes = new HashMap<>();
 
     /** The name-resolution scope of a LATERAL TABLE clause. */
-    private TableScope tableScope = null;
+    private @Nullable TableScope tableScope = null;
 
     /**
      * Maps a {@link SqlNode node} to the {@link SqlValidatorNamespace namespace} which describes
      * what columns they contain.
      */
-    protected final Map<SqlNode, SqlValidatorNamespace> namespaces = new IdentityHashMap<>();
+    protected final IdentityHashMap<SqlNode, SqlValidatorNamespace> namespaces =
+            new IdentityHashMap<>();
 
     /**
      * Set of select expressions used as cursor definitions. In standard SQL, only the top-level
@@ -225,7 +233,11 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      * Map of derived RelDataType for each node. This is an IdentityHashMap since in some cases
      * (such as null literals) we need to discriminate by instance.
      */
-    private final Map<SqlNode, RelDataType> nodeToTypeMap = new IdentityHashMap<>();
+    private final IdentityHashMap<SqlNode, RelDataType> nodeToTypeMap = new IdentityHashMap<>();
+
+    /** Provides the data for {@link #getValidatedOperandTypes(SqlCall)}. */
+    public final IdentityHashMap<SqlCall, List<RelDataType>> callToOperandTypesMap =
+            new IdentityHashMap<>();
 
     private final AggFinder aggFinder;
     private final AggFinder aggOrOverFinder;
@@ -237,7 +249,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
     private final Map<SqlNode, SqlNode> originalExprs = new HashMap<>();
 
-    private SqlNode top;
+    private @Nullable SqlNode top;
 
     // TODO jvs 11-Dec-2008:  make this local to performUnconditionalRewrites
     // if it's OK to expand the signature of that method.
@@ -266,10 +278,10 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             SqlValidatorCatalogReader catalogReader,
             RelDataTypeFactory typeFactory,
             Config config) {
-        this.opTab = Objects.requireNonNull(opTab);
-        this.catalogReader = Objects.requireNonNull(catalogReader);
-        this.typeFactory = Objects.requireNonNull(typeFactory);
-        this.config = Objects.requireNonNull(config);
+        this.opTab = requireNonNull(opTab, "opTab");
+        this.catalogReader = requireNonNull(catalogReader, "catalogReader");
+        this.typeFactory = requireNonNull(typeFactory, "typeFactory");
+        this.config = requireNonNull(config, "config");
 
         unknownType = typeFactory.createUnknownType();
         booleanType = typeFactory.createSqlType(SqlTypeName.BOOLEAN);
@@ -280,7 +292,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         overFinder = new AggFinder(opTab, true, false, false, aggOrOverFinder, nameMatcher);
         groupFinder = new AggFinder(opTab, false, false, true, null, nameMatcher);
         aggOrOverOrGroupFinder = new AggFinder(opTab, true, true, true, null, nameMatcher);
-        this.typeCoercion = config.typeCoercionFactory().create(typeFactory, this);
+        @SuppressWarnings("argument.type.incompatible")
+        TypeCoercion typeCoercion = config.typeCoercionFactory().create(typeFactory, this);
+        this.typeCoercion = typeCoercion;
         if (config.typeCoercionRules() != null) {
             SqlTypeCoercionRule.THREAD_PROVIDERS.set(config.typeCoercionRules());
         }
@@ -292,22 +306,30 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         return config.sqlConformance();
     }
 
+    @Pure
+    @Override
     public SqlValidatorCatalogReader getCatalogReader() {
         return catalogReader;
     }
 
+    @Pure
+    @Override
     public SqlOperatorTable getOperatorTable() {
         return opTab;
     }
 
+    @Pure
+    @Override
     public RelDataTypeFactory getTypeFactory() {
         return typeFactory;
     }
 
+    @Override
     public RelDataType getUnknownType() {
         return unknownType;
     }
 
+    @Override
     public SqlNodeList expandStar(
             SqlNodeList selectList, SqlSelect select, boolean includeSystemVars) {
         final List<SqlNode> list = new ArrayList<>();
@@ -324,17 +346,18 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                     types,
                     includeSystemVars);
         }
-        getRawSelectScope(select).setExpandedSelectList(list);
+        getRawSelectScopeNonNull(select).setExpandedSelectList(list);
         return new SqlNodeList(list, SqlParserPos.ZERO);
     }
 
     // implement SqlValidator
+    @Override
     public void declareCursor(SqlSelect select, SqlValidatorScope parentScope) {
         cursorSet.add(select);
 
         // add the cursor to a map that maps the cursor to its select based on
         // the position of the cursor relative to other cursors in that call
-        FunctionParamInfo funcParamInfo = functionCallStack.peek();
+        FunctionParamInfo funcParamInfo = requireNonNull(functionCallStack.peek(), "functionCall");
         Map<Integer, SqlSelect> cursorMap = funcParamInfo.cursorPosToSelectMap;
         int numCursors = cursorMap.size();
         cursorMap.put(numCursors, select);
@@ -350,19 +373,22 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     }
 
     // implement SqlValidator
+    @Override
     public void pushFunctionCall() {
         FunctionParamInfo funcInfo = new FunctionParamInfo();
         functionCallStack.push(funcInfo);
     }
 
     // implement SqlValidator
+    @Override
     public void popFunctionCall() {
         functionCallStack.pop();
     }
 
     // implement SqlValidator
-    public String getParentCursor(String columnListParamName) {
-        FunctionParamInfo funcParamInfo = functionCallStack.peek();
+    @Override
+    public @Nullable String getParentCursor(String columnListParamName) {
+        FunctionParamInfo funcParamInfo = requireNonNull(functionCallStack.peek(), "functionCall");
         Map<String, String> parentCursorMap = funcParamInfo.columnListParamToParentCursorMap;
         return parentCursorMap.get(columnListParamName);
     }
@@ -396,13 +422,13 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         // parentheses-free functions such as LOCALTIME into explicit function
         // calls.
         SqlNode expanded = expandSelectExpr(selectItem, scope, select);
-        final String alias = deriveAlias(selectItem, aliases.size());
+        final String alias = deriveAliasNonNull(selectItem, aliases.size());
 
         // If expansion has altered the natural alias, supply an explicit 'AS'.
         final SqlValidatorScope selectScope = getSelectScope(select);
         if (expanded != selectItem) {
-            String newAlias = deriveAlias(expanded, aliases.size());
-            if (!newAlias.equals(alias)) {
+            String newAlias = deriveAliasNonNull(expanded, aliases.size());
+            if (!Objects.equals(newAlias, alias)) {
                 expanded =
                         SqlStdOperatorTable.AS.createCall(
                                 selectItem.getParserPosition(),
@@ -425,16 +451,15 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     }
 
     private static SqlNode expandExprFromJoin(
-            SqlJoin join, SqlIdentifier identifier, SelectScope scope) {
+            SqlJoin join, SqlIdentifier identifier, @Nullable SelectScope scope) {
         if (join.getConditionType() != JoinConditionType.USING) {
             return identifier;
         }
 
-        for (SqlNode node : (SqlNodeList) join.getCondition()) {
-            final String name = ((SqlIdentifier) node).getSimple();
+        for (String name : SqlIdentifier.simpleNames((SqlNodeList) getCondition(join))) {
             if (identifier.getSimple().equals(name)) {
                 final List<SqlNode> qualifiedNode = new ArrayList<>();
-                for (ScopeChild child : scope.children) {
+                for (ScopeChild child : requireNonNull(scope, "scope").children) {
                     if (child.namespace.getRowType().getFieldNames().indexOf(name) >= 0) {
                         final SqlIdentifier exp =
                                 new SqlIdentifier(
@@ -471,13 +496,12 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      * Returns the set of field names in the join condition specified by USING or implicitly by
      * NATURAL, de-duplicated and in order.
      */
-    public List<String> usingNames(SqlJoin join) {
+    public @Nullable List<String> usingNames(SqlJoin join) {
         switch (join.getConditionType()) {
             case USING:
                 final ImmutableList.Builder<String> list = ImmutableList.builder();
                 final Set<String> names = catalogReader.nameMatcher().createSet();
-                for (SqlNode node : (SqlNodeList) join.getCondition()) {
-                    final String name = ((SqlIdentifier) node).getSimple();
+                for (String name : SqlIdentifier.simpleNames((SqlNodeList) getCondition(join))) {
                     if (names.add(name)) {
                         list.add(name);
                     }
@@ -490,6 +514,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                     return SqlValidatorUtil.deriveNaturalJoinColumnList(
                             catalogReader.nameMatcher(), t0, t1);
                 }
+                break;
+            default:
+                break;
         }
         return null;
     }
@@ -497,7 +524,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     private static SqlNode expandCommonColumn(
             SqlSelect sqlSelect,
             SqlNode selectItem,
-            SelectScope scope,
+            @Nullable SelectScope scope,
             SqlValidatorImpl validator) {
         if (!(selectItem instanceof SqlIdentifier)) {
             return selectItem;
@@ -520,18 +547,22 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     }
 
     private static void validateQualifiedCommonColumn(
-            SqlJoin join, SqlIdentifier identifier, SelectScope scope, SqlValidatorImpl validator) {
+            SqlJoin join,
+            SqlIdentifier identifier,
+            @Nullable SelectScope scope,
+            SqlValidatorImpl validator) {
         List<String> names = validator.usingNames(join);
         if (names == null) {
             // Not USING or NATURAL.
             return;
         }
 
+        requireNonNull(scope, "scope");
         // First we should make sure that the first component is the table name.
         // Then check whether the qualified identifier contains common column.
         for (ScopeChild child : scope.children) {
-            if (child.name.equals(identifier.getComponent(0).toString())) {
-                if (names.indexOf(identifier.getComponent(1).toString()) >= 0) {
+            if (Objects.equals(child.name, identifier.getComponent(0).toString())) {
+                if (names.contains(identifier.getComponent(1).toString())) {
                     throw validator.newValidationError(
                             identifier,
                             RESOURCE.disallowsQualifyingCommonColumn(identifier.toString()));
@@ -580,9 +611,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                         addToSelectList(
                                 selectItems, aliases, fields, exp, scope, includeSystemVars);
                     } else {
-                        final SqlNode from = child.namespace.getNode();
-                        final SqlValidatorNamespace fromNs = getNamespace(from, scope);
-                        assert fromNs != null;
+                        final SqlNode from = SqlNonNullableAccessors.getNode(child);
+                        final SqlValidatorNamespace fromNs = getNamespaceOrThrow(from, scope);
                         final RelDataType rowType = fromNs.getRowType();
                         for (RelDataTypeField field : rowType.getFieldList()) {
                             String columnName = field.getName();
@@ -622,7 +652,11 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 // If NATURAL JOIN or USING is present, move key fields to the front of
                 // the list, per standard SQL. Disabled if there are dynamic fields.
                 if (!hasDynamicStruct || Bug.CALCITE_2400_FIXED) {
-                    new Permute(scope.getNode().getFrom(), 0).permute(selectItems, fields);
+                    SqlNode from =
+                            requireNonNull(
+                                    scope.getNode().getFrom(),
+                                    () -> "getFrom for " + scope.getNode());
+                    new Permute(from, 0).permute(selectItems, fields);
                 }
                 return true;
 
@@ -698,6 +732,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         return false;
     }
 
+    @Override
     public SqlNode validate(SqlNode topNode) {
         SqlValidatorScope scope = new EmptyScope(this);
         scope = new CatalogScope(scope, ImmutableList.of("CATALOG"));
@@ -707,6 +742,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         return topNode2;
     }
 
+    @Override
     public List<SqlMoniker> lookupHints(SqlNode topNode, SqlParserPos pos) {
         SqlValidatorScope scope = new EmptyScope(this);
         SqlNode outermostNode = performUnconditionalRewrites(topNode, false);
@@ -723,7 +759,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         return ImmutableList.copyOf(hintList);
     }
 
-    public SqlMoniker lookupQualifiedName(SqlNode topNode, SqlParserPos pos) {
+    @Override
+    public @Nullable SqlMoniker lookupQualifiedName(SqlNode topNode, SqlParserPos pos) {
         final String posString = pos.toString();
         IdInfo info = idPositions.get(posString);
         if (info != null) {
@@ -764,15 +801,15 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     }
 
     private void lookupFromHints(
-            SqlNode node,
-            SqlValidatorScope scope,
+            @Nullable SqlNode node,
+            @Nullable SqlValidatorScope scope,
             SqlParserPos pos,
             Collection<SqlMoniker> hintList) {
         if (node == null) {
             // This can happen in cases like "select * _suggest_", so from clause is absent
             return;
         }
-        final SqlValidatorNamespace ns = getNamespace(node);
+        final SqlValidatorNamespace ns = getNamespaceOrThrow(node);
         if (ns.isWrapperFor(IdentifierNamespace.class)) {
             IdentifierNamespace idNs = ns.unwrap(IdentifierNamespace.class);
             final SqlIdentifier id = idNs.getId();
@@ -802,7 +839,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
     private void lookupJoinHints(
             SqlJoin join,
-            SqlValidatorScope scope,
+            @Nullable SqlValidatorScope scope,
             SqlParserPos pos,
             Collection<SqlMoniker> hintList) {
         SqlNode left = join.getLeft();
@@ -817,10 +854,10 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             return;
         }
         final JoinConditionType conditionType = join.getConditionType();
-        final SqlValidatorScope joinScope = scopes.get(join);
         switch (conditionType) {
             case ON:
-                condition.findValidOptions(this, joinScope, pos, hintList);
+                requireNonNull(condition, () -> "join.getCondition() for " + join)
+                        .findValidOptions(this, getScopeOrThrow(join), pos, hintList);
                 return;
             default:
 
@@ -935,6 +972,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
     }
 
+    @Override
     public SqlNode validateParameterizedExpression(
             SqlNode topNode, final Map<String, RelDataType> nameToTypeMap) {
         SqlValidatorScope scope = new ParameterScope(this, nameToTypeMap);
@@ -959,8 +997,10 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         return outermostNode;
     }
 
-    public void validateQuery(SqlNode node, SqlValidatorScope scope, RelDataType targetRowType) {
-        final SqlValidatorNamespace ns = getNamespace(node, scope);
+    @Override
+    public void validateQuery(
+            SqlNode node, @Nullable SqlValidatorScope scope, RelDataType targetRowType) {
+        final SqlValidatorNamespace ns = getNamespaceOrThrow(node, scope);
         if (node.getKind() == SqlKind.TABLESAMPLE) {
             List<SqlNode> operands = ((SqlCall) node).getOperandList();
             SqlSampleSpec sampleSpec = SqlLiteral.sampleValue(operands.get(1));
@@ -976,7 +1016,10 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         switch (node.getKind()) {
             case EXTEND:
                 // Until we have a dedicated namespace for EXTEND
-                deriveType(scope, node);
+                deriveType(requireNonNull(scope, "scope"), node);
+                break;
+            default:
+                break;
         }
         if (node == top) {
             validateModality(node);
@@ -995,8 +1038,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     protected void validateNamespace(
             final SqlValidatorNamespace namespace, RelDataType targetRowType) {
         namespace.validate(targetRowType);
-        if (namespace.getNode() != null) {
-            setValidatedNodeType(namespace.getNode(), namespace.getType());
+        SqlNode node = namespace.getNode();
+        if (node != null) {
+            setValidatedNodeType(node, namespace.getType());
         }
     }
 
@@ -1005,57 +1049,82 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         return new EmptyScope(this);
     }
 
+    private SqlValidatorScope getScope(SqlSelect select, Clause clause) {
+        return requireNonNull(
+                clauseScopes.get(IdPair.of(select, clause)),
+                () -> "no " + clause + " scope for " + select);
+    }
+
     public SqlValidatorScope getCursorScope(SqlSelect select) {
-        return clauseScopes.get(IdPair.of(select, Clause.CURSOR));
+        return getScope(select, Clause.CURSOR);
     }
 
+    @Override
     public SqlValidatorScope getWhereScope(SqlSelect select) {
-        return clauseScopes.get(IdPair.of(select, Clause.WHERE));
+        return getScope(select, Clause.WHERE);
     }
 
+    @Override
     public SqlValidatorScope getSelectScope(SqlSelect select) {
-        return clauseScopes.get(IdPair.of(select, Clause.SELECT));
+        return getScope(select, Clause.SELECT);
     }
 
-    public SelectScope getRawSelectScope(SqlSelect select) {
-        SqlValidatorScope scope = getSelectScope(select);
+    @Override
+    public @Nullable SelectScope getRawSelectScope(SqlSelect select) {
+        SqlValidatorScope scope = clauseScopes.get(IdPair.of(select, Clause.SELECT));
         if (scope instanceof AggregatingSelectScope) {
             scope = ((AggregatingSelectScope) scope).getParent();
         }
         return (SelectScope) scope;
     }
 
+    private SelectScope getRawSelectScopeNonNull(SqlSelect select) {
+        return requireNonNull(getRawSelectScope(select), () -> "getRawSelectScope for " + select);
+    }
+
+    @Override
     public SqlValidatorScope getHavingScope(SqlSelect select) {
         // Yes, it's the same as getSelectScope
-        return clauseScopes.get(IdPair.of(select, Clause.SELECT));
+        return getScope(select, Clause.SELECT);
     }
 
+    @Override
     public SqlValidatorScope getGroupScope(SqlSelect select) {
         // Yes, it's the same as getWhereScope
-        return clauseScopes.get(IdPair.of(select, Clause.GROUP_BY));
+        return getScope(select, Clause.WHERE);
     }
 
-    public SqlValidatorScope getFromScope(SqlSelect select) {
+    @Override
+    public @Nullable SqlValidatorScope getFromScope(SqlSelect select) {
         return scopes.get(select);
     }
 
+    @Override
     public SqlValidatorScope getOrderScope(SqlSelect select) {
-        return clauseScopes.get(IdPair.of(select, Clause.ORDER));
+        return getScope(select, Clause.ORDER);
     }
 
+    @Override
     public SqlValidatorScope getMatchRecognizeScope(SqlMatchRecognize node) {
-        return scopes.get(node);
+        return getScopeOrThrow(node);
     }
 
-    public SqlValidatorScope getJoinScope(SqlNode node) {
+    @Override
+    public @Nullable SqlValidatorScope getJoinScope(SqlNode node) {
         return scopes.get(stripAs(node));
     }
 
+    @Override
     public SqlValidatorScope getOverScope(SqlNode node) {
-        return scopes.get(node);
+        return getScopeOrThrow(node);
+    }
+
+    private SqlValidatorScope getScopeOrThrow(SqlNode node) {
+        return requireNonNull(scopes.get(node), () -> "scope for " + node);
     }
 
-    private SqlValidatorNamespace getNamespace(SqlNode node, SqlValidatorScope scope) {
+    private @Nullable SqlValidatorNamespace getNamespace(
+            SqlNode node, @Nullable SqlValidatorScope scope) {
         if (node instanceof SqlIdentifier && scope instanceof DelegatingScope) {
             final SqlIdentifier id = (SqlIdentifier) node;
             final DelegatingScope idScope = (DelegatingScope) ((DelegatingScope) scope).getParent();
@@ -1080,18 +1149,24 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                         case TABLE_REF:
                         case EXTEND:
                             return getNamespace(nested, scope);
+                        default:
+                            break;
                     }
                     break;
+                default:
+                    break;
             }
         }
         return getNamespace(node);
     }
 
-    private SqlValidatorNamespace getNamespace(SqlIdentifier id, DelegatingScope scope) {
+    private @Nullable SqlValidatorNamespace getNamespace(
+            SqlIdentifier id, @Nullable DelegatingScope scope) {
         if (id.isSimple()) {
             final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
             final SqlValidatorScope.ResolvedImpl resolved = new SqlValidatorScope.ResolvedImpl();
-            scope.resolve(id.names, nameMatcher, false, resolved);
+            requireNonNull(scope, () -> "scope needed to lookup " + id)
+                    .resolve(id.names, nameMatcher, false, resolved);
             if (resolved.count() == 1) {
                 return resolved.only().namespace;
             }
@@ -1099,7 +1174,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         return getNamespace(id);
     }
 
-    public SqlValidatorNamespace getNamespace(SqlNode node) {
+    @Override
+    public @Nullable SqlValidatorNamespace getNamespace(SqlNode node) {
         switch (node.getKind()) {
             case AS:
 
@@ -1121,7 +1197,47 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
     }
 
-    private void handleOffsetFetch(SqlNode offset, SqlNode fetch) {
+    /**
+     * Namespace for the given node.
+     *
+     * @param node node to compute the namespace for
+     * @return namespace for the given node, never null
+     * @see #getNamespace(SqlNode)
+     */
+    @API(since = "1.27", status = API.Status.INTERNAL)
+    SqlValidatorNamespace getNamespaceOrThrow(SqlNode node) {
+        return requireNonNull(getNamespace(node), () -> "namespace for " + node);
+    }
+
+    /**
+     * Namespace for the given node.
+     *
+     * @param node node to compute the namespace for
+     * @param scope namespace scope
+     * @return namespace for the given node, never null
+     * @see #getNamespace(SqlNode)
+     */
+    @API(since = "1.27", status = API.Status.INTERNAL)
+    SqlValidatorNamespace getNamespaceOrThrow(SqlNode node, @Nullable SqlValidatorScope scope) {
+        return requireNonNull(
+                getNamespace(node, scope), () -> "namespace for " + node + ", scope " + scope);
+    }
+
+    /**
+     * Namespace for the given node.
+     *
+     * @param id identifier to resolve
+     * @param scope namespace scope
+     * @return namespace for the given node, never null
+     * @see #getNamespace(SqlIdentifier, DelegatingScope)
+     */
+    @API(since = "1.26", status = API.Status.INTERNAL)
+    SqlValidatorNamespace getNamespaceOrThrow(SqlIdentifier id, @Nullable DelegatingScope scope) {
+        return requireNonNull(
+                getNamespace(id, scope), () -> "namespace for " + id + ", scope " + scope);
+    }
+
+    private void handleOffsetFetch(@Nullable SqlNode offset, @Nullable SqlNode fetch) {
         if (offset instanceof SqlDynamicParam) {
             setValidatedNodeType(offset, typeFactory.createSqlType(SqlTypeName.INTEGER));
         }
@@ -1135,17 +1251,18 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      * the expression tree into a standard form so that the rest of the validation logic can be
      * simpler.
      *
+     * <p>Returns null if and only if the original expression is null.
+     *
      * @param node expression to be rewritten
      * @param underFrom whether node appears directly under a FROM clause
-     * @return rewritten expression
+     * @return rewritten expression, or null if the original expression is null
      */
-    protected SqlNode performUnconditionalRewrites(SqlNode node, boolean underFrom) {
+    protected @PolyNull SqlNode performUnconditionalRewrites(
+            @PolyNull SqlNode node, boolean underFrom) {
         if (node == null) {
             return null;
         }
 
-        SqlNode newOperand;
-
         // first transform operands and invoke generic call rewrite
         if (node instanceof SqlCall) {
             if (node instanceof SqlMerge) {
@@ -1166,7 +1283,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 } else {
                     childUnderFrom = false;
                 }
-                newOperand = performUnconditionalRewrites(operand, childUnderFrom);
+                SqlNode newOperand = performUnconditionalRewrites(operand, childUnderFrom);
                 if (newOperand != null && newOperand != operand) {
                     call.setOperand(i, newOperand);
                 }
@@ -1194,12 +1311,12 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 node = call.getOperator().rewriteCall(this, call);
             }
         } else if (node instanceof SqlNodeList) {
-            SqlNodeList list = (SqlNodeList) node;
-            for (int i = 0, count = list.size(); i < count; i++) {
+            final SqlNodeList list = (SqlNodeList) node;
+            for (int i = 0; i < list.size(); i++) {
                 SqlNode operand = list.get(i);
-                newOperand = performUnconditionalRewrites(operand, false);
+                SqlNode newOperand = performUnconditionalRewrites(operand, false);
                 if (newOperand != null) {
-                    list.getList().set(i, newOperand);
+                    list.set(i, newOperand);
                 }
             }
         }
@@ -1267,13 +1384,15 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                     final SqlNodeList selectList = new SqlNodeList(SqlParserPos.ZERO);
                     selectList.add(SqlIdentifier.star(SqlParserPos.ZERO));
                     final SqlNodeList orderList;
-                    if (getInnerSelect(node) != null && isAggregate(getInnerSelect(node))) {
+                    SqlSelect innerSelect = getInnerSelect(node);
+                    if (innerSelect != null && isAggregate(innerSelect)) {
                         orderList = SqlNode.clone(orderBy.orderList);
                         // We assume that ORDER BY item does not have ASC etc.
                         // We assume that ORDER BY item is present in SELECT list.
                         for (int i = 0; i < orderList.size(); i++) {
                             SqlNode sqlNode = orderList.get(i);
-                            SqlNodeList selectList2 = getInnerSelect(node).getSelectList();
+                            SqlNodeList selectList2 =
+                                    SqlNonNullableAccessors.getSelectList(innerSelect);
                             for (Ord<SqlNode> sel : Ord.zip(selectList2)) {
                                 if (stripAs(sel.e).equalsDeep(sqlNode, Litmus.IGNORE)) {
                                     orderList.set(
@@ -1356,11 +1475,13 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                     rewriteMerge(call);
                     break;
                 }
+            default:
+                break;
         }
         return node;
     }
 
-    private SqlSelect getInnerSelect(SqlNode node) {
+    private static @Nullable SqlSelect getInnerSelect(SqlNode node) {
         for (; ; ) {
             if (node instanceof SqlSelect) {
                 return (SqlSelect) node;
@@ -1374,7 +1495,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
     }
 
-    private void rewriteMerge(SqlMerge call) {
+    private static void rewriteMerge(SqlMerge call) {
         SqlNodeList selectList;
         SqlUpdate updateStmt = call.getUpdateCall();
         if (updateStmt != null) {
@@ -1382,7 +1503,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             // from the update statement's source since it's the same as
             // what we want for the select list of the merge source -- '*'
             // followed by the update set expressions
-            selectList = SqlNode.clone(updateStmt.getSourceSelect().getSelectList());
+            SqlSelect sourceSelect = SqlNonNullableAccessors.getSourceSelect(updateStmt);
+            selectList = SqlNode.clone(SqlNonNullableAccessors.getSelectList(sourceSelect));
         } else {
             // otherwise, just use select *
             selectList = new SqlNodeList(SqlParserPos.ZERO);
@@ -1457,12 +1579,13 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
     private SqlNode rewriteUpdateToMerge(SqlUpdate updateCall, SqlNode selfJoinSrcExpr) {
         // Make sure target has an alias.
-        if (updateCall.getAlias() == null) {
-            updateCall.setAlias(new SqlIdentifier(UPDATE_TGT_ALIAS, SqlParserPos.ZERO));
+        SqlIdentifier updateAlias = updateCall.getAlias();
+        if (updateAlias == null) {
+            updateAlias = new SqlIdentifier(UPDATE_TGT_ALIAS, SqlParserPos.ZERO);
+            updateCall.setAlias(updateAlias);
         }
         SqlNode selfJoinTgtExpr =
-                getSelfJoinExprForUpdate(
-                        updateCall.getTargetTable(), updateCall.getAlias().getSimple());
+                getSelfJoinExprForUpdate(updateCall.getTargetTable(), updateAlias.getSimple());
         assert selfJoinTgtExpr != null;
 
         // Create join condition between source and target exprs,
@@ -1488,7 +1611,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         // Note that we anonymize the source rather than the
         // target because downstream, the optimizer rules
         // don't want to see any projection on top of the target.
-        IdentifierNamespace ns = new IdentifierNamespace(this, target, null, null);
+        IdentifierNamespace ns = new IdentifierNamespace(this, target, null, castNonNull(null));
         RelDataType rowType = ns.getRowType();
         SqlNode source = updateCall.getTargetTable().clone(SqlParserPos.ZERO);
         final SqlNodeList selectList = new SqlNodeList(SqlParserPos.ZERO);
@@ -1538,7 +1661,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      *     been anonymized to "SYS$ANONx", where x is the 1-based column number.
      * @return expression for unique identifier, or null to prevent conversion
      */
-    protected SqlNode getSelfJoinExprForUpdate(SqlNode table, String alias) {
+    protected @Nullable SqlNode getSelfJoinExprForUpdate(SqlNode table, String alias) {
         return null;
     }
 
@@ -1561,8 +1684,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             ++ordinal;
         }
         SqlNode sourceTable = call.getTargetTable();
-        if (call.getAlias() != null) {
-            sourceTable = SqlValidatorUtil.addAlias(sourceTable, call.getAlias().getSimple());
+        SqlIdentifier alias = call.getAlias();
+        if (alias != null) {
+            sourceTable = SqlValidatorUtil.addAlias(sourceTable, alias.getSimple());
         }
         return new SqlSelect(
                 SqlParserPos.ZERO,
@@ -1590,8 +1714,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         final SqlNodeList selectList = new SqlNodeList(SqlParserPos.ZERO);
         selectList.add(SqlIdentifier.star(SqlParserPos.ZERO));
         SqlNode sourceTable = call.getTargetTable();
-        if (call.getAlias() != null) {
-            sourceTable = SqlValidatorUtil.addAlias(sourceTable, call.getAlias().getSimple());
+        SqlIdentifier alias = call.getAlias();
+        if (alias != null) {
+            sourceTable = SqlValidatorUtil.addAlias(sourceTable, alias.getSimple());
         }
         return new SqlSelect(
                 SqlParserPos.ZERO,
@@ -1611,6 +1736,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     /**
      * Returns null if there is no common type. E.g. if the rows have a different number of columns.
      */
+    @Nullable
     RelDataType getTableConstructorRowType(SqlCall values, SqlValidatorScope scope) {
         final List<SqlNode> rows = values.getOperandList();
         assert rows.size() >= 1;
@@ -1624,7 +1750,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             final List<String> aliasList = new ArrayList<>();
             final List<RelDataType> typeList = new ArrayList<>();
             for (Ord<SqlNode> column : Ord.zip(rowConstructor.getOperandList())) {
-                final String alias = deriveAlias(column.e, column.i);
+                final String alias = deriveAliasNonNull(column.e, column.i);
                 aliasList.add(alias);
                 final RelDataType type = deriveType(scope, column.e);
                 typeList.add(type);
@@ -1639,16 +1765,21 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         return typeFactory.leastRestrictive(rowTypes);
     }
 
+    @Override
     public RelDataType getValidatedNodeType(SqlNode node) {
         RelDataType type = getValidatedNodeTypeIfKnown(node);
         if (type == null) {
+            if (node.getKind() == SqlKind.IDENTIFIER) {
+                throw newValidationError(node, RESOURCE.unknownIdentifier(node.toString()));
+            }
             throw Util.needToImplement(node);
         } else {
             return type;
         }
     }
 
-    public RelDataType getValidatedNodeTypeIfKnown(SqlNode node) {
+    @Override
+    public @Nullable RelDataType getValidatedNodeTypeIfKnown(SqlNode node) {
         final RelDataType type = nodeToTypeMap.get(node);
         if (type != null) {
             return type;
@@ -1667,6 +1798,11 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         return null;
     }
 
+    @Override
+    public @Nullable List<RelDataType> getValidatedOperandTypes(SqlCall call) {
+        return callToOperandTypesMap.get(call);
+    }
+
     /**
      * Saves the type of a {@link SqlNode}, now that it has been validated.
      *
@@ -1676,9 +1812,10 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      * @param node A SQL parse tree node, never null
      * @param type Its type; must not be null
      */
+    @Override
     public final void setValidatedNodeType(SqlNode node, RelDataType type) {
-        Objects.requireNonNull(type);
-        Objects.requireNonNull(node);
+        requireNonNull(type, "type");
+        requireNonNull(node, "node");
         if (type.equals(unknownType)) {
             // don't set anything until we know what it is, and don't overwrite
             // a known type with the unknown type
@@ -1687,12 +1824,13 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         nodeToTypeMap.put(node, type);
     }
 
+    @Override
     public void removeValidatedNodeType(SqlNode node) {
         nodeToTypeMap.remove(node);
     }
 
-    @Nullable
-    public SqlCall makeNullaryCall(SqlIdentifier id) {
+    @Override
+    public @Nullable SqlCall makeNullaryCall(SqlIdentifier id) {
         if (id.names.size() == 1 && !id.isComponentQuoted(0)) {
             final List<SqlOperator> list = new ArrayList<>();
             opTab.lookupOperatorOverloads(
@@ -1711,9 +1849,10 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         return null;
     }
 
+    @Override
     public RelDataType deriveType(SqlValidatorScope scope, SqlNode expr) {
-        Objects.requireNonNull(scope);
-        Objects.requireNonNull(expr);
+        requireNonNull(scope, "scope");
+        requireNonNull(expr, "expr");
 
         // if we already know the type, no need to re-derive
         RelDataType type = nodeToTypeMap.get(expr);
@@ -1734,14 +1873,15 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     RelDataType deriveTypeImpl(SqlValidatorScope scope, SqlNode operand) {
         DeriveTypeVisitor v = new DeriveTypeVisitor(scope);
         final RelDataType type = operand.accept(v);
-        return Objects.requireNonNull(scope.nullifyType(operand, type));
+        return requireNonNull(scope.nullifyType(operand, type));
     }
 
+    @Override
     public RelDataType deriveConstructorType(
             SqlValidatorScope scope,
             SqlCall call,
             SqlFunction unresolvedConstructor,
-            SqlFunction resolvedConstructor,
+            @Nullable SqlFunction resolvedConstructor,
             List<RelDataType> argTypes) {
         SqlIdentifier sqlIdentifier = unresolvedConstructor.getSqlIdentifier();
         assert sqlIdentifier != null;
@@ -1773,7 +1913,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 ((SqlBasicCall) call)
                         .setOperator(
                                 new SqlFunction(
-                                        type.getSqlIdentifier(),
+                                        requireNonNull(
+                                                type.getSqlIdentifier(),
+                                                () -> "sqlIdentifier of " + type),
                                         ReturnTypes.explicit(type),
                                         null,
                                         null,
@@ -1784,11 +1926,12 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         return type;
     }
 
+    @Override
     public CalciteException handleUnresolvedFunction(
             SqlCall call,
-            SqlFunction unresolvedFunction,
+            SqlOperator unresolvedFunction,
             List<RelDataType> argTypes,
-            List<String> argNames) {
+            @Nullable List<String> argNames) {
         // For builtins, we can give a better error message
         final List<SqlOperator> overloads = new ArrayList<>();
         opTab.lookupOperatorOverloads(
@@ -1807,20 +1950,24 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             }
         }
 
-        AssignableOperandTypeChecker typeChecking =
-                new AssignableOperandTypeChecker(argTypes, argNames);
-        String signature =
-                typeChecking.getAllowedSignatures(unresolvedFunction, unresolvedFunction.getName());
+        final String signature;
+        if (unresolvedFunction instanceof SqlFunction) {
+            final SqlOperandTypeChecker typeChecking =
+                    new AssignableOperandTypeChecker(argTypes, argNames);
+            signature =
+                    typeChecking.getAllowedSignatures(
+                            unresolvedFunction, unresolvedFunction.getName());
+        } else {
+            signature = unresolvedFunction.getName();
+        }
         throw newValidationError(call, RESOURCE.validatorUnknownFunction(signature));
     }
 
     protected void inferUnknownTypes(
-            @Nonnull RelDataType inferredType,
-            @Nonnull SqlValidatorScope scope,
-            @Nonnull SqlNode node) {
-        Objects.requireNonNull(inferredType);
-        Objects.requireNonNull(scope);
-        Objects.requireNonNull(node);
+            RelDataType inferredType, SqlValidatorScope scope, SqlNode node) {
+        requireNonNull(inferredType, "inferredType");
+        requireNonNull(scope, "scope");
+        requireNonNull(node, "node");
         final SqlValidatorScope newScope = scopes.get(node);
         if (newScope != null) {
             scope = newScope;
@@ -1847,8 +1994,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 newInferredType =
                         typeFactory.createTypeWithCharsetAndCollation(
                                 newInferredType,
-                                inferredType.getCharset(),
-                                inferredType.getCollation());
+                                getCharset(inferredType),
+                                getCollation(inferredType));
             }
             setValidatedNodeType(node, newInferredType);
         } else if (node instanceof SqlNodeList) {
@@ -1877,18 +2024,20 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
             final RelDataType whenType =
                     caseCall.getValueOperand() == null ? booleanType : unknownType;
-            for (SqlNode sqlNode : caseCall.getWhenOperands().getList()) {
+            for (SqlNode sqlNode : caseCall.getWhenOperands()) {
                 inferUnknownTypes(whenType, scope, sqlNode);
             }
             RelDataType returnType = deriveType(scope, node);
-            for (SqlNode sqlNode : caseCall.getThenOperands().getList()) {
+            for (SqlNode sqlNode : caseCall.getThenOperands()) {
                 inferUnknownTypes(returnType, scope, sqlNode);
             }
 
-            if (!SqlUtil.isNullLiteral(caseCall.getElseOperand(), false)) {
-                inferUnknownTypes(returnType, scope, caseCall.getElseOperand());
+            SqlNode elseOperand =
+                    requireNonNull(caseCall.getElseOperand(), () -> "elseOperand for " + caseCall);
+            if (!SqlUtil.isNullLiteral(elseOperand, false)) {
+                inferUnknownTypes(returnType, scope, elseOperand);
             } else {
-                setValidatedNodeType(caseCall.getElseOperand(), returnType);
+                setValidatedNodeType(elseOperand, returnType);
             }
         } else if (node.getKind() == SqlKind.AS) {
             // For AS operator, only infer the operand not the alias
@@ -1936,10 +2085,17 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         list.add(exp);
     }
 
-    public String deriveAlias(SqlNode node, int ordinal) {
+    @Override
+    public @Nullable String deriveAlias(SqlNode node, int ordinal) {
         return SqlValidatorUtil.getAlias(node, ordinal);
     }
 
+    private String deriveAliasNonNull(SqlNode node, int ordinal) {
+        return requireNonNull(
+                deriveAlias(node, ordinal),
+                () -> "non-null alias expected for node = " + node + ", ordinal = " + ordinal);
+    }
+
     protected boolean shouldAllowIntermediateOrderBy() {
         return true;
     }
@@ -1949,7 +2105,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             SqlValidatorScope usingScope,
             SqlMatchRecognize call,
             SqlNode enclosingNode,
-            String alias,
+            @Nullable String alias,
             boolean forceNullable) {
 
         final MatchRecognizeNamespace matchRecognizeNamespace =
@@ -1985,27 +2141,55 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     private void registerPivot(
             SqlValidatorScope parentScope,
             SqlValidatorScope usingScope,
-            SqlPivot call,
+            SqlPivot pivot,
             SqlNode enclosingNode,
-            String alias,
+            @Nullable String alias,
             boolean forceNullable) {
-        final PivotNamespace namespace = createPivotNameSpace(call, enclosingNode);
+        final PivotNamespace namespace = createPivotNameSpace(pivot, enclosingNode);
         registerNamespace(usingScope, alias, namespace, forceNullable);
 
-        final SqlValidatorScope scope = new PivotScope(parentScope, call);
+        final SqlValidatorScope scope = new PivotScope(parentScope, pivot);
+        scopes.put(pivot, scope);
+
+        // parse input query
+        SqlNode expr = pivot.query;
+        SqlNode newExpr =
+                registerFrom(
+                        parentScope, scope, true, expr, expr, null, null, forceNullable, false);
+        if (expr != newExpr) {
+            pivot.setOperand(0, newExpr);
+        }
+    }
+
+    protected PivotNamespace createPivotNameSpace(SqlPivot call, SqlNode enclosingNode) {
+        return new PivotNamespace(this, call, enclosingNode);
+    }
+
+    private void registerUnpivot(
+            SqlValidatorScope parentScope,
+            SqlValidatorScope usingScope,
+            SqlUnpivot call,
+            SqlNode enclosingNode,
+            @Nullable String alias,
+            boolean forceNullable) {
+        final UnpivotNamespace namespace = createUnpivotNameSpace(call, enclosingNode);
+        registerNamespace(usingScope, alias, namespace, forceNullable);
+
+        final SqlValidatorScope scope = new UnpivotScope(parentScope, call);
         scopes.put(call, scope);
 
         // parse input query
         SqlNode expr = call.query;
         SqlNode newExpr =
-                registerFrom(scope, scope, true, expr, expr, null, null, forceNullable, false);
+                registerFrom(
+                        parentScope, scope, true, expr, expr, null, null, forceNullable, false);
         if (expr != newExpr) {
             call.setOperand(0, newExpr);
         }
     }
 
-    protected PivotNamespace createPivotNameSpace(SqlPivot call, SqlNode enclosingNode) {
-        return new PivotNamespace(this, call, enclosingNode);
+    protected UnpivotNamespace createUnpivotNameSpace(SqlUnpivot call, SqlNode enclosingNode) {
+        return new UnpivotNamespace(this, call, enclosingNode);
     }
 
     /**
@@ -2018,12 +2202,18 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      * @param forceNullable Whether to force the type of namespace to be nullable
      */
     protected void registerNamespace(
-            SqlValidatorScope usingScope,
-            String alias,
+            @Nullable SqlValidatorScope usingScope,
+            @Nullable String alias,
             SqlValidatorNamespace ns,
             boolean forceNullable) {
-        namespaces.put(ns.getNode(), ns);
+        namespaces.put(requireNonNull(ns.getNode(), () -> "ns.getNode() for " + ns), ns);
         if (usingScope != null) {
+            assert alias != null
+                    : "Registering namespace "
+                            + ns
+                            + ", into scope "
+                            + usingScope
+                            + ", so alias must not be null";
             usingScope.addChild(ns, alias, forceNullable);
         }
     }
@@ -2059,8 +2249,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             boolean register,
             final SqlNode node,
             SqlNode enclosingNode,
-            String alias,
-            SqlNodeList extendList,
+            @Nullable String alias,
+            @Nullable SqlNodeList extendList,
             boolean forceNullable,
             final boolean lateral) {
         final SqlKind kind = node.getKind();
@@ -2076,7 +2266,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 case OVER:
                     alias = deriveAlias(node, -1);
                     if (alias == null) {
-                        alias = deriveAlias(node, nextGeneratedId++);
+                        alias = deriveAliasNonNull(node, nextGeneratedId++);
                     }
                     if (config.identifierExpansion()) {
                         newNode = SqlValidatorUtil.addAlias(node, alias);
@@ -2092,11 +2282,12 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 case OTHER_FUNCTION:
                 case COLLECTION_TABLE:
                 case PIVOT:
+                case UNPIVOT:
                 case MATCH_RECOGNIZE:
 
                     // give this anonymous construct a name since later
                     // query processing stages rely on it
-                    alias = deriveAlias(node, nextGeneratedId++);
+                    alias = deriveAliasNonNull(node, nextGeneratedId++);
                     if (config.identifierExpansion()) {
                         // Since we're expanding identifiers, we should make the
                         // aliases explicit too, otherwise the expanded query
@@ -2105,6 +2296,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                         newNode = SqlValidatorUtil.addAlias(node, alias);
                     }
                     break;
+                default:
+                    break;
             }
         }
 
@@ -2131,10 +2324,17 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             case AS:
                 call = (SqlCall) node;
                 if (alias == null) {
-                    alias = call.operand(1).toString();
+                    alias = String.valueOf(call.operand(1));
                 }
-                final boolean needAlias = call.operandCount() > 2;
                 expr = call.operand(0);
+                final boolean needAlias =
+                        call.operandCount() > 2
+                                || expr.getKind() == SqlKind.VALUES
+                                || expr.getKind() == SqlKind.UNNEST
+                                        && (((SqlCall) expr).operand(0).getKind()
+                                                        == SqlKind.ARRAY_VALUE_CONSTRUCTOR
+                                                || ((SqlCall) expr).operand(0).getKind()
+                                                        == SqlKind.MULTISET_VALUE_CONSTRUCTOR);
                 newExpr =
                         registerFrom(
                                 parentScope,
@@ -2181,6 +2381,16 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                         forceNullable);
                 return node;
 
+            case UNPIVOT:
+                registerUnpivot(
+                        parentScope,
+                        usingScope,
+                        (SqlUnpivot) node,
+                        enclosingNode,
+                        alias,
+                        forceNullable);
+                return node;
+
             case TABLESAMPLE:
                 call = (SqlCall) node;
                 expr = call.operand(0);
@@ -2219,6 +2429,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                         forceLeftNullable = true;
                         forceRightNullable = true;
                         break;
+                    default:
+                        break;
                 }
                 final SqlNode newLeft =
                         registerFrom(
@@ -2261,7 +2473,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 if (tableScope == null) {
                     tableScope = new TableScope(parentScope, node);
                 }
-                tableScope.addChild(newNs, alias, forceNullable);
+                tableScope.addChild(newNs, requireNonNull(alias, "alias"), forceNullable);
                 if (extendList != null && extendList.size() != 0) {
                     return enclosingNode;
                 }
@@ -2335,7 +2547,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             case WITH:
             case OTHER_FUNCTION:
                 if (alias == null) {
-                    alias = deriveAlias(node, nextGeneratedId++);
+                    alias = deriveAliasNonNull(node, nextGeneratedId++);
                 }
                 registerQuery(
                         parentScope,
@@ -2475,10 +2687,10 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      */
     private void registerQuery(
             SqlValidatorScope parentScope,
-            SqlValidatorScope usingScope,
+            @Nullable SqlValidatorScope usingScope,
             SqlNode node,
             SqlNode enclosingNode,
-            String alias,
+            @Nullable String alias,
             boolean forceNullable) {
         Preconditions.checkArgument(usingScope == null || alias != null);
         registerQuery(parentScope, usingScope, node, enclosingNode, alias, forceNullable, true);
@@ -2496,14 +2708,14 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      */
     private void registerQuery(
             SqlValidatorScope parentScope,
-            SqlValidatorScope usingScope,
+            @Nullable SqlValidatorScope usingScope,
             SqlNode node,
             SqlNode enclosingNode,
-            String alias,
+            @Nullable String alias,
             boolean forceNullable,
             boolean checkUpdate) {
-        Objects.requireNonNull(node);
-        Objects.requireNonNull(enclosingNode);
+        requireNonNull(node, "node");
+        requireNonNull(enclosingNode, "enclosingNode");
         Preconditions.checkArgument(usingScope == null || alias != null);
 
         SqlCall call;
@@ -2560,7 +2772,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                     registerSubQueries(groupByScope, select.getGroup());
                 }
                 registerOperandSubQueries(aggScope, select, SqlSelect.HAVING_OPERAND);
-                registerSubQueries(aggScope, select.getSelectList());
+                registerSubQueries(aggScope, SqlNonNullableAccessors.getSelectList(select));
                 final SqlNodeList orderList = select.getOrderList();
                 if (orderList != null) {
                     // If the query is 'SELECT DISTINCT', restrict the columns
@@ -2647,7 +2859,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 registerQuery(
                         parentScope,
                         usingScope,
-                        deleteCall.getSourceSelect(),
+                        SqlNonNullableAccessors.getSourceSelect(deleteCall),
                         enclosingNode,
                         null,
                         false);
@@ -2664,7 +2876,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 registerQuery(
                         parentScope,
                         usingScope,
-                        updateCall.getSourceSelect(),
+                        SqlNonNullableAccessors.getSourceSelect(updateCall),
                         enclosingNode,
                         null,
                         false);
@@ -2679,7 +2891,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 registerQuery(
                         parentScope,
                         usingScope,
-                        mergeCall.getSourceSelect(),
+                        SqlNonNullableAccessors.getSourceSelect(mergeCall),
                         enclosingNode,
                         null,
                         false);
@@ -2688,24 +2900,22 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 // or the target table, so set its parent scope to the merge's
                 // source select; when validating the update, skip the feature
                 // validation check
-                if (mergeCall.getUpdateCall() != null) {
+                SqlUpdate mergeUpdateCall = mergeCall.getUpdateCall();
+                if (mergeUpdateCall != null) {
                     registerQuery(
-                            clauseScopes.get(IdPair.of(mergeCall.getSourceSelect(), Clause.WHERE)),
+                            getScope(
+                                    SqlNonNullableAccessors.getSourceSelect(mergeCall),
+                                    Clause.WHERE),
                             null,
-                            mergeCall.getUpdateCall(),
+                            mergeUpdateCall,
                             enclosingNode,
                             null,
                             false,
                             false);
                 }
-                if (mergeCall.getInsertCall() != null) {
-                    registerQuery(
-                            parentScope,
-                            null,
-                            mergeCall.getInsertCall(),
-                            enclosingNode,
-                            null,
-                            false);
+                SqlInsert mergeInsertCall = mergeCall.getInsertCall();
+                if (mergeInsertCall != null) {
+                    registerQuery(parentScope, null, mergeInsertCall, enclosingNode, null, false);
                 }
                 break;
 
@@ -2732,7 +2942,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 CollectScope cs = new CollectScope(parentScope, usingScope, call);
                 final CollectNamespace tableConstructorNs =
                         new CollectNamespace(call, cs, enclosingNode);
-                final String alias2 = deriveAlias(node, nextGeneratedId++);
+                final String alias2 = deriveAliasNonNull(node, nextGeneratedId++);
                 registerNamespace(usingScope, alias2, tableConstructorNs, forceNullable);
                 operands = call.getOperandList();
                 for (int i = 0; i < operands.size(); i++) {
@@ -2747,10 +2957,10 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
     private void registerSetop(
             SqlValidatorScope parentScope,
-            SqlValidatorScope usingScope,
+            @Nullable SqlValidatorScope usingScope,
             SqlNode node,
             SqlNode enclosingNode,
-            String alias,
+            @Nullable String alias,
             boolean forceNullable) {
         SqlCall call = (SqlCall) node;
         final SetopNamespace setopNamespace = createSetopNamespace(call, enclosingNode);
@@ -2765,10 +2975,10 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
     private void registerWith(
             SqlValidatorScope parentScope,
-            SqlValidatorScope usingScope,
+            @Nullable SqlValidatorScope usingScope,
             SqlWith with,
             SqlNode enclosingNode,
-            String alias,
+            @Nullable String alias,
             boolean forceNullable,
             boolean checkUpdate) {
         final WithNamespace withNamespace = new WithNamespace(this, with, enclosingNode);
@@ -2789,12 +2999,13 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         registerQuery(scope, null, with.body, enclosingNode, alias, forceNullable, checkUpdate);
     }
 
+    @Override
     public boolean isAggregate(SqlSelect select) {
         if (getAggregate(select) != null) {
             return true;
         }
         // Also when nested window aggregates are present
-        for (SqlCall call : overFinder.findAll(select.getSelectList())) {
+        for (SqlCall call : overFinder.findAll(SqlNonNullableAccessors.getSelectList(select))) {
             assert call.getKind() == SqlKind.OVER;
             if (isNestedAggregateWindow(call.operand(0))) {
                 return true;
@@ -2823,7 +3034,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      * <p>The node is useful context for error messages, but you cannot assume that the node is the
      * only aggregate function.
      */
-    protected SqlNode getAggregate(SqlSelect select) {
+    protected @Nullable SqlNode getAggregate(SqlSelect select) {
         SqlNode node = select.getGroup();
         if (node != null) {
             return node;
@@ -2836,7 +3047,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     }
 
     /** If there is at least one call to an aggregate function, returns the first. */
-    private SqlNode getAgg(SqlSelect select) {
+    private @Nullable SqlNode getAgg(SqlSelect select) {
         final SelectScope selectScope = getRawSelectScope(select);
         if (selectScope != null) {
             final List<SqlNode> selectList = selectScope.getExpandedSelectList();
@@ -2844,7 +3055,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 return aggFinder.findAgg(selectList);
             }
         }
-        return aggFinder.findAgg(select.getSelectList());
+        return aggFinder.findAgg(SqlNonNullableAccessors.getSelectList(select));
     }
 
     @Deprecated
@@ -2858,10 +3069,12 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             case MULTISET_VALUE_CONSTRUCTOR:
                 validateFeature(RESOURCE.sQLFeature_S271(), node.getParserPosition());
                 break;
+            default:
+                break;
         }
     }
 
-    private void registerSubQueries(SqlValidatorScope parentScope, SqlNode node) {
+    private void registerSubQueries(SqlValidatorScope parentScope, @Nullable SqlNode node) {
         if (node == null) {
             return;
         }
@@ -2917,6 +3130,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         registerSubQueries(parentScope, operand);
     }
 
+    @Override
     public void validateIdentifier(SqlIdentifier id, SqlValidatorScope scope) {
         final SqlQualified fqId = scope.fullyQualify(id);
         if (this.config.columnReferenceExpansion()) {
@@ -2928,6 +3142,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
     }
 
+    @Override
     public void validateLiteral(SqlLiteral literal) {
         switch (literal.getTypeName()) {
             case DECIMAL:
@@ -2940,7 +3155,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 //
                 // jhyde 2006/12/21: I think the limits should be baked into the
                 // type system, not dependent on the calculator implementation.
-                BigDecimal bd = (BigDecimal) literal.getValue();
+                BigDecimal bd = literal.getValueAs(BigDecimal.class);
                 BigInteger unscaled = bd.unscaledValue();
                 long longValue = unscaled.longValue();
                 if (!BigInteger.valueOf(longValue).equals(unscaled)) {
@@ -2955,7 +3170,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 break;
 
             case BINARY:
-                final BitString bitString = (BitString) literal.getValue();
+                final BitString bitString = literal.getValueAs(BitString.class);
                 if ((bitString.getBitCount() % 8) != 0) {
                     throw newValidationError(literal, RESOURCE.binaryLiteralOdd());
                 }
@@ -3009,7 +3224,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     }
 
     private void validateLiteralAsDouble(SqlLiteral literal) {
-        BigDecimal bd = (BigDecimal) literal.getValue();
+        BigDecimal bd = literal.getValueAs(BigDecimal.class);
         double d = bd.doubleValue();
         if (Double.isInfinite(d) || Double.isNaN(d)) {
             // overflow
@@ -3020,6 +3235,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         // REVIEW jvs 4-Aug-2004:  what about underflow?
     }
 
+    @Override
     public void validateIntervalQualifier(SqlIntervalQualifier qualifier) {
         assert qualifier != null;
         boolean startPrecisionOutOfRange = false;
@@ -3063,7 +3279,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      * @param scope Scope
      */
     protected void validateFrom(SqlNode node, RelDataType targetRowType, SqlValidatorScope scope) {
-        Objects.requireNonNull(targetRowType);
+        requireNonNull(targetRowType, "targetRowType");
         switch (node.getKind()) {
             case AS:
             case TABLE_REF:
@@ -3088,7 +3304,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
         // Validate the namespace representation of the node, just in case the
         // validation did not occur implicitly.
-        getNamespace(node, scope).validate(targetRowType);
+        getNamespaceOrThrow(node, scope).validate(targetRowType);
     }
 
     protected void validateOver(SqlCall call, SqlValidatorScope scope) {
@@ -3110,7 +3326,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         if (namespace != null) {
             SqlValidatorTable sqlValidatorTable = namespace.getTable();
             if (sqlValidatorTable != null) {
-                Table table = sqlValidatorTable.unwrap(Table.class);
+                Table table = sqlValidatorTable.table();
                 String column = Util.last(identifier.names);
 
                 if (table.isRolledUp(column)) {
@@ -3128,7 +3344,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         boolean natural = join.isNatural();
         final JoinType joinType = join.getJoinType();
         final JoinConditionType conditionType = join.getConditionType();
-        final SqlValidatorScope joinScope = scopes.get(join);
+        final SqlValidatorScope joinScope = getScopeOrThrow(join); // getJoinScope?
         validateFrom(left, unknownType, joinScope);
         validateFrom(right, unknownType, joinScope);
 
@@ -3138,15 +3354,15 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 Preconditions.checkArgument(condition == null);
                 break;
             case ON:
-                Preconditions.checkArgument(condition != null);
+                requireNonNull(condition, "join.getCondition()");
                 SqlNode expandedCondition = expand(condition, joinScope);
                 join.setOperand(5, expandedCondition);
-                condition = join.getCondition();
+                condition = getCondition(join);
                 validateWhereOrOn(joinScope, condition, "ON");
                 checkRollUp(null, join, condition, joinScope, "ON");
                 break;
             case USING:
-                SqlNodeList list = (SqlNodeList) condition;
+                SqlNodeList list = (SqlNodeList) requireNonNull(condition, "join.getCondition()");
 
                 // Parser ensures that using clause is not empty.
                 Preconditions.checkArgument(list.size() > 0, "Empty USING clause");
@@ -3178,8 +3394,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
             // Join on fields that occur exactly once on each side. Ignore
             // fields that occur more than once on either side.
-            final RelDataType leftRowType = getNamespace(left).getRowType();
-            final RelDataType rightRowType = getNamespace(right).getRowType();
+            final RelDataType leftRowType = getNamespaceOrThrow(left).getRowType();
+            final RelDataType rightRowType = getNamespaceOrThrow(right).getRowType();
             final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
             List<String> naturalColumnNames =
                     SqlValidatorUtil.deriveNaturalJoinColumnList(
@@ -3187,8 +3403,24 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
             // Check compatibility of the chosen columns.
             for (String name : naturalColumnNames) {
-                final RelDataType leftColType = nameMatcher.field(leftRowType, name).getType();
-                final RelDataType rightColType = nameMatcher.field(rightRowType, name).getType();
+                final RelDataType leftColType =
+                        requireNonNull(
+                                        nameMatcher.field(leftRowType, name),
+                                        () ->
+                                                "unable to find left field "
+                                                        + name
+                                                        + " in "
+                                                        + leftRowType)
+                                .getType();
+                final RelDataType rightColType =
+                        requireNonNull(
+                                        nameMatcher.field(rightRowType, name),
+                                        () ->
+                                                "unable to find right field "
+                                                        + name
+                                                        + " in "
+                                                        + rightRowType)
+                                .getType();
                 if (!SqlTypeUtil.isComparable(leftColType, rightColType)) {
                     throw newValidationError(
                             join,
@@ -3258,7 +3490,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     private RelDataType validateUsingCol(SqlIdentifier id, SqlNode leftOrRight) {
         if (id.names.size() == 1) {
             String name = id.names.get(0);
-            final SqlValidatorNamespace namespace = getNamespace(leftOrRight);
+            final SqlValidatorNamespace namespace = getNamespaceOrThrow(leftOrRight);
             final RelDataType rowType = namespace.getRowType();
             final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
             final RelDataTypeField field = nameMatcher.field(rowType, name);
@@ -3281,20 +3513,19 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     protected void validateSelect(SqlSelect select, RelDataType targetRowType) {
         assert targetRowType != null;
         // Namespace is either a select namespace or a wrapper around one.
-        final SelectNamespace ns = getNamespace(select).unwrap(SelectNamespace.class);
+        final SelectNamespace ns = getNamespaceOrThrow(select).unwrap(SelectNamespace.class);
 
         // Its rowtype is null, meaning it hasn't been validated yet.
         // This is important, because we need to take the targetRowType into
         // account.
         assert ns.rowType == null;
 
-        if (select.isDistinct()) {
-            validateFeature(
-                    RESOURCE.sQLFeature_E051_01(),
-                    select.getModifierNode(SqlSelectKeyword.DISTINCT).getParserPosition());
+        SqlNode distinctNode = select.getModifierNode(SqlSelectKeyword.DISTINCT);
+        if (distinctNode != null) {
+            validateFeature(RESOURCE.sQLFeature_E051_01(), distinctNode.getParserPosition());
         }
 
-        final SqlNodeList selectItems = select.getSelectList();
+        final SqlNodeList selectItems = SqlNonNullableAccessors.getSelectList(select);
         RelDataType fromType = unknownType;
         if (selectItems.size() == 1) {
             final SqlNode selectItem = selectItems.get(0);
@@ -3312,19 +3543,25 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
 
         // Make sure that items in FROM clause have distinct aliases.
-        final SelectScope fromScope = (SelectScope) getFromScope(select);
-        List<String> names = fromScope.getChildNames();
+        final SelectScope fromScope =
+                (SelectScope) requireNonNull(getFromScope(select), () -> "fromScope for " + select);
+        List<@Nullable String> names = fromScope.getChildNames();
         if (!catalogReader.nameMatcher().isCaseSensitive()) {
+            //noinspection RedundantTypeArguments
             names =
                     names.stream()
-                            .map(s -> s.toUpperCase(Locale.ROOT))
+                            .<@Nullable String>map(
+                                    s -> s == null ? null : s.toUpperCase(Locale.ROOT))
                             .collect(Collectors.toList());
         }
         final int duplicateAliasOrdinal = Util.firstDuplicate(names);
         if (duplicateAliasOrdinal >= 0) {
             final ScopeChild child = fromScope.children.get(duplicateAliasOrdinal);
             throw newValidationError(
-                    child.namespace.getEnclosingNode(), RESOURCE.fromAliasDuplicate(child.name));
+                    requireNonNull(
+                            child.namespace.getEnclosingNode(),
+                            () -> "enclosingNode of namespace of " + child.name),
+                    RESOURCE.fromAliasDuplicate(child.name));
         }
 
         if (select.getFrom() == null) {
@@ -3364,7 +3601,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
     private void checkRollUpInSelectList(SqlSelect select) {
         SqlValidatorScope scope = getSelectScope(select);
-        for (SqlNode item : select.getSelectList()) {
+        for (SqlNode item : SqlNonNullableAccessors.getSelectList(select)) {
             checkRollUp(null, select, item, scope);
         }
     }
@@ -3387,7 +3624,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
     }
 
-    private void checkRollUpInWindow(SqlWindow window, SqlValidatorScope scope) {
+    private void checkRollUpInWindow(@Nullable SqlWindow window, SqlValidatorScope scope) {
         if (window != null) {
             for (SqlNode node : window.getPartitionList()) {
                 checkRollUp(null, window, node, scope, "PARTITION BY");
@@ -3405,7 +3642,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
     }
 
-    private SqlNode stripDot(SqlNode node) {
+    private static @Nullable SqlNode stripDot(@Nullable SqlNode node) {
         if (node != null && node.getKind() == SqlKind.DOT) {
             return stripDot(((SqlCall) node).operand(0));
         }
@@ -3413,42 +3650,47 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     }
 
     private void checkRollUp(
-            SqlNode grandParent,
-            SqlNode parent,
-            SqlNode current,
+            @Nullable SqlNode grandParent,
+            @Nullable SqlNode parent,
+            @Nullable SqlNode current,
             SqlValidatorScope scope,
-            String optionalClause) {
+            @Nullable String optionalClause) {
         current = stripAs(current);
         if (current instanceof SqlCall && !(current instanceof SqlSelect)) {
             // Validate OVER separately
             checkRollUpInWindow(getWindowInOver(current), scope);
             current = stripOver(current);
 
-            List<SqlNode> children = ((SqlCall) stripAs(stripDot(current))).getOperandList();
+            SqlNode stripDot = requireNonNull(stripDot(current), "stripDot(current)");
+            List<? extends @Nullable SqlNode> children =
+                    ((SqlCall) stripAs(stripDot)).getOperandList();
             for (SqlNode child : children) {
                 checkRollUp(parent, current, child, scope, optionalClause);
             }
         } else if (current instanceof SqlIdentifier) {
             SqlIdentifier id = (SqlIdentifier) current;
             if (!id.isStar() && isRolledUpColumn(id, scope)) {
-                if (!isAggregation(parent.getKind())
+                if (!isAggregation(requireNonNull(parent, "parent").getKind())
                         || !isRolledUpColumnAllowedInAgg(
                                 id, scope, (SqlCall) parent, grandParent)) {
                     String context =
                             optionalClause != null ? optionalClause : parent.getKind().toString();
                     throw newValidationError(
-                            id, RESOURCE.rolledUpNotAllowed(deriveAlias(id, 0), context));
+                            id, RESOURCE.rolledUpNotAllowed(deriveAliasNonNull(id, 0), context));
                 }
             }
         }
     }
 
     private void checkRollUp(
-            SqlNode grandParent, SqlNode parent, SqlNode current, SqlValidatorScope scope) {
+            @Nullable SqlNode grandParent,
+            SqlNode parent,
+            @Nullable SqlNode current,
+            SqlValidatorScope scope) {
         checkRollUp(grandParent, parent, current, scope, null);
     }
 
-    private SqlWindow getWindowInOver(SqlNode over) {
+    private static @Nullable SqlWindow getWindowInOver(SqlNode over) {
         if (over.getKind() == SqlKind.OVER) {
             SqlNode window = ((SqlCall) over).getOperandList().get(1);
             if (window instanceof SqlWindow) {
@@ -3469,7 +3711,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
     }
 
-    private Pair<String, String> findTableColumnPair(
+    private @Nullable Pair<String, String> findTableColumnPair(
             SqlIdentifier identifier, SqlValidatorScope scope) {
         final SqlCall call = makeNullaryCall(identifier);
         if (call != null) {
@@ -3487,7 +3729,10 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
     // Returns true iff the given column is valid inside the given aggCall.
     private boolean isRolledUpColumnAllowedInAgg(
-            SqlIdentifier identifier, SqlValidatorScope scope, SqlCall aggCall, SqlNode parent) {
+            SqlIdentifier identifier,
+            SqlValidatorScope scope,
+            SqlCall aggCall,
+            @Nullable SqlNode parent) {
         Pair<String, String> pair = findTableColumnPair(identifier, scope);
 
         if (pair == null) {
@@ -3496,15 +3741,24 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
         String columnName = pair.right;
 
-        SqlValidatorTable sqlValidatorTable = scope.fullyQualify(identifier).namespace.getTable();
-        if (sqlValidatorTable != null) {
-            Table table = sqlValidatorTable.unwrap(Table.class);
+        Table table = resolveTable(identifier, scope);
+        if (table != null) {
             return table.rolledUpColumnValidInsideAgg(
                     columnName, aggCall, parent, catalogReader.getConfig());
         }
         return true;
     }
 
+    private static @Nullable Table resolveTable(SqlIdentifier identifier, SqlValidatorScope scope) {
+        SqlQualified fullyQualified = scope.fullyQualify(identifier);
+        assert fullyQualified.namespace != null : "namespace must not be null in " + fullyQualified;
+        SqlValidatorTable sqlValidatorTable = fullyQualified.namespace.getTable();
+        if (sqlValidatorTable != null) {
+            return sqlValidatorTable.table();
+        }
+        return null;
+    }
+
     // Returns true iff the given column is actually rolled up.
     private boolean isRolledUpColumn(SqlIdentifier identifier, SqlValidatorScope scope) {
         Pair<String, String> pair = findTableColumnPair(identifier, scope);
@@ -3515,15 +3769,14 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
         String columnName = pair.right;
 
-        SqlValidatorTable sqlValidatorTable = scope.fullyQualify(identifier).namespace.getTable();
-        if (sqlValidatorTable != null) {
-            Table table = sqlValidatorTable.unwrap(Table.class);
+        Table table = resolveTable(identifier, scope);
+        if (table != null) {
             return table.isRolledUp(columnName);
         }
         return false;
     }
 
-    private boolean shouldCheckForRollUp(SqlNode from) {
+    private static boolean shouldCheckForRollUp(@Nullable SqlNode from) {
         if (from != null) {
             SqlKind kind = stripAs(from).getKind();
             return kind != SqlKind.VALUES && kind != SqlKind.SELECT;
@@ -3544,6 +3797,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             switch (modality) {
                 case STREAM:
                     throw newValidationError(query, Static.RESOURCE.cannotStreamValues());
+                default:
+                    break;
             }
         } else {
             assert query.isA(SqlKind.SET_QUERY);
@@ -3559,7 +3814,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     }
 
     /** Return the intended modality of a SELECT or set-op. */
-    private SqlModality deduceModality(SqlNode query) {
+    private static SqlModality deduceModality(SqlNode query) {
         if (query instanceof SqlSelect) {
             SqlSelect select = (SqlSelect) query;
             return select.getModifierNode(SqlSelectKeyword.STREAM) != null
@@ -3574,8 +3829,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
     }
 
+    @Override
     public boolean validateModality(SqlSelect select, SqlModality modality, boolean fail) {
-        final SelectScope scope = getRawSelectScope(select);
+        final SelectScope scope = getRawSelectScopeNonNull(select);
 
         switch (modality) {
             case STREAM:
@@ -3583,9 +3839,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                     for (ScopeChild child : scope.children) {
                         if (!child.namespace.supportsModality(modality)) {
                             if (fail) {
+                                SqlNode node = SqlNonNullableAccessors.getNode(child);
                                 throw newValidationError(
-                                        child.namespace.getNode(),
-                                        Static.RESOURCE.cannotConvertToStream(child.name));
+                                        node, Static.RESOURCE.cannotConvertToStream(child.name));
                             } else {
                                 return false;
                             }
@@ -3616,9 +3872,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 for (ScopeChild child : scope.children) {
                     if (!child.namespace.supportsModality(modality)) {
                         if (fail) {
+                            SqlNode node = SqlNonNullableAccessors.getNode(child);
                             throw newValidationError(
-                                    child.namespace.getNode(),
-                                    Static.RESOURCE.cannotConvertToRelation(child.name));
+                                    node, Static.RESOURCE.cannotConvertToRelation(child.name));
                         } else {
                             return false;
                         }
@@ -3641,6 +3897,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                             return false;
                         }
                     }
+                    break;
+                default:
+                    break;
             }
         }
 
@@ -3657,20 +3916,25 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                             return false;
                         }
                     }
+                    break;
+                default:
+                    break;
             }
         }
         return true;
     }
 
     /** Returns whether the prefix is sorted. */
-    private boolean hasSortedPrefix(SelectScope scope, SqlNodeList orderList) {
+    private static boolean hasSortedPrefix(SelectScope scope, SqlNodeList orderList) {
         return isSortCompatible(scope, orderList.get(0), false);
     }
 
-    private boolean isSortCompatible(SelectScope scope, SqlNode node, boolean descending) {
+    private static boolean isSortCompatible(SelectScope scope, SqlNode node, boolean descending) {
         switch (node.getKind()) {
             case DESCENDING:
                 return isSortCompatible(scope, ((SqlCall) node).getOperandList().get(0), true);
+            default:
+                break;
         }
         final SqlMonotonicity monotonicity = scope.getMonotonicity(node);
         switch (monotonicity) {
@@ -3685,21 +3949,22 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
     }
 
+    @SuppressWarnings({"unchecked", "rawtypes"})
     protected void validateWindowClause(SqlSelect select) {
         final SqlNodeList windowList = select.getWindowList();
-        @SuppressWarnings("unchecked")
-        final List<SqlWindow> windows = (List) windowList.getList();
-        if (windows.isEmpty()) {
+        if (windowList.isEmpty()) {
             return;
         }
 
-        final SelectScope windowScope = (SelectScope) getFromScope(select);
-        assert windowScope != null;
+        final SelectScope windowScope =
+                (SelectScope) requireNonNull(getFromScope(select), () -> "fromScope for " + select);
 
         // 1. ensure window names are simple
         // 2. ensure they are unique within this scope
-        for (SqlWindow window : windows) {
-            SqlIdentifier declName = window.getDeclName();
+        for (SqlWindow window : (List<SqlWindow>) (List) windowList) {
+            SqlIdentifier declName =
+                    requireNonNull(
+                            window.getDeclName(), () -> "window.getDeclName() for " + window);
             if (!declName.isSimple()) {
                 throw newValidationError(declName, RESOURCE.windowNameMustBeSimple());
             }
@@ -3713,17 +3978,17 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
         // 7.10 rule 2
         // Check for pairs of windows which are equivalent.
-        for (int i = 0; i < windows.size(); i++) {
-            SqlNode window1 = windows.get(i);
-            for (int j = i + 1; j < windows.size(); j++) {
-                SqlNode window2 = windows.get(j);
+        for (int i = 0; i < windowList.size(); i++) {
+            SqlNode window1 = windowList.get(i);
+            for (int j = i + 1; j < windowList.size(); j++) {
+                SqlNode window2 = windowList.get(j);
                 if (window1.equalsDeep(window2, Litmus.IGNORE)) {
                     throw newValidationError(window2, RESOURCE.dupWindowSpec());
                 }
             }
         }
 
-        for (SqlWindow window : windows) {
+        for (SqlWindow window : (List<SqlWindow>) (List) windowList) {
             final SqlNodeList expandedOrderList =
                     (SqlNodeList) expand(window.getOrderList(), windowScope);
             window.setOrderList(expandedOrderList);
@@ -3739,20 +4004,22 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         windowList.validate(this, windowScope);
     }
 
+    @Override
     public void validateWith(SqlWith with, SqlValidatorScope scope) {
-        final SqlValidatorNamespace namespace = getNamespace(with);
+        final SqlValidatorNamespace namespace = getNamespaceOrThrow(with);
         validateNamespace(namespace, unknownType);
     }
 
+    @Override
     public void validateWithItem(SqlWithItem withItem) {
-        if (withItem.columnList != null) {
+        SqlNodeList columnList = withItem.columnList;
+        if (columnList != null) {
             final RelDataType rowType = getValidatedNodeType(withItem.query);
             final int fieldCount = rowType.getFieldCount();
-            if (withItem.columnList.size() != fieldCount) {
-                throw newValidationError(withItem.columnList, RESOURCE.columnCountMismatch());
+            if (columnList.size() != fieldCount) {
+                throw newValidationError(columnList, RESOURCE.columnCountMismatch());
             }
-            SqlValidatorUtil.checkIdentifierListForDuplicates(
-                    withItem.columnList.getList(), validationErrorFunction);
+            SqlValidatorUtil.checkIdentifierListForDuplicates(columnList, validationErrorFunction);
         } else {
             // Luckily, field names have not been make unique yet.
             final List<String> fieldNames = getValidatedNodeType(withItem.query).getFieldNames();
@@ -3764,6 +4031,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
     }
 
+    @Override
     public void validateSequenceValue(SqlValidatorScope scope, SqlIdentifier id) {
         // Resolve identifier as a table.
         final SqlValidatorScope.ResolvedImpl resolved = new SqlValidatorScope.ResolvedImpl();
@@ -3775,30 +4043,36 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         // We've found a table. But is it a sequence?
         final SqlValidatorNamespace ns = resolved.only().namespace;
         if (ns instanceof TableNamespace) {
-            final Table table = ns.getTable().unwrap(Table.class);
+            final Table table = getTable(ns).table();
             switch (table.getJdbcTableType()) {
                 case SEQUENCE:
                 case TEMPORARY_SEQUENCE:
                     return;
+                default:
+                    break;
             }
         }
         throw newValidationError(id, RESOURCE.notASequence(id.toString()));
     }
 
-    public SqlValidatorScope getWithScope(SqlNode withItem) {
+    @Override
+    public @Nullable SqlValidatorScope getWithScope(SqlNode withItem) {
         assert withItem.getKind() == SqlKind.WITH_ITEM;
         return scopes.get(withItem);
     }
 
+    @Override
     public TypeCoercion getTypeCoercion() {
         assert config.typeCoercionEnabled();
         return this.typeCoercion;
     }
 
+    @Override
     public Config config() {
         return this.config;
     }
 
+    @Override
     public SqlValidator transform(UnaryOperator<Config> transform) {
         this.config = transform.apply(this.config);
         return this;
@@ -3823,7 +4097,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             }
         }
         final SqlValidatorScope orderScope = getOrderScope(select);
-        Objects.requireNonNull(orderScope);
+        requireNonNull(orderScope, "orderScope");
 
         List<SqlNode> expandList = new ArrayList<>();
         for (SqlNode orderItem : orderList) {
@@ -3879,12 +4153,15 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                         RESOURCE.sQLConformance_OrderByDesc(), orderItem.getParserPosition());
                 validateOrderItem(select, ((SqlCall) orderItem).operand(0));
                 return;
+            default:
+                break;
         }
 
         final SqlValidatorScope orderScope = getOrderScope(select);
         validateExpr(orderItem, orderScope);
     }
 
+    @Override
     public SqlNode expandOrderExpr(SqlSelect select, SqlNode orderExpr) {
         final SqlNode newSqlNode = new OrderExpressionExpander(select, orderExpr).go();
         if (newSqlNode != orderExpr) {
@@ -3960,7 +4237,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
     private void validateGroupItem(
             SqlValidatorScope groupScope,
-            AggregatingSelectScope aggregatingScope,
+            @Nullable AggregatingSelectScope aggregatingScope,
             SqlNode groupItem) {
         switch (groupItem.getKind()) {
             case GROUPING_SETS:
@@ -3979,7 +4256,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
     private void validateGroupingSets(
             SqlValidatorScope groupScope,
-            AggregatingSelectScope aggregatingScope,
+            @Nullable AggregatingSelectScope aggregatingScope,
             SqlCall groupItem) {
         for (SqlNode node : groupItem.getOperandList()) {
             validateGroupItem(groupScope, aggregatingScope, node);
@@ -4077,7 +4354,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         if (config.identifierExpansion()) {
             select.setSelectList(newSelectList);
         }
-        getRawSelectScope(select).setExpandedSelectList(expandedSelectItems);
+        getRawSelectScopeNonNull(select).setExpandedSelectList(expandedSelectItems);
 
         // TODO: when SELECT appears as a value sub-query, should be using
         // something other than unknownType for targetRowType
@@ -4133,7 +4410,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             Set<String> aliasList,
             List<Map.Entry<String, RelDataType>> fieldList) {
         // A scalar sub-query only has one output column.
-        if (1 != selectItem.getSelectList().size()) {
+        if (1 != SqlNonNullableAccessors.getSelectList(selectItem).size()) {
             throw newValidationError(selectItem, RESOURCE.onlyScalarSubQueryAllowed());
         }
 
@@ -4141,7 +4418,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         expandedSelectItems.add(selectItem);
 
         // Get or generate alias and add to list.
-        final String alias = deriveAlias(selectItem, aliasList.size());
+        final String alias = deriveAliasNonNull(selectItem, aliasList.size());
         aliasList.add(alias);
 
         final SelectScope scope = (SelectScope) getWhereScope(parentSelect);
@@ -4169,7 +4446,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      * @return Rowtype
      */
     protected RelDataType createTargetRowType(
-            SqlValidatorTable table, SqlNodeList targetColumnList, boolean append) {
+            SqlValidatorTable table, @Nullable SqlNodeList targetColumnList, boolean append) {
         RelDataType baseRowType = table.getRowType();
         if (targetColumnList == null) {
             return baseRowType;
@@ -4202,8 +4479,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         return typeFactory.createStructType(fields);
     }
 
+    @Override
     public void validateInsert(SqlInsert insert) {
-        final SqlValidatorNamespace targetNamespace = getNamespace(insert);
+        final SqlValidatorNamespace targetNamespace = getNamespaceOrThrow(insert);
         validateNamespace(targetNamespace, unknownType);
         final RelOptTable relOptTable =
                 SqlValidatorUtil.getRelOptTable(
@@ -4213,8 +4491,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                         null);
         final SqlValidatorTable table =
                 relOptTable == null
-                        ? targetNamespace.getTable()
-                        : relOptTable.unwrap(SqlValidatorTable.class);
+                        ? getTable(targetNamespace)
+                        : relOptTable.unwrapOrThrow(SqlValidatorTable.class);
 
         // INSERT has an optional column name list.  If present then
         // reduce the rowtype to the columns specified.  If not present
@@ -4236,13 +4514,13 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         // from validateSelect above).  It would be better if that information
         // were used here so that we never saw any untyped nulls during
         // checkTypeAssignment.
-        final RelDataType sourceRowType = getNamespace(source).getRowType();
+        final RelDataType sourceRowType = getNamespaceOrThrow(source).getRowType();
         final RelDataType logicalTargetRowType = getLogicalTargetRowType(targetRowType, insert);
         setValidatedNodeType(insert, logicalTargetRowType);
         final RelDataType logicalSourceRowType = getLogicalSourceRowType(sourceRowType, insert);
 
         final List<ColumnStrategy> strategies =
-                table.unwrap(RelOptTable.class).getColumnStrategies();
+                table.unwrapOrThrow(RelOptTable.class).getColumnStrategies();
 
         final RelDataType realTargetRowType =
                 typeFactory.createStructType(
@@ -4288,7 +4566,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         final ModifiableViewTable modifiableViewTable =
                 validatorTable.unwrap(ModifiableViewTable.class);
         if (modifiableViewTable != null && source instanceof SqlCall) {
-            final Table table = modifiableViewTable.unwrap(Table.class);
+            final Table table = modifiableViewTable.getTable();
             final RelDataType tableRowType = table.getRowType(typeFactory);
             final List<RelDataTypeField> tableFields = tableRowType.getFieldList();
 
@@ -4302,15 +4580,19 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
             // Determine columns (indexed to the underlying table) that need
             // to be validated against the view constraint.
+            @SuppressWarnings("RedundantCast")
             final ImmutableBitSet targetColumns =
-                    ImmutableBitSet.of(tableIndexToTargetField.keySet());
-            final ImmutableBitSet constrainedColumns = ImmutableBitSet.of(projectMap.keySet());
-            final ImmutableBitSet constrainedTargetColumns =
-                    targetColumns.intersect(constrainedColumns);
+                    ImmutableBitSet.of((Iterable<Integer>) tableIndexToTargetField.keySet());
+            @SuppressWarnings("RedundantCast")
+            final ImmutableBitSet constrainedColumns =
+                    ImmutableBitSet.of((Iterable<Integer>) projectMap.keySet());
+            @SuppressWarnings("assignment.type.incompatible")
+            List<@KeyFor({"tableIndexToTargetField", "projectMap"}) Integer>
+                    constrainedTargetColumns = targetColumns.intersect(constrainedColumns).asList();
 
             // Validate insert values against the view constraint.
             final List<SqlNode> values = ((SqlCall) source).getOperandList();
-            for (final int colIndex : constrainedTargetColumns.asList()) {
+            for (final int colIndex : constrainedTargetColumns) {
                 final String colName = tableFields.get(colIndex).getName();
                 final RelDataTypeField targetField = tableIndexToTargetField.get(colIndex);
                 for (SqlNode row : values) {
@@ -4340,7 +4622,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         final ModifiableViewTable modifiableViewTable =
                 validatorTable.unwrap(ModifiableViewTable.class);
         if (modifiableViewTable != null) {
-            final Table table = modifiableViewTable.unwrap(Table.class);
+            final Table table = modifiableViewTable.getTable();
             final RelDataType tableRowType = table.getRowType(typeFactory);
 
             final Map<Integer, RexNode> projectMap =
@@ -4350,23 +4632,26 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                     SqlValidatorUtil.mapNameToIndex(tableRowType.getFieldList());
 
             // Validate update values against the view constraint.
-            final List<SqlNode> targets = update.getTargetColumnList().getList();
-            final List<SqlNode> sources = update.getSourceExpressionList().getList();
-            for (final Pair<SqlNode, SqlNode> column : Pair.zip(targets, sources)) {
-                final String columnName = ((SqlIdentifier) column.left).getSimple();
-                final Integer columnIndex = nameToIndex.get(columnName);
-                if (projectMap.containsKey(columnIndex)) {
-                    final RexNode columnConstraint = projectMap.get(columnIndex);
-                    final ValidationError validationError =
-                            new ValidationError(
-                                    column.right,
-                                    RESOURCE.viewConstraintNotSatisfied(
-                                            columnName,
-                                            Util.last(validatorTable.getQualifiedName())));
-                    RelOptUtil.validateValueAgainstConstraint(
-                            column.right, columnConstraint, validationError);
-                }
-            }
+            final List<String> targetNames =
+                    SqlIdentifier.simpleNames(update.getTargetColumnList());
+            final List<SqlNode> sources = update.getSourceExpressionList();
+            Pair.forEach(
+                    targetNames,
+                    sources,
+                    (columnName, expr) -> {
+                        final Integer columnIndex = nameToIndex.get(columnName);
+                        if (projectMap.containsKey(columnIndex)) {
+                            final RexNode columnConstraint = projectMap.get(columnIndex);
+                            final ValidationError validationError =
+                                    new ValidationError(
+                                            expr,
+                                            RESOURCE.viewConstraintNotSatisfied(
+                                                    columnName,
+                                                    Util.last(validatorTable.getQualifiedName())));
+                            RelOptUtil.validateValueAgainstConstraint(
+                                    expr, columnConstraint, validationError);
+                        }
+                    });
         }
     }
 
@@ -4424,12 +4709,15 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                         throw newValidationError(
                                 node, RESOURCE.insertIntoAlwaysGenerated(field.getName()));
                     }
+                    break;
+                default:
+                    break;
             }
         }
     }
 
     /** Returns whether a query uses {@code DEFAULT} to populate a given column. */
-    private boolean isValuesWithDefault(SqlNode source, int column) {
+    private static boolean isValuesWithDefault(SqlNode source, int column) {
         switch (source.getKind()) {
             case VALUES:
                 for (SqlNode operand : ((SqlCall) source).getOperandList()) {
@@ -4438,16 +4726,20 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                     }
                 }
                 return true;
+            default:
+                break;
         }
         return false;
     }
 
-    private boolean isRowWithDefault(SqlNode operand, int column) {
+    private static boolean isRowWithDefault(SqlNode operand, int column) {
         switch (operand.getKind()) {
             case ROW:
                 final SqlCall row = (SqlCall) operand;
                 return row.getOperandList().size() >= column
                         && row.getOperandList().get(column).getKind() == SqlKind.DEFAULT;
+            default:
+                break;
         }
         return false;
     }
@@ -4457,14 +4749,14 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 && this.config.sqlConformance().isInsertSubsetColumnsAllowed()) {
             // Target an implicit subset of columns.
             final SqlNode source = insert.getSource();
-            final RelDataType sourceRowType = getNamespace(source).getRowType();
+            final RelDataType sourceRowType = getNamespaceOrThrow(source).getRowType();
             final RelDataType logicalSourceRowType = getLogicalSourceRowType(sourceRowType, insert);
             final RelDataType implicitTargetRowType =
                     typeFactory.createStructType(
                             targetRowType
                                     .getFieldList()
                                     .subList(0, logicalSourceRowType.getFieldCount()));
-            final SqlValidatorNamespace targetNamespace = getNamespace(insert);
+            final SqlValidatorNamespace targetNamespace = getNamespaceOrThrow(insert);
             validateNamespace(targetNamespace, implicitTargetRowType);
             return implicitTargetRowType;
         } else {
@@ -4492,7 +4784,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      * @param query The query
      */
     protected void checkTypeAssignment(
-            SqlValidatorScope sourceScope,
+            @Nullable SqlValidatorScope sourceScope,
             SqlValidatorTable table,
             RelDataType sourceRowType,
             RelDataType targetRowType,
@@ -4565,7 +4857,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      * @param sourceCount Number of expressions
      * @return Ordinal'th expression, never null
      */
-    private SqlNode getNthExpr(SqlNode query, int ordinal, int sourceCount) {
+    private static SqlNode getNthExpr(SqlNode query, int ordinal, int sourceCount) {
         if (query instanceof SqlInsert) {
             SqlInsert insert = (SqlInsert) query;
             if (insert.getTargetColumnList() != null) {
@@ -4578,12 +4870,14 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             if (update.getSourceExpressionList() != null) {
                 return update.getSourceExpressionList().get(ordinal);
             } else {
-                return getNthExpr(update.getSourceSelect(), ordinal, sourceCount);
+                return getNthExpr(
+                        SqlNonNullableAccessors.getSourceSelect(update), ordinal, sourceCount);
             }
         } else if (query instanceof SqlSelect) {
             SqlSelect select = (SqlSelect) query;
-            if (select.getSelectList().size() == sourceCount) {
-                return select.getSelectList().get(ordinal);
+            SqlNodeList selectList = SqlNonNullableAccessors.getSelectList(select);
+            if (selectList.size() == sourceCount) {
+                return selectList.get(ordinal);
             } else {
                 return query; // give up
             }
@@ -4592,35 +4886,37 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
     }
 
+    @Override
     public void validateDelete(SqlDelete call) {
-        final SqlSelect sqlSelect = call.getSourceSelect();
+        final SqlSelect sqlSelect = SqlNonNullableAccessors.getSourceSelect(call);
         validateSelect(sqlSelect, unknownType);
 
-        final SqlValidatorNamespace targetNamespace = getNamespace(call);
+        final SqlValidatorNamespace targetNamespace = getNamespaceOrThrow(call);
         validateNamespace(targetNamespace, unknownType);
         final SqlValidatorTable table = targetNamespace.getTable();
 
         validateAccess(call.getTargetTable(), table, SqlAccessEnum.DELETE);
     }
 
+    @Override
     public void validateUpdate(SqlUpdate call) {
-        final SqlValidatorNamespace targetNamespace = getNamespace(call);
+        final SqlValidatorNamespace targetNamespace = getNamespaceOrThrow(call);
         validateNamespace(targetNamespace, unknownType);
         final RelOptTable relOptTable =
                 SqlValidatorUtil.getRelOptTable(
                         targetNamespace,
-                        catalogReader.unwrap(Prepare.CatalogReader.class),
+                        castNonNull(catalogReader.unwrap(Prepare.CatalogReader.class)),
                         null,
                         null);
         final SqlValidatorTable table =
                 relOptTable == null
-                        ? targetNamespace.getTable()
-                        : relOptTable.unwrap(SqlValidatorTable.class);
+                        ? getTable(targetNamespace)
+                        : relOptTable.unwrapOrThrow(SqlValidatorTable.class);
 
         final RelDataType targetRowType =
                 createTargetRowType(table, call.getTargetColumnList(), true);
 
-        final SqlSelect select = call.getSourceSelect();
+        final SqlSelect select = SqlNonNullableAccessors.getSourceSelect(call);
         validateSelect(select, targetRowType);
 
         final RelDataType sourceRowType = getValidatedNodeType(select);
@@ -4631,8 +4927,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         validateAccess(call.getTargetTable(), table, SqlAccessEnum.UPDATE);
     }
 
+    @Override
     public void validateMerge(SqlMerge call) {
-        SqlSelect sqlSelect = call.getSourceSelect();
+        SqlSelect sqlSelect = SqlNonNullableAccessors.getSourceSelect(call);
         // REVIEW zfong 5/25/06 - Does an actual type have to be passed into
         // validateSelect()?
 
@@ -4646,7 +4943,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         // since validateSelect() would bail.
         // Let's use the update/insert targetRowType when available.
         IdentifierNamespace targetNamespace =
-                (IdentifierNamespace) getNamespace(call.getTargetTable());
+                (IdentifierNamespace) getNamespaceOrThrow(call.getTargetTable());
         validateNamespace(targetNamespace, unknownType);
 
         SqlValidatorTable table = targetNamespace.getTable();
@@ -4654,22 +4951,26 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
         RelDataType targetRowType = unknownType;
 
-        if (call.getUpdateCall() != null) {
-            targetRowType =
-                    createTargetRowType(table, call.getUpdateCall().getTargetColumnList(), true);
+        SqlUpdate updateCall = call.getUpdateCall();
+        if (updateCall != null) {
+            requireNonNull(table, () -> "ns.getTable() for " + targetNamespace);
+            targetRowType = createTargetRowType(table, updateCall.getTargetColumnList(), true);
         }
-        if (call.getInsertCall() != null) {
-            targetRowType =
-                    createTargetRowType(table, call.getInsertCall().getTargetColumnList(), false);
+        SqlInsert insertCall = call.getInsertCall();
+        if (insertCall != null) {
+            requireNonNull(table, () -> "ns.getTable() for " + targetNamespace);
+            targetRowType = createTargetRowType(table, insertCall.getTargetColumnList(), false);
         }
 
         validateSelect(sqlSelect, targetRowType);
 
-        if (call.getUpdateCall() != null) {
-            validateUpdate(call.getUpdateCall());
+        SqlUpdate updateCallAfterValidate = call.getUpdateCall();
+        if (updateCallAfterValidate != null) {
+            validateUpdate(updateCallAfterValidate);
         }
-        if (call.getInsertCall() != null) {
-            validateInsert(call.getInsertCall());
+        SqlInsert insertCallAfterValidate = call.getInsertCall();
+        if (insertCallAfterValidate != null) {
+            validateInsert(insertCallAfterValidate);
         }
     }
 
@@ -4680,7 +4981,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      * @param requiredAccess Access requested on table
      */
     private void validateAccess(
-            SqlNode node, SqlValidatorTable table, SqlAccessEnum requiredAccess) {
+            SqlNode node, @Nullable SqlValidatorTable table, SqlAccessEnum requiredAccess) {
         if (table != null) {
             SqlAccessType access = table.getAllowedAccess();
             if (!access.allowsAccess(requiredAccess)) {
@@ -4699,11 +5000,13 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      * @param scope Validator scope to derive type
      * @param ns The namespace to lookup table
      */
-    private void validateSnapshot(SqlNode node, SqlValidatorScope scope, SqlValidatorNamespace ns) {
+    private void validateSnapshot(
+            SqlNode node, @Nullable SqlValidatorScope scope, SqlValidatorNamespace ns) {
         if (node.getKind() == SqlKind.SNAPSHOT) {
             SqlSnapshot snapshot = (SqlSnapshot) node;
             SqlNode period = snapshot.getPeriod();
-            RelDataType dataType = deriveType(scope, period);
+            RelDataType dataType = deriveType(requireNonNull(scope, "scope"), period);
+            // ----- FLINK MODIFICATION BEGIN -----
             if (!(dataType.getSqlTypeName() == SqlTypeName.TIMESTAMP
                     || dataType.getSqlTypeName() == SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)) {
                 throw newValidationError(
@@ -4716,9 +5019,10 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 // table to operate on. This will be rechecked later in the planner rules.
                 return;
             }
-            assert ns.getTable() != null;
-            if (!ns.getTable().isTemporal()) {
-                List<String> qualifiedName = ns.getTable().getQualifiedName();
+            // ----- FLINK MODIFICATION END -----
+            SqlValidatorTable table = getTable(ns);
+            if (!table.isTemporal()) {
+                List<String> qualifiedName = table.getQualifiedName();
                 String tableName = qualifiedName.get(qualifiedName.size() - 1);
                 throw newValidationError(
                         snapshot.getTableRef(), Static.RESOURCE.notTemporalTable(tableName));
@@ -4800,11 +5104,13 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 final RelDataType type =
                         typeFactory.leastRestrictive(
                                 new AbstractList<RelDataType>() {
+                                    @Override
                                     public RelDataType get(int row) {
                                         SqlCall thisRow = (SqlCall) operands.get(row);
                                         return deriveType(scope, thisRow.operand(c));
                                     }
 
+                                    @Override
                                     public int size() {
                                         return rowCount;
                                     }
@@ -4819,8 +5125,10 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
     }
 
+    @Override
     public void validateDataType(SqlDataTypeSpec dataType) {}
 
+    @Override
     public void validateDynamicParam(SqlDynamicParam dynamicParam) {}
 
     /**
@@ -4837,6 +5145,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             this.validatorException = validatorException;
         }
 
+        @Override
         public CalciteContextException get() {
             return newValidationError(sqlNode, validatorException);
         }
@@ -4847,7 +5156,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      * determined when the function is applied.
      */
     class ValidationErrorFunction
-            implements Function2<
+            implements BiFunction<
                     SqlNode, Resources.ExInst<SqlValidatorException>, CalciteContextException> {
         @Override
         public CalciteContextException apply(
@@ -4860,6 +5169,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         return validationErrorFunction;
     }
 
+    @Override
     public CalciteContextException newValidationError(
             SqlNode node, Resources.ExInst<SqlValidatorException> e) {
         assert node != null;
@@ -4879,6 +5189,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         return window;
     }
 
+    @Override
     public SqlWindow resolveWindow(SqlNode windowOrRef, SqlValidatorScope scope) {
         SqlWindow window;
         if (windowOrRef instanceof SqlIdentifier) {
@@ -4915,6 +5226,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         originalExprs.putIfAbsent(expr, original);
     }
 
+    @Nullable
     SqlValidatorNamespace lookupFieldNamespace(RelDataType rowType, String name) {
         final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
         final RelDataTypeField field = nameMatcher.field(rowType, name);
@@ -4924,7 +5236,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         return new FieldNamespace(this, field.getType());
     }
 
-    public void validateWindow(SqlNode windowOrId, SqlValidatorScope scope, SqlCall call) {
+    @Override
+    public void validateWindow(
+            SqlNode windowOrId, SqlValidatorScope scope, @Nullable SqlCall call) {
         // Enable nested aggregates with window aggregates (OVER operator)
         inWindow = true;
 
@@ -4942,13 +5256,14 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 throw Util.unexpected(windowOrId.getKind());
         }
 
+        requireNonNull(call, () -> "call is null when validating windowOrId " + windowOrId);
         assert targetWindow.getWindowCall() == null;
         targetWindow.setWindowCall(call);
         targetWindow.validate(this, scope);
         targetWindow.setWindowCall(null);
         call.validate(this, scope);
 
-        validateAggregateParams(call, null, null, scope);
+        validateAggregateParams(call, null, null, null, scope);
 
         // Disable nested aggregates post validation
         inWindow = false;
@@ -4960,7 +5275,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         final MatchRecognizeScope scope =
                 (MatchRecognizeScope) getMatchRecognizeScope(matchRecognize);
 
-        final MatchRecognizeNamespace ns = getNamespace(call).unwrap(MatchRecognizeNamespace.class);
+        final MatchRecognizeNamespace ns =
+                getNamespaceOrThrow(call).unwrap(MatchRecognizeNamespace.class);
         assert ns.rowType == null;
 
         // rows per match
@@ -4990,9 +5306,13 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 node.validate(this, scope);
                 SqlIdentifier identifier;
                 if (node instanceof SqlBasicCall) {
-                    identifier = (SqlIdentifier) ((SqlBasicCall) node).getOperands()[0];
+                    identifier = (SqlIdentifier) ((SqlBasicCall) node).operand(0);
                 } else {
-                    identifier = (SqlIdentifier) node;
+                    identifier =
+                            (SqlIdentifier)
+                                    requireNonNull(
+                                            node,
+                                            () -> "order by field is null. All fields: " + orderBy);
                 }
 
                 if (allRows) {
@@ -5006,7 +5326,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
 
         if (allRows) {
-            final SqlValidatorNamespace sqlNs = getNamespace(matchRecognize.getTableRef());
+            final SqlValidatorNamespace sqlNs = getNamespaceOrThrow(matchRecognize.getTableRef());
             final RelDataType inputDataType = sqlNs.getRowType();
             for (RelDataTypeField fs : inputDataType.getFieldList()) {
                 if (!typeBuilder.nameExists(fs.getName())) {
@@ -5024,26 +5344,32 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         if (interval != null) {
             interval.validate(this, scope);
             if (((SqlIntervalLiteral) interval).signum() < 0) {
+                String intervalValue = interval.toValue();
                 throw newValidationError(
-                        interval, RESOURCE.intervalMustBeNonNegative(interval.toValue()));
+                        interval,
+                        RESOURCE.intervalMustBeNonNegative(
+                                intervalValue != null ? intervalValue : interval.toString()));
             }
             if (orderBy == null || orderBy.size() == 0) {
                 throw newValidationError(interval, RESOURCE.cannotUseWithinWithoutOrderBy());
             }
 
-            SqlNode firstOrderByColumn = orderBy.getList().get(0);
+            SqlNode firstOrderByColumn = orderBy.get(0);
             SqlIdentifier identifier;
             if (firstOrderByColumn instanceof SqlBasicCall) {
-                identifier = (SqlIdentifier) ((SqlBasicCall) firstOrderByColumn).getOperands()[0];
+                identifier = ((SqlBasicCall) firstOrderByColumn).operand(0);
             } else {
-                identifier = (SqlIdentifier) firstOrderByColumn;
+                identifier =
+                        (SqlIdentifier) requireNonNull(firstOrderByColumn, "firstOrderByColumn");
             }
             RelDataType firstOrderByColumnType = deriveType(scope, identifier);
+            // ----- FLINK MODIFICATION BEGIN -----
             if (!(firstOrderByColumnType.getSqlTypeName() == SqlTypeName.TIMESTAMP
                     || firstOrderByColumnType.getSqlTypeName()
                             == SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)) {
                 throw newValidationError(interval, RESOURCE.firstColumnOfOrderByMustBeTimestamp());
             }
+            // ----- FLINK MODIFICATION END -----
 
             SqlNode expand = expand(interval, scope);
             RelDataType type = deriveType(scope, expand);
@@ -5092,7 +5418,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
         final RelDataType rowType = typeBuilder.build();
         if (matchRecognize.getMeasureList().size() == 0) {
-            ns.setType(getNamespace(matchRecognize.getTableRef()).getRowType());
+            ns.setType(getNamespaceOrThrow(matchRecognize.getTableRef()).getRowType());
         } else {
             ns.setType(rowType);
         }
@@ -5107,7 +5433,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
         for (SqlNode measure : measures) {
             assert measure instanceof SqlCall;
-            final String alias = deriveAlias(measure, aliases.size());
+            final String alias = deriveAliasNonNull(measure, aliases.size());
             aliases.add(alias);
 
             SqlNode expand = expand(measure, scope);
@@ -5157,7 +5483,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
     private void validateDefinitions(SqlMatchRecognize mr, MatchRecognizeScope scope) {
         final Set<String> aliases = catalogReader.nameMatcher().createSet();
-        for (SqlNode item : mr.getPatternDefList().getList()) {
+        for (SqlNode item : mr.getPatternDefList()) {
             final String alias = alias(item);
             if (!aliases.add(alias)) {
                 throw newValidationError(item, Static.RESOURCE.patternVarAlreadyDefined(alias));
@@ -5166,7 +5492,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
 
         final List<SqlNode> sqlNodes = new ArrayList<>();
-        for (SqlNode item : mr.getPatternDefList().getList()) {
+        for (SqlNode item : mr.getPatternDefList()) {
             final String alias = alias(item);
             SqlNode expand = expand(item, scope);
             expand = navigationInDefine(expand, alias);
@@ -5198,10 +5524,19 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         mr.setOperand(SqlMatchRecognize.OPERAND_PATTERN_DEFINES, list);
     }
 
+    /** Returns the alias of a "expr AS alias" expression. */
+    private static String alias(SqlNode item) {
+        assert item instanceof SqlCall;
+        assert item.getKind() == SqlKind.AS;
+        final SqlIdentifier identifier = ((SqlCall) item).operand(1);
+        return identifier.getSimple();
+    }
+
     public void validatePivot(SqlPivot pivot) {
-        final PivotScope scope = (PivotScope) getJoinScope(pivot);
+        final PivotScope scope =
+                (PivotScope) requireNonNull(getJoinScope(pivot), () -> "joinScope for " + pivot);
 
-        final PivotNamespace ns = getNamespace(pivot).unwrap(PivotNamespace.class);
+        final PivotNamespace ns = getNamespaceOrThrow(pivot).unwrap(PivotNamespace.class);
         assert ns.rowType == null;
 
         // Given
@@ -5215,7 +5550,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         // an aggregate or as an axis.
 
         // Aggregates, e.g. "PIVOT (sum(x) AS sum_x, count(*) AS c)"
-        final List<Pair<String, RelDataType>> aggNames = new ArrayList<>();
+        final List<Pair<@Nullable String, RelDataType>> aggNames = new ArrayList<>();
         pivot.forEachAgg(
                 (alias, call) -> {
                     call.validate(this, scope);
@@ -5290,12 +5625,143 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         ns.setType(rowType);
     }
 
-    /** Returns the alias of a "expr AS alias" expression. */
-    private static String alias(SqlNode item) {
-        assert item instanceof SqlCall;
-        assert item.getKind() == SqlKind.AS;
-        final SqlIdentifier identifier = ((SqlCall) item).operand(1);
-        return identifier.getSimple();
+    public void validateUnpivot(SqlUnpivot unpivot) {
+        final UnpivotScope scope =
+                (UnpivotScope) requireNonNull(getJoinScope(unpivot), () -> "scope for " + unpivot);
+
+        final UnpivotNamespace ns = getNamespaceOrThrow(unpivot).unwrap(UnpivotNamespace.class);
+        assert ns.rowType == null;
+
+        // Given
+        //   query UNPIVOT ((measure1, ..., measureM)
+        //   FOR (axis1, ..., axisN)
+        //   IN ((c11, ..., c1M) AS (value11, ..., value1N),
+        //       (c21, ..., c2M) AS (value21, ..., value2N), ...)
+        // the type is
+        //   k1, ... kN, axis1, ..., axisN, measure1, ..., measureM
+        // where k1, ... kN are columns that are not referenced as an argument to
+        // an aggregate or as an axis.
+
+        // First, And make sure that each
+        final int measureCount = unpivot.measureList.size();
+        final int axisCount = unpivot.axisList.size();
+        unpivot.forEachNameValues(
+                (nodeList, valueList) -> {
+                    // Make sure that each (ci1, ... ciM) list has the same arity as
+                    // (measure1, ..., measureM).
+                    if (nodeList.size() != measureCount) {
+                        throw newValidationError(
+                                nodeList,
+                                RESOURCE.unpivotValueArityMismatch(nodeList.size(), measureCount));
+                    }
+
+                    // Make sure that each (vi1, ... viN) list has the same arity as
+                    // (axis1, ..., axisN).
+                    if (valueList != null && valueList.size() != axisCount) {
+                        throw newValidationError(
+                                valueList,
+                                RESOURCE.unpivotValueArityMismatch(valueList.size(), axisCount));
+                    }
+
+                    // Make sure that each IN expression is a valid column from the input.
+                    nodeList.forEach(node -> deriveType(scope, node));
+                });
+
+        // What columns from the input are not referenced by a column in the IN
+        // list?
+        final SqlValidatorNamespace inputNs = Objects.requireNonNull(getNamespace(unpivot.query));
+        final Set<String> unusedColumnNames = catalogReader.nameMatcher().createSet();
+        unusedColumnNames.addAll(inputNs.getRowType().getFieldNames());
+        unusedColumnNames.removeAll(unpivot.usedColumnNames());
+
+        // What columns will be present in the output row type?
+        final Set<String> columnNames = catalogReader.nameMatcher().createSet();
+        columnNames.addAll(unusedColumnNames);
+
+        // Gather the name and type of each measure.
+        final List<Pair<String, RelDataType>> measureNameTypes = new ArrayList<>();
+        Ord.forEach(
+                unpivot.measureList,
+                (measure, i) -> {
+                    final String measureName = ((SqlIdentifier) measure).getSimple();
+                    final List<RelDataType> types = new ArrayList<>();
+                    final List<SqlNode> nodes = new ArrayList<>();
+                    unpivot.forEachNameValues(
+                            (nodeList, valueList) -> {
+                                final SqlNode alias = nodeList.get(i);
+                                nodes.add(alias);
+                                types.add(deriveType(scope, alias));
+                            });
+                    final RelDataType type0 = typeFactory.leastRestrictive(types);
+                    if (type0 == null) {
+                        throw newValidationError(
+                                nodes.get(0), RESOURCE.unpivotCannotDeriveMeasureType(measureName));
+                    }
+                    final RelDataType type =
+                            typeFactory.createTypeWithNullability(
+                                    type0, unpivot.includeNulls || unpivot.measureList.size() > 1);
+                    setValidatedNodeType(measure, type);
+                    if (!columnNames.add(measureName)) {
+                        throw newValidationError(measure, RESOURCE.unpivotDuplicate(measureName));
+                    }
+                    measureNameTypes.add(Pair.of(measureName, type));
+                });
+
+        // Gather the name and type of each axis.
+        // Consider
+        //   FOR (job, deptno)
+        //   IN (a AS ('CLERK', 10),
+        //       b AS ('ANALYST', 20))
+        // There are two axes, (job, deptno), and so each value list ('CLERK', 10),
+        // ('ANALYST', 20) must have arity two.
+        //
+        // The type of 'job' is derived as the least restrictive type of the values
+        // ('CLERK', 'ANALYST'), namely VARCHAR(7). The derived type of 'deptno' is
+        // the type of values (10, 20), namely INTEGER.
+        final List<Pair<String, RelDataType>> axisNameTypes = new ArrayList<>();
+        Ord.forEach(
+                unpivot.axisList,
+                (axis, i) -> {
+                    final String axisName = ((SqlIdentifier) axis).getSimple();
+                    final List<RelDataType> types = new ArrayList<>();
+                    unpivot.forEachNameValues(
+                            (aliasList, valueList) ->
+                                    types.add(
+                                            valueList == null
+                                                    ? typeFactory.createSqlType(
+                                                            SqlTypeName.VARCHAR,
+                                                            SqlUnpivot.aliasValue(aliasList)
+                                                                    .length())
+                                                    : deriveType(scope, valueList.get(i))));
+                    final RelDataType type = typeFactory.leastRestrictive(types);
+                    if (type == null) {
+                        throw newValidationError(
+                                axis, RESOURCE.unpivotCannotDeriveAxisType(axisName));
+                    }
+                    setValidatedNodeType(axis, type);
+                    if (!columnNames.add(axisName)) {
+                        throw newValidationError(axis, RESOURCE.unpivotDuplicate(axisName));
+                    }
+                    axisNameTypes.add(Pair.of(axisName, type));
+                });
+
+        // Columns that have been seen as arguments to aggregates or as axes
+        // do not appear in the output.
+        final RelDataTypeFactory.Builder typeBuilder = typeFactory.builder();
+        scope.getChild()
+                .getRowType()
+                .getFieldList()
+                .forEach(
+                        field -> {
+                            if (unusedColumnNames.contains(field.getName())) {
+                                typeBuilder.add(field);
+                            }
+                        });
+        typeBuilder.addAll(axisNameTypes);
+        typeBuilder.addAll(measureNameTypes);
+
+        final RelDataType rowType = typeBuilder.build();
+        ns.setType(rowType);
     }
 
     /**
@@ -5310,8 +5776,13 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         return node;
     }
 
+    @Override
     public void validateAggregateParams(
-            SqlCall aggCall, SqlNode filter, SqlNodeList orderList, SqlValidatorScope scope) {
+            SqlCall aggCall,
+            @Nullable SqlNode filter,
+            @Nullable SqlNodeList distinctList,
+            @Nullable SqlNodeList orderList,
+            SqlValidatorScope scope) {
         // For "agg(expr)", expr cannot itself contain aggregate function
         // invocations.  For example, "SUM(2 * MAX(x))" is illegal; when
         // we see it, we'll report the error for the SUM (not the MAX).
@@ -5345,6 +5816,13 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 throw newValidationError(filter, RESOURCE.aggregateInFilterIllegal());
             }
         }
+        if (distinctList != null) {
+            for (SqlNode param : distinctList) {
+                if (a.findAgg(param) != null) {
+                    throw newValidationError(aggCall, RESOURCE.aggregateInWithinDistinctIllegal());
+                }
+            }
+        }
         if (orderList != null) {
             for (SqlNode param : orderList) {
                 if (a.findAgg(param) != null) {
@@ -5366,7 +5844,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             case IGNORED:
                 // rewrite the order list to empty
                 if (orderList != null) {
-                    orderList.getList().clear();
+                    orderList.clear();
                 }
                 break;
             case FORBIDDEN:
@@ -5380,6 +5858,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
     }
 
+    @Override
     public void validateCall(SqlCall call, SqlValidatorScope scope) {
         final SqlOperator operator = call.getOperator();
         if ((call.operandCount() == 0)
@@ -5389,7 +5868,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             // For example, "LOCALTIME()" is illegal. (It should be
             // "LOCALTIME", which would have been handled as a
             // SqlIdentifier.)
-            throw handleUnresolvedFunction(call, (SqlFunction) operator, ImmutableList.of(), null);
+            throw handleUnresolvedFunction(call, operator, ImmutableList.of(), null);
         }
 
         SqlValidatorScope operandScope = scope.getOperandScope(call);
@@ -5419,16 +5898,17 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
 
     public SqlNode expandSelectExpr(SqlNode expr, SelectScope scope, SqlSelect select) {
         final Expander expander = new SelectExpander(this, scope, select);
-        final SqlNode newExpr = expr.accept(expander);
+        final SqlNode newExpr = expander.go(expr);
         if (expr != newExpr) {
             setOriginal(newExpr, expr);
         }
         return newExpr;
     }
 
+    @Override
     public SqlNode expand(SqlNode expr, SqlValidatorScope scope) {
         final Expander expander = new Expander(this, scope);
-        SqlNode newExpr = expr.accept(expander);
+        SqlNode newExpr = expander.go(expr);
         if (expr != newExpr) {
             setOriginal(newExpr, expr);
         }
@@ -5438,18 +5918,20 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     public SqlNode expandGroupByOrHavingExpr(
             SqlNode expr, SqlValidatorScope scope, SqlSelect select, boolean havingExpression) {
         final Expander expander = new ExtendedExpander(this, scope, select, expr, havingExpression);
-        SqlNode newExpr = expr.accept(expander);
+        SqlNode newExpr = expander.go(expr);
         if (expr != newExpr) {
             setOriginal(newExpr, expr);
         }
         return newExpr;
     }
 
+    @Override
     public boolean isSystemField(RelDataTypeField field) {
         return false;
     }
 
-    public List<List<String>> getFieldOrigins(SqlNode sqlQuery) {
+    @Override
+    public List<@Nullable List<String>> getFieldOrigins(SqlNode sqlQuery) {
         if (sqlQuery instanceof SqlExplain) {
             return Collections.emptyList();
         }
@@ -5458,22 +5940,25 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         if (!sqlQuery.isA(SqlKind.QUERY)) {
             return Collections.nCopies(fieldCount, null);
         }
-        final List<List<String>> list = new ArrayList<>();
+        final List<@Nullable List<String>> list = new ArrayList<>();
         for (int i = 0; i < fieldCount; i++) {
             list.add(getFieldOrigin(sqlQuery, i));
         }
         return ImmutableNullableList.copyOf(list);
     }
 
-    private List<String> getFieldOrigin(SqlNode sqlQuery, int i) {
+    private @Nullable List<String> getFieldOrigin(SqlNode sqlQuery, int i) {
         if (sqlQuery instanceof SqlSelect) {
             SqlSelect sqlSelect = (SqlSelect) sqlQuery;
-            final SelectScope scope = getRawSelectScope(sqlSelect);
-            final List<SqlNode> selectList = scope.getExpandedSelectList();
+            final SelectScope scope = getRawSelectScopeNonNull(sqlSelect);
+            final List<SqlNode> selectList =
+                    requireNonNull(
+                            scope.getExpandedSelectList(), () -> "expandedSelectList for " + scope);
             final SqlNode selectItem = stripAs(selectList.get(i));
             if (selectItem instanceof SqlIdentifier) {
                 final SqlQualified qualified = scope.fullyQualify((SqlIdentifier) selectItem);
-                SqlValidatorNamespace namespace = qualified.namespace;
+                SqlValidatorNamespace namespace =
+                        requireNonNull(qualified.namespace, () -> "namespace for " + qualified);
                 final SqlValidatorTable table = namespace.getTable();
                 if (table == null) {
                     return null;
@@ -5496,6 +5981,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
     }
 
+    @Override
     public RelDataType getParameterRowType(SqlNode sqlQuery) {
         // NOTE: We assume that bind variables occur in depth-first tree
         // traversal in the same order that they occurred in the SQL text.
@@ -5530,6 +6016,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 });
     }
 
+    @Override
     public void validateColumnListParams(
             SqlFunction function, List<RelDataType> argTypes, List<SqlNode> operands) {
         throw new UnsupportedOperationException();
@@ -5583,10 +6070,11 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 SqlNode enclosingNode,
                 SqlValidatorScope parentScope) {
             super(validator, node.getTargetTable(), enclosingNode, parentScope);
-            this.node = Objects.requireNonNull(node);
+            this.node = requireNonNull(node, "node");
         }
 
-        public SqlInsert getNode() {
+        @Override
+        public @Nullable SqlNode getNode() {
             return node;
         }
     }
@@ -5601,10 +6089,11 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 SqlNode enclosingNode,
                 SqlValidatorScope parentScope) {
             super(validator, node.getTargetTable(), enclosingNode, parentScope);
-            this.node = Objects.requireNonNull(node);
+            this.node = requireNonNull(node, "node");
         }
 
-        public SqlUpdate getNode() {
+        @Override
+        public @Nullable SqlNode getNode() {
             return node;
         }
     }
@@ -5619,10 +6108,11 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 SqlNode enclosingNode,
                 SqlValidatorScope parentScope) {
             super(validator, node.getTargetTable(), enclosingNode, parentScope);
-            this.node = Objects.requireNonNull(node);
+            this.node = requireNonNull(node, "node");
         }
 
-        public SqlDelete getNode() {
+        @Override
+        public @Nullable SqlNode getNode() {
             return node;
         }
     }
@@ -5637,10 +6127,11 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 SqlNode enclosingNode,
                 SqlValidatorScope parentScope) {
             super(validator, node.getTargetTable(), enclosingNode, parentScope);
-            this.node = Objects.requireNonNull(node);
+            this.node = requireNonNull(node, "node");
         }
 
-        public SqlMerge getNode() {
+        @Override
+        public @Nullable SqlNode getNode() {
             return node;
         }
     }
@@ -5707,15 +6198,18 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             this.scope = scope;
         }
 
+        @Override
         public RelDataType visit(SqlLiteral literal) {
             return literal.createSqlType(typeFactory);
         }
 
+        @Override
         public RelDataType visit(SqlCall call) {
             final SqlOperator operator = call.getOperator();
             return operator.deriveType(SqlValidatorImpl.this, scope, call);
         }
 
+        @Override
         public RelDataType visit(SqlNodeList nodeList) {
             // Operand is of a type that we can't derive a type for. If the
             // operand is of a peculiar type, such as a SqlNodeList, then you
@@ -5724,6 +6218,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             throw Util.needToImplement(nodeList);
         }
 
+        @Override
         public RelDataType visit(SqlIdentifier id) {
             // First check for builtin functions which don't have parentheses,
             // like "LOCALTIME".
@@ -5802,6 +6297,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             return type;
         }
 
+        @Override
         public RelDataType visit(SqlDataTypeSpec dataType) {
             // Q. How can a data type have a type?
             // A. When it appears in an expression. (Say as the 2nd arg to the
@@ -5810,10 +6306,12 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             return dataType.deriveType(SqlValidatorImpl.this);
         }
 
+        @Override
         public RelDataType visit(SqlDynamicParam param) {
             return unknownType;
         }
 
+        @Override
         public RelDataType visit(SqlIntervalQualifier intervalQualifier) {
             return typeFactory.createSqlIntervalType(intervalQualifier);
         }
@@ -5828,8 +6326,12 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             this.validator = validator;
         }
 
+        public SqlNode go(SqlNode root) {
+            return requireNonNull(root.accept(this), () -> this + " returned null for " + root);
+        }
+
         @Override
-        public SqlNode visit(SqlIdentifier id) {
+        public @Nullable SqlNode visit(SqlIdentifier id) {
             // First check for builtin functions which don't have
             // parentheses, like "LOCALTIME".
             final SqlCall call = validator.makeNullaryCall(id);
@@ -5850,10 +6352,12 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 case NEXT_VALUE:
                 case WITH:
                     return call;
+                default:
+                    break;
             }
             // Only visits arguments which are expressions. We don't want to
             // qualify non-expressions such as 'x' in 'empno * 5 AS x'.
-            ArgHandler<SqlNode> argHandler = new CallCopyingArgHandler(call, false);
+            CallCopyingArgHandler argHandler = new CallCopyingArgHandler(call, false);
             call.getOperator().acceptCall(this, call, true, argHandler);
             final SqlNode result = argHandler.result();
             validator.setOriginal(result, call);
@@ -5888,14 +6392,16 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             super(getOrderScope(select));
             this.select = select;
             this.root = root;
-            this.aliasList = getNamespace(select).getRowType().getFieldNames();
+            this.aliasList = getNamespaceOrThrow(select).getRowType().getFieldNames();
         }
 
         public SqlNode go() {
-            return root.accept(this);
+            return requireNonNull(
+                    root.accept(this), () -> "OrderExpressionExpander returned null for " + root);
         }
 
-        public SqlNode visit(SqlLiteral literal) {
+        @Override
+        public @Nullable SqlNode visit(SqlLiteral literal) {
             // Ordinal markers, e.g. 'select a, b from t order by 2'.
             // Only recognize them if they are the whole expression,
             // and if the dialect permits.
@@ -5915,6 +6421,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                             return nthSelectItem(ordinal, literal.getParserPosition());
                         }
                         break;
+                    default:
+                        break;
                 }
             }
 
@@ -5927,7 +6435,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             // version of each expression -- select lists and identifiers -- in
             // the validator.
 
-            SqlNodeList expandedSelectList = expandStar(select.getSelectList(), select, false);
+            SqlNodeList expandedSelectList =
+                    expandStar(SqlNonNullableAccessors.getSelectList(select), select, false);
             SqlNode expr = expandedSelectList.get(ordinal);
             expr = stripAs(expr);
             if (expr instanceof SqlIdentifier) {
@@ -5939,11 +6448,12 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             return expr.clone(pos);
         }
 
+        @Override
         public SqlNode visit(SqlIdentifier id) {
             // Aliases, e.g. 'select a as x, b from t order by x'.
             if (id.isSimple() && config.sqlConformance().isSortByAlias()) {
                 String alias = id.getSimple();
-                final SqlValidatorNamespace selectNs = getNamespace(select);
+                final SqlValidatorNamespace selectNs = getNamespaceOrThrow(select);
                 final RelDataType rowType = selectNs.getRowTypeSansSystemColumns();
                 final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
                 RelDataTypeField field = nameMatcher.field(rowType, alias);
@@ -5956,7 +6466,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             return getScope().fullyQualify(id).identifier;
         }
 
-        protected SqlNode visitScoped(SqlCall call) {
+        @Override
+        protected @Nullable SqlNode visitScoped(SqlCall call) {
             // Don't attempt to expand sub-queries. We haven't implemented
             // these yet.
             if (call instanceof SqlSelect) {
@@ -5979,7 +6490,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
 
         @Override
-        public SqlNode visit(SqlIdentifier id) {
+        public @Nullable SqlNode visit(SqlIdentifier id) {
             final SqlNode node =
                     expandCommonColumn(select, id, (SelectScope) getScope(), validator);
             if (node != id) {
@@ -6012,7 +6523,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
 
         @Override
-        public SqlNode visit(SqlIdentifier id) {
+        public @Nullable SqlNode visit(SqlIdentifier id) {
             if (id.isSimple()
                     && (havingExpr
                             ? validator.config().sqlConformance().isHavingAlias()
@@ -6021,7 +6532,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                 SqlNode expr = null;
                 final SqlNameMatcher nameMatcher = validator.catalogReader.nameMatcher();
                 int n = 0;
-                for (SqlNode s : select.getSelectList()) {
+                for (SqlNode s : SqlNonNullableAccessors.getSelectList(select)) {
                     final String alias = SqlValidatorUtil.getAlias(s, -1);
                     if (alias != null && nameMatcher.matches(alias, name)) {
                         expr = s;
@@ -6055,7 +6566,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             return super.visit(id);
         }
 
-        public SqlNode visit(SqlLiteral literal) {
+        @Override
+        public @Nullable SqlNode visit(SqlLiteral literal) {
             if (havingExpr || !validator.config().sqlConformance().isGroupByOrdinal()) {
                 return super.visit(literal);
             }
@@ -6074,6 +6586,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                         }
                     }
                     break;
+                default:
+                    break;
             }
             if (isOrdinalLiteral) {
                 switch (literal.getTypeName()) {
@@ -6081,16 +6595,22 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                     case DOUBLE:
                         final int intValue = literal.intValue(false);
                         if (intValue >= 0) {
-                            if (intValue < 1 || intValue > select.getSelectList().size()) {
+                            if (intValue < 1
+                                    || intValue
+                                            > SqlNonNullableAccessors.getSelectList(select)
+                                                    .size()) {
                                 throw validator.newValidationError(
                                         literal, RESOURCE.orderByOrdinalOutOfRange());
                             }
 
                             // SQL ordinals are 1-based, but Sort's are 0-based
                             int ordinal = intValue - 1;
-                            return SqlUtil.stripAs(select.getSelectList().get(ordinal));
+                            return SqlUtil.stripAs(
+                                    SqlNonNullableAccessors.getSelectList(select).get(ordinal));
                         }
                         break;
+                    default:
+                        break;
                 }
             }
 
@@ -6132,7 +6652,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     /** Modify the nodes in navigation function such as FIRST, LAST, PREV AND NEXT. */
     private static class NavigationModifier extends SqlShuttle {
         public SqlNode go(SqlNode node) {
-            return node.accept(this);
+            return requireNonNull(
+                    node.accept(this), () -> "NavigationModifier returned for " + node);
         }
     }
 
@@ -6147,23 +6668,23 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
      * </ul>
      */
     private static class NavigationExpander extends NavigationModifier {
-        final SqlOperator op;
-        final SqlNode offset;
+        final @Nullable SqlOperator op;
+        final @Nullable SqlNode offset;
 
         NavigationExpander() {
             this(null, null);
         }
 
-        NavigationExpander(SqlOperator operator, SqlNode offset) {
+        NavigationExpander(@Nullable SqlOperator operator, @Nullable SqlNode offset) {
             this.offset = offset;
             this.op = operator;
         }
 
         @Override
-        public SqlNode visit(SqlCall call) {
+        public @Nullable SqlNode visit(SqlCall call) {
             SqlKind kind = call.getKind();
             List<SqlNode> operands = call.getOperandList();
-            List<SqlNode> newOperands = new ArrayList<>();
+            List<@Nullable SqlNode> newOperands = new ArrayList<>();
 
             if (call.getFunctionQuantifier() != null
                     && call.getFunctionQuantifier().getValue() == SqlSelectKeyword.DISTINCT) {
@@ -6251,7 +6772,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
 
         @Override
-        public SqlNode visit(SqlCall call) {
+        public @Nullable SqlNode visit(SqlCall call) {
             SqlKind kind = call.getKind();
             if (isLogicalNavigation(kind) || isAggregation(kind) || isRunningOrFinal(kind)) {
                 return call;
@@ -6266,6 +6787,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                                 ? call
                                 : SqlStdOperatorTable.LAST.createCall(SqlParserPos.ZERO, operands);
                     }
+                    break;
+                default:
+                    break;
             }
             return super.visit(call);
         }
@@ -6286,7 +6810,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
     }
 
     /** Validates that within one navigation function, the pattern var is the same. */
-    private class PatternValidator extends SqlBasicVisitor<Set<String>> {
+    private class PatternValidator extends SqlBasicVisitor<@Nullable Set<String>> {
         private final boolean isMeasure;
         int firstLastCount;
         int prevNextCount;
@@ -6352,12 +6876,14 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
             for (SqlNode node : operands) {
                 if (node != null) {
                     vars.addAll(
-                            node.accept(
-                                    new PatternValidator(
-                                            isMeasure,
-                                            firstLastCount,
-                                            prevNextCount,
-                                            aggregateCount)));
+                            requireNonNull(
+                                    node.accept(
+                                            new PatternValidator(
+                                                    isMeasure,
+                                                    firstLastCount,
+                                                    prevNextCount,
+                                                    aggregateCount)),
+                                    () -> "node.accept(PatternValidator) for node " + node));
                 }
             }
 
@@ -6488,7 +7014,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
         }
 
         private RelDataTypeField field(String name) {
-            return catalogReader.nameMatcher().field(rowType, name);
+            RelDataTypeField field = catalogReader.nameMatcher().field(rowType, name);
+            assert field != null : "field " + name + " was not found in " + rowType;
+            return field;
         }
 
         /** Moves fields according to the permutation. */
@@ -6514,8 +7042,16 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
                     final RelDataType type1 = field1.getValue();
                     // output is nullable only if both inputs are
                     final boolean nullable = type.isNullable() && type1.isNullable();
+                    RelDataType currentType = type;
                     final RelDataType type2 =
-                            SqlTypeUtil.leastRestrictiveForComparison(typeFactory, type, type1);
+                            requireNonNull(
+                                    SqlTypeUtil.leastRestrictiveForComparison(
+                                            typeFactory, type, type1),
+                                    () ->
+                                            "leastRestrictiveForComparison for types "
+                                                    + currentType
+                                                    + " and "
+                                                    + type1);
                     selectItem =
                             SqlStdOperatorTable.AS.createCall(
                                     SqlParserPos.ZERO,
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
index cf2d4de2c65..192e338e1c4 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
@@ -34,11 +34,13 @@ import org.apache.calcite.linq4j.function.Function2;
 import org.apache.calcite.plan.Context;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptCostImpl;
+import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.RelRule;
 import org.apache.calcite.plan.hep.HepPlanner;
 import org.apache.calcite.plan.hep.HepProgram;
+import org.apache.calcite.plan.hep.HepProgramBuilder;
 import org.apache.calcite.plan.hep.HepRelVertex;
 import org.apache.calcite.rel.BiRel;
 import org.apache.calcite.rel.RelCollation;
@@ -102,10 +104,9 @@ import org.apache.calcite.util.ReflectiveVisitor;
 import org.apache.calcite.util.Util;
 import org.apache.calcite.util.mapping.Mappings;
 import org.apache.calcite.util.trace.CalciteTrace;
+import org.checkerframework.checker.nullness.qual.Nullable;
 import org.slf4j.Logger;
 
-import javax.annotation.Nonnull;
-
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -117,11 +118,23 @@ import java.util.Map;
 import java.util.NavigableMap;
 import java.util.Objects;
 import java.util.Set;
-import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
 
-/** Copied to fix calcite issues. */
+import static java.util.Objects.requireNonNull;
+import static org.apache.calcite.linq4j.Nullness.castNonNull;
+
+/**
+ * Copied to fix calcite issues. FLINK modifications are at lines
+ *
+ * <ol>
+ *   <li>Was changed within FLINK-29280, FLINK-28682: Line 224 ~ 234
+ *   <li>Should be removed after fix of FLINK-29540: Line 296 ~ 302
+ *   <li>Should be removed after fix of FLINK-29540: Line 314 ~ 320
+ *   <li>Was changed within FLINK-21592: Line 1954 ~ 1962, Should be removed after update to Calcite
+ *       1.28.0 as it is fixed at CALCITE-4773
+ * </ol>
+ */
 public class RelDecorrelator implements ReflectiveVisitor {
     // ~ Static fields/initializers ---------------------------------------------
 
@@ -129,27 +142,29 @@ public class RelDecorrelator implements ReflectiveVisitor {
 
     // ~ Instance fields --------------------------------------------------------
 
-    private final RelBuilder relBuilder;
+    protected final RelBuilder relBuilder;
 
     // map built during translation
     protected CorelMap cm;
 
-    private final ReflectUtil.MethodDispatcher<Frame> dispatcher =
-            ReflectUtil.createMethodDispatcher(Frame.class, this, "decorrelateRel", RelNode.class);
+    @SuppressWarnings("method.invocation.invalid")
+    protected final ReflectUtil.MethodDispatcher<@Nullable Frame> dispatcher =
+            ReflectUtil.<RelNode, @Nullable Frame>createMethodDispatcher(
+                    Frame.class, getVisitor(), "decorrelateRel", RelNode.class, boolean.class);
 
     // The rel which is being visited
-    private RelNode currentRel;
+    protected @Nullable RelNode currentRel;
 
-    private final Context context;
+    protected final Context context;
 
     /**
      * Built during decorrelation, of rel to all the newly created correlated variables in its
      * output, and to map old input positions to new input positions. This is from the view point of
      * the parent rel of a new rel.
      */
-    private final Map<RelNode, Frame> map = new HashMap<>();
+    protected final Map<RelNode, Frame> map = new HashMap<>();
 
-    private final HashSet<Correlate> generatedCorRels = new HashSet<>();
+    protected final HashSet<Correlate> generatedCorRels = new HashSet<>();
 
     // ~ Constructors -----------------------------------------------------------
 
@@ -221,7 +236,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
         return newRootRel;
     }
 
-    private void setCurrent(RelNode root, Correlate corRel) {
+    private void setCurrent(@Nullable RelNode root, @Nullable Correlate corRel) {
         currentRel = corRel;
         if (corRel != null) {
             cm = new CorelMapBuilder().build(Util.first(root, corRel));
@@ -278,20 +293,42 @@ public class RelDecorrelator implements ReflectiveVisitor {
                                 FilterCorrelateRule.Config.DEFAULT
                                         .withRelBuilderFactory(f)
                                         .toRule())
+                        /* ----- FLINK MODIFICATION BEGIN -----
+                        This is commented as a workaround for https://issues.apache.org/jira/browse/FLINK-29540
+                        .addRuleInstance(
+                                 FilterFlattenCorrelatedConditionRule.Config.DEFAULT
+                                         .withRelBuilderFactory(f)
+                                         .toRule())
+                                         ----- FLINK MODIFICATION END -----*/
                         .build();
 
         HepPlanner planner = createPlanner(program);
 
         planner.setRoot(root);
         root = planner.findBestExp();
-
+        if (SQL2REL_LOGGER.isDebugEnabled()) {
+            SQL2REL_LOGGER.debug(
+                    "Plan before extracting correlated computations:\n"
+                            + RelOptUtil.toString(root));
+        }
+        /* ----- FLINK MODIFICATION BEGIN -----
+        This is commented as a workaround for https://issues.apache.org/jira/browse/FLINK-29540
+        root = root.accept(new CorrelateProjectExtractor(f));
+        // Necessary to update cm (CorrelMap) since CorrelateProjectExtractor above may modify the
+        // plan
+        this.cm = new CorelMapBuilder().build(root);
+         ----- FLINK MODIFICATION END ----- */
+        if (SQL2REL_LOGGER.isDebugEnabled()) {
+            SQL2REL_LOGGER.debug(
+                    "Plan after extracting correlated computations:\n" + RelOptUtil.toString(root));
+        }
         // Perform decorrelation.
         map.clear();
 
-        final Frame frame = getInvoke(root, null);
+        final Frame frame = getInvoke(root, false, null);
         if (frame != null) {
             // has been rewritten; apply rules post-decorrelation
-            final HepProgram program2 =
+            final HepProgramBuilder builder =
                     HepProgram.builder()
                             .addRuleInstance(
                                     CoreRules.FILTER_INTO_JOIN
@@ -302,8 +339,11 @@ public class RelDecorrelator implements ReflectiveVisitor {
                                     CoreRules.JOIN_CONDITION_PUSH
                                             .config
                                             .withRelBuilderFactory(f)
-                                            .toRule())
-                            .build();
+                                            .toRule());
+            if (!getPostDecorrelateRules().isEmpty()) {
+                builder.addRuleCollection(getPostDecorrelateRules());
+            }
+            final HepProgram program2 = builder.build();
 
             final HepPlanner planner2 = createPlanner(program2);
             final RelNode newRoot = frame.r;
@@ -314,7 +354,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
         return root;
     }
 
-    private Function2<RelNode, RelNode, Void> createCopyHook() {
+    private Function2<RelNode, RelNode, @Nullable Void> createCopyHook() {
         return (oldNode, newNode) -> {
             if (cm.mapRefRelToCorRef.containsKey(oldNode)) {
                 cm.mapRefRelToCorRef.putAll(newNode, cm.mapRefRelToCorRef.get(oldNode));
@@ -396,14 +436,14 @@ public class RelDecorrelator implements ReflectiveVisitor {
     }
 
     /** Fallback if none of the other {@code decorrelateRel} methods match. */
-    public Frame decorrelateRel(RelNode rel) {
+    public @Nullable Frame decorrelateRel(RelNode rel, boolean isCorVarDefined) {
         RelNode newRel = rel.copy(rel.getTraitSet(), rel.getInputs());
 
         if (rel.getInputs().size() > 0) {
             List<RelNode> oldInputs = rel.getInputs();
             List<RelNode> newInputs = new ArrayList<>();
             for (int i = 0; i < oldInputs.size(); ++i) {
-                final Frame frame = getInvoke(oldInputs.get(i), rel);
+                final Frame frame = getInvoke(oldInputs.get(i), isCorVarDefined, rel);
                 if (frame == null || !frame.corDefOutputs.isEmpty()) {
                     // if input is not rewritten, or if it produces correlated
                     // variables, terminate rewrite
@@ -427,7 +467,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
                 ImmutableSortedMap.of());
     }
 
-    public Frame decorrelateRel(Sort rel) {
+    public @Nullable Frame decorrelateRel(Sort rel, boolean isCorVarDefined) {
         //
         // Rewrite logic:
         //
@@ -444,15 +484,12 @@ public class RelDecorrelator implements ReflectiveVisitor {
         // need to call propagateExpr.
 
         final RelNode oldInput = rel.getInput();
-        final Frame frame = getInvoke(oldInput, rel);
+        final Frame frame = getInvoke(oldInput, isCorVarDefined, rel);
         if (frame == null) {
             // If input has not been rewritten, do not rewrite this rel.
             return null;
         }
 
-        // BEGIN FLINK MODIFICATION
-        // Reason: to de-correlate sort rel when its parent is not a correlate
-        // Should be removed after CALCITE-4333 is fixed
         final RelNode newInput = frame.r;
 
         Mappings.TargetMapping mapping =
@@ -466,7 +503,6 @@ public class RelDecorrelator implements ReflectiveVisitor {
 
         final int offset = rel.offset == null ? -1 : RexLiteral.intValue(rel.offset);
         final int fetch = rel.fetch == null ? -1 : RexLiteral.intValue(rel.fetch);
-        // END FLINK MODIFICATION
 
         final RelNode newSort =
                 relBuilder
@@ -478,16 +514,16 @@ public class RelDecorrelator implements ReflectiveVisitor {
         return register(rel, newSort, frame.oldToNewOutputs, frame.corDefOutputs);
     }
 
-    public Frame decorrelateRel(Values rel) {
+    public @Nullable Frame decorrelateRel(Values rel, boolean isCorVarDefined) {
         // There are no inputs, so rel does not need to be changed.
         return null;
     }
 
-    public Frame decorrelateRel(LogicalAggregate rel) {
-        return decorrelateRel((Aggregate) rel);
+    public @Nullable Frame decorrelateRel(LogicalAggregate rel, boolean isCorVarDefined) {
+        return decorrelateRel((Aggregate) rel, isCorVarDefined);
     }
 
-    public Frame decorrelateRel(Aggregate rel) {
+    public @Nullable Frame decorrelateRel(Aggregate rel, boolean isCorVarDefined) {
         //
         // Rewrite logic:
         //
@@ -501,7 +537,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
         assert !cm.mapRefRelToCorRef.containsKey(rel);
 
         final RelNode oldInput = rel.getInput();
-        final Frame frame = getInvoke(oldInput, rel);
+        final Frame frame = getInvoke(oldInput, isCorVarDefined, rel);
         if (frame == null) {
             // If input has not been rewritten, do not rewrite this rel.
             return null;
@@ -542,7 +578,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
             newPos++;
         }
 
-        final SortedMap<CorDef, Integer> corDefOutputs = new TreeMap<>();
+        final NavigableMap<CorDef, Integer> corDefOutputs = new TreeMap<>();
         if (!frame.corDefOutputs.isEmpty()) {
             // If input produces correlated variables, move them to the front,
             // right after any existing GROUP BY fields.
@@ -594,10 +630,12 @@ public class RelDecorrelator implements ReflectiveVisitor {
         //                newInput
         Map<Integer, Integer> combinedMap = new HashMap<>();
 
-        for (Integer oldInputPos : frame.oldToNewOutputs.keySet()) {
+        for (Map.Entry<Integer, Integer> entry : frame.oldToNewOutputs.entrySet()) {
             combinedMap.put(
-                    oldInputPos,
-                    mapNewInputToProjOutputs.get(frame.oldToNewOutputs.get(oldInputPos)));
+                    entry.getKey(),
+                    requireNonNull(
+                            mapNewInputToProjOutputs.get(entry.getValue()),
+                            () -> "mapNewInputToProjOutputs.get(" + entry.getValue() + ")"));
         }
 
         register(oldInput, newProject, combinedMap, corDefOutputs);
@@ -634,12 +672,16 @@ public class RelDecorrelator implements ReflectiveVisitor {
             // output position mapping can be used to derive the new positions
             // for the argument.
             for (int oldPos : oldAggArgs) {
-                aggArgs.add(combinedMap.get(oldPos));
+                aggArgs.add(
+                        requireNonNull(
+                                combinedMap.get(oldPos), () -> "combinedMap.get(" + oldPos + ")"));
             }
             final int filterArg =
                     oldAggCall.filterArg < 0
                             ? oldAggCall.filterArg
-                            : combinedMap.get(oldAggCall.filterArg);
+                            : requireNonNull(
+                                    combinedMap.get(oldAggCall.filterArg),
+                                    () -> "combinedMap.get(" + oldAggCall.filterArg + ")");
 
             newAggCalls.add(
                     oldAggCall.adaptTo(
@@ -691,39 +733,32 @@ public class RelDecorrelator implements ReflectiveVisitor {
     private static void shiftMapping(Map<Integer, Integer> mapping, int startIndex, int offset) {
         for (Map.Entry<Integer, Integer> entry : mapping.entrySet()) {
             if (entry.getValue() >= startIndex) {
-                mapping.put(entry.getKey(), entry.getValue() + offset);
-            } else {
-                mapping.put(entry.getKey(), entry.getValue());
+                entry.setValue(entry.getValue() + offset);
             }
         }
     }
 
-    public Frame getInvoke(RelNode r, RelNode parent) {
-        final Frame frame = dispatcher.invoke(r);
-        // BEGIN FLINK MODIFICATION
-        // Reason: to de-correlate sort rel when its parent is not a correlate
-        // Should be removed after CALCITE-4333 is fixed
-        if (frame != null && parent instanceof Correlate && r instanceof Sort) {
-            Sort sort = (Sort) r;
+    public @Nullable Frame getInvoke(RelNode r, boolean isCorVarDefined, @Nullable RelNode parent) {
+        final Frame frame = dispatcher.invoke(r, isCorVarDefined);
+        currentRel = parent;
+        if (frame != null && isCorVarDefined && r instanceof Sort) {
+            final Sort sort = (Sort) r;
             // Can not decorrelate if the sort has per-correlate-key attributes like
             // offset or fetch limit, because these attributes scope would change to
             // global after decorrelation. They should take effect within the scope
             // of the correlation key actually.
             if (sort.offset != null || sort.fetch != null) {
-                currentRel = parent;
                 return null;
             }
         }
-        // END FLINK MODIFICATION
         if (frame != null) {
             map.put(r, frame);
         }
-        currentRel = parent;
         return frame;
     }
 
     /** Returns a literal output field, or null if it is not literal. */
-    private static RexLiteral projectedLiteral(RelNode rel, int i) {
+    private static @Nullable RexLiteral projectedLiteral(RelNode rel, int i) {
         if (rel instanceof Project) {
             final Project project = (Project) rel;
             final RexNode node = project.getProjects().get(i);
@@ -734,11 +769,11 @@ public class RelDecorrelator implements ReflectiveVisitor {
         return null;
     }
 
-    public Frame decorrelateRel(LogicalProject rel) {
-        return decorrelateRel((Project) rel);
+    public @Nullable Frame decorrelateRel(LogicalProject rel, boolean isCorVarDefined) {
+        return decorrelateRel((Project) rel, isCorVarDefined);
     }
 
-    public Frame decorrelateRel(Project rel) {
+    public @Nullable Frame decorrelateRel(Project rel, boolean isCorVarDefined) {
         //
         // Rewrite logic:
         //
@@ -746,7 +781,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
         //
 
         final RelNode oldInput = rel.getInput();
-        Frame frame = getInvoke(oldInput, rel);
+        Frame frame = getInvoke(oldInput, isCorVarDefined, rel);
         if (frame == null) {
             // If input has not been rewritten, do not rewrite this rel.
             return null;
@@ -771,13 +806,17 @@ public class RelDecorrelator implements ReflectiveVisitor {
             projects.add(
                     newPos,
                     Pair.of(
-                            decorrelateExpr(currentRel, map, cm, oldProjects.get(newPos)),
+                            decorrelateExpr(
+                                    requireNonNull(currentRel, "currentRel"),
+                                    map,
+                                    cm,
+                                    oldProjects.get(newPos)),
                             relOutput.get(newPos).getName()));
             mapOldToNewOutputs.put(newPos, newPos);
         }
 
         // Project any correlated variables the input wants to pass along.
-        final SortedMap<CorDef, Integer> corDefOutputs = new TreeMap<>();
+        final NavigableMap<CorDef, Integer> corDefOutputs = new TreeMap<>();
         for (Map.Entry<CorDef, Integer> entry : frame.corDefOutputs.entrySet()) {
             projects.add(RexInputRef.of2(entry.getValue(), frame.r.getRowType().getFieldList()));
             corDefOutputs.put(entry.getKey(), newPos);
@@ -803,10 +842,10 @@ public class RelDecorrelator implements ReflectiveVisitor {
      * @param corDefOutputs output positions for the correlated variables generated
      * @return RelNode the root of the resultant RelNode tree
      */
-    private RelNode createValueGenerator(
+    private @Nullable RelNode createValueGenerator(
             Iterable<CorRef> correlations,
             int valueGenFieldOffset,
-            SortedMap<CorDef, Integer> corDefOutputs) {
+            NavigableMap<CorDef, Integer> corDefOutputs) {
         final Map<RelNode, List<Integer>> mapNewInputToOutputs = new HashMap<>();
 
         final Map<RelNode, Integer> mapNewInputToNewOffset = new HashMap<>();
@@ -818,7 +857,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
 
             final RelNode oldInput = getCorRel(corVar);
             assert oldInput != null;
-            final Frame frame = getFrame(oldInput, true);
+            final Frame frame = getOrCreateFrame(oldInput);
             assert frame != null;
             final RelNode newInput = frame.r;
 
@@ -851,12 +890,14 @@ public class RelDecorrelator implements ReflectiveVisitor {
         for (CorRef corVar : correlations) {
             final RelNode oldInput = getCorRel(corVar);
             assert oldInput != null;
-            final RelNode newInput = getFrame(oldInput, true).r;
+            final RelNode newInput = getOrCreateFrame(oldInput).r;
             assert newInput != null;
 
             if (!joinedInputs.contains(newInput)) {
-                final List<Integer> positions = mapNewInputToOutputs.get(newInput);
-                final List<String> fieldNames = newInput.getRowType().getFieldNames();
+                final List<Integer> positions =
+                        requireNonNull(
+                                mapNewInputToOutputs.get(newInput),
+                                () -> "mapNewInputToOutputs.get(" + newInput + ")");
 
                 RelNode distinct =
                         relBuilder
@@ -894,11 +935,14 @@ public class RelDecorrelator implements ReflectiveVisitor {
             // the correlated variables.
             final RelNode oldInput = getCorRel(corRef);
             assert oldInput != null;
-            final Frame frame = getFrame(oldInput, true);
+            final Frame frame = getOrCreateFrame(oldInput);
             final RelNode newInput = frame.r;
             assert newInput != null;
 
-            final List<Integer> newLocalOutputs = mapNewInputToOutputs.get(newInput);
+            final List<Integer> newLocalOutputs =
+                    requireNonNull(
+                            mapNewInputToOutputs.get(newInput),
+                            () -> "mapNewInputToOutputs.get(" + newInput + ")");
 
             final int newLocalOutput = frame.oldToNewOutputs.get(corRef.field);
 
@@ -907,7 +951,9 @@ public class RelDecorrelator implements ReflectiveVisitor {
             // each newInput.
             final int newOutput =
                     newLocalOutputs.indexOf(newLocalOutput)
-                            + mapNewInputToNewOffset.get(newInput)
+                            + requireNonNull(
+                                    mapNewInputToNewOffset.get(newInput),
+                                    () -> "mapNewInputToNewOffset.get(" + newInput + ")")
                             + valueGenFieldOffset;
 
             corDefOutputs.put(corRef.def(), newOutput);
@@ -916,18 +962,25 @@ public class RelDecorrelator implements ReflectiveVisitor {
         return r;
     }
 
-    private Frame getFrame(RelNode r, boolean safe) {
-        final Frame frame = map.get(r);
-        if (frame == null && safe) {
+    private Frame getOrCreateFrame(RelNode r) {
+        final Frame frame = getFrame(r);
+        if (frame == null) {
             return new Frame(
                     r, r, ImmutableSortedMap.of(), identityMap(r.getRowType().getFieldCount()));
         }
         return frame;
     }
 
+    private @Nullable Frame getFrame(RelNode r) {
+        return map.get(r);
+    }
+
     private RelNode getCorRel(CorRef corVar) {
-        final RelNode r = cm.mapCorToCorRel.get(corVar.corr);
-        return r.getInput(0);
+        final RelNode r =
+                requireNonNull(
+                        cm.mapCorToCorRel.get(corVar.corr),
+                        () -> "cm.mapCorToCorRel.get(" + corVar.corr + ")");
+        return requireNonNull(r.getInput(0), () -> "r.getInput(0) is null for " + r);
     }
 
     /**
@@ -951,7 +1004,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
      * Returns whether all of a collection of {@link CorRef}s are satisfied by at least one of a
      * collection of {@link CorDef}s.
      */
-    private boolean hasAll(Collection<CorRef> corRefs, Collection<CorDef> corDefs) {
+    private static boolean hasAll(Collection<CorRef> corRefs, Collection<CorDef> corDefs) {
         for (CorRef corRef : corRefs) {
             if (!has(corDefs, corRef)) {
                 return false;
@@ -964,7 +1017,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
      * Returns whether a {@link CorrelationId} is satisfied by at least one of a collection of
      * {@link CorDef}s.
      */
-    private boolean has(Collection<CorDef> corDefs, CorRef corr) {
+    private static boolean has(Collection<CorDef> corDefs, CorRef corr) {
         for (CorDef corDef : corDefs) {
             if (corDef.corr.equals(corr.corr) && corDef.field == corr.field) {
                 return true;
@@ -978,14 +1031,14 @@ public class RelDecorrelator implements ReflectiveVisitor {
         assert rel.getInputs().size() == 1;
         RelNode oldInput = frame.r;
 
-        final SortedMap<CorDef, Integer> corDefOutputs = new TreeMap<>(frame.corDefOutputs);
+        final NavigableMap<CorDef, Integer> corDefOutputs = new TreeMap<>(frame.corDefOutputs);
 
         final Collection<CorRef> corVarList = cm.mapRefRelToCorRef.get(rel);
 
         // Try to populate correlation variables using local fields.
         // This means that we do not need a value generator.
         if (rel instanceof Filter) {
-            SortedMap<CorDef, Integer> map = new TreeMap<>();
+            NavigableMap<CorDef, Integer> map = new TreeMap<>();
             List<RexNode> projects = new ArrayList<>();
             for (CorRef correlation : corVarList) {
                 final CorDef def = correlation.def();
@@ -995,11 +1048,12 @@ public class RelDecorrelator implements ReflectiveVisitor {
                 try {
                     findCorrelationEquivalent(correlation, ((Filter) rel).getCondition());
                 } catch (Util.FoundOne e) {
-                    if (e.getNode() instanceof RexInputRef) {
-                        map.put(def, ((RexInputRef) e.getNode()).getIndex());
+                    Object node = requireNonNull(e.getNode(), "e.getNode()");
+                    if (node instanceof RexInputRef) {
+                        map.put(def, ((RexInputRef) node).getIndex());
                     } else {
                         map.put(def, frame.r.getRowType().getFieldCount() + projects.size());
-                        projects.add((RexNode) e.getNode());
+                        projects.add((RexNode) node);
                     }
                 }
             }
@@ -1024,7 +1078,10 @@ public class RelDecorrelator implements ReflectiveVisitor {
 
         // can directly add positions into corDefOutputs since join
         // does not change the output ordering from the inputs.
-        RelNode valueGen = createValueGenerator(corVarList, leftInputOutputCount, corDefOutputs);
+        RelNode valueGen =
+                requireNonNull(
+                        createValueGenerator(corVarList, leftInputOutputCount, corDefOutputs),
+                        "createValueGenerator(...) is null");
 
         RelNode join =
                 relBuilder
@@ -1043,7 +1100,8 @@ public class RelDecorrelator implements ReflectiveVisitor {
      * Finds a {@link RexInputRef} that is equivalent to a {@link CorRef}, and if found, throws a
      * {@link org.apache.calcite.util.Util.FoundOne}.
      */
-    private void findCorrelationEquivalent(CorRef correlation, RexNode e) throws Util.FoundOne {
+    private static void findCorrelationEquivalent(CorRef correlation, RexNode e)
+            throws Util.FoundOne {
         switch (e.getKind()) {
             case EQUALS:
                 final RexCall call = (RexCall) e;
@@ -1059,10 +1117,13 @@ public class RelDecorrelator implements ReflectiveVisitor {
                 for (RexNode operand : ((RexCall) e).getOperands()) {
                     findCorrelationEquivalent(correlation, operand);
                 }
+                break;
+            default:
+                break;
         }
     }
 
-    private boolean references(RexNode e, CorRef correlation) {
+    private static boolean references(RexNode e, CorRef correlation) {
         switch (e.getKind()) {
             case CAST:
                 final RexNode operand = ((RexCall) e).getOperands().get(0);
@@ -1094,30 +1155,30 @@ public class RelDecorrelator implements ReflectiveVisitor {
      *   <li>{@code VARCHAR(10)} is a widening of {@code VARCHAR(10) NOT NULL}.
      * </ul>
      */
-    private boolean isWidening(RelDataType type, RelDataType type1) {
+    private static boolean isWidening(RelDataType type, RelDataType type1) {
         return type.getSqlTypeName() == type1.getSqlTypeName()
                 && type.getPrecision() >= type1.getPrecision();
     }
 
-    public Frame decorrelateRel(LogicalSnapshot rel) {
+    public @Nullable Frame decorrelateRel(LogicalSnapshot rel, boolean isCorVarDefined) {
         if (RexUtil.containsCorrelation(rel.getPeriod())) {
             return null;
         }
-        return decorrelateRel((RelNode) rel);
+        return decorrelateRel((RelNode) rel, isCorVarDefined);
     }
 
-    public Frame decorrelateRel(LogicalTableFunctionScan rel) {
+    public @Nullable Frame decorrelateRel(LogicalTableFunctionScan rel, boolean isCorVarDefined) {
         if (RexUtil.containsCorrelation(rel.getCall())) {
             return null;
         }
-        return decorrelateRel((RelNode) rel);
+        return decorrelateRel((RelNode) rel, isCorVarDefined);
     }
 
-    public Frame decorrelateRel(LogicalFilter rel) {
-        return decorrelateRel((Filter) rel);
+    public @Nullable Frame decorrelateRel(LogicalFilter rel, boolean isCorVarDefined) {
+        return decorrelateRel((Filter) rel, isCorVarDefined);
     }
 
-    public Frame decorrelateRel(Filter rel) {
+    public @Nullable Frame decorrelateRel(Filter rel, boolean isCorVarDefined) {
         //
         // Rewrite logic:
         //
@@ -1135,7 +1196,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
         //
 
         final RelNode oldInput = rel.getInput();
-        Frame frame = getInvoke(oldInput, rel);
+        Frame frame = getInvoke(oldInput, isCorVarDefined, rel);
         if (frame == null) {
             // If input has not been rewritten, do not rewrite this rel.
             return null;
@@ -1155,7 +1216,9 @@ public class RelDecorrelator implements ReflectiveVisitor {
 
         // Replace the filter expression to reference output of the join
         // Map filter to the new filter over join
-        relBuilder.push(frame.r).filter(decorrelateExpr(currentRel, map, cm2, rel.getCondition()));
+        relBuilder
+                .push(frame.r)
+                .filter(decorrelateExpr(castNonNull(currentRel), map, cm2, rel.getCondition()));
 
         // Filter does not change the input ordering.
         // Filter rel does not permute the input.
@@ -1164,11 +1227,11 @@ public class RelDecorrelator implements ReflectiveVisitor {
         return register(rel, relBuilder.build(), frame.oldToNewOutputs, frame.corDefOutputs);
     }
 
-    public Frame decorrelateRel(LogicalCorrelate rel) {
-        return decorrelateRel((Correlate) rel);
+    public @Nullable Frame decorrelateRel(LogicalCorrelate rel, boolean isCorVarDefined) {
+        return decorrelateRel((Correlate) rel, isCorVarDefined);
     }
 
-    public Frame decorrelateRel(Correlate rel) {
+    public @Nullable Frame decorrelateRel(Correlate rel, boolean isCorVarDefined) {
         //
         // Rewrite logic:
         //
@@ -1182,8 +1245,8 @@ public class RelDecorrelator implements ReflectiveVisitor {
         final RelNode oldLeft = rel.getInput(0);
         final RelNode oldRight = rel.getInput(1);
 
-        final Frame leftFrame = getInvoke(oldLeft, rel);
-        final Frame rightFrame = getInvoke(oldRight, rel);
+        final Frame leftFrame = getInvoke(oldLeft, isCorVarDefined, rel);
+        final Frame rightFrame = getInvoke(oldRight, true, rel);
 
         if (leftFrame == null || rightFrame == null) {
             // If any input has not been rewritten, do not rewrite this rel.
@@ -1199,7 +1262,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
         // Change correlator rel into a join.
         // Join all the correlated variables produced by this correlator rel
         // with the values generated and propagated from the right input
-        final SortedMap<CorDef, Integer> corDefOutputs = new TreeMap<>(rightFrame.corDefOutputs);
+        final NavigableMap<CorDef, Integer> corDefOutputs = new TreeMap<>(rightFrame.corDefOutputs);
         final List<RexNode> conditions = new ArrayList<>();
         final List<RelDataTypeField> newLeftOutput = leftFrame.r.getRowType().getFieldList();
         int newLeftFieldCount = newLeftOutput.size();
@@ -1227,9 +1290,8 @@ public class RelDecorrelator implements ReflectiveVisitor {
 
         // Update the output position for the corVars: only pass on the cor
         // vars that are not used in the join key.
-        for (CorDef corDef : corDefOutputs.keySet()) {
-            int newPos = corDefOutputs.get(corDef) + newLeftFieldCount;
-            corDefOutputs.put(corDef, newPos);
+        for (Map.Entry<CorDef, Integer> entry : corDefOutputs.entrySet()) {
+            entry.setValue(entry.getValue() + newLeftFieldCount);
         }
 
         // then add any corVar from the left input. Do not need to change
@@ -1267,16 +1329,16 @@ public class RelDecorrelator implements ReflectiveVisitor {
         return register(rel, newJoin, mapOldToNewOutputs, corDefOutputs);
     }
 
-    public Frame decorrelateRel(LogicalJoin rel) {
-        return decorrelateRel((Join) rel);
+    public @Nullable Frame decorrelateRel(LogicalJoin rel, boolean isCorVarDefined) {
+        return decorrelateRel((Join) rel, isCorVarDefined);
     }
 
-    public Frame decorrelateRel(Join rel) {
+    public @Nullable Frame decorrelateRel(Join rel, boolean isCorVarDefined) {
         // For SEMI/ANTI join decorrelate it's input directly,
         // because the correlate variables can only be propagated from
         // the left side, which is not supported yet.
         if (!rel.getJoinType().projectsRight()) {
-            return decorrelateRel((RelNode) rel);
+            return decorrelateRel((RelNode) rel, isCorVarDefined);
         }
         //
         // Rewrite logic:
@@ -1288,8 +1350,8 @@ public class RelDecorrelator implements ReflectiveVisitor {
         final RelNode oldLeft = rel.getInput(0);
         final RelNode oldRight = rel.getInput(1);
 
-        final Frame leftFrame = getInvoke(oldLeft, rel);
-        final Frame rightFrame = getInvoke(oldRight, rel);
+        final Frame leftFrame = getInvoke(oldLeft, isCorVarDefined, rel);
+        final Frame rightFrame = getInvoke(oldRight, isCorVarDefined, rel);
 
         if (leftFrame == null || rightFrame == null) {
             // If any input has not been rewritten, do not rewrite this rel.
@@ -1302,7 +1364,8 @@ public class RelDecorrelator implements ReflectiveVisitor {
                         .push(rightFrame.r)
                         .join(
                                 rel.getJoinType(),
-                                decorrelateExpr(currentRel, map, cm, rel.getCondition()),
+                                decorrelateExpr(
+                                        castNonNull(currentRel), map, cm, rel.getCondition()),
                                 ImmutableSet.of())
                         .hints(rel.getHints())
                         .build();
@@ -1327,7 +1390,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
                     i + oldLeftFieldCount, rightFrame.oldToNewOutputs.get(i) + newLeftFieldCount);
         }
 
-        final SortedMap<CorDef, Integer> corDefOutputs = new TreeMap<>(leftFrame.corDefOutputs);
+        final NavigableMap<CorDef, Integer> corDefOutputs = new TreeMap<>(leftFrame.corDefOutputs);
 
         // Right input positions are shifted by newLeftFieldCount.
         for (Map.Entry<CorDef, Integer> entry : rightFrame.corDefOutputs.entrySet()) {
@@ -1354,7 +1417,9 @@ public class RelDecorrelator implements ReflectiveVisitor {
                 oldInput = oldInput0;
                 break;
             }
-            RelNode newInput = map.get(oldInput0).r;
+            RelNode newInput =
+                    requireNonNull(map.get(oldInput0), () -> "map.get(oldInput0) for " + oldInput0)
+                            .r;
             newOrdinal += newInput.getRowType().getFieldCount();
             oldOrdinal -= n;
         }
@@ -1485,9 +1550,9 @@ public class RelDecorrelator implements ReflectiveVisitor {
      */
     private boolean checkCorVars(
             Correlate correlate,
-            Project project,
-            Filter filter,
-            List<RexFieldAccess> correlatedJoinKeys) {
+            @Nullable Project project,
+            @Nullable Filter filter,
+            @Nullable List<RexFieldAccess> correlatedJoinKeys) {
         if (filter != null) {
             assert correlatedJoinKeys != null;
 
@@ -1534,9 +1599,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
      * @param correlate Correlate
      */
     private void removeCorVarFromTree(Correlate correlate) {
-        if (cm.mapCorToCorRel.get(correlate.getCorrelationId()) == correlate) {
-            cm.mapCorToCorRel.remove(correlate.getCorrelationId());
-        }
+        cm.mapCorToCorRel.remove(correlate.getCorrelationId(), correlate);
     }
 
     /**
@@ -1581,7 +1644,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
             RelNode rel,
             RelNode newRel,
             Map<Integer, Integer> oldToNewOutputs,
-            SortedMap<CorDef, Integer> corDefOutputs) {
+            NavigableMap<CorDef, Integer> corDefOutputs) {
         final Frame frame = new Frame(rel, newRel, corDefOutputs, oldToNewOutputs);
         map.put(rel, frame);
         return frame;
@@ -1613,9 +1676,9 @@ public class RelDecorrelator implements ReflectiveVisitor {
         private final CorelMap cm;
 
         private DecorrelateRexShuttle(RelNode currentRel, Map<RelNode, Frame> map, CorelMap cm) {
-            this.currentRel = Objects.requireNonNull(currentRel);
-            this.map = Objects.requireNonNull(map);
-            this.cm = Objects.requireNonNull(cm);
+            this.currentRel = requireNonNull(currentRel, "currentRel");
+            this.map = requireNonNull(map, "map");
+            this.cm = requireNonNull(cm, "cm");
         }
 
         @Override
@@ -1663,13 +1726,13 @@ public class RelDecorrelator implements ReflectiveVisitor {
         final RexBuilder rexBuilder;
         final RelDataTypeFactory typeFactory;
         final boolean projectPulledAboveLeftCorrelator;
-        final RexInputRef nullIndicator;
+        final @Nullable RexInputRef nullIndicator;
         final ImmutableSet<Integer> isCount;
 
         RemoveCorrelationRexShuttle(
                 RexBuilder rexBuilder,
                 boolean projectPulledAboveLeftCorrelator,
-                RexInputRef nullIndicator,
+                @Nullable RexInputRef nullIndicator,
                 Set<Integer> isCount) {
             this.projectPulledAboveLeftCorrelator = projectPulledAboveLeftCorrelator;
             this.nullIndicator = nullIndicator; // may be null
@@ -1679,7 +1742,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
         }
 
         private RexNode createCaseExpression(
-                RexInputRef nullInputRef, RexLiteral lit, RexNode rexNode) {
+                RexInputRef nullInputRef, @Nullable RexLiteral lit, RexNode rexNode) {
             RexNode[] caseOperands = new RexNode[3];
 
             // Construct a CASE expression to handle the null indicator.
@@ -1754,7 +1817,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
                 int pos = inputRef.getIndex();
                 RexInputRef newInputRef = new RexInputRef(leftInputFieldCount + pos, newType);
 
-                if ((isCount != null) && isCount.contains(pos)) {
+                if (isCount.contains(pos)) {
                     return createCaseExpression(
                             newInputRef, rexBuilder.makeExactLiteral(BigDecimal.ZERO), newInputRef);
                 } else {
@@ -1857,7 +1920,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
         }
 
         /** Creates a RemoveSingleAggregateRule. */
-        protected RemoveSingleAggregateRule(Config config) {
+        RemoveSingleAggregateRule(Config config) {
             super(config);
         }
 
@@ -1868,7 +1931,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
             Aggregate aggregate = call.rel(2);
 
             // check singleAggRel is single_value agg
-            if ((!singleAggregate.getGroupSet().isEmpty())
+            if (!singleAggregate.getGroupSet().isEmpty()
                     || (singleAggregate.getAggCallList().size() != 1)
                     || !(singleAggregate.getAggCallList().get(0).getAggregation()
                             instanceof SqlSingleValueAggFunction)) {
@@ -1943,9 +2006,9 @@ public class RelDecorrelator implements ReflectiveVisitor {
         }
 
         /** Creates a RemoveCorrelationForScalarProjectRule. */
-        protected RemoveCorrelationForScalarProjectRule(Config config) {
+        RemoveCorrelationForScalarProjectRule(Config config) {
             super(config);
-            this.d = Objects.requireNonNull(config.decorrelator());
+            this.d = requireNonNull(config.decorrelator());
         }
 
         @Override
@@ -1979,7 +2042,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
 
             // check that the agg is of the following type:
             // doing a single_value() on the entire input
-            if ((!aggregate.getGroupSet().isEmpty())
+            if (!aggregate.getGroupSet().isEmpty()
                     || (aggregate.getAggCallList().size() != 1)
                     || !(aggregate.getAggCallList().get(0).getAggregation()
                             instanceof SqlSingleValueAggFunction)) {
@@ -2174,9 +2237,9 @@ public class RelDecorrelator implements ReflectiveVisitor {
         }
 
         /** Creates a RemoveCorrelationForScalarAggregateRule. */
-        protected RemoveCorrelationForScalarAggregateRule(Config config) {
+        RemoveCorrelationForScalarAggregateRule(Config config) {
             super(config);
-            d = Objects.requireNonNull(config.decorrelator());
+            d = requireNonNull(config.decorrelator());
         }
 
         @Override
@@ -2568,9 +2631,9 @@ public class RelDecorrelator implements ReflectiveVisitor {
         }
 
         /** Creates an AdjustProjectForCountAggregateRule. */
-        protected AdjustProjectForCountAggregateRule(Config config) {
+        AdjustProjectForCountAggregateRule(Config config) {
             super(config);
-            this.d = Objects.requireNonNull(config.decorrelator());
+            this.d = requireNonNull(config.decorrelator());
         }
 
         @Override
@@ -2741,7 +2804,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
         }
 
         @Override
-        public boolean equals(Object o) {
+        public boolean equals(@Nullable Object o) {
             return this == o
                     || o instanceof CorRef
                             && uniqueKey == ((CorRef) o).uniqueKey
@@ -2749,7 +2812,8 @@ public class RelDecorrelator implements ReflectiveVisitor {
                             && field == ((CorRef) o).field;
         }
 
-        public int compareTo(@Nonnull CorRef o) {
+        @Override
+        public int compareTo(CorRef o) {
             int c = corr.compareTo(o.corr);
             if (c != 0) {
                 return c;
@@ -2787,14 +2851,15 @@ public class RelDecorrelator implements ReflectiveVisitor {
         }
 
         @Override
-        public boolean equals(Object o) {
+        public boolean equals(@Nullable Object o) {
             return this == o
                     || o instanceof CorDef
                             && corr == ((CorDef) o).corr
                             && field == ((CorDef) o).field;
         }
 
-        public int compareTo(@Nonnull CorDef o) {
+        @Override
+        public int compareTo(CorDef o) {
             int c = corr.compareTo(o.corr);
             if (c != 0) {
                 return c;
@@ -2824,13 +2889,13 @@ public class RelDecorrelator implements ReflectiveVisitor {
      */
     protected static class CorelMap {
         private final Multimap<RelNode, CorRef> mapRefRelToCorRef;
-        private final SortedMap<CorrelationId, RelNode> mapCorToCorRel;
+        private final NavigableMap<CorrelationId, RelNode> mapCorToCorRel;
         private final Map<RexFieldAccess, CorRef> mapFieldAccessToCorRef;
 
         // TODO: create immutable copies of all maps
         private CorelMap(
                 Multimap<RelNode, CorRef> mapRefRelToCorRef,
-                SortedMap<CorrelationId, RelNode> mapCorToCorRel,
+                NavigableMap<CorrelationId, RelNode> mapCorToCorRel,
                 Map<RexFieldAccess, CorRef> mapFieldAccessToCorRef) {
             this.mapRefRelToCorRef = mapRefRelToCorRef;
             this.mapCorToCorRel = mapCorToCorRel;
@@ -2848,10 +2913,12 @@ public class RelDecorrelator implements ReflectiveVisitor {
                     + "\n";
         }
 
+        @SuppressWarnings("UndefinedEquals")
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             return obj == this
                     || obj instanceof CorelMap
+                            // TODO: Multimap does not have well-defined equals behavior
                             && mapRefRelToCorRef.equals(((CorelMap) obj).mapRefRelToCorRef)
                             && mapCorToCorRel.equals(((CorelMap) obj).mapCorToCorRel)
                             && mapFieldAccessToCorRef.equals(
@@ -2866,12 +2933,12 @@ public class RelDecorrelator implements ReflectiveVisitor {
         /** Creates a CorelMap with given contents. */
         public static CorelMap of(
                 SortedSetMultimap<RelNode, CorRef> mapRefRelToCorVar,
-                SortedMap<CorrelationId, RelNode> mapCorToCorRel,
+                NavigableMap<CorrelationId, RelNode> mapCorToCorRel,
                 Map<RexFieldAccess, CorRef> mapFieldAccessToCorVar) {
             return new CorelMap(mapRefRelToCorVar, mapCorToCorRel, mapFieldAccessToCorVar);
         }
 
-        public SortedMap<CorrelationId, RelNode> getMapCorToCorRel() {
+        public NavigableMap<CorrelationId, RelNode> getMapCorToCorRel() {
             return mapCorToCorRel;
         }
 
@@ -2887,7 +2954,7 @@ public class RelDecorrelator implements ReflectiveVisitor {
 
     /** Builds a {@link org.apache.calcite.sql2rel.RelDecorrelator.CorelMap}. */
     public static class CorelMapBuilder extends RelHomogeneousShuttle {
-        final SortedMap<CorrelationId, RelNode> mapCorToCorRel = new TreeMap<>();
+        final NavigableMap<CorrelationId, RelNode> mapCorToCorRel = new TreeMap<>();
 
         final SortedSetMultimap<RelNode, CorRef> mapRefRelToCorRef =
                 MultimapBuilder.SortedSetMultimapBuilder.hashKeys().treeSetValues().build();
@@ -3003,9 +3070,9 @@ public class RelDecorrelator implements ReflectiveVisitor {
         Frame(
                 RelNode oldRel,
                 RelNode r,
-                SortedMap<CorDef, Integer> corDefOutputs,
+                NavigableMap<CorDef, Integer> corDefOutputs,
                 Map<Integer, Integer> oldToNewOutputs) {
-            this.r = Objects.requireNonNull(r);
+            this.r = requireNonNull(r, "r");
             this.corDefOutputs = ImmutableSortedMap.copyOf(corDefOutputs);
             this.oldToNewOutputs = ImmutableSortedMap.copyOf(oldToNewOutputs);
             assert allLessThan(
@@ -3028,4 +3095,22 @@ public class RelDecorrelator implements ReflectiveVisitor {
         /** Sets {@link #decorrelator}. */
         Config withDecorrelator(RelDecorrelator decorrelator);
     }
+
+    // -------------------------------------------------------------------------
+    //  Getter/Setter
+    // -------------------------------------------------------------------------
+
+    /**
+     * Returns the {@code visitor} on which the {@code MethodDispatcher} dispatches each {@code
+     * decorrelateRel} method, the default implementation returns this instance, if you got a
+     * sub-class, override this method to replace the {@code visitor} as the sub-class instance.
+     */
+    protected RelDecorrelator getVisitor() {
+        return this;
+    }
+
+    /** Returns the rules applied on the rel after decorrelation, never null. */
+    protected Collection<RelOptRule> getPostDecorrelateRules() {
+        return Collections.emptyList();
+    }
 }
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index 908f788cfa0..7aaf3b01eeb 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -21,6 +21,11 @@ package org.apache.calcite.sql2rel;
 import org.apache.flink.table.planner.alias.ClearJoinHintWithInvalidPropagationShuttle;
 import org.apache.flink.table.planner.hint.FlinkHints;
 
+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.Iterables;
 import org.apache.calcite.avatica.util.Spaces;
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.plan.Convention;
@@ -78,7 +83,6 @@ import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeField;
 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.RexFieldAccess;
@@ -132,6 +136,7 @@ import org.apache.calcite.sql.SqlSelectKeyword;
 import org.apache.calcite.sql.SqlSetOperator;
 import org.apache.calcite.sql.SqlSnapshot;
 import org.apache.calcite.sql.SqlUnnestOperator;
+import org.apache.calcite.sql.SqlUnpivot;
 import org.apache.calcite.sql.SqlUpdate;
 import org.apache.calcite.sql.SqlUtil;
 import org.apache.calcite.sql.SqlValuesOperator;
@@ -139,7 +144,6 @@ import org.apache.calcite.sql.SqlWindow;
 import org.apache.calcite.sql.SqlWith;
 import org.apache.calcite.sql.SqlWithItem;
 import org.apache.calcite.sql.fun.SqlCase;
-import org.apache.calcite.sql.fun.SqlCountAggFunction;
 import org.apache.calcite.sql.fun.SqlInOperator;
 import org.apache.calcite.sql.fun.SqlQuantifyOperator;
 import org.apache.calcite.sql.fun.SqlRowOperator;
@@ -180,10 +184,9 @@ import org.apache.calcite.util.NumberUtil;
 import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 import org.apache.calcite.util.trace.CalciteTrace;
+import org.checkerframework.checker.nullness.qual.Nullable;
 import org.slf4j.Logger;
 
-import javax.annotation.Nonnull;
-
 import java.lang.reflect.Type;
 import java.math.BigDecimal;
 import java.util.AbstractList;
@@ -201,17 +204,29 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.function.BiFunction;
 import java.util.function.Supplier;
 import java.util.function.UnaryOperator;
 import java.util.stream.Collectors;
 
+import static java.util.Objects.requireNonNull;
+import static org.apache.calcite.linq4j.Nullness.castNonNull;
 import static org.apache.calcite.sql.SqlUtil.stripAs;
 
 /**
- * Converts a SQL parse tree (consisting of {@link SqlNode} objects) into a relational algebra
- * expression (consisting of {@link RelNode} objects).
+ * Converts a SQL parse tree (consisting of {@link org.apache.calcite.sql.SqlNode} objects) into a
+ * relational algebra expression (consisting of {@link org.apache.calcite.rel.RelNode} objects).
  *
  * <p>The public entry points are: {@link #convertQuery}, {@link #convertExpression(SqlNode)}.
+ *
+ * <p>FLINK modifications are at lines
+ *
+ * <ol>
+ *   <li>Added in FLINK-29081, FLINK-28682: Lines 634 ~ 644
+ *   <li>Added in FLINK-28682: Lines 2097 ~ 2114
+ *   <li>Added in FLINK-28682: Lines 2152 ~ 2180
+ *   <li>Added in FLINK-20873: Lines 5159 ~ 5168
+ * </ol>
  */
 @SuppressWarnings("UnstableApiUsage")
 public class SqlToRelConverter {
@@ -234,19 +249,19 @@ public class SqlToRelConverter {
 
     // ~ Instance fields --------------------------------------------------------
 
-    protected final SqlValidator validator;
+    protected final @Nullable SqlValidator validator;
     protected final RexBuilder rexBuilder;
     protected final Prepare.CatalogReader catalogReader;
     protected final RelOptCluster cluster;
     private SubQueryConverter subQueryConverter;
     protected final Map<RelNode, Integer> leaves = new HashMap<>();
-    private final List<SqlDynamicParam> dynamicParamSqlNodes = new ArrayList<>();
+    private final List<@Nullable SqlDynamicParam> dynamicParamSqlNodes = new ArrayList<>();
     private final SqlOperatorTable opTab;
     protected final RelDataTypeFactory typeFactory;
     private final SqlNodeToRexConverter exprConverter;
     private final HintStrategyTable hintStrategies;
     private int explainParamCount;
-    public final Config config;
+    public final SqlToRelConverter.Config config;
     private final RelBuilder relBuilder;
 
     /** Fields used in name resolution for correlated sub-queries. */
@@ -313,7 +328,7 @@ public class SqlToRelConverter {
     /* Creates a converter. */
     public SqlToRelConverter(
             RelOptTable.ViewExpander viewExpander,
-            SqlValidator validator,
+            @Nullable SqlValidator validator,
             Prepare.CatalogReader catalogReader,
             RelOptCluster cluster,
             SqlRexConvertletTable convertletTable,
@@ -328,7 +343,7 @@ public class SqlToRelConverter {
         this.typeFactory = rexBuilder.getTypeFactory();
         this.exprConverter = new SqlNodeToRexConverterImpl(convertletTable);
         this.explainParamCount = 0;
-        this.config = Objects.requireNonNull(config);
+        this.config = requireNonNull(config, "config");
         this.relBuilder =
                 config.getRelBuilderFactory()
                         .create(cluster, null)
@@ -336,11 +351,27 @@ public class SqlToRelConverter {
         this.hintStrategies = config.getHintStrategyTable();
 
         cluster.setHintStrategies(this.hintStrategies);
-        this.cluster = Objects.requireNonNull(cluster);
+        this.cluster = requireNonNull(cluster, "cluster");
     }
 
     // ~ Methods ----------------------------------------------------------------
 
+    private SqlValidator validator() {
+        return requireNonNull(validator, "validator");
+    }
+
+    private <T extends SqlValidatorNamespace> T getNamespace(SqlNode node) {
+        //noinspection unchecked
+        return (T)
+                requireNonNull(
+                        getNamespaceOrNull(node), () -> "Namespace is not found for " + node);
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T extends SqlValidatorNamespace> @Nullable T getNamespaceOrNull(SqlNode node) {
+        return (@Nullable T) validator().getNamespace(node);
+    }
+
     /** Returns the RelOptCluster in use. */
     public RelOptCluster getCluster() {
         return cluster;
@@ -372,7 +403,7 @@ public class SqlToRelConverter {
         if (sqlNode == null) {
             throw Util.needToImplement("dynamic param type inference");
         }
-        return validator.getValidatedNodeType(sqlNode);
+        return validator().getValidatedNodeType(sqlNode);
     }
 
     /**
@@ -438,9 +469,9 @@ public class SqlToRelConverter {
         // validator type information associated with its result,
         // hence the namespace check above.)
         final List<RelDataTypeField> validatedFields =
-                validator.getValidatedNodeType(query).getFieldList();
+                validator().getValidatedNodeType(query).getFieldList();
         final RelDataType validatedRowType =
-                validator
+                validator()
                         .getTypeFactory()
                         .createStructType(
                                 Pair.right(validatedFields),
@@ -451,7 +482,7 @@ public class SqlToRelConverter {
         final List<RelDataTypeField> convertedFields =
                 result.getRowType().getFieldList().subList(0, validatedFields.size());
         final RelDataType convertedRowType =
-                validator.getTypeFactory().createStructType(convertedFields);
+                validator().getTypeFactory().createStructType(convertedFields);
 
         if (!RelOptUtil.equal(
                 "validated row type",
@@ -476,7 +507,7 @@ public class SqlToRelConverter {
                 new RelStructuredTypeFlattener(
                         relBuilder,
                         rexBuilder,
-                        createToRelContext(com.google.common.collect.ImmutableList.of()),
+                        createToRelContext(ImmutableList.of()),
                         restructure);
         return typeFlattener.rewrite(rootRel);
     }
@@ -526,8 +557,7 @@ public class SqlToRelConverter {
             if (!ordered
                     && collations != null
                     && !collations.isEmpty()
-                    && !collations.equals(
-                            com.google.common.collect.ImmutableList.of(RelCollations.EMPTY))) {
+                    && !collations.equals(ImmutableList.of(RelCollations.EMPTY))) {
                 final RelTraitSet traitSet =
                         rootRel.getTraitSet().replace(RelCollationTraitDef.INSTANCE, collations);
                 rootRel = rootRel.copy(traitSet, rootRel.getInputs());
@@ -564,7 +594,7 @@ public class SqlToRelConverter {
      */
     public RelRoot convertQuery(SqlNode query, final boolean needsValidation, final boolean top) {
         if (needsValidation) {
-            query = validator.validate(query);
+            query = validator().validate(query);
         }
 
         RelNode result = convertQueryRecursive(query, top, null).rel;
@@ -590,7 +620,7 @@ public class SqlToRelConverter {
                             SqlExplainLevel.EXPPLAN_ATTRIBUTES));
         }
 
-        final RelDataType validatedRowType = validator.getValidatedNodeType(query);
+        final RelDataType validatedRowType = validator().getValidatedNodeType(query);
         List<RelHint> hints = new ArrayList<>();
         if (query.getKind() == SqlKind.SELECT) {
             final SqlSelect select = (SqlSelect) query;
@@ -598,7 +628,7 @@ public class SqlToRelConverter {
                 hints = SqlUtil.getRelHint(hintStrategies, select.getHints());
             }
         }
-
+        // propagate the hints.
         result = RelOptUtil.propagateRelHints(result, false);
 
         // ----- FLINK MODIFICATION BEGIN -----
@@ -626,8 +656,8 @@ public class SqlToRelConverter {
     public static boolean isOrdered(SqlNode query) {
         switch (query.getKind()) {
             case SELECT:
-                return ((SqlSelect) query).getOrderList() != null
-                        && ((SqlSelect) query).getOrderList().size() > 0;
+                SqlNodeList orderList = ((SqlSelect) query).getOrderList();
+                return orderList != null && orderList.size() > 0;
             case WITH:
                 return isOrdered(((SqlWith) query).body);
             case ORDER_BY:
@@ -637,7 +667,7 @@ public class SqlToRelConverter {
         }
     }
 
-    private RelCollation requiredCollation(RelNode r) {
+    private static RelCollation requiredCollation(RelNode r) {
         if (r instanceof Sort) {
             return ((Sort) r).collation;
         }
@@ -652,15 +682,17 @@ public class SqlToRelConverter {
 
     /** Converts a SELECT statement's parse tree into a relational expression. */
     public RelNode convertSelect(SqlSelect select, boolean top) {
-        final SqlValidatorScope selectScope = validator.getWhereScope(select);
+        final SqlValidatorScope selectScope = validator().getWhereScope(select);
         final Blackboard bb = createBlackboard(selectScope, null, top);
         convertSelectImpl(bb, select);
-        return bb.root;
+        return castNonNull(bb.root);
     }
 
     /** Factory method for creating translation workspace. */
     protected Blackboard createBlackboard(
-            SqlValidatorScope scope, Map<String, RexNode> nameToNodeMap, boolean top) {
+            @Nullable SqlValidatorScope scope,
+            @Nullable Map<String, RexNode> nameToNodeMap,
+            boolean top) {
         return new Blackboard(scope, nameToNodeMap, top);
     }
 
@@ -674,7 +706,7 @@ public class SqlToRelConverter {
         gatherOrderExprs(bb, select, select.getOrderList(), orderExprList, collationList);
         final RelCollation collation = cluster.traitSet().canonize(RelCollations.of(collationList));
 
-        if (validator.isAggregate(select)) {
+        if (validator().isAggregate(select)) {
             convertAgg(bb, select, orderExprList);
         } else {
             convertSelectList(bb, select, orderExprList);
@@ -690,23 +722,25 @@ public class SqlToRelConverter {
             final List<RelHint> hints = SqlUtil.getRelHint(hintStrategies, select.getHints());
             // Attach the hints to the first Hintable node we found from the root node.
             bb.setRoot(
-                    bb.root.accept(
-                            new RelShuttleImpl() {
-                                boolean attached = false;
-
-                                @Override
-                                public RelNode visitChild(RelNode parent, int i, RelNode child) {
-                                    if (parent instanceof Hintable && !attached) {
-                                        attached = true;
-                                        return ((Hintable) parent).attachHints(hints);
-                                    } else {
-                                        return super.visitChild(parent, i, child);
-                                    }
-                                }
-                            }),
+                    bb.root()
+                            .accept(
+                                    new RelShuttleImpl() {
+                                        boolean attached = false;
+
+                                        @Override
+                                        public RelNode visitChild(
+                                                RelNode parent, int i, RelNode child) {
+                                            if (parent instanceof Hintable && !attached) {
+                                                attached = true;
+                                                return ((Hintable) parent).attachHints(hints);
+                                            } else {
+                                                return super.visitChild(parent, i, child);
+                                            }
+                                        }
+                                    }),
                     true);
         } else {
-            bb.setRoot(bb.root, true);
+            bb.setRoot(bb.root(), true);
         }
     }
 
@@ -714,9 +748,10 @@ public class SqlToRelConverter {
      * Having translated 'SELECT ... FROM ... [GROUP BY ...] [HAVING ...]', adds a relational
      * expression to make the results unique.
      *
-     * <p>If the SELECT clause contains duplicate expressions, adds {@link LogicalProject}s so that
-     * we are grouping on the minimal set of keys. The performance gain isn't huge, but it is
-     * difficult to detect these duplicate expressions later.
+     * <p>If the SELECT clause contains duplicate expressions, adds {@link
+     * org.apache.calcite.rel.logical.LogicalProject}s so that we are grouping on the minimal set of
+     * keys. The performance gain isn't huge, but it is difficult to detect these duplicate
+     * expressions later.
      *
      * @param bb Blackboard
      * @param checkForDupExprs Check for duplicate expressions
@@ -758,12 +793,12 @@ public class SqlToRelConverter {
             rel =
                     LogicalProject.create(
                             rel,
-                            com.google.common.collect.ImmutableList.of(),
+                            ImmutableList.of(),
                             Pair.left(newProjects),
                             Pair.right(newProjects));
             bb.root = rel;
             distinctify(bb, false);
-            rel = bb.root;
+            rel = bb.root();
 
             // Create the expressions to reverse the mapping.
             // Project($0, $1, $0, $2).
@@ -773,14 +808,14 @@ public class SqlToRelConverter {
                 RelDataTypeField field = fields.get(i);
                 undoProjects.add(
                         Pair.of(
-                                new RexInputRef(squished.get(origin), field.getType()),
+                                new RexInputRef(castNonNull(squished.get(origin)), field.getType()),
                                 field.getName()));
             }
 
             rel =
                     LogicalProject.create(
                             rel,
-                            com.google.common.collect.ImmutableList.of(),
+                            ImmutableList.of(),
                             Pair.left(undoProjects),
                             Pair.right(undoProjects));
             bb.setRoot(rel, false);
@@ -788,15 +823,11 @@ public class SqlToRelConverter {
             return;
         }
 
+        assert rel != null : "rel must not be null, root = " + bb.root;
         // Usual case: all of the expressions in the SELECT clause are
         // different.
         final ImmutableBitSet groupSet = ImmutableBitSet.range(rel.getRowType().getFieldCount());
-        rel =
-                createAggregate(
-                        bb,
-                        groupSet,
-                        com.google.common.collect.ImmutableList.of(groupSet),
-                        com.google.common.collect.ImmutableList.of());
+        rel = createAggregate(bb, groupSet, ImmutableList.of(groupSet), ImmutableList.of());
 
         bb.setRoot(rel, false);
     }
@@ -820,17 +851,17 @@ public class SqlToRelConverter {
             Blackboard bb,
             RelCollation collation,
             List<SqlNode> orderExprList,
-            SqlNode offset,
-            SqlNode fetch) {
+            @Nullable SqlNode offset,
+            @Nullable SqlNode fetch) {
         if (removeSortInSubQuery(bb.top)
                 || select.getOrderList() == null
-                || select.getOrderList().getList().isEmpty()) {
+                || select.getOrderList().isEmpty()) {
             assert removeSortInSubQuery(bb.top) || collation.getFieldCollations().isEmpty();
             if ((offset == null
                             || (offset instanceof SqlLiteral
-                                    && ((SqlLiteral) offset)
-                                            .bigDecimalValue()
-                                            .equals(BigDecimal.ZERO)))
+                                    && Objects.equals(
+                                            ((SqlLiteral) offset).bigDecimalValue(),
+                                            BigDecimal.ZERO)))
                     && fetch == null) {
                 return;
             }
@@ -839,7 +870,7 @@ public class SqlToRelConverter {
         // Create a sorter using the previously constructed collations.
         bb.setRoot(
                 LogicalSort.create(
-                        bb.root,
+                        bb.root(),
                         collation,
                         offset == null ? null : convertExpression(offset),
                         fetch == null ? null : convertExpression(fetch)),
@@ -852,15 +883,15 @@ public class SqlToRelConverter {
         // If it is the top node, use the real collation, but don't trim fields.
         if (orderExprList.size() > 0 && !bb.top) {
             final List<RexNode> exprs = new ArrayList<>();
-            final RelDataType rowType = bb.root.getRowType();
+            final RelDataType rowType = bb.root().getRowType();
             final int fieldCount = rowType.getFieldCount() - orderExprList.size();
             for (int i = 0; i < fieldCount; i++) {
-                exprs.add(rexBuilder.makeInputRef(bb.root, i));
+                exprs.add(rexBuilder.makeInputRef(bb.root(), i));
             }
             bb.setRoot(
                     LogicalProject.create(
-                            bb.root,
-                            com.google.common.collect.ImmutableList.of(),
+                            bb.root(),
+                            ImmutableList.of(),
                             exprs,
                             rowType.getFieldNames().subList(0, fieldCount)),
                     false);
@@ -885,6 +916,7 @@ public class SqlToRelConverter {
         try {
             SqlVisitor<Void> visitor =
                     new SqlBasicVisitor<Void>() {
+                        @Override
                         public Void visit(SqlCall call) {
                             if (call.getOperator() instanceof SqlInOperator) {
                                 throw new Util.FoundOne(call);
@@ -937,7 +969,10 @@ public class SqlToRelConverter {
                                             SqlParserPos.ZERO, thenOperand);
                             thenOperands.add(pushDownNotForIn(scope, reg(scope, not)));
                         }
-                        SqlNode elseOperand = caseNode.getElseOperand();
+                        SqlNode elseOperand =
+                                requireNonNull(
+                                        caseNode.getElseOperand(),
+                                        "getElseOperand for " + caseNode);
                         if (!SqlUtil.isNull(elseOperand)) {
                             // "not(unknown)" is "unknown", so no need to simplify
                             final SqlCall not =
@@ -1000,7 +1035,12 @@ public class SqlToRelConverter {
                                 scope,
                                 SqlStdOperatorTable.NOT_IN.createCall(
                                         SqlParserPos.ZERO, call.getOperandList()));
+                    default:
+                        break;
                 }
+                break;
+            default:
+                break;
         }
         return sqlNode;
     }
@@ -1020,11 +1060,11 @@ public class SqlToRelConverter {
      * @param bb Blackboard
      * @param where WHERE clause, may be null
      */
-    private void convertWhere(final Blackboard bb, final SqlNode where) {
+    private void convertWhere(final Blackboard bb, final @Nullable SqlNode where) {
         if (where == null) {
             return;
         }
-        SqlNode newWhere = pushDownNotForIn(bb.scope, where);
+        SqlNode newWhere = pushDownNotForIn(bb.scope(), where);
         replaceSubQueries(bb, newWhere, RelOptUtil.Logic.UNKNOWN_AS_FALSE);
         final RexNode convertedWhere = bb.convertExpression(newWhere);
         final RexNode convertedWhere2 = RexUtil.removeNullabilityCast(typeFactory, convertedWhere);
@@ -1036,18 +1076,13 @@ public class SqlToRelConverter {
 
         final RelFactories.FilterFactory filterFactory = RelFactories.DEFAULT_FILTER_FACTORY;
         final RelNode filter =
-                filterFactory.createFilter(
-                        bb.root, convertedWhere2, com.google.common.collect.ImmutableSet.of());
+                filterFactory.createFilter(bb.root(), convertedWhere2, ImmutableSet.of());
         final RelNode r;
         final CorrelationUse p = getCorrelationUse(bb, filter);
         if (p != null) {
             assert p.r instanceof Filter;
             Filter f = (Filter) p.r;
-            r =
-                    LogicalFilter.create(
-                            f.getInput(),
-                            f.getCondition(),
-                            com.google.common.collect.ImmutableSet.of(p.id));
+            r = LogicalFilter.create(f.getInput(), f.getCondition(), ImmutableSet.of(p.id));
         } else {
             r = filter;
         }
@@ -1082,9 +1117,7 @@ public class SqlToRelConverter {
             case MULTISET_QUERY_CONSTRUCTOR:
             case MULTISET_VALUE_CONSTRUCTOR:
             case ARRAY_QUERY_CONSTRUCTOR:
-                rel =
-                        convertMultisets(
-                                com.google.common.collect.ImmutableList.of(subQuery.node), bb);
+                rel = convertMultisets(ImmutableList.of(subQuery.node), bb);
                 subQuery.expr = bb.register(rel, JoinRelType.INNER);
                 return;
 
@@ -1108,9 +1141,7 @@ public class SqlToRelConverter {
                         }
                         break;
                     default:
-                        leftKeys =
-                                com.google.common.collect.ImmutableList.of(
-                                        bb.convertExpression(leftKeyNode));
+                        leftKeys = ImmutableList.of(bb.convertExpression(leftKeyNode));
                 }
 
                 if (query instanceof SqlNodeList) {
@@ -1167,7 +1198,7 @@ public class SqlToRelConverter {
                 }
                 final RelDataType targetRowType =
                         SqlTypeUtil.promoteToRowType(
-                                typeFactory, validator.getValidatedNodeType(leftKeyNode), null);
+                                typeFactory, validator().getValidatedNodeType(leftKeyNode), null);
                 final boolean notIn = call.getOperator().kind == SqlKind.NOT_IN;
                 converted =
                         convertExists(
@@ -1187,17 +1218,18 @@ public class SqlToRelConverter {
                     LogicalAggregate aggregate =
                             LogicalAggregate.create(
                                     seek,
-                                    com.google.common.collect.ImmutableList.of(),
+                                    ImmutableList.of(),
                                     ImmutableBitSet.of(),
                                     null,
-                                    com.google.common.collect.ImmutableList.of(
+                                    ImmutableList.of(
                                             AggregateCall.create(
                                                     SqlStdOperatorTable.COUNT,
                                                     false,
                                                     false,
                                                     false,
-                                                    com.google.common.collect.ImmutableList.of(),
+                                                    ImmutableList.of(),
                                                     -1,
+                                                    null,
                                                     RelCollations.EMPTY,
                                                     longType,
                                                     null),
@@ -1208,16 +1240,17 @@ public class SqlToRelConverter {
                                                     false,
                                                     args,
                                                     -1,
+                                                    null,
                                                     RelCollations.EMPTY,
                                                     longType,
                                                     null)));
                     LogicalJoin join =
                             LogicalJoin.create(
-                                    bb.root,
+                                    bb.root(),
                                     aggregate,
-                                    com.google.common.collect.ImmutableList.of(),
+                                    ImmutableList.of(),
                                     rexBuilder.makeLiteral(true),
-                                    com.google.common.collect.ImmutableSet.of(),
+                                    ImmutableSet.of(),
                                     JoinRelType.INNER);
                     bb.setRoot(join, false);
                 }
@@ -1234,6 +1267,9 @@ public class SqlToRelConverter {
                         if (!converted.indicator) {
                             logic = RelOptUtil.Logic.TRUE_FALSE;
                         }
+                        break;
+                    default:
+                        break;
                 }
                 subQuery.expr = translateIn(logic, bb.root, rex);
                 if (notIn) {
@@ -1258,10 +1294,11 @@ public class SqlToRelConverter {
                 }
                 final SqlValidatorScope seekScope =
                         (query instanceof SqlSelect)
-                                ? validator.getSelectScope((SqlSelect) query)
+                                ? validator().getSelectScope((SqlSelect) query)
                                 : null;
                 final Blackboard seekBb = createBlackboard(seekScope, null, false);
                 final RelNode seekRel = convertQueryOrInList(seekBb, query, null);
+                requireNonNull(seekRel, () -> "seelkRel is null for query " + query);
                 // An EXIST sub-query whose inner child has at least 1 tuple
                 // (e.g. an Aggregate with no grouping columns or non-empty Values
                 // node) should be simplified to a Boolean constant expression.
@@ -1331,7 +1368,7 @@ public class SqlToRelConverter {
         }
     }
 
-    private RexNode translateIn(RelOptUtil.Logic logic, RelNode root, final RexNode rex) {
+    private RexNode translateIn(RelOptUtil.Logic logic, @Nullable RelNode root, final RexNode rex) {
         switch (logic) {
             case TRUE:
                 return rexBuilder.makeLiteral(true);
@@ -1377,7 +1414,7 @@ public class SqlToRelConverter {
                 // cross join (select count(*) as c, count(deptno) as ck from v) as ct
                 // left join (select distinct deptno, true as i from v) as dt
                 //   on e.deptno = dt.deptno
-                final Join join = (Join) root;
+                final Join join = (Join) requireNonNull(root, "root");
                 final Project left = (Project) join.getLeft();
                 final RelNode leftLeft = ((Join) left.getInput()).getLeft();
                 final int leftLeftCount = leftLeft.getRowType().getFieldCount();
@@ -1392,8 +1429,7 @@ public class SqlToRelConverter {
                 final RexLiteral falseLiteral = rexBuilder.makeLiteral(false);
                 final RexNode unknownLiteral = rexBuilder.makeNullLiteral(trueLiteral.getType());
 
-                final com.google.common.collect.ImmutableList.Builder<RexNode> args =
-                        com.google.common.collect.ImmutableList.builder();
+                final ImmutableList.Builder<RexNode> args = ImmutableList.builder();
                 args.add(
                         rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, cRef, zero),
                         falseLiteral,
@@ -1418,7 +1454,7 @@ public class SqlToRelConverter {
     }
 
     private static boolean containsNullLiteral(SqlNodeList valueList) {
-        for (SqlNode node : valueList.getList()) {
+        for (SqlNode node : valueList) {
             if (node instanceof SqlLiteral) {
                 SqlLiteral lit = (SqlLiteral) node;
                 if (lit.getValue() == null) {
@@ -1484,9 +1520,10 @@ public class SqlToRelConverter {
 
                 // If there is a limit with 0 or 1,
                 // it is ensured to produce a single value
-                if (select.getFetch() != null && select.getFetch() instanceof SqlNumericLiteral) {
-                    SqlNumericLiteral limitNum = (SqlNumericLiteral) select.getFetch();
-                    if (((BigDecimal) limitNum.getValue()).intValue() < 2) {
+                SqlNode fetch = select.getFetch();
+                if (fetch instanceof SqlNumericLiteral) {
+                    long value = ((SqlNumericLiteral) fetch).getValueAs(Long.class);
+                    if (value < 2) {
                         return plan;
                     }
                 }
@@ -1514,7 +1551,7 @@ public class SqlToRelConverter {
      * @param op The operator (IN, NOT IN, &gt; SOME, ...)
      * @return converted expression
      */
-    private RexNode convertInToOr(
+    private @Nullable RexNode convertInToOr(
             final Blackboard bb,
             final List<RexNode> leftKeys,
             SqlNodeList valuesList,
@@ -1553,8 +1590,13 @@ public class SqlToRelConverter {
                                                 rexBuilder.makeCall(
                                                         comparisonOp,
                                                         pair.left,
+                                                        // TODO: remove requireNonNull when
+                                                        // checkerframework issue resolved
                                                         ensureSqlType(
-                                                                pair.left.getType(),
+                                                                requireNonNull(
+                                                                                pair.left,
+                                                                                "pair.left")
+                                                                        .getType(),
                                                                 bb.convertExpression(
                                                                         pair.right)))));
             }
@@ -1567,7 +1609,7 @@ public class SqlToRelConverter {
             case NOT_IN:
                 return rexBuilder.makeCall(
                         SqlStdOperatorTable.NOT,
-                        RexUtil.composeDisjunction(rexBuilder, comparisons, true));
+                        RexUtil.composeDisjunction(rexBuilder, comparisons));
             case IN:
             case SOME:
                 return RexUtil.composeDisjunction(rexBuilder, comparisons, true);
@@ -1592,8 +1634,9 @@ public class SqlToRelConverter {
     /**
      * Gets the list size threshold under which {@link #convertInToOr} is used. Lists of this size
      * or greater will instead be converted to use a join against an inline table ({@link
-     * LogicalValues}) rather than a predicate. A threshold of 0 forces usage of an inline table in
-     * all cases; a threshold of Integer.MAX_VALUE forces usage of OR in all cases
+     * org.apache.calcite.rel.logical.LogicalValues}) rather than a predicate. A threshold of 0
+     * forces usage of an inline table in all cases; a threshold of Integer.MAX_VALUE forces usage
+     * of OR in all cases
      *
      * @return threshold, default {@link #DEFAULT_IN_SUB_QUERY_THRESHOLD}
      */
@@ -1621,16 +1664,18 @@ public class SqlToRelConverter {
             RelOptUtil.SubQueryType subQueryType,
             RelOptUtil.Logic logic,
             boolean notIn,
-            RelDataType targetDataType) {
+            @Nullable RelDataType targetDataType) {
         final SqlValidatorScope seekScope =
-                (seek instanceof SqlSelect) ? validator.getSelectScope((SqlSelect) seek) : null;
+                (seek instanceof SqlSelect) ? validator().getSelectScope((SqlSelect) seek) : null;
         final Blackboard seekBb = createBlackboard(seekScope, null, false);
         RelNode seekRel = convertQueryOrInList(seekBb, seek, targetDataType);
+        requireNonNull(seekRel, () -> "seelkRel is null for query " + seek);
 
         return RelOptUtil.createExistsPlan(seekRel, subQueryType, logic, notIn, relBuilder);
     }
 
-    private RelNode convertQueryOrInList(Blackboard bb, SqlNode seek, RelDataType targetRowType) {
+    private @Nullable RelNode convertQueryOrInList(
+            Blackboard bb, SqlNode seek, @Nullable RelDataType targetRowType) {
         // NOTE: Once we start accepting single-row queries as row constructors,
         // there will be an ambiguity here for a case like X IN ((SELECT Y FROM
         // Z)).  The SQL standard resolves the ambiguity by saying that a lone
@@ -1638,34 +1683,32 @@ public class SqlToRelConverter {
         // expression.  The semantic difference is that a table expression can
         // return multiple rows.
         if (seek instanceof SqlNodeList) {
-            return convertRowValues(bb, seek, ((SqlNodeList) seek).getList(), false, targetRowType);
+            return convertRowValues(bb, seek, (SqlNodeList) seek, false, targetRowType);
         } else {
             return convertQueryRecursive(seek, false, null).project();
         }
     }
 
-    private RelNode convertRowValues(
+    private @Nullable RelNode convertRowValues(
             Blackboard bb,
             SqlNode rowList,
             Collection<SqlNode> rows,
             boolean allowLiteralsOnly,
-            RelDataType targetRowType) {
+            @Nullable RelDataType targetRowType) {
         // NOTE jvs 30-Apr-2006: We combine all rows consisting entirely of
         // literals into a single LogicalValues; this gives the optimizer a smaller
         // input tree.  For everything else (computed expressions, row
         // sub-queries), we union each row in as a projection on top of a
         // LogicalOneRow.
 
-        final com.google.common.collect.ImmutableList.Builder<
-                        com.google.common.collect.ImmutableList<RexLiteral>>
-                tupleList = com.google.common.collect.ImmutableList.builder();
+        final ImmutableList.Builder<ImmutableList<RexLiteral>> tupleList = ImmutableList.builder();
         final RelDataType rowType;
         if (targetRowType != null) {
             rowType = targetRowType;
         } else {
             rowType =
                     SqlTypeUtil.promoteToRowType(
-                            typeFactory, validator.getValidatedNodeType(rowList), null);
+                            typeFactory, validator().getValidatedNodeType(rowList), null);
         }
 
         final List<RelNode> unionInputs = new ArrayList<>();
@@ -1673,9 +1716,8 @@ public class SqlToRelConverter {
             SqlBasicCall call;
             if (isRowConstructor(node)) {
                 call = (SqlBasicCall) node;
-                com.google.common.collect.ImmutableList.Builder<RexLiteral> tuple =
-                        com.google.common.collect.ImmutableList.builder();
-                for (Ord<SqlNode> operand : Ord.zip(call.operands)) {
+                ImmutableList.Builder<RexLiteral> tuple = ImmutableList.builder();
+                for (Ord<@Nullable SqlNode> operand : Ord.zip(call.operands)) {
                     RexLiteral rexLiteral =
                             convertLiteralInValuesList(operand.e, bb, rowType, operand.i);
                     if ((rexLiteral == null) && allowLiteralsOnly) {
@@ -1695,7 +1737,7 @@ public class SqlToRelConverter {
             } else {
                 RexLiteral rexLiteral = convertLiteralInValuesList(node, bb, rowType, 0);
                 if ((rexLiteral != null) && config.isCreateValuesRel()) {
-                    tupleList.add(com.google.common.collect.ImmutableList.of(rexLiteral));
+                    tupleList.add(ImmutableList.of(rexLiteral));
                     continue;
                 } else {
                     if ((rexLiteral == null) && allowLiteralsOnly) {
@@ -1722,8 +1764,8 @@ public class SqlToRelConverter {
         return resultRel;
     }
 
-    private RexLiteral convertLiteralInValuesList(
-            SqlNode sqlNode, Blackboard bb, RelDataType rowType, int iField) {
+    private @Nullable RexLiteral convertLiteralInValuesList(
+            @Nullable SqlNode sqlNode, Blackboard bb, RelDataType rowType, int iField) {
         if (!(sqlNode instanceof SqlLiteral)) {
             return null;
         }
@@ -1735,8 +1777,11 @@ public class SqlToRelConverter {
             // don't use LogicalValues for those
             return null;
         }
+        return convertLiteral((SqlLiteral) sqlNode, bb, type);
+    }
 
-        RexNode literalExpr = exprConverter.convertLiteral(bb, (SqlLiteral) sqlNode);
+    private RexLiteral convertLiteral(SqlLiteral sqlLiteral, Blackboard bb, RelDataType type) {
+        RexNode literalExpr = exprConverter.convertLiteral(bb, sqlLiteral);
 
         if (!(literalExpr instanceof RexLiteral)) {
             assert literalExpr.isA(SqlKind.CAST);
@@ -1770,7 +1815,7 @@ public class SqlToRelConverter {
         return literal;
     }
 
-    private boolean isRowConstructor(SqlNode node) {
+    private static boolean isRowConstructor(SqlNode node) {
         if (!(node.getKind() == SqlKind.ROW)) {
             return false;
         }
@@ -1814,6 +1859,8 @@ public class SqlToRelConverter {
             case NOT:
                 logic = logic.negate();
                 break;
+            default:
+                break;
         }
         if (node instanceof SqlCall) {
             switch (kind) {
@@ -1868,7 +1915,7 @@ public class SqlToRelConverter {
             case ALL:
                 switch (logic) {
                     case TRUE_FALSE_UNKNOWN:
-                        RelDataType type = validator.getValidatedNodeTypeIfKnown(node);
+                        RelDataType type = validator().getValidatedNodeTypeIfKnown(node);
                         if (type == null) {
                             // The node might not be validated if we still don't know type of the
                             // node.
@@ -1877,12 +1924,16 @@ public class SqlToRelConverter {
                         } else {
                             break;
                         }
-                        // fall through
                     case UNKNOWN_AS_FALSE:
                         logic = RelOptUtil.Logic.TRUE;
+                        break;
+                    default:
+                        break;
                 }
                 bb.registerSubQuery(node, logic);
                 break;
+            default:
+                break;
         }
     }
 
@@ -1895,8 +1946,9 @@ public class SqlToRelConverter {
     public RexNode convertExpression(SqlNode node) {
         Map<String, RelDataType> nameToTypeMap = Collections.emptyMap();
         final ParameterScope scope =
-                new ParameterScope((SqlValidatorImpl) validator, nameToTypeMap);
+                new ParameterScope((SqlValidatorImpl) validator(), nameToTypeMap);
         final Blackboard bb = createBlackboard(scope, null, false);
+        replaceSubQueries(bb, node, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN);
         return bb.convertExpression(node);
     }
 
@@ -1915,8 +1967,9 @@ public class SqlToRelConverter {
             nameToTypeMap.put(entry.getKey(), entry.getValue().getType());
         }
         final ParameterScope scope =
-                new ParameterScope((SqlValidatorImpl) validator, nameToTypeMap);
+                new ParameterScope((SqlValidatorImpl) validator(), nameToTypeMap);
         final Blackboard bb = createBlackboard(scope, nameToNodeMap, false);
+        replaceSubQueries(bb, node, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN);
         return bb.convertExpression(node);
     }
 
@@ -1931,7 +1984,7 @@ public class SqlToRelConverter {
      * @param bb Blackboard
      * @return null to proceed with the usual expression translation process
      */
-    protected RexNode convertExtendedExpression(SqlNode node, Blackboard bb) {
+    protected @Nullable RexNode convertExtendedExpression(SqlNode node, Blackboard bb) {
         return null;
     }
 
@@ -1945,10 +1998,13 @@ public class SqlToRelConverter {
                 // fall through
             case RESPECT_NULLS:
                 aggCall = aggCall.operand(0);
+                break;
+            default:
+                break;
         }
 
         SqlNode windowOrRef = call.operand(1);
-        final SqlWindow window = validator.resolveWindow(windowOrRef, bb.scope);
+        final SqlWindow window = validator().resolveWindow(windowOrRef, bb.scope());
 
         SqlNode sqlLowerBound = window.getLowerBound();
         SqlNode sqlUpperBound = window.getUpperBound();
@@ -1977,39 +2033,39 @@ public class SqlToRelConverter {
             sqlLowerBound = SqlWindow.createCurrentRow(SqlParserPos.ZERO);
         }
         final SqlNodeList partitionList = window.getPartitionList();
-        final com.google.common.collect.ImmutableList.Builder<RexNode> partitionKeys =
-                com.google.common.collect.ImmutableList.builder();
+        final ImmutableList.Builder<RexNode> partitionKeys = ImmutableList.builder();
         for (SqlNode partition : partitionList) {
             partitionKeys.add(bb.convertExpression(partition));
         }
-        final RexNode lowerBound = bb.convertExpression(sqlLowerBound);
-        final RexNode upperBound = bb.convertExpression(sqlUpperBound);
+        final RexNode lowerBound =
+                bb.convertExpression(requireNonNull(sqlLowerBound, "sqlLowerBound"));
+        final RexNode upperBound =
+                bb.convertExpression(requireNonNull(sqlUpperBound, "sqlUpperBound"));
         if (orderList.size() == 0 && !rows) {
             // A logical range requires an ORDER BY clause. Use the implicit
             // ordering of this relation. There must be one, otherwise it would
             // have failed validation.
-            orderList = bb.scope.getOrderList();
+            orderList = bb.scope().getOrderList();
             if (orderList == null) {
                 throw new AssertionError("Relation should have sort key for implicit ORDER BY");
             }
         }
 
-        final com.google.common.collect.ImmutableList.Builder<RexFieldCollation> orderKeys =
-                com.google.common.collect.ImmutableList.builder();
+        final ImmutableList.Builder<RexNode> orderKeys = ImmutableList.builder();
         for (SqlNode order : orderList) {
             orderKeys.add(
                     bb.convertSortExpression(
                             order,
                             RelFieldCollation.Direction.ASCENDING,
-                            RelFieldCollation.NullDirection.UNSPECIFIED));
+                            RelFieldCollation.NullDirection.UNSPECIFIED,
+                            bb::sortToRex));
         }
 
         try {
-            com.google.common.base.Preconditions.checkArgument(
-                    bb.window == null, "already in window agg mode");
+            Preconditions.checkArgument(bb.window == null, "already in window agg mode");
             bb.window = window;
             RexNode rexAgg = exprConverter.convertCall(bb, aggCall);
-            rexAgg = rexBuilder.ensureType(validator.getValidatedNodeType(call), rexAgg, false);
+            rexAgg = rexBuilder.ensureType(validator().getValidatedNodeType(call), rexAgg, false);
 
             // Walk over the tree and apply 'over' to all agg functions. This is
             // necessary because the returned expression is not necessarily a call
@@ -2022,9 +2078,9 @@ public class SqlToRelConverter {
                     new HistogramShuttle(
                             partitionKeys.build(),
                             orderKeys.build(),
+                            rows,
                             RexWindowBounds.create(sqlLowerBound, lowerBound),
                             RexWindowBounds.create(sqlUpperBound, upperBound),
-                            rows,
                             window.isAllowPartial(),
                             isDistinct,
                             ignoreNulls);
@@ -2034,7 +2090,7 @@ public class SqlToRelConverter {
         }
     }
 
-    protected void convertFrom(Blackboard bb, SqlNode from) {
+    protected void convertFrom(Blackboard bb, @Nullable SqlNode from) {
         convertFrom(bb, from, Collections.emptyList());
     }
 
@@ -2072,26 +2128,25 @@ public class SqlToRelConverter {
      *       <li>or any combination of the above.
      *     </ul>
      *
-     * @param fieldNames Field aliases, usually come from AS clause
+     * @param fieldNames Field aliases, usually come from AS clause, or null
      */
-    protected void convertFrom(Blackboard bb, SqlNode from, List<String> fieldNames) {
+    protected void convertFrom(
+            Blackboard bb, @Nullable SqlNode from, @Nullable List<String> fieldNames) {
         if (from == null) {
             bb.setRoot(LogicalValues.createOneRow(cluster), false);
             return;
         }
 
         final SqlCall call;
-        final SqlNode[] operands;
+        final @Nullable SqlNode[] operands;
         switch (from.getKind()) {
             case AS:
                 call = (SqlCall) from;
                 SqlNode firstOperand = call.operand(0);
-                final List<String> fieldNameList = new ArrayList<>();
-                if (call.operandCount() > 2) {
-                    for (SqlNode node : Util.skip(call.getOperandList(), 2)) {
-                        fieldNameList.add(((SqlIdentifier) node).getSimple());
-                    }
-                }
+                final List<String> fieldNameList =
+                        call.operandCount() > 2
+                                ? SqlIdentifier.simpleNames(Util.skip(call.getOperandList(), 2))
+                                : null;
                 convertFrom(bb, firstOperand, fieldNameList);
 
                 // ----- FLINK MODIFICATION BEGIN -----
@@ -2133,6 +2188,10 @@ public class SqlToRelConverter {
                 convertPivot(bb, (SqlPivot) from);
                 return;
 
+            case UNPIVOT:
+                convertUnpivot(bb, (SqlUnpivot) from);
+                return;
+
             case WITH_ITEM:
                 convertFrom(bb, ((SqlWithItem) from).query);
                 return;
@@ -2143,7 +2202,9 @@ public class SqlToRelConverter {
 
             case TABLESAMPLE:
                 operands = ((SqlBasicCall) from).getOperands();
-                SqlSampleSpec sampleSpec = SqlLiteral.sampleValue(operands[1]);
+                SqlSampleSpec sampleSpec =
+                        SqlLiteral.sampleValue(
+                                requireNonNull(operands[1], () -> "operand[1] of " + from));
                 if (sampleSpec instanceof SqlSampleSpec.SqlSubstitutionSampleSpec) {
                     String sampleName =
                             ((SqlSampleSpec.SqlSubstitutionSampleSpec) sampleSpec).getName();
@@ -2160,7 +2221,7 @@ public class SqlToRelConverter {
                                     tableSampleSpec.getSamplePercentage(),
                                     tableSampleSpec.isRepeatable(),
                                     tableSampleSpec.getRepeatableSeed());
-                    bb.setRoot(new Sample(cluster, bb.root, params), false);
+                    bb.setRoot(new Sample(cluster, bb.root(), params), false);
                 } else {
                     throw new AssertionError("unknown TABLESAMPLE type: " + sampleSpec);
                 }
@@ -2204,8 +2265,8 @@ public class SqlToRelConverter {
 
             case VALUES:
                 convertValuesImpl(bb, (SqlCall) from, null);
-                if (fieldNames.size() > 0) {
-                    bb.setRoot(relBuilder.push(bb.root).rename(fieldNames).build(), true);
+                if (fieldNames != null) {
+                    bb.setRoot(relBuilder.push(bb.root()).rename(fieldNames).build(), true);
                 }
                 return;
 
@@ -2227,7 +2288,7 @@ public class SqlToRelConverter {
         }
     }
 
-    private void convertUnnest(Blackboard bb, SqlCall call, List<String> fieldNames) {
+    private void convertUnnest(Blackboard bb, SqlCall call, @Nullable List<String> fieldNames) {
... 3412 lines suppressed ...