Support SQL-compliant group-by syntax.

1. Add AST-level deep equality and expression subsutitions;
2. Fix DeepCopyVisitor and InlineColumnAliasVisitor for edge cases;
3. Add deep equality tests;
4. Add group-by tests for the SQL-compliant syntax.

Change-Id: Ia1cbe1fab216b5f47577d75fd870a537cfe1e84f
Reviewed-on: https://asterix-gerrit.ics.uci.edu/990
Sonar-Qube: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Till Westmann <tillw@apache.org>
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/sqlpp/ParserTestExecutor.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/sqlpp/ParserTestExecutor.java
index fa7b272..0989545 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/sqlpp/ParserTestExecutor.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/sqlpp/ParserTestExecutor.java
@@ -45,6 +45,7 @@
 import org.apache.asterix.lang.sqlpp.parser.SqlppParserFactory;
 import org.apache.asterix.lang.sqlpp.rewrites.SqlppRewriterFactory;
 import org.apache.asterix.lang.sqlpp.util.SqlppAstPrintUtil;
+import org.apache.asterix.lang.sqlpp.util.SqlppRewriteUtil;
 import org.apache.asterix.metadata.declared.AqlMetadataProvider;
 import org.apache.asterix.metadata.entities.Dataset;
 import org.apache.asterix.test.aql.TestExecutor;
@@ -52,6 +53,7 @@
 import org.apache.asterix.testframework.context.TestFileContext;
 import org.apache.asterix.testframework.xml.TestCase.CompilationUnit;
 import org.apache.asterix.testframework.xml.TestGroup;
+import org.junit.Assert;
 
 import junit.extensions.PA;
 
@@ -80,8 +82,8 @@
 
                     // Runs the test query.
                     File expectedResultFile = expectedResultFileCtxs.get(queryCount).getFile();
-                    File actualResultFile = testCaseCtx.getActualResultFile(cUnit, expectedResultFile,
-                            new File(actualPath));
+                    File actualResultFile =
+                            testCaseCtx.getActualResultFile(cUnit, expectedResultFile, new File(actualPath));
                     testSQLPPParser(testFile, actualResultFile, expectedResultFile);
 
                     LOGGER.info(
@@ -132,6 +134,11 @@
                     IQueryRewriter rewriter = sqlppRewriterFactory.createQueryRewriter();
                     rewrite(rewriter, functions, query, aqlMetadataProvider,
                             new LangRewritingContext(query.getVarCounter()));
+
+                    // Tests deep copy and deep equality.
+                    Query copiedQuery = (Query) SqlppRewriteUtil.deepCopy(query);
+                    Assert.assertEquals(query.hashCode(), copiedQuery.hashCode());
+                    Assert.assertEquals(query, copiedQuery);
                 }
                 SqlppAstPrintUtil.print(st, writer);
             }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/dapd/q2-6/q2-6.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/dapd/q2-6/q2-6.1.ddl.sqlpp
new file mode 100644
index 0000000..90482a7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/dapd/q2-6/q2-6.1.ddl.sqlpp
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+drop  database test if exists;
+create  database test;
+
+use test;
+
+
+create type test.AddressType as
+ closed {
+  street : string,
+  city : string,
+  zip : string,
+  latlong : point
+}
+
+create type test.EventType as
+ closed {
+  event_id : int64,
+  name : string,
+  location : AddressType?,
+  organizers : {{{
+          name : string
+      }
+}},
+  sponsoring_sigs : [{
+          sig_id : int64,
+          chapter_name : string
+      }
+],
+  interest_keywords : {{string}},
+  price : double?,
+  start_time : datetime,
+  end_time : datetime
+}
+
+create external  table Event(EventType) using localfs((`path`=`asterix_nc1://data/events/tiny/event.adm`),(`format`=`adm`));
+
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/dapd/q2-6/q2-6.2.update.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/dapd/q2-6/q2-6.2.update.sqlpp
index 64ec0af..bd244d0 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/dapd/q2-6/q2-6.2.update.sqlpp
@@ -16,22 +16,4 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
-
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
-
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/dapd/q2-6/q2-6.3.query.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/dapd/q2-6/q2-6.3.query.sqlpp
index 64ec0af..1c7e44a 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/dapd/q2-6/q2-6.3.query.sqlpp
@@ -16,22 +16,20 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+USE test;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
+
+SELECT sponsor.sig_id, COUNT(1) total_count,
+       (
+         SELECT e.sponsor.chapter_name, COUNT(e) AS escount
+         FROM es AS e
+         GROUP BY e.sponsor.chapter_name
+       ) chapter_breakdown
+FROM  Event,
+      Event.sponsoring_sigs AS sponsor
+GROUP BY sponsor.sig_id GROUP AS es
+ORDER BY total_count DESC
+LIMIT 5
+;
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-2/gby-expr-2.1.ddl.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-2/gby-expr-2.1.ddl.sqlpp
index 64ec0af..b4ebc49 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-2/gby-expr-2.1.ddl.sqlpp
@@ -16,22 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+DROP DATABASE gby IF EXISTS;
+CREATE DATABASE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+USE gby;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
+CREATE TYPE EmployeeType AS {
+  id : string
 }
+
+CREATE TYPE IncentiveType AS {
+  job_category: string
+}
+
+CREATE TYPE SuperStarType AS {
+  id : string
+}
+
+CREATE TABLE Employee(EmployeeType) PRIMARY KEY id;
+CREATE TABLE Incentive(IncentiveType) PRIMARY KEY job_category;
+CREATE TABLE SuperStars(SuperStarType) PRIMARY KEY id;
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-2/gby-expr-2.2.update.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-2/gby-expr-2.2.update.sqlpp
index 64ec0af..e6545e0 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-2/gby-expr-2.2.update.sqlpp
@@ -16,22 +16,28 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+USE gby;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
+INSERT INTO Employee (
+   {
+    'id': '123',
+    'job_category': 'Cook',
+    'salary': 2000,
+    'department_id': 'K55'
+   }
+);
+
+INSERT INTO Incentive (
+  { 'job_category': 'Cook',
+    'bonus': 1000
+  }
+)
+
+INSERT INTO SuperStars (
+  {
+    'id': '123'
+  }
+)
+
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-2/gby-expr-2.3.query.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-2/gby-expr-2.3.query.sqlpp
index 64ec0af..5c6cc38 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-2/gby-expr-2.3.query.sqlpp
@@ -16,22 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+USE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+FROM Employee e
+    JOIN Incentive i ON e.job_category = i.job_category
+    JOIN SuperStars s ON e.id = s.id
+GROUP BY substr(e.department_id, 1)
+SELECT substr(e.department_id, 1), SUM(e.salary + i.bonus);
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-3/gby-expr-3.1.ddl.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-3/gby-expr-3.1.ddl.sqlpp
index 64ec0af..b4ebc49 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-3/gby-expr-3.1.ddl.sqlpp
@@ -16,22 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+DROP DATABASE gby IF EXISTS;
+CREATE DATABASE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+USE gby;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
+CREATE TYPE EmployeeType AS {
+  id : string
 }
+
+CREATE TYPE IncentiveType AS {
+  job_category: string
+}
+
+CREATE TYPE SuperStarType AS {
+  id : string
+}
+
+CREATE TABLE Employee(EmployeeType) PRIMARY KEY id;
+CREATE TABLE Incentive(IncentiveType) PRIMARY KEY job_category;
+CREATE TABLE SuperStars(SuperStarType) PRIMARY KEY id;
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-3/gby-expr-3.2.update.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-3/gby-expr-3.2.update.sqlpp
index 64ec0af..e6545e0 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-3/gby-expr-3.2.update.sqlpp
@@ -16,22 +16,28 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+USE gby;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
+INSERT INTO Employee (
+   {
+    'id': '123',
+    'job_category': 'Cook',
+    'salary': 2000,
+    'department_id': 'K55'
+   }
+);
+
+INSERT INTO Incentive (
+  { 'job_category': 'Cook',
+    'bonus': 1000
+  }
+)
+
+INSERT INTO SuperStars (
+  {
+    'id': '123'
+  }
+)
+
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-3/gby-expr-3.3.query.sqlpp
similarity index 64%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-3/gby-expr-3.3.query.sqlpp
index 64ec0af..c2fe80b 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr-3/gby-expr-3.3.query.sqlpp
@@ -16,22 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
+
+USE gby;
+
+/*
+ *  This query tests that the expression substitution rewriter should
+ *  try to visit (i.e., substitute) parent expressions first before visiting
+ *  child expressions, e.g.: i.bonus + e.salary is a parent expression of i.bonus.
  */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+FROM Employee e
+    JOIN Incentive i ON e.job_category = i.job_category
+    JOIN SuperStars s ON e.id = s.id
+GROUP BY i.bonus, i.bonus + e.salary
+SELECT i.bonus, i.bonus + e.salary, COUNT(1);
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
-
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr/gby-expr.1.ddl.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr/gby-expr.1.ddl.sqlpp
index 64ec0af..b4ebc49 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr/gby-expr.1.ddl.sqlpp
@@ -16,22 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+DROP DATABASE gby IF EXISTS;
+CREATE DATABASE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+USE gby;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
+CREATE TYPE EmployeeType AS {
+  id : string
 }
+
+CREATE TYPE IncentiveType AS {
+  job_category: string
+}
+
+CREATE TYPE SuperStarType AS {
+  id : string
+}
+
+CREATE TABLE Employee(EmployeeType) PRIMARY KEY id;
+CREATE TABLE Incentive(IncentiveType) PRIMARY KEY job_category;
+CREATE TABLE SuperStars(SuperStarType) PRIMARY KEY id;
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr/gby-expr.2.update.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr/gby-expr.2.update.sqlpp
index 64ec0af..e6545e0 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr/gby-expr.2.update.sqlpp
@@ -16,22 +16,28 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+USE gby;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
+INSERT INTO Employee (
+   {
+    'id': '123',
+    'job_category': 'Cook',
+    'salary': 2000,
+    'department_id': 'K55'
+   }
+);
+
+INSERT INTO Incentive (
+  { 'job_category': 'Cook',
+    'bonus': 1000
+  }
+)
+
+INSERT INTO SuperStars (
+  {
+    'id': '123'
+  }
+)
+
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr/gby-expr.3.query.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr/gby-expr.3.query.sqlpp
index 64ec0af..c2d046d 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/gby-expr/gby-expr.3.query.sqlpp
@@ -16,22 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+USE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+FROM Employee e
+    JOIN Incentive i ON e.job_category = i.job_category
+    JOIN SuperStars s ON e.id = s.id
+GROUP BY substr(e.department_id, 1)
+SELECT substr(e.department_id, 1) as deptId, SUM(e.salary + i.bonus) AS star_cost;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-01-2/sugar-01-2.1.ddl.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-01-2/sugar-01-2.1.ddl.sqlpp
index 64ec0af..bbccf8c 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-01-2/sugar-01-2.1.ddl.sqlpp
@@ -16,22 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+DROP DATABASE gby IF EXISTS;
+CREATE DATABASE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+USE gby;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
+CREATE TYPE EmpType AS {
+  name : string
 }
+
+CREATE TABLE Employee(EmpType) PRIMARY KEY name;
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-01-2/sugar-01-2.2.update.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-01-2/sugar-01-2.2.update.sqlpp
index 64ec0af..99116f6 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-01-2/sugar-01-2.2.update.sqlpp
@@ -16,22 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+USE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+INSERT INTO Employee (
+  [
+   { 'name': 'Bill',
+     'deptno': 'K55',
+     'salary': 2000 },
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
+   { 'name': 'Fred',
+     'deptno': 'K55',
+     'salary': 3000 }
+  ]
+);
\ No newline at end of file
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-01-2/sugar-01-2.3.query.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-01-2/sugar-01-2.3.query.sqlpp
index 64ec0af..6187e72 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-01-2/sugar-01-2.3.query.sqlpp
@@ -16,22 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+USE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
+FROM Employee e
+GROUP BY e.deptno GROUP AS g
+SELECT e.deptno AS deptno, AVG(e.salary) AS avgpay,
+       (SELECT i.e.name AS name, i.e.salary AS salary FROM g AS i) AS workers;
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-02-2/sugar-02-2.1.ddl.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-02-2/sugar-02-2.1.ddl.sqlpp
index 64ec0af..b4ebc49 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-02-2/sugar-02-2.1.ddl.sqlpp
@@ -16,22 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+DROP DATABASE gby IF EXISTS;
+CREATE DATABASE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+USE gby;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
+CREATE TYPE EmployeeType AS {
+  id : string
 }
+
+CREATE TYPE IncentiveType AS {
+  job_category: string
+}
+
+CREATE TYPE SuperStarType AS {
+  id : string
+}
+
+CREATE TABLE Employee(EmployeeType) PRIMARY KEY id;
+CREATE TABLE Incentive(IncentiveType) PRIMARY KEY job_category;
+CREATE TABLE SuperStars(SuperStarType) PRIMARY KEY id;
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-02-2/sugar-02-2.2.update.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-02-2/sugar-02-2.2.update.sqlpp
index 64ec0af..e6545e0 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-02-2/sugar-02-2.2.update.sqlpp
@@ -16,22 +16,28 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+USE gby;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
+INSERT INTO Employee (
+   {
+    'id': '123',
+    'job_category': 'Cook',
+    'salary': 2000,
+    'department_id': 'K55'
+   }
+);
+
+INSERT INTO Incentive (
+  { 'job_category': 'Cook',
+    'bonus': 1000
+  }
+)
+
+INSERT INTO SuperStars (
+  {
+    'id': '123'
+  }
+)
+
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-02-2/sugar-02-2.3.query.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-02-2/sugar-02-2.3.query.sqlpp
index 64ec0af..2b18b3c 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-02-2/sugar-02-2.3.query.sqlpp
@@ -16,22 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+USE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+FROM Employee e
+    JOIN Incentive i ON e.job_category = i.job_category
+    JOIN SuperStars s ON e.id = s.id
+GROUP BY e.department_id
+SELECT e.department_id as deptId, SUM(e.salary + i.bonus) AS star_cost;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-02-2/sugar-02-2.4.query.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-02-2/sugar-02-2.4.query.sqlpp
index 64ec0af..8e9eddb 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-02-2/sugar-02-2.4.query.sqlpp
@@ -16,22 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+/* This query tests the column alias rewriting. */
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+USE gby;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
+FROM Employee e
+    JOIN Incentive i ON e.job_category = i.job_category
+    JOIN SuperStars s ON e.id = s.id
+GROUP BY e.department_id
+HAVING deptId = 'K55'
+SELECT e.department_id AS deptId, SUM(e.salary + i.bonus) AS star_cost
+ORDER BY deptId;
+
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-03-2/sugar-03-2.1.ddl.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-03-2/sugar-03-2.1.ddl.sqlpp
index 64ec0af..7a281d2 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-03-2/sugar-03-2.1.ddl.sqlpp
@@ -16,22 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+DROP DATABASE gby IF EXISTS;
+CREATE DATABASE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+USE gby;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
+CREATE TYPE EmployeeType AS {
+  id : string
 }
+
+CREATE TYPE IncentiveType AS {
+  job_category: string
+}
+
+CREATE TYPE SuperStarType AS {
+  id : string
+}
+
+CREATE TABLE Employee(EmployeeType) PRIMARY KEY id;
+CREATE TABLE Incentive(IncentiveType) PRIMARY KEY job_category;
+CREATE TABLE SuperStars(SuperStarType) PRIMARY KEY id;
+
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-03-2/sugar-03-2.2.update.sqlpp
similarity index 67%
rename from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-03-2/sugar-03-2.2.update.sqlpp
index 64ec0af..19f0c1f 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-03-2/sugar-03-2.2.update.sqlpp
@@ -16,22 +16,27 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+USE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+INSERT INTO Employee (
+   {
+    'id': '123',
+    'job_category': 'Cook',
+    'salary': 2000,
+    'department_id': 'K55'
+   }
+);
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
+INSERT INTO Incentive (
+  { 'job_category': 'Cook',
+    'bonus': 1000
+  }
+)
+
+INSERT INTO SuperStars (
+  {
+    'id': '123'
+  }
+)
+
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-03-2/sugar-03-2.3.query.sqlpp
similarity index 66%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-03-2/sugar-03-2.3.query.sqlpp
index 64ec0af..4979375 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-03-2/sugar-03-2.3.query.sqlpp
@@ -16,22 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+USE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
-
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
+FROM Employee e
+    JOIN Incentive i ON e.job_category = i.job_category
+    JOIN SuperStars s ON e.id = s.id
+GROUP BY e.department_id GROUP AS eis
+SELECT e.department_id as deptId,
+    AVG(e.salary + i.bonus) AS avgpay,
+    (    FROM eis AS v
+         SELECT v.e.id AS id, v.e.salary AS salary, v.i.bonus AS bonus
+         ORDER BY v.i.bonus DESC LIMIT 3
+    ) AS topstar_details
+    ;
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-04-2/sugar-04-2.1.ddl.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-04-2/sugar-04-2.1.ddl.sqlpp
index 64ec0af..7a281d2 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-04-2/sugar-04-2.1.ddl.sqlpp
@@ -16,22 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+DROP DATABASE gby IF EXISTS;
+CREATE DATABASE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+USE gby;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
+CREATE TYPE EmployeeType AS {
+  id : string
 }
+
+CREATE TYPE IncentiveType AS {
+  job_category: string
+}
+
+CREATE TYPE SuperStarType AS {
+  id : string
+}
+
+CREATE TABLE Employee(EmployeeType) PRIMARY KEY id;
+CREATE TABLE Incentive(IncentiveType) PRIMARY KEY job_category;
+CREATE TABLE SuperStars(SuperStarType) PRIMARY KEY id;
+
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-04-2/sugar-04-2.2.update.sqlpp
similarity index 64%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-04-2/sugar-04-2.2.update.sqlpp
index 64ec0af..52443b5 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-04-2/sugar-04-2.2.update.sqlpp
@@ -16,22 +16,40 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+USE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+INSERT INTO Employee (
+  [
+   {
+    'id': '123',
+    'job_category': 'Cook',
+    'salary': 2000,
+    'department_id': 'K55'
+   },
+   {
+    'id': '234',
+    'job_category': 'Teach',
+    'salary': 2500,
+    'department_id': 'K54'
+   }
+  ]
+);
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
+INSERT INTO Incentive (
+ [
+  { 'job_category': 'Cook',
+    'bonus': 1000
+  },
+  { 'job_category': 'Teach',
+    'bonus': 2000
+  }
+ ]
+)
+
+INSERT INTO SuperStars (
+  {
+    'id': '123'
+  }
+)
+
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-04-2/sugar-04-2.3.query.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-04-2/sugar-04-2.3.query.sqlpp
index 64ec0af..4ba84b7 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-04-2/sugar-04-2.3.query.sqlpp
@@ -16,22 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+USE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+FROM  Employee e, Incentive i
+WHERE e.job_category = i.job_category
+GROUP BY e.department_id
+SELECT e.department_id AS deptId,
+     (  FROM i AS i
+        SELECT i.job_category AS category, i.bonus AS bonus
+        ORDER BY i.bonus DESC LIMIT 3
+      ) AS job_category_details
+ORDER BY deptId DESC;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-05-2/sugar-05-2.1.ddl.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-05-2/sugar-05-2.1.ddl.sqlpp
index 64ec0af..2030780 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-05-2/sugar-05-2.1.ddl.sqlpp
@@ -16,22 +16,20 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+DROP DATABASE gby IF EXISTS;
+CREATE DATABASE gby;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+USE gby;
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
+CREATE TYPE CarType AS {
+  cid : string
 }
+
+CREATE TYPE TireType AS {
+  tid: string
+}
+
+CREATE TABLE Car(CarType) PRIMARY KEY cid;
+CREATE TABLE Tire(TireType) PRIMARY KEY tid;
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-05-2/sugar-05-2.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-05-2/sugar-05-2.2.update.sqlpp
new file mode 100644
index 0000000..275e4fc
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-05-2/sugar-05-2.2.update.sqlpp
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+USE gby;
+
+INSERT INTO Car (
+  [
+   {
+    'cid': '1',
+    'make': 'Honda',
+    'model': 'Civic',
+    'price': 28000,
+    'tire_size': 'P205/55R16'
+   },
+   {
+    'cid': '2',
+    'make': 'Toyota',
+    'model': 'Corolla',
+    'price': 27000,
+    'tire_size': 'P205/55R16'
+   }
+  ]
+);
+
+INSERT INTO Tire (
+ [
+   {
+        'tid': '1',
+        'mfr': 'Bridgestone',
+        'brand': 'Turanza',
+        'size': 'P205/55R16',
+        'price': 150
+    },
+    {
+        'tid': '2',
+        'mfr': 'Goodyear',
+        'brand': 'Eagle',
+        'size': 'P205/55R16',
+        'price': 100
+    }
+ ]
+)
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-05-2/sugar-05-2.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-05-2/sugar-05-2.3.query.sqlpp
new file mode 100644
index 0000000..4bc3555
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/sugar-05-2/sugar-05-2.3.query.sqlpp
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+USE gby;
+
+SELECT ELEMENT {
+        'Vehicle prices including 4 tires':
+        (
+            FROM Car c JOIN Tire t ON c.tire_size = t.size
+            GROUP BY c.tire_size GROUP AS g
+            SELECT c.tire_size,
+                   AVG(c.price + 4 * t.price) AS avg_total_price,
+                   ( FROM g AS g
+                     SELECT g.c.make AS make,
+                            g.c.model AS model,
+                            g.t.mfr AS mfr,
+                            g.t.brand AS brand,
+                            g.c.price + 4 * g.t.price AS price
+                     ORDER BY g.c.make, g.c.model, g.t.mfr, g.t.brand
+                   ) AS combinations
+        )
+    };
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/subquery/alias_negative/alias_negative.1.query.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/subquery/alias_negative/alias_negative.1.query.sqlpp
index 64ec0af..a05a418 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/subquery/alias_negative/alias_negative.1.query.sqlpp
@@ -16,22 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+USE test;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
+/** This test case checks the error message for a missing alias.*/
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
+SELECT * FROM (SELECT 1);
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/tpch-sql-sugar/q01_pricing_summary_report_2/q01_pricing_summary_report_2.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/tpch-sql-sugar/q01_pricing_summary_report_2/q01_pricing_summary_report_2.1.ddl.sqlpp
new file mode 100644
index 0000000..8649a3b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/tpch-sql-sugar/q01_pricing_summary_report_2/q01_pricing_summary_report_2.1.ddl.sqlpp
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+drop  database tpch if exists;
+create  database tpch;
+
+use tpch;
+
+
+create type tpch.LineItemType as
+ closed {
+  l_orderkey : int64,
+  l_partkey : int64,
+  l_suppkey : int64,
+  l_linenumber : int64,
+  l_quantity : double,
+  l_extendedprice : double,
+  l_discount : double,
+  l_tax : double,
+  l_returnflag : string,
+  l_linestatus : string,
+  l_shipdate : string,
+  l_commitdate : string,
+  l_receiptdate : string,
+  l_shipinstruct : string,
+  l_shipmode : string,
+  l_comment : string
+}
+
+create  table LineItem(LineItemType) primary key l_orderkey,l_linenumber;
+
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/tpch-sql-sugar/q01_pricing_summary_report_2/q01_pricing_summary_report_2.2.update.sqlpp
similarity index 67%
copy from asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/tpch-sql-sugar/q01_pricing_summary_report_2/q01_pricing_summary_report_2.2.update.sqlpp
index 64ec0af..d996e74 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/AdmSplitInfo.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/tpch-sql-sugar/q01_pricing_summary_report_2/q01_pricing_summary_report_2.2.update.sqlpp
@@ -16,22 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- *
- */
-package org.apache.asterix.lang.common.struct;
 
-public class AdmSplitInfo {
-    public Identifier nodeName;
-    public String fileName;
+use tpch;
 
-    public AdmSplitInfo(Identifier nodeName, String fileName) {
-        this.nodeName = nodeName;
-        this.fileName = fileName;
-    }
 
-    @Override
-    public String toString() {
-        return nodeName.value + ":" + fileName;
-    }
-}
+load  table LineItem using localfs ((`path`=`asterix_nc1://data/tpch0.001/lineitem.tbl`),(`format`=`delimited-text`),(`delimiter`=`|`)) pre-sorted;
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/tpch-sql-sugar/q01_pricing_summary_report_2/q01_pricing_summary_report_2.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/tpch-sql-sugar/q01_pricing_summary_report_2/q01_pricing_summary_report_2.3.query.sqlpp
new file mode 100644
index 0000000..3999ace
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/tpch-sql-sugar/q01_pricing_summary_report_2/q01_pricing_summary_report_2.3.query.sqlpp
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+USE tpch;
+
+/**
+ * This query tests the different treatments of l.l_returnflag
+ * for the case that it is in a non-aggregate projection expression and
+ * the case that it is in a SQL-92 aggregate function projection.
+ */
+
+SELECT  l.l_returnflag,
+        l.l_linestatus,
+        sum(l_quantity) AS sum_qty,
+        sum(l_extendedprice) AS sum_base_price,
+        sum(l_extendedprice * (1 - l_discount)) AS sum_disc_price,
+        sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) AS sum_charge,
+        avg(l_quantity) AS ave_qty,
+        avg(l_extendedprice) AS ave_price,
+        avg(l_discount) AS ave_disc,
+        count(l.l_returnflag) AS count_order
+FROM  LineItem l
+WHERE l_shipdate <= '1998-09-02'
+/* +hash */
+GROUP BY l.l_returnflag, l.l_linestatus
+ORDER BY l.l_returnflag, l.l_linestatus
+;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/gby-expr-2/gby-expr-2.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/gby-expr-2/gby-expr-2.1.adm
new file mode 100644
index 0000000..fe9bd8c
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/gby-expr-2/gby-expr-2.1.adm
@@ -0,0 +1 @@
+{ "$1": "K55", "$2": 3000 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/gby-expr-3/gby-expr-3.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/gby-expr-3/gby-expr-3.1.adm
new file mode 100644
index 0000000..fdb2461
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/gby-expr-3/gby-expr-3.1.adm
@@ -0,0 +1 @@
+{ "$2": 1, "bonus": 1000, "$1": 3000 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/gby-expr/gby-expr.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/gby-expr/gby-expr.1.adm
new file mode 100644
index 0000000..4281a7b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/gby-expr/gby-expr.1.adm
@@ -0,0 +1 @@
+{ "deptId": "K55", "star_cost": 3000 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
index 494e830..f66e3a8 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -1419,6 +1419,11 @@
         <output-dir compare="Text">q2</output-dir>
       </compilation-unit>
     </test-case>
+    <test-case FilePath="dapd">
+      <compilation-unit name="q2-6">
+        <output-dir compare="Text">q2</output-dir>
+      </compilation-unit>
+    </test-case>
     <!--
         <test-case FilePath="dapd">
           <compilation-unit name="q3">
@@ -2388,30 +2393,70 @@
       </compilation-unit>
     </test-case>
     <test-case FilePath="group-by">
+      <compilation-unit name="sugar-01-2">
+        <output-dir compare="Text">core-01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="group-by">
       <compilation-unit name="sugar-02">
         <output-dir compare="Text">core-02</output-dir>
       </compilation-unit>
     </test-case>
     <test-case FilePath="group-by">
+      <compilation-unit name="sugar-02-2">
+        <output-dir compare="Text">core-02</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="group-by">
       <compilation-unit name="sugar-03">
         <output-dir compare="Text">core-03</output-dir>
       </compilation-unit>
     </test-case>
     <test-case FilePath="group-by">
+      <compilation-unit name="sugar-03-2">
+        <output-dir compare="Text">core-03</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="group-by">
       <compilation-unit name="sugar-04">
         <output-dir compare="Text">core-04</output-dir>
       </compilation-unit>
     </test-case>
     <test-case FilePath="group-by">
+      <compilation-unit name="sugar-04-2">
+        <output-dir compare="Text">core-04</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="group-by">
       <compilation-unit name="sugar-05">
         <output-dir compare="Text">core-05</output-dir>
       </compilation-unit>
     </test-case>
     <test-case FilePath="group-by">
+      <compilation-unit name="sugar-05-2">
+        <output-dir compare="Text">core-05</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="group-by">
       <compilation-unit name="null">
         <output-dir compare="Text">null</output-dir>
       </compilation-unit>
     </test-case>
+    <test-case FilePath="group-by">
+      <compilation-unit name="gby-expr">
+        <output-dir compare="Text">gby-expr</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="group-by">
+      <compilation-unit name="gby-expr-2">
+        <output-dir compare="Text">gby-expr-2</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="group-by">
+      <compilation-unit name="gby-expr-3">
+        <output-dir compare="Text">gby-expr-3</output-dir>
+      </compilation-unit>
+    </test-case>
   </test-group>
   <test-group name="index-join">
     <test-case FilePath="index-join">
@@ -5271,6 +5316,12 @@
         <output-dir compare="Text">not_in</output-dir>
       </compilation-unit>
     </test-case>
+    <test-case FilePath="subquery">
+      <compilation-unit name="alias_negative">
+        <output-dir compare="Text">alias_negative</output-dir>
+        <expected-error>Need an alias for the enclosed expression</expected-error>
+      </compilation-unit>
+    </test-case>
   </test-group>
   <test-group name="subset-collection">
     <test-case FilePath="subset-collection">
@@ -5852,6 +5903,11 @@
       </compilation-unit>
     </test-case>
     <test-case FilePath="tpch-sql-sugar">
+      <compilation-unit name="q01_pricing_summary_report_2">
+        <output-dir compare="Text">q01_pricing_summary_report_nt</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="tpch-sql-sugar">
       <compilation-unit name="q20_potential_part_promotion">
         <output-dir compare="Text">q20_potential_part_promotion</output-dir>
       </compilation-unit>
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/GroupbyClause.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/GroupbyClause.java
index b5d78e2..3565c5a 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/GroupbyClause.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/GroupbyClause.java
@@ -28,6 +28,7 @@
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 import org.apache.hyracks.algebricks.common.utils.Pair;
 
 public class GroupbyClause implements Clause {
@@ -36,11 +37,12 @@
     private List<GbyVariableExpressionPair> decorPairList;
     private List<VariableExpr> withVarList;
     private VariableExpr groupVar;
-    private List<Pair<Expression, Identifier>> groupFieldList = new ArrayList<Pair<Expression, Identifier>>();
+    private List<Pair<Expression, Identifier>> groupFieldList = new ArrayList<>();
     private boolean hashGroupByHint;
     private boolean groupAll;
 
     public GroupbyClause() {
+        // Default constructor.
     }
 
     public GroupbyClause(List<GbyVariableExpressionPair> gbyPairList, List<GbyVariableExpressionPair> decorPairList,
@@ -122,11 +124,11 @@
     }
 
     public boolean hasDecorList() {
-        return decorPairList != null && decorPairList.size() > 0;
+        return decorPairList != null && !decorPairList.isEmpty();
     }
 
     public boolean hasWithList() {
-        return withVarList != null && withVarList.size() > 0;
+        return withVarList != null && !withVarList.isEmpty();
     }
 
     public boolean hasGroupVar() {
@@ -134,10 +136,32 @@
     }
 
     public boolean hasGroupFieldList() {
-        return groupFieldList != null && groupFieldList.size() > 0;
+        return groupFieldList != null && !groupFieldList.isEmpty();
     }
 
     public boolean isGroupAll() {
         return groupAll;
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(decorPairList, gbyPairList, groupAll, groupFieldList, groupVar,
+                hashGroupByHint, withVarList);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof GroupbyClause)) {
+            return false;
+        }
+        GroupbyClause target = (GroupbyClause) object;
+        boolean equals = ObjectUtils.equals(decorPairList, target.decorPairList)
+                && ObjectUtils.equals(gbyPairList, target.gbyPairList) && groupAll == target.groupAll
+                && ObjectUtils.equals(groupFieldList, target.groupFieldList);
+        return equals && ObjectUtils.equals(groupVar, target.groupVar) && hashGroupByHint == target.hashGroupByHint
+                && ObjectUtils.equals(withVarList, target.withVarList);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/LetClause.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/LetClause.java
index 6a4866d..7043a34 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/LetClause.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/LetClause.java
@@ -23,6 +23,7 @@
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class LetClause implements Clause {
     private VariableExpr varExpr;
@@ -64,4 +65,21 @@
         return visitor.visit(this, arg);
     }
 
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(bindExpr, varExpr);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof LetClause)) {
+            return false;
+        }
+        LetClause target = (LetClause) object;
+        return bindExpr.equals(target.getBindingExpr()) && varExpr.equals(target.getVarExpr());
+    }
+
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/LimitClause.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/LimitClause.java
index 7ebedc2..f9123ef 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/LimitClause.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/LimitClause.java
@@ -22,25 +22,27 @@
 import org.apache.asterix.lang.common.base.Clause;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class LimitClause implements Clause {
-    private Expression limitexpr;
+    private Expression limitExpr;
     private Expression offset;
 
     public LimitClause() {
+        // Default constructor.
     }
 
     public LimitClause(Expression limitexpr, Expression offset) {
-        this.limitexpr = limitexpr;
+        this.limitExpr = limitexpr;
         this.offset = offset;
     }
 
     public Expression getLimitExpr() {
-        return limitexpr;
+        return limitExpr;
     }
 
     public void setLimitExpr(Expression limitexpr) {
-        this.limitexpr = limitexpr;
+        this.limitExpr = limitexpr;
     }
 
     public Expression getOffset() {
@@ -64,4 +66,21 @@
     public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws AsterixException {
         return visitor.visit(this, arg);
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(limitExpr, offset);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof LimitClause)) {
+            return false;
+        }
+        LimitClause target = (LimitClause) object;
+        return limitExpr.equals(target.getLimitExpr()) && ObjectUtils.equals(offset, target.getOffset());
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/OrderbyClause.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/OrderbyClause.java
index 58acd43..8574c76 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/OrderbyClause.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/OrderbyClause.java
@@ -24,6 +24,7 @@
 import org.apache.asterix.lang.common.base.Clause;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 import org.apache.hyracks.dataflow.common.data.partition.range.IRangeMap;
 
 public class OrderbyClause implements Clause {
@@ -34,6 +35,7 @@
     private int numTuples = -1;
 
     public OrderbyClause() {
+        // Default constructor.
     }
 
     public OrderbyClause(List<Expression> orderbyList, List<OrderModifier> modifierList) {
@@ -95,4 +97,22 @@
     public void setRangeMap(IRangeMap rangeMap) {
         this.rangeMap = rangeMap;
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(modifierList, numFrames, numTuples, orderbyList);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof OrderbyClause)) {
+            return false;
+        }
+        OrderbyClause target = (OrderbyClause) object;
+        return ObjectUtils.equals(modifierList, target.modifierList) && numFrames == target.numFrames
+                && numTuples == target.numTuples && orderbyList.equals(target.orderbyList);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/UpdateClause.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/UpdateClause.java
index 90576ce..50bf7a5 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/UpdateClause.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/UpdateClause.java
@@ -25,6 +25,7 @@
 import org.apache.asterix.lang.common.statement.InsertStatement;
 import org.apache.asterix.lang.common.statement.UpdateStatement;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class UpdateClause implements Clause {
 
@@ -115,4 +116,25 @@
         return elsebranch != null;
     }
 
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(condition, ds, elsebranch, ifbranch, is, target, us, value);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return false;
+        }
+        if (!(object instanceof UpdateClause)) {
+            return false;
+        }
+        UpdateClause other = (UpdateClause) object;
+        boolean equals = ObjectUtils.equals(condition, other.condition) && ObjectUtils.equals(ds, other.ds)
+                && ObjectUtils.equals(elsebranch, other.elsebranch) && ObjectUtils.equals(ifbranch, other.ifbranch);
+        equals = equals && ObjectUtils.equals(is, other.is) && ObjectUtils.equals(target, other.target)
+                && ObjectUtils.equals(us, other.us);
+        return equals && ObjectUtils.equals(value, other.value);
+    }
+
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/WhereClause.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/WhereClause.java
index d0af3e9..eaa17e2 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/WhereClause.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/WhereClause.java
@@ -26,12 +26,8 @@
 public class WhereClause implements Clause {
     private Expression whereExpr;
 
-    public Expression getWhereExpr() {
-        return whereExpr;
-    }
-
-    public void setWhereExpr(Expression whereExpr) {
-        this.whereExpr = whereExpr;
+    public WhereClause() {
+        // Default constructor.
     }
 
     public WhereClause(Expression whereExpr) {
@@ -39,7 +35,12 @@
         this.whereExpr = whereExpr;
     }
 
-    public WhereClause() {
+    public Expression getWhereExpr() {
+        return whereExpr;
+    }
+
+    public void setWhereExpr(Expression whereExpr) {
+        this.whereExpr = whereExpr;
     }
 
     @Override
@@ -52,4 +53,20 @@
         return visitor.visit(this, arg);
     }
 
+    @Override
+    public int hashCode() {
+        return whereExpr.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof WhereClause)) {
+            return false;
+        }
+        WhereClause whereClause = (WhereClause) object;
+        return whereExpr.equals(whereClause.getWhereExpr());
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/AbstractAccessor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/AbstractAccessor.java
index c7ac0a1..2b7b748 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/AbstractAccessor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/AbstractAccessor.java
@@ -19,6 +19,7 @@
 package org.apache.asterix.lang.common.expression;
 
 import org.apache.asterix.lang.common.base.Expression;
+import org.apache.commons.lang.ObjectUtils;
 
 public abstract class AbstractAccessor implements Expression {
     protected Expression expr;
@@ -36,4 +37,21 @@
         this.expr = expr;
     }
 
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCode(expr);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof AbstractAccessor)) {
+            return false;
+        }
+        AbstractAccessor target = (AbstractAccessor) object;
+        return expr.equals(target.expr);
+    }
+
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/CallExpr.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/CallExpr.java
index 44a40c4..1b7b918 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/CallExpr.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/CallExpr.java
@@ -25,6 +25,7 @@
 import org.apache.asterix.lang.common.base.AbstractExpression;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class CallExpr extends AbstractExpression {
     private FunctionSignature functionSignature;
@@ -68,6 +69,24 @@
 
     @Override
     public String toString() {
-        return "call " + String.valueOf(functionSignature);
+        return "call " + functionSignature;
+    }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(exprList, functionSignature, isBuiltin);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof CallExpr)) {
+            return false;
+        }
+        CallExpr target = (CallExpr) object;
+        return ObjectUtils.equals(exprList, target.exprList)
+                && ObjectUtils.equals(functionSignature, target.functionSignature) && isBuiltin == target.isBuiltin;
     }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/FieldAccessor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/FieldAccessor.java
index e23c799..0a76730 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/FieldAccessor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/FieldAccessor.java
@@ -22,6 +22,7 @@
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class FieldAccessor extends AbstractAccessor {
     private Identifier ident;
@@ -53,4 +54,21 @@
     public String toString() {
         return String.valueOf(expr) + "." + ident.toString();
     }
+
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() + ObjectUtils.hashCode(ident);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof FieldAccessor)) {
+            return false;
+        }
+        FieldAccessor target = (FieldAccessor) object;
+        return super.equals(target) && ObjectUtils.equals(ident, target.ident);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/FieldBinding.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/FieldBinding.java
index abfbbe3..afadddd 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/FieldBinding.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/FieldBinding.java
@@ -19,12 +19,14 @@
 package org.apache.asterix.lang.common.expression;
 
 import org.apache.asterix.lang.common.base.Expression;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class FieldBinding {
     private Expression leftExpr;
     private Expression rightExpr;
 
     public FieldBinding() {
+        // default constructor.
     }
 
     public FieldBinding(Expression leftExpr, Expression rightExpr) {
@@ -49,4 +51,26 @@
         this.rightExpr = rightExpr;
     }
 
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(leftExpr, rightExpr);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof FieldBinding)) {
+            return false;
+        }
+        FieldBinding target = (FieldBinding) object;
+        return ObjectUtils.equals(leftExpr, target.leftExpr) && ObjectUtils.equals(rightExpr, target.rightExpr);
+    }
+
+    @Override
+    public String toString() {
+        return leftExpr + ": " + rightExpr;
+    }
+
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/GbyVariableExpressionPair.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/GbyVariableExpressionPair.java
index bb33918..aaa6526 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/GbyVariableExpressionPair.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/GbyVariableExpressionPair.java
@@ -19,6 +19,7 @@
 package org.apache.asterix.lang.common.expression;
 
 import org.apache.asterix.lang.common.base.Expression;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class GbyVariableExpressionPair {
     private VariableExpr var; // can be null
@@ -50,4 +51,25 @@
         this.expr = expr;
     }
 
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(expr, var);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof GbyVariableExpressionPair)) {
+            return false;
+        }
+        GbyVariableExpressionPair target = (GbyVariableExpressionPair) object;
+        return ObjectUtils.equals(expr, target.expr) && ObjectUtils.equals(var, target.var);
+    }
+
+    @Override
+    public String toString() {
+        return expr + " AS " + var;
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/IfExpr.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/IfExpr.java
index f32a9cc..2709f80 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/IfExpr.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/IfExpr.java
@@ -21,6 +21,7 @@
 import org.apache.asterix.common.exceptions.AsterixException;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class IfExpr implements Expression {
     private Expression condExpr;
@@ -28,6 +29,7 @@
     private Expression elseExpr;
 
     public IfExpr() {
+        // default constructor
     }
 
     public IfExpr(Expression condExpr, Expression thenExpr, Expression elseExpr) {
@@ -69,4 +71,27 @@
     public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws AsterixException {
         return visitor.visit(this, arg);
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(condExpr, elseExpr, thenExpr);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof IfExpr)) {
+            return false;
+        }
+        IfExpr target = (IfExpr) object;
+        return ObjectUtils.equals(condExpr, target.condExpr) && ObjectUtils.equals(elseExpr, target.elseExpr)
+                && ObjectUtils.equals(thenExpr, target.thenExpr);
+    }
+
+    @Override
+    public String toString() {
+        return "if(" + condExpr + ") then " + thenExpr + " else " + elseExpr;
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/IndexAccessor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/IndexAccessor.java
index 898870e..07a8254 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/IndexAccessor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/IndexAccessor.java
@@ -21,26 +21,26 @@
 import org.apache.asterix.common.exceptions.AsterixException;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class IndexAccessor extends AbstractAccessor {
-    private boolean any;
+    private boolean isAny;
     private Expression indexExpr;
 
-    public final static int ANY = -1;
-
     public IndexAccessor(Expression expr, Expression indexExpr) {
         super(expr);
-        if (indexExpr == null)
-            this.any = true;
+        if (indexExpr == null) {
+            this.isAny = true;
+        }
         this.indexExpr = indexExpr;
     }
 
     public boolean isAny() {
-        return any;
+        return isAny;
     }
 
     public void setAny(boolean any) {
-        this.any = any;
+        this.isAny = any;
     }
 
     public Expression getIndexExpr() {
@@ -60,4 +60,26 @@
     public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws AsterixException {
         return visitor.visit(this, arg);
     }
+
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() + ObjectUtils.hashCodeMulti(indexExpr, isAny);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof IndexAccessor)) {
+            return false;
+        }
+        IndexAccessor target = (IndexAccessor) object;
+        return super.equals(target) && isAny == target.isAny && ObjectUtils.equals(indexExpr, target.indexExpr);
+    }
+
+    @Override
+    public String toString() {
+        return expr + "[" + (isAny ? "?" : indexExpr) + "]";
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/ListConstructor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/ListConstructor.java
index 10e1baa..7fdca69 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/ListConstructor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/ListConstructor.java
@@ -23,12 +23,14 @@
 import org.apache.asterix.common.exceptions.AsterixException;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class ListConstructor implements Expression {
     private List<Expression> exprList;
     private Type type;
 
     public ListConstructor() {
+        // default constructor.
     }
 
     public ListConstructor(Type type, List<Expression> exprList) {
@@ -66,4 +68,21 @@
     public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws AsterixException {
         return visitor.visit(this, arg);
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(exprList, type);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof ListConstructor)) {
+            return false;
+        }
+        ListConstructor target = (ListConstructor) object;
+        return ObjectUtils.equals(exprList, target.exprList) && ObjectUtils.equals(type, target.type);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/LiteralExpr.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/LiteralExpr.java
index a9fa748f..46c3c32 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/LiteralExpr.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/LiteralExpr.java
@@ -22,11 +22,13 @@
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.Literal;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class LiteralExpr implements Expression {
     private Literal value;
 
     public LiteralExpr() {
+        // default constructor.
     }
 
     public LiteralExpr(Literal value) {
@@ -51,4 +53,21 @@
         return visitor.visit(this, arg);
     }
 
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCode(value);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof LiteralExpr)) {
+            return false;
+        }
+        LiteralExpr target = (LiteralExpr) object;
+        return ObjectUtils.equals(value, target.value);
+    }
+
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/OperatorExpr.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/OperatorExpr.java
index bf70df4..fb64ad8 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/OperatorExpr.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/OperatorExpr.java
@@ -27,6 +27,7 @@
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.struct.OperatorType;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class OperatorExpr extends AbstractExpression {
     private List<Expression> exprList;
@@ -117,4 +118,22 @@
         }
         return false;
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(currentop, exprBroadcastIdx, exprList, opList);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof OperatorExpr)) {
+            return false;
+        }
+        OperatorExpr target = (OperatorExpr) object;
+        return currentop == target.isCurrentop() && ObjectUtils.equals(exprBroadcastIdx, target.exprBroadcastIdx)
+                && ObjectUtils.equals(exprList, target.exprList) && ObjectUtils.equals(opList, target.opList);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/OrderedListTypeDefinition.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/OrderedListTypeDefinition.java
index 99d0674..08b9c53 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/OrderedListTypeDefinition.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/OrderedListTypeDefinition.java
@@ -20,8 +20,9 @@
 
 import org.apache.asterix.common.exceptions.AsterixException;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang.ObjectUtils;
 
-public class OrderedListTypeDefinition extends TypeExpression {
+public class OrderedListTypeDefinition implements TypeExpression {
 
     private TypeExpression itemTypeExpression;
 
@@ -43,4 +44,20 @@
         return itemTypeExpression;
     }
 
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCode(itemTypeExpression);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof OrderedListTypeDefinition)) {
+            return false;
+        }
+        OrderedListTypeDefinition target = (OrderedListTypeDefinition) object;
+        return ObjectUtils.equals(itemTypeExpression, target.itemTypeExpression);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/QuantifiedExpression.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/QuantifiedExpression.java
index 78c8ab1..e1372cd 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/QuantifiedExpression.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/QuantifiedExpression.java
@@ -24,6 +24,7 @@
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.struct.QuantifiedPair;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class QuantifiedExpression implements Expression {
     private List<QuantifiedPair> quantifiedList;
@@ -79,4 +80,23 @@
         EVERY,
         SOME
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(quantifiedList, quantifier, satisfiesExpr);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof QuantifiedExpression)) {
+            return false;
+        }
+        QuantifiedExpression target = (QuantifiedExpression) object;
+        return ObjectUtils.equals(quantifiedList, target.quantifiedList)
+                && ObjectUtils.equals(quantifier, target.quantifier)
+                && ObjectUtils.equals(satisfiesExpr, target.satisfiesExpr);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/RecordConstructor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/RecordConstructor.java
index 6c6b415..144b27c 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/RecordConstructor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/RecordConstructor.java
@@ -23,6 +23,7 @@
 import org.apache.asterix.common.exceptions.AsterixException;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class RecordConstructor implements Expression {
     private List<FieldBinding> fbList;
@@ -53,4 +54,21 @@
     public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws AsterixException {
         return visitor.visit(this, arg);
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCode(fbList);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof RecordConstructor)) {
+            return false;
+        }
+        RecordConstructor target = (RecordConstructor) object;
+        return ObjectUtils.equals(fbList, target.fbList);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/RecordTypeDefinition.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/RecordTypeDefinition.java
index 5e5208e..800af9f 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/RecordTypeDefinition.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/RecordTypeDefinition.java
@@ -25,26 +25,24 @@
 import org.apache.asterix.common.annotations.UndeclaredFieldsDataGen;
 import org.apache.asterix.common.exceptions.AsterixException;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
-public class RecordTypeDefinition extends TypeExpression {
+public class RecordTypeDefinition implements TypeExpression {
 
     public enum RecordKind {
         OPEN,
         CLOSED
     }
 
-    private ArrayList<String> fieldNames;
-    private ArrayList<TypeExpression> fieldTypes;
-    private ArrayList<IRecordFieldDataGen> fieldDataGen;
-    private ArrayList<Boolean> optionalFields;
+    private final List<String> fieldNames = new ArrayList<>();
+    private final List<TypeExpression> fieldTypes = new ArrayList<>();
+    private final List<IRecordFieldDataGen> fieldDataGen = new ArrayList<>();
+    private final List<Boolean> optionalFields = new ArrayList<>();
     private RecordKind recordKind;
     private UndeclaredFieldsDataGen undeclaredFieldsDataGen;
 
     public RecordTypeDefinition() {
-        fieldNames = new ArrayList<>();
-        fieldTypes = new ArrayList<>();
-        optionalFields = new ArrayList<>();
-        fieldDataGen = new ArrayList<>();
+        // Default constructor.
     }
 
     @Override
@@ -102,4 +100,26 @@
         return undeclaredFieldsDataGen;
     }
 
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(fieldDataGen, fieldNames, fieldTypes, optionalFields, recordKind,
+                undeclaredFieldsDataGen);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof RecordTypeDefinition)) {
+            return false;
+        }
+        RecordTypeDefinition target = (RecordTypeDefinition) object;
+        boolean equals = fieldDataGen.equals(target.getFieldDataGen()) && fieldNames.equals(target.getFieldNames())
+                && fieldTypes.equals(target.getFieldNames()) && optionalFields.equals(target.getOptionableFields());
+        equals = equals && ObjectUtils.equals(recordKind, target.getRecordKind())
+                && ObjectUtils.equals(undeclaredFieldsDataGen, target.getUndeclaredFieldsDataGen());
+        return equals;
+    }
+
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/TypeExpression.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/TypeExpression.java
index 409e683..3359be6 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/TypeExpression.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/TypeExpression.java
@@ -20,7 +20,7 @@
 
 import org.apache.asterix.lang.common.base.ILangExpression;
 
-public abstract class TypeExpression implements ILangExpression {
+public interface TypeExpression extends ILangExpression {
 
     public enum TypeExprKind {
         RECORD,
@@ -29,6 +29,6 @@
         UNORDEREDLIST
     }
 
-    public abstract TypeExprKind getTypeKind();
+    public TypeExprKind getTypeKind();
 
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/TypeReferenceExpression.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/TypeReferenceExpression.java
index 2c35cc3..49f0667 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/TypeReferenceExpression.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/TypeReferenceExpression.java
@@ -21,8 +21,9 @@
 import org.apache.asterix.common.exceptions.AsterixException;
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang.ObjectUtils;
 
-public class TypeReferenceExpression extends TypeExpression {
+public class TypeReferenceExpression implements TypeExpression {
 
     private final Identifier ident;
 
@@ -43,4 +44,21 @@
     public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws AsterixException {
         return visitor.visit(this, arg);
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCode(ident);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof TypeReferenceExpression)) {
+            return false;
+        }
+        TypeReferenceExpression target = (TypeReferenceExpression) object;
+        return ObjectUtils.equals(ident, target.ident);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/UnaryExpr.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/UnaryExpr.java
index cf5eefe..eef8f1c 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/UnaryExpr.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/UnaryExpr.java
@@ -21,12 +21,14 @@
 import org.apache.asterix.common.exceptions.AsterixException;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class UnaryExpr implements Expression {
     private Sign sign;
     private Expression expr;
 
     public UnaryExpr() {
+        // default constructor
     }
 
     public UnaryExpr(Sign sign, Expression expr) {
@@ -64,4 +66,21 @@
     public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws AsterixException {
         return visitor.visit(this, arg);
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(expr, sign);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof UnaryExpr)) {
+            return false;
+        }
+        UnaryExpr target = (UnaryExpr) object;
+        return ObjectUtils.equals(expr, target.expr) && ObjectUtils.equals(sign, target.sign);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/UnorderedListTypeDefinition.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/UnorderedListTypeDefinition.java
index b47ade4..a0726e2 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/UnorderedListTypeDefinition.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/UnorderedListTypeDefinition.java
@@ -20,8 +20,9 @@
 
 import org.apache.asterix.common.exceptions.AsterixException;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
-public class UnorderedListTypeDefinition extends TypeExpression {
+public class UnorderedListTypeDefinition implements TypeExpression {
 
     private TypeExpression itemTypeExpression;
 
@@ -43,4 +44,20 @@
         return itemTypeExpression;
     }
 
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCode(itemTypeExpression);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof OrderedListTypeDefinition)) {
+            return false;
+        }
+        UnorderedListTypeDefinition target = (UnorderedListTypeDefinition) object;
+        return ObjectUtils.equals(itemTypeExpression, target.itemTypeExpression);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/VariableExpr.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/VariableExpr.java
index 9eeb6f8..534c74f 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/VariableExpr.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/expression/VariableExpr.java
@@ -22,6 +22,7 @@
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.struct.VarIdentifier;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class VariableExpr implements Expression {
     private VarIdentifier var;
@@ -75,16 +76,19 @@
 
     @Override
     public int hashCode() {
-        return var.hashCode();
+        return ObjectUtils.hashCode(var);
     }
 
     @Override
     public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
         if (!(obj instanceof VariableExpr)) {
             return false;
         }
         VariableExpr expr = (VariableExpr) obj;
-        return var.equals(expr.getVar());
+        return ObjectUtils.equals(var, expr.var);
     }
 
     @Override
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/DoubleLiteral.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/DoubleLiteral.java
index 1a5066d..f362c15 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/DoubleLiteral.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/DoubleLiteral.java
@@ -19,11 +19,9 @@
 package org.apache.asterix.lang.common.literal;
 
 import org.apache.asterix.lang.common.base.Literal;
+import org.apache.commons.lang.ObjectUtils;
 
 public class DoubleLiteral extends Literal {
-    /**
-     *
-     */
     private static final long serialVersionUID = -5685491458356989250L;
     private Double value;
 
@@ -45,4 +43,26 @@
     public Type getLiteralType() {
         return Type.DOUBLE;
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCode(value);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof DoubleLiteral)) {
+            return false;
+        }
+        DoubleLiteral target = (DoubleLiteral) object;
+        return ObjectUtils.equals(value, target.value);
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(value);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/FalseLiteral.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/FalseLiteral.java
index 61db616..bde5260 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/FalseLiteral.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/FalseLiteral.java
@@ -23,12 +23,11 @@
 public class FalseLiteral extends Literal {
 
     private static final long serialVersionUID = -750814844423165149L;
+    public static final FalseLiteral INSTANCE = new FalseLiteral();
 
     private FalseLiteral() {
     }
 
-    public final static FalseLiteral INSTANCE = new FalseLiteral();
-
     @Override
     public Type getLiteralType() {
         return Type.FALSE;
@@ -45,8 +44,8 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
-        return obj == INSTANCE;
+    public Boolean getValue() {
+        return Boolean.FALSE;
     }
 
     @Override
@@ -55,7 +54,8 @@
     }
 
     @Override
-    public Boolean getValue() {
-        return Boolean.FALSE;
+    public boolean equals(Object obj) {
+        return obj == INSTANCE;
     }
+
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/FloatLiteral.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/FloatLiteral.java
index d7c54fa..a3dfd6a 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/FloatLiteral.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/FloatLiteral.java
@@ -19,11 +19,9 @@
 package org.apache.asterix.lang.common.literal;
 
 import org.apache.asterix.lang.common.base.Literal;
+import org.apache.commons.lang.ObjectUtils;
 
 public class FloatLiteral extends Literal {
-    /**
-     *
-     */
     private static final long serialVersionUID = 3273563021227964396L;
     private Float value;
 
@@ -45,4 +43,26 @@
     public Type getLiteralType() {
         return Type.FLOAT;
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCode(value);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof FloatLiteral)) {
+            return false;
+        }
+        FloatLiteral target = (FloatLiteral) object;
+        return ObjectUtils.equals(value, target.value);
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(value);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/IntegerLiteral.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/IntegerLiteral.java
index e0c1de3..a46a736 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/IntegerLiteral.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/IntegerLiteral.java
@@ -19,11 +19,9 @@
 package org.apache.asterix.lang.common.literal;
 
 import org.apache.asterix.lang.common.base.Literal;
+import org.apache.commons.lang.ObjectUtils;
 
 public class IntegerLiteral extends Literal {
-    /**
-     *
-     */
     private static final long serialVersionUID = -8633520244871361967L;
     private Integer value;
 
@@ -32,6 +30,7 @@
         this.value = value;
     }
 
+    @Override
     public Integer getValue() {
         return value;
     }
@@ -44,4 +43,26 @@
     public Type getLiteralType() {
         return Type.INTEGER;
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCode(value);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof IntegerLiteral)) {
+            return false;
+        }
+        IntegerLiteral target = (IntegerLiteral) object;
+        return value.equals(target.getValue());
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(value);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/LongIntegerLiteral.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/LongIntegerLiteral.java
index 3320dac..66164d1 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/LongIntegerLiteral.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/LongIntegerLiteral.java
@@ -19,11 +19,9 @@
 package org.apache.asterix.lang.common.literal;
 
 import org.apache.asterix.lang.common.base.Literal;
+import org.apache.commons.lang.ObjectUtils;
 
 public class LongIntegerLiteral extends Literal {
-    /**
-     *
-     */
     private static final long serialVersionUID = -8633520244871361967L;
     private Long value;
 
@@ -45,4 +43,26 @@
     public Type getLiteralType() {
         return Type.LONG;
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCode(value);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof LongIntegerLiteral)) {
+            return false;
+        }
+        LongIntegerLiteral target = (LongIntegerLiteral) object;
+        return value.equals(target.getValue());
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(value);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/MissingLiteral.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/MissingLiteral.java
index 2c2fa8e..c12ed6f 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/MissingLiteral.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/MissingLiteral.java
@@ -22,12 +22,11 @@
 
 public class MissingLiteral extends Literal {
     private static final long serialVersionUID = 1L;
+    public static final MissingLiteral INSTANCE = new MissingLiteral();
 
     private MissingLiteral() {
     }
 
-    public final static MissingLiteral INSTANCE = new MissingLiteral();
-
     @Override
     public Type getLiteralType() {
         return Type.MISSING;
@@ -52,4 +51,9 @@
     public Object getValue() {
         return null;
     }
+
+    @Override
+    public String toString() {
+        return getStringValue();
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/NullLiteral.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/NullLiteral.java
index e5fdb1d..197aa6e 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/NullLiteral.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/NullLiteral.java
@@ -21,17 +21,12 @@
 import org.apache.asterix.lang.common.base.Literal;
 
 public class NullLiteral extends Literal {
-
-    /**
-     *
-     */
     private static final long serialVersionUID = -7782153599294838739L;
+    public static final NullLiteral INSTANCE = new NullLiteral();
 
     private NullLiteral() {
     }
 
-    public final static NullLiteral INSTANCE = new NullLiteral();
-
     @Override
     public Type getLiteralType() {
         return Type.NULL;
@@ -56,4 +51,9 @@
     public Object getValue() {
         return null;
     }
+
+    @Override
+    public String toString() {
+        return getStringValue();
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/StringLiteral.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/StringLiteral.java
index 623c9ee..9caad3e 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/StringLiteral.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/StringLiteral.java
@@ -19,6 +19,7 @@
 package org.apache.asterix.lang.common.literal;
 
 import org.apache.asterix.lang.common.base.Literal;
+import org.apache.commons.lang.ObjectUtils;
 
 public class StringLiteral extends Literal {
 
@@ -30,6 +31,7 @@
         this.value = value;
     }
 
+    @Override
     public String getValue() {
         return value;
     }
@@ -47,4 +49,26 @@
     public String getStringValue() {
         return value;
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCode(value);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof StringLiteral)) {
+            return false;
+        }
+        StringLiteral target = (StringLiteral) object;
+        return ObjectUtils.equals(value, target.value);
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/TrueLiteral.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/TrueLiteral.java
index a919216..e0ae87d 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/TrueLiteral.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/literal/TrueLiteral.java
@@ -22,12 +22,11 @@
 
 public class TrueLiteral extends Literal {
     private static final long serialVersionUID = -8513245514578847512L;
+    public static final TrueLiteral INSTANCE = new TrueLiteral();
 
     private TrueLiteral() {
     }
 
-    public final static TrueLiteral INSTANCE = new TrueLiteral();
-
     @Override
     public Type getLiteralType() {
         return Type.TRUE;
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/rewrites/ExpressionSubstitutionEnvironment.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/rewrites/ExpressionSubstitutionEnvironment.java
new file mode 100644
index 0000000..8f5597f
--- /dev/null
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/rewrites/ExpressionSubstitutionEnvironment.java
@@ -0,0 +1,165 @@
+/*
+ * 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.asterix.lang.common.rewrites;
+
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.asterix.common.exceptions.AsterixException;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.Multiset;
+
+/**
+ * This class is in charge of substitute expressions by a given mapping while
+ * traversing a given AST. The traversal and expression substitution should
+ * be based on the correct variable scoping. In order to support scoping at the
+ * caller, this class provides methods like mark, reset and pop.
+ */
+public class ExpressionSubstitutionEnvironment {
+
+    @FunctionalInterface
+    public static interface FreeVariableCollector {
+        public Collection<VariableExpr> getFreeVariable(ILangExpression expr) throws AsterixException;
+    }
+
+    @FunctionalInterface
+    public static interface DeepCopier {
+        public ILangExpression deepCopy(ILangExpression expr) throws AsterixException;
+    }
+
+    private Map<Expression, Expression> exprMap = new HashMap<>();
+    private Map<VariableExpr, Expression> freeVarToExprMap = new HashMap<>();
+
+    // We use multiset here because variables can be defined multiple times
+    // in the scope stack.
+    private Multiset<Expression> disabledExpr = HashMultiset.create();
+
+    // Snapshots of variables that should be disabled for replacement,
+    // e.g., if a variable is redefined in a closer scope.
+    private Deque<Multiset<Expression>> disabledExprBackup = new ArrayDeque<>();
+
+    public ExpressionSubstitutionEnvironment() {
+        // Default constructor.
+    }
+
+    public ExpressionSubstitutionEnvironment(Map<Expression, Expression> map, FreeVariableCollector freeVarCollector)
+            throws AsterixException {
+        addMapping(map, freeVarCollector);
+    }
+
+    /**
+     * Finds a substitution expression.
+     *
+     * @param expr
+     *            the original expression.
+     * @return the new, replaced expression.
+     */
+    public Expression findSubstitution(Expression expr, DeepCopier deepCopier) throws AsterixException {
+        Expression replacementExpr = exprMap.get(expr);
+        if (replacementExpr != null && !disabledExpr.contains(replacementExpr)) {
+            return (Expression) deepCopier.deepCopy(replacementExpr);
+        }
+        return expr;
+    }
+
+    /**
+     * Disable a substitution when a free variable in the expression is re-defined.
+     *
+     * @param var
+     *            a re-defined variable.
+     */
+    public void disableVariable(VariableExpr var) {
+        Expression expr = freeVarToExprMap.get(var);
+        if (expr != null) {
+            disabledExpr.add(expr);
+        }
+    }
+
+    /**
+     * Re-enable a substitution when a re-defined variable exits its scope.
+     *
+     * @param var
+     *            a re-defined variable.
+     */
+    public void enableVariable(VariableExpr var) {
+        Expression expr = freeVarToExprMap.get(var);
+        if (expr != null) {
+            disabledExpr.remove(expr);
+        }
+    }
+
+    /**
+     * Tasks a snapshot of the current states.
+     *
+     * @return the snapshot id that can be reset to in the future.
+     */
+    public int mark() {
+        Multiset<Expression> copyOfDisabledExprs = HashMultiset.create();
+        copyOfDisabledExprs.addAll(disabledExpr);
+        disabledExprBackup.push(copyOfDisabledExprs);
+        return disabledExprBackup.size() - 1;
+    }
+
+    /**
+     * Resets the internal states to a snapshot.
+     *
+     * @param depth,
+     *            the snapshot id that the caller wants to recover to.
+     */
+    public void reset(int depth) {
+        while (disabledExprBackup.size() > depth) {
+            disabledExpr = disabledExprBackup.pop();
+        }
+    }
+
+    /**
+     * Restores to the most-recent snapshot.
+     */
+    public void pop() {
+        if (!disabledExprBackup.isEmpty()) {
+            disabledExpr = disabledExprBackup.pop();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return exprMap.toString();
+    }
+
+    private void addMapping(Map<Expression, Expression> map, FreeVariableCollector freeVarCollector)
+            throws AsterixException {
+        exprMap.putAll(map);
+        // Put free variable to target expression map.
+        for (Entry<Expression, Expression> entry : map.entrySet()) {
+            Expression targetExpr = entry.getKey();
+            for (VariableExpr freeVar : freeVarCollector.getFreeVariable(targetExpr)) {
+                freeVarToExprMap.put(freeVar, targetExpr);
+            }
+        }
+    }
+
+}
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/rewrites/LangRewritingContext.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/rewrites/LangRewritingContext.java
index 9036413..10cd7d8 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/rewrites/LangRewritingContext.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/rewrites/LangRewritingContext.java
@@ -25,6 +25,7 @@
 public final class LangRewritingContext {
     private int mark = 0;
     private int varCounter;
+    private int systemVarCounter = 1;
     private HashMap<Integer, VarIdentifier> oldVarIdToNewVarId = new HashMap<>();
 
     public LangRewritingContext(int varCounter) {
@@ -63,7 +64,7 @@
     public VarIdentifier newVariable() {
         int id = newId();
         // Prefixes system-generated variables with "#".
-        return new VarIdentifier("#" + id, id);
+        return new VarIdentifier("#" + (systemVarCounter++), id);
     }
 
     public void markCounter() {
@@ -78,4 +79,5 @@
         varCounter++;
         return varCounter;
     }
+
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/rewrites/VariableSubstitutionEnvironment.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/rewrites/VariableSubstitutionEnvironment.java
index 4c4767e..26f528c 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/rewrites/VariableSubstitutionEnvironment.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/rewrites/VariableSubstitutionEnvironment.java
@@ -29,7 +29,7 @@
     private Map<String, Expression> oldVarToNewExpressionMap = new HashMap<String, Expression>();
 
     public VariableSubstitutionEnvironment() {
-
+        // Default constructor.
     }
 
     public VariableSubstitutionEnvironment(Map<VariableExpr, Expression> varExprMap) {
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/DeleteStatement.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/DeleteStatement.java
index 3a2a933..ba3440f 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/DeleteStatement.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/DeleteStatement.java
@@ -26,6 +26,7 @@
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class DeleteStatement implements Statement {
 
@@ -95,4 +96,25 @@
         return datasets;
     }
 
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(condition, datasetName, datasets, dataverseName, dataverses, rewrittenQuery,
+                vars);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof DeleteStatement)) {
+            return false;
+        }
+        DeleteStatement target = (DeleteStatement) object;
+        boolean equals = ObjectUtils.equals(condition, target.condition)
+                && ObjectUtils.equals(datasetName, target.datasetName) && ObjectUtils.equals(datasets, target.datasets)
+                && ObjectUtils.equals(dataverseName, target.dataverseName);
+        return equals && ObjectUtils.equals(dataverses, target.dataverses)
+                && ObjectUtils.equals(rewrittenQuery, target.rewrittenQuery) && ObjectUtils.equals(vars, target.vars);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/InsertStatement.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/InsertStatement.java
index de81de0..aab0979 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/InsertStatement.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/InsertStatement.java
@@ -22,6 +22,7 @@
 import org.apache.asterix.lang.common.base.Statement;
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class InsertStatement implements Statement {
 
@@ -63,4 +64,22 @@
         return visitor.visit(this, arg);
     }
 
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(datasetName, dataverseName, query);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof InsertStatement)) {
+            return false;
+        }
+        InsertStatement target = (InsertStatement) object;
+        return ObjectUtils.equals(datasetName, target.datasetName)
+                && ObjectUtils.equals(dataverseName, target.dataverseName) && ObjectUtils.equals(query, target.query);
+    }
+
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/Query.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/Query.java
index 64ca0c1..a9cba21 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/Query.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/Query.java
@@ -25,16 +25,17 @@
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.Statement;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class Query implements Statement {
     private boolean topLevel = true;
     private Expression body;
     private int varCounter;
-    private List<String> dataverses = new ArrayList<String>();
-    private List<String> datasets = new ArrayList<String>();
+    private List<String> dataverses = new ArrayList<>();
+    private List<String> datasets = new ArrayList<>();
 
     public Query() {
-
+        // Default constructor.
     }
 
     public Query(boolean topLevel, Expression body, int varCounter, List<String> dataverses, List<String> datasets) {
@@ -94,4 +95,22 @@
     public List<String> getDatasets() {
         return datasets;
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(body, datasets, dataverses, topLevel);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof Query)) {
+            return false;
+        }
+        Query target = (Query) object;
+        return ObjectUtils.equals(body, target.body) && ObjectUtils.equals(datasets, target.datasets)
+                && ObjectUtils.equals(dataverses, target.dataverses) && topLevel == target.topLevel;
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/UpdateStatement.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/UpdateStatement.java
index c82bbad..c61ec0b 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/UpdateStatement.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/UpdateStatement.java
@@ -26,6 +26,7 @@
 import org.apache.asterix.lang.common.clause.UpdateClause;
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class UpdateStatement implements Statement {
 
@@ -67,4 +68,22 @@
         return visitor.visit(this, arg);
     }
 
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(condition, target, ucs, vars);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof UpdateStatement)) {
+            return false;
+        }
+        UpdateStatement update = (UpdateStatement) object;
+        return ObjectUtils.equals(condition, update.condition) && ObjectUtils.equals(target, update.target)
+                && ObjectUtils.equals(ucs, update.ucs) && ObjectUtils.equals(vars, update.vars);
+    }
+
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/UpsertStatement.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/UpsertStatement.java
index f415951..fa11930 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/UpsertStatement.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/UpsertStatement.java
@@ -31,4 +31,20 @@
         return Kind.UPSERT;
     }
 
+    @Override
+    public int hashCode() {
+        return super.hashCode() + Kind.UPSERT.ordinal();
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof UpsertStatement)) {
+            return false;
+        }
+        return super.equals(object);
+    }
+
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/Identifier.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/Identifier.java
index d029e3e..b77fd94 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/Identifier.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/Identifier.java
@@ -18,10 +18,13 @@
  */
 package org.apache.asterix.lang.common.struct;
 
+import org.apache.commons.lang.ObjectUtils;
+
 public class Identifier {
     protected String value;
 
     public Identifier() {
+        // default constructor.
     }
 
     public Identifier(String value) {
@@ -36,22 +39,25 @@
         this.value = value;
     }
 
+    @Override
     public String toString() {
         return value;
     }
 
     @Override
     public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
         if (!(o instanceof Identifier)) {
             return false;
-        } else {
-            Identifier i = (Identifier) o;
-            return this.value.equals(i.value);
         }
+        Identifier target = (Identifier) o;
+        return ObjectUtils.equals(value, target.value);
     }
 
     @Override
     public int hashCode() {
-        return value.hashCode();
+        return ObjectUtils.hashCode(value);
     }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/QuantifiedPair.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/QuantifiedPair.java
index 6be8918..ab8a3f3 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/QuantifiedPair.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/QuantifiedPair.java
@@ -20,12 +20,14 @@
 
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class QuantifiedPair {
     private VariableExpr varExpr;
     private Expression expr;
 
     public QuantifiedPair() {
+        // default constructor
     }
 
     public QuantifiedPair(VariableExpr varExpr, Expression expr) {
@@ -49,4 +51,17 @@
         this.expr = expr;
     }
 
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(expr, varExpr);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (!(object instanceof QuantifiedPair)) {
+            return false;
+        }
+        QuantifiedPair target = (QuantifiedPair) object;
+        return ObjectUtils.equals(expr, target.expr) && ObjectUtils.equals(varExpr, target.varExpr);
+    }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/VarIdentifier.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/VarIdentifier.java
index 6549151..a217bea 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/VarIdentifier.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/struct/VarIdentifier.java
@@ -18,6 +18,8 @@
  */
 package org.apache.asterix.lang.common.struct;
 
+import org.apache.commons.lang3.ObjectUtils;
+
 public final class VarIdentifier extends Identifier {
     private int id = 0;
 
@@ -53,7 +55,7 @@
 
     @Override
     public int hashCode() {
-        return value.hashCode();
+        return ObjectUtils.hashCodeMulti(value);
     }
 
     @Override
@@ -62,6 +64,6 @@
             return false;
         }
         VarIdentifier vid = (VarIdentifier) obj;
-        return value.equals(vid.value);
+        return ObjectUtils.equals(value, vid.value);
     }
 }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/VariableCloneAndSubstitutionUtil.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/VariableCloneAndSubstitutionUtil.java
index 29298db..1948b84 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/VariableCloneAndSubstitutionUtil.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/VariableCloneAndSubstitutionUtil.java
@@ -34,19 +34,22 @@
 
 public class VariableCloneAndSubstitutionUtil {
 
+    private VariableCloneAndSubstitutionUtil() {
+    }
+
     public static List<GbyVariableExpressionPair> substInVarExprPair(LangRewritingContext context,
-            List<GbyVariableExpressionPair> gbyVeList, VariableSubstitutionEnvironment arg,
-            VariableSubstitutionEnvironment newSubs, CloneAndSubstituteVariablesVisitor visitor)
-                    throws AsterixException {
-        List<GbyVariableExpressionPair> veList = new LinkedList<GbyVariableExpressionPair>();
+            List<GbyVariableExpressionPair> gbyVeList, VariableSubstitutionEnvironment newSubs,
+            CloneAndSubstituteVariablesVisitor visitor) throws AsterixException {
+        VariableSubstitutionEnvironment subs = newSubs;
+        List<GbyVariableExpressionPair> veList = new LinkedList<>();
         for (GbyVariableExpressionPair vep : gbyVeList) {
             VariableExpr oldGbyVar = vep.getVar();
             VariableExpr newGbyVar = null;
             if (oldGbyVar != null) {
                 newGbyVar = visitor.generateNewVariable(context, oldGbyVar);
-                newSubs = eliminateSubstFromList(newGbyVar, newSubs);
+                subs = eliminateSubstFromList(newGbyVar, subs);
             }
-            Pair<ILangExpression, VariableSubstitutionEnvironment> p1 = vep.getExpr().accept(visitor, newSubs);
+            Pair<ILangExpression, VariableSubstitutionEnvironment> p1 = vep.getExpr().accept(visitor, subs);
             GbyVariableExpressionPair ve2 = new GbyVariableExpressionPair(newGbyVar, (Expression) p1.first);
             veList.add(ve2);
         }
@@ -62,7 +65,7 @@
 
     public static List<Expression> visitAndCloneExprList(List<Expression> oldExprList,
             VariableSubstitutionEnvironment arg, CloneAndSubstituteVariablesVisitor visitor) throws AsterixException {
-        List<Expression> exprs = new ArrayList<Expression>(oldExprList.size());
+        List<Expression> exprs = new ArrayList<>(oldExprList.size());
         for (Expression e : oldExprList) {
             Pair<ILangExpression, VariableSubstitutionEnvironment> p1 = e.accept(visitor, arg);
             exprs.add((Expression) p1.first);
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/CloneAndSubstituteVariablesVisitor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/CloneAndSubstituteVariablesVisitor.java
index d380862..57264be 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/CloneAndSubstituteVariablesVisitor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/CloneAndSubstituteVariablesVisitor.java
@@ -71,25 +71,24 @@
         VariableExpr varExpr = lc.getVarExpr();
         VariableExpr newVe = generateNewVariable(context, varExpr);
         LetClause newLet = new LetClause(newVe, (Expression) p1.first);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(newLet,
-                VariableCloneAndSubstitutionUtil.eliminateSubstFromList(lc.getVarExpr(), env));
+        return new Pair<>(newLet, VariableCloneAndSubstitutionUtil.eliminateSubstFromList(lc.getVarExpr(), env));
     }
 
     @Override
     public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(GroupbyClause gc,
             VariableSubstitutionEnvironment env) throws AsterixException {
         VariableSubstitutionEnvironment newSubs = env;
-        List<GbyVariableExpressionPair> newGbyList = VariableCloneAndSubstitutionUtil.substInVarExprPair(context,
-                gc.getGbyPairList(), env, newSubs, this);
-        List<GbyVariableExpressionPair> newDecorList = gc.hasDecorList() ? VariableCloneAndSubstitutionUtil
-                .substInVarExprPair(context, gc.getDecorPairList(), env, newSubs, this)
-                : new ArrayList<GbyVariableExpressionPair>();
+        List<GbyVariableExpressionPair> newGbyList =
+                VariableCloneAndSubstitutionUtil.substInVarExprPair(context, gc.getGbyPairList(), newSubs, this);
+        List<GbyVariableExpressionPair> newDecorList = gc.hasDecorList()
+                ? VariableCloneAndSubstitutionUtil.substInVarExprPair(context, gc.getDecorPairList(), newSubs, this)
+                : new ArrayList<>();
 
         VariableExpr newGroupVar = null;
         if (gc.hasGroupVar()) {
             newGroupVar = generateNewVariable(context, gc.getGroupVar());
         }
-        List<VariableExpr> wList = new LinkedList<VariableExpr>();
+        List<VariableExpr> wList = new LinkedList<>();
         if (gc.hasWithList()) {
             for (VariableExpr w : gc.getWithVarList()) {
                 VarIdentifier newVar = context.getRewrittenVar(w.getVar().getId());
@@ -109,26 +108,26 @@
         }
         GroupbyClause newGroup = new GroupbyClause(newGbyList, newDecorList, wList, newGroupVar, newGroupFieldList,
                 gc.hasHashGroupByHint(), gc.isGroupAll());
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(newGroup, newSubs);
+        return new Pair<>(newGroup, newSubs);
     }
 
     @Override
     public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(QuantifiedExpression qe,
             VariableSubstitutionEnvironment env) throws AsterixException {
         List<QuantifiedPair> oldPairs = qe.getQuantifiedList();
-        List<QuantifiedPair> newPairs = new ArrayList<QuantifiedPair>(oldPairs.size());
+        List<QuantifiedPair> newPairs = new ArrayList<>(oldPairs.size());
         VariableSubstitutionEnvironment newSubs = env;
         for (QuantifiedPair t : oldPairs) {
             VariableExpr newVar = generateNewVariable(context, t.getVarExpr());
             newSubs = VariableCloneAndSubstitutionUtil.eliminateSubstFromList(newVar, newSubs);
-            Pair<ILangExpression, VariableSubstitutionEnvironment> p1 = visitUnnesBindingExpression(t.getExpr(),
-                    newSubs);
+            Pair<ILangExpression, VariableSubstitutionEnvironment> p1 =
+                    visitUnnesBindingExpression(t.getExpr(), newSubs);
             QuantifiedPair t2 = new QuantifiedPair(newVar, (Expression) p1.first);
             newPairs.add(t2);
         }
         Pair<ILangExpression, VariableSubstitutionEnvironment> p2 = qe.getSatisfiesExpr().accept(this, newSubs);
         QuantifiedExpression qe2 = new QuantifiedExpression(qe.getQuantifier(), newPairs, (Expression) p2.first);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(qe2, newSubs);
+        return new Pair<>(qe2, newSubs);
     }
 
     @Override
@@ -136,7 +135,7 @@
             VariableSubstitutionEnvironment env) throws AsterixException {
         Pair<ILangExpression, VariableSubstitutionEnvironment> p1 = wc.getWhereExpr().accept(this, env);
         WhereClause newW = new WhereClause((Expression) p1.first);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(newW, p1.second);
+        return new Pair<>(newW, p1.second);
     }
 
     @Override
@@ -144,13 +143,13 @@
             VariableSubstitutionEnvironment env) throws AsterixException {
         List<Expression> exprList = VariableCloneAndSubstitutionUtil.visitAndCloneExprList(pf.getExprList(), env, this);
         CallExpr f = new CallExpr(pf.getFunctionSignature(), exprList);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(f, env);
+        return new Pair<>(f, env);
     }
 
     @Override
     public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(FunctionDecl fd,
             VariableSubstitutionEnvironment env) throws AsterixException {
-        List<VarIdentifier> newList = new ArrayList<VarIdentifier>(fd.getParamList().size());
+        List<VarIdentifier> newList = new ArrayList<>(fd.getParamList().size());
         for (VarIdentifier vi : fd.getParamList()) {
             VariableExpr varExpr = new VariableExpr(vi);
             if (!env.constainsOldVar(varExpr)) {
@@ -165,7 +164,7 @@
 
         Pair<ILangExpression, VariableSubstitutionEnvironment> p1 = fd.getFuncBody().accept(this, env);
         FunctionDecl newF = new FunctionDecl(fd.getSignature(), newList, (Expression) p1.first);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(newF, env);
+        return new Pair<>(newF, env);
     }
 
     @Override
@@ -175,22 +174,22 @@
         Pair<ILangExpression, VariableSubstitutionEnvironment> p2 = ifexpr.getThenExpr().accept(this, env);
         Pair<ILangExpression, VariableSubstitutionEnvironment> p3 = ifexpr.getElseExpr().accept(this, env);
         IfExpr i = new IfExpr((Expression) p1.first, (Expression) p2.first, (Expression) p3.first);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(i, env);
+        return new Pair<>(i, env);
     }
 
     @Override
     public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(LimitClause lc,
             VariableSubstitutionEnvironment env) throws AsterixException {
         Pair<ILangExpression, VariableSubstitutionEnvironment> p1 = lc.getLimitExpr().accept(this, env);
-        Pair<ILangExpression, VariableSubstitutionEnvironment> p2 = null;
+        Pair<ILangExpression, VariableSubstitutionEnvironment> p2;
         Expression lcOffsetExpr = lc.getOffset();
         if (lcOffsetExpr != null) {
             p2 = lcOffsetExpr.accept(this, env);
         } else {
-            p2 = new Pair<ILangExpression, VariableSubstitutionEnvironment>(null, null);
+            p2 = new Pair<>(null, null);
         }
         LimitClause c = new LimitClause((Expression) p1.first, (Expression) p2.first);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(c, env);
+        return new Pair<>(c, env);
     }
 
     @Override
@@ -199,38 +198,38 @@
         List<Expression> oldExprList = lc.getExprList();
         List<Expression> exprs = VariableCloneAndSubstitutionUtil.visitAndCloneExprList(oldExprList, env, this);
         ListConstructor c = new ListConstructor(lc.getType(), exprs);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(c, env);
+        return new Pair<>(c, env);
     }
 
     @Override
     public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(LiteralExpr l,
             VariableSubstitutionEnvironment env) throws AsterixException {
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(l, env);
+        return new Pair<>(l, env);
     }
 
     @Override
     public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(OperatorExpr op,
             VariableSubstitutionEnvironment env) throws AsterixException {
         List<Expression> oldExprList = op.getExprList();
-        List<Expression> exprs = new ArrayList<Expression>(oldExprList.size());
+        List<Expression> exprs = new ArrayList<>(oldExprList.size());
         for (Expression e : oldExprList) {
             Pair<ILangExpression, VariableSubstitutionEnvironment> p1 = e.accept(this, env);
             exprs.add((Expression) p1.first);
         }
         OperatorExpr oe = new OperatorExpr(exprs, op.getExprBroadcastIdx(), op.getOpList(), op.isCurrentop());
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(oe, env);
+        return new Pair<>(oe, env);
     }
 
     @Override
     public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(OrderbyClause oc,
             VariableSubstitutionEnvironment env) throws AsterixException {
-        List<Expression> exprList = VariableCloneAndSubstitutionUtil.visitAndCloneExprList(oc.getOrderbyList(), env,
-                this);
+        List<Expression> exprList =
+                VariableCloneAndSubstitutionUtil.visitAndCloneExprList(oc.getOrderbyList(), env, this);
         OrderbyClause oc2 = new OrderbyClause(exprList, oc.getModifierList());
         oc2.setNumFrames(oc.getNumFrames());
         oc2.setNumTuples(oc.getNumTuples());
         oc2.setRangeMap(oc.getRangeMap());
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(oc2, env);
+        return new Pair<>(oc2, env);
     }
 
     @Override
@@ -239,14 +238,14 @@
         Query newQ = new Query();
         Pair<ILangExpression, VariableSubstitutionEnvironment> p1 = q.getBody().accept(this, env);
         newQ.setBody((Expression) p1.first);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(newQ, p1.second);
+        return new Pair<>(newQ, p1.second);
     }
 
     @Override
     public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(RecordConstructor rc,
             VariableSubstitutionEnvironment env) throws AsterixException {
         List<FieldBinding> oldFbs = rc.getFbList();
-        ArrayList<FieldBinding> newFbs = new ArrayList<FieldBinding>(oldFbs.size());
+        ArrayList<FieldBinding> newFbs = new ArrayList<>(oldFbs.size());
         for (FieldBinding fb : oldFbs) {
             Pair<ILangExpression, VariableSubstitutionEnvironment> p1 = fb.getLeftExpr().accept(this, env);
             Pair<ILangExpression, VariableSubstitutionEnvironment> p2 = fb.getRightExpr().accept(this, env);
@@ -254,7 +253,7 @@
             newFbs.add(fb2);
         }
         RecordConstructor newRc = new RecordConstructor(newFbs);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(newRc, env);
+        return new Pair<>(newRc, env);
     }
 
     @Override
@@ -262,7 +261,7 @@
             VariableSubstitutionEnvironment env) throws AsterixException {
         Pair<ILangExpression, VariableSubstitutionEnvironment> p1 = u.getExpr().accept(this, env);
         UnaryExpr newU = new UnaryExpr(u.getSign(), (Expression) p1.first);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(newU, env);
+        return new Pair<>(newU, env);
     }
 
     @Override
@@ -276,7 +275,7 @@
         }
         IndexAccessor i = new IndexAccessor((Expression) p1.first, indexExpr);
         i.setAny(ia.isAny());
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(i, env);
+        return new Pair<>(i, env);
     }
 
     @Override
@@ -284,13 +283,13 @@
             VariableSubstitutionEnvironment env) throws AsterixException {
         Pair<ILangExpression, VariableSubstitutionEnvironment> p = fa.getExpr().accept(this, env);
         FieldAccessor newF = new FieldAccessor((Expression) p.first, fa.getIdent());
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(newF, p.second);
+        return new Pair<>(newF, p.second);
     }
 
     @Override
     public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(VariableExpr v,
             VariableSubstitutionEnvironment env) throws AsterixException {
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(rewriteVariableExpr(v, env), env);
+        return new Pair<>(rewriteVariableExpr(v, env), env);
     }
 
     // Replace a variable expression if the variable is to-be substituted.
@@ -320,8 +319,7 @@
     public VariableExpr generateNewVariable(LangRewritingContext context, VariableExpr varExpr) {
         VarIdentifier vi = varExpr.getVar();
         VarIdentifier newVar = context.mapOldId(vi.getId(), vi.getValue());
-        VariableExpr newVarExpr = new VariableExpr(newVar);
-        return newVarExpr;
+        return new VariableExpr(newVar);
     }
 
     /**
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/SubstituteExpressionVisitor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/SubstituteExpressionVisitor.java
new file mode 100644
index 0000000..1fd561e
--- /dev/null
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/SubstituteExpressionVisitor.java
@@ -0,0 +1,245 @@
+/*
+ * 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.asterix.lang.common.visitor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.asterix.common.exceptions.AsterixException;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.clause.LimitClause;
+import org.apache.asterix.lang.common.clause.OrderbyClause;
+import org.apache.asterix.lang.common.clause.WhereClause;
+import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.FieldAccessor;
+import org.apache.asterix.lang.common.expression.FieldBinding;
+import org.apache.asterix.lang.common.expression.IfExpr;
+import org.apache.asterix.lang.common.expression.IndexAccessor;
+import org.apache.asterix.lang.common.expression.ListConstructor;
+import org.apache.asterix.lang.common.expression.LiteralExpr;
+import org.apache.asterix.lang.common.expression.OperatorExpr;
+import org.apache.asterix.lang.common.expression.QuantifiedExpression;
+import org.apache.asterix.lang.common.expression.RecordConstructor;
+import org.apache.asterix.lang.common.expression.UnaryExpr;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.rewrites.ExpressionSubstitutionEnvironment;
+import org.apache.asterix.lang.common.rewrites.ExpressionSubstitutionEnvironment.DeepCopier;
+import org.apache.asterix.lang.common.statement.FunctionDecl;
+import org.apache.asterix.lang.common.statement.Query;
+import org.apache.asterix.lang.common.struct.QuantifiedPair;
+import org.apache.asterix.lang.common.visitor.base.AbstractQueryExpressionVisitor;
+
+public abstract class SubstituteExpressionVisitor
+        extends AbstractQueryExpressionVisitor<Expression, ExpressionSubstitutionEnvironment> {
+    private final DeepCopier deepCopier;
+
+    public SubstituteExpressionVisitor(DeepCopier deepCopier) {
+        this.deepCopier = deepCopier;
+    }
+
+    @Override
+    public Expression visit(LetClause lc, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        // Marks the binding variable for future visiting until it exists its scope.
+        env.disableVariable(lc.getVarExpr());
+        lc.setBindingExpr(lc.getBindingExpr().accept(this, env));
+        return null;
+    }
+
+    @Override
+    public Expression visit(Query q, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        q.setBody(q.getBody().accept(this, env));
+        return null;
+    }
+
+    @Override
+    public Expression visit(FunctionDecl fd, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        // Do nothing for a function declaration.
+        return null;
+    }
+
+    @Override
+    public Expression visit(LiteralExpr l, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        return env.findSubstitution(l, deepCopier);
+    }
+
+    @Override
+    public Expression visit(VariableExpr v, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        return env.findSubstitution(v, deepCopier);
+    }
+
+    @Override
+    public Expression visit(ListConstructor lc, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        Expression newLc = env.findSubstitution(lc, deepCopier);
+        if (newLc == lc) {
+            lc.setExprList(rewriteExpressionList(lc.getExprList(), env));
+            return lc;
+        } else {
+            return newLc.accept(this, env);
+        }
+    }
+
+    @Override
+    public Expression visit(RecordConstructor rc, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        Expression newRc = env.findSubstitution(rc, deepCopier);
+        if (newRc == rc) {
+            for (FieldBinding fb : rc.getFbList()) {
+                fb.setLeftExpr(fb.getLeftExpr().accept(this, env));
+                fb.setRightExpr(fb.getRightExpr().accept(this, env));
+            }
+            return rc;
+        } else {
+            return newRc.accept(this, env);
+        }
+    }
+
+    @Override
+    public Expression visit(OperatorExpr operatorExpr, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        Expression newOpertorExpr = env.findSubstitution(operatorExpr, deepCopier);
+        if (newOpertorExpr == operatorExpr) {
+            operatorExpr.setExprList(rewriteExpressionList(operatorExpr.getExprList(), env));
+            return operatorExpr;
+        } else {
+            return newOpertorExpr.accept(this, env);
+        }
+    }
+
+    @Override
+    public Expression visit(FieldAccessor fa, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        Expression newFa = env.findSubstitution(fa, deepCopier);
+        if (newFa == fa) {
+            fa.setExpr(fa.getExpr().accept(this, env));
+            return fa;
+        } else {
+            return newFa.accept(this, env);
+        }
+    }
+
+    @Override
+    public Expression visit(IndexAccessor ia, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        Expression newIa = env.findSubstitution(ia, deepCopier);
+        if (newIa == ia) {
+            ia.setExpr(ia.getExpr().accept(this, env));
+            ia.setIndexExpr(ia.getIndexExpr().accept(this, env));
+            return ia;
+        } else {
+            return newIa.accept(this, env);
+        }
+    }
+
+    @Override
+    public Expression visit(IfExpr ifexpr, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        Expression newIfExpr = env.findSubstitution(ifexpr, deepCopier);
+        if (newIfExpr == ifexpr) {
+            ifexpr.setCondExpr(ifexpr.getCondExpr().accept(this, env));
+            ifexpr.setThenExpr(ifexpr.getThenExpr().accept(this, env));
+            ifexpr.setElseExpr(ifexpr.getElseExpr().accept(this, env));
+            return ifexpr;
+        } else {
+            return newIfExpr.accept(this, env);
+        }
+    }
+
+    @Override
+    public Expression visit(QuantifiedExpression qe, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        Expression newQe = env.findSubstitution(qe, deepCopier);
+        if (newQe == qe) {
+            // Rewrites the quantifier list.
+            for (QuantifiedPair pair : qe.getQuantifiedList()) {
+                pair.setExpr(pair.getExpr().accept(this, env));
+            }
+
+            // Rewrites the condition.
+            for (QuantifiedPair pair : qe.getQuantifiedList()) {
+                // Marks each binding var.
+                env.disableVariable(pair.getVarExpr());
+            }
+            qe.setSatisfiesExpr(qe.getSatisfiesExpr().accept(this, env));
+            for (QuantifiedPair pair : qe.getQuantifiedList()) {
+                // Let each binding var exit its scope.
+                env.enableVariable(pair.getVarExpr());
+            }
+            return qe;
+        } else {
+            return newQe.accept(this, env);
+        }
+    }
+
+    @Override
+    public Expression visit(WhereClause wc, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        wc.setWhereExpr(wc.getWhereExpr().accept(this, env));
+        return null;
+    }
+
+    @Override
+    public Expression visit(OrderbyClause oc, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        oc.setOrderbyList(rewriteExpressionList(oc.getOrderbyList(), env));
+        return null;
+    }
+
+    @Override
+    public Expression visit(LimitClause lc, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        lc.setLimitExpr(lc.getLimitExpr().accept(this, env));
+        if (lc.hasOffset()) {
+            lc.setOffset(lc.getOffset().accept(this, env));
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(UnaryExpr u, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        Expression newU = env.findSubstitution(u, deepCopier);
+        if (newU == u) {
+            u.setExpr(u.getExpr().accept(this, env));
+            return u;
+        } else {
+            return newU.accept(this, env);
+        }
+    }
+
+    @Override
+    public Expression visit(CallExpr callExpr, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        Expression newCallExpr = env.findSubstitution(callExpr, deepCopier);
+        if (newCallExpr == callExpr) {
+            callExpr.setExprList(rewriteExpressionList(callExpr.getExprList(), env));
+            return callExpr;
+        } else {
+            return newCallExpr.accept(this, env);
+        }
+    }
+
+    /**
+     * Rewrites the expression list.
+     *
+     * @param exprs,
+     *            list of expressions.
+     * @param env,
+     *            ExpressionSubstitutionEnvironment.
+     * @return a list of rewritten expressions.
+     * @throws AsterixException
+     */
+    protected List<Expression> rewriteExpressionList(List<Expression> exprs, ExpressionSubstitutionEnvironment env)
+            throws AsterixException {
+        List<Expression> newExprs = new ArrayList<>();
+        for (Expression expr : exprs) {
+            newExprs.add(env.findSubstitution(expr, deepCopier));
+        }
+        return newExprs;
+    }
+}
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/AbstractBinaryCorrelateClause.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/AbstractBinaryCorrelateClause.java
index b1dbba5..ed5f404 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/AbstractBinaryCorrelateClause.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/AbstractBinaryCorrelateClause.java
@@ -23,6 +23,7 @@
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.sqlpp.optype.JoinType;
+import org.apache.commons.lang3.ObjectUtils;
 
 public abstract class AbstractBinaryCorrelateClause implements Clause {
 
@@ -63,4 +64,22 @@
         return rightPosVar != null;
     }
 
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(joinType, rightExpr, rightPosVar, rightVar);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof AbstractBinaryCorrelateClause)) {
+            return false;
+        }
+        AbstractBinaryCorrelateClause target = (AbstractBinaryCorrelateClause) object;
+        return ObjectUtils.equals(joinType, target.joinType) && ObjectUtils.equals(rightExpr, target.rightExpr)
+                && ObjectUtils.equals(rightPosVar, target.rightPosVar) && ObjectUtils.equals(rightVar, target.rightVar);
+    }
+
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/AbstractBinaryCorrelateWithConditionClause.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/AbstractBinaryCorrelateWithConditionClause.java
index e1d4a7b..3773ea9 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/AbstractBinaryCorrelateWithConditionClause.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/AbstractBinaryCorrelateWithConditionClause.java
@@ -41,4 +41,21 @@
         this.conditionExpr = conditionExpr;
     }
 
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() + conditionExpr.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof AbstractBinaryCorrelateWithConditionClause)) {
+            return false;
+        }
+        AbstractBinaryCorrelateWithConditionClause target = (AbstractBinaryCorrelateWithConditionClause) object;
+        return super.equals(target) && conditionExpr.equals(target.getConditionExpression());
+    }
+
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/FromClause.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/FromClause.java
index da6c512..aff731b 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/FromClause.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/FromClause.java
@@ -30,7 +30,7 @@
 
 public class FromClause implements Clause {
 
-    private List<FromTerm> fromTerms = new ArrayList<FromTerm>();
+    private List<FromTerm> fromTerms = new ArrayList<>();
 
     public FromClause(List<FromTerm> fromTerms) {
         this.fromTerms = fromTerms;
@@ -54,4 +54,21 @@
     public String toString() {
         return fromTerms.stream().map(String::valueOf).collect(Collectors.joining(", "));
     }
+
+    @Override
+    public int hashCode() {
+        return fromTerms.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof FromClause)) {
+            return false;
+        }
+        FromClause target = (FromClause) object;
+        return fromTerms.equals(target.getFromTerms());
+    }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/FromTerm.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/FromTerm.java
index 82e1f02..d4e522a 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/FromTerm.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/FromTerm.java
@@ -28,19 +28,22 @@
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class FromTerm implements Clause {
     private Expression leftExpr;
     private VariableExpr leftVar;
     private VariableExpr posVar;
-    private List<AbstractBinaryCorrelateClause> correlateClauses;
+    private List<AbstractBinaryCorrelateClause> correlateClauses = new ArrayList<>();
 
     public FromTerm(Expression leftExpr, VariableExpr leftVar, VariableExpr posVar,
             List<AbstractBinaryCorrelateClause> correlateClauses) {
         this.leftExpr = leftExpr;
         this.leftVar = leftVar;
         this.posVar = posVar;
-        this.correlateClauses = correlateClauses == null ? new ArrayList<>() : correlateClauses;
+        if (correlateClauses != null) {
+            this.correlateClauses.addAll(correlateClauses);
+        }
     }
 
     @Override
@@ -85,4 +88,23 @@
     public String toString() {
         return String.valueOf(leftExpr) + " AS " + leftVar;
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(correlateClauses, leftExpr, leftVar, posVar);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof FromTerm)) {
+            return false;
+        }
+        FromTerm target = (FromTerm) object;
+        return ObjectUtils.equals(correlateClauses, target.correlateClauses)
+                && ObjectUtils.equals(leftExpr, target.leftExpr) && ObjectUtils.equals(leftVar, target.leftVar)
+                && ObjectUtils.equals(posVar, target.posVar);
+    }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/HavingClause.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/HavingClause.java
index 98cca9c..19b7692 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/HavingClause.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/HavingClause.java
@@ -50,4 +50,21 @@
         this.filterExpression = filterExpression;
     }
 
+    @Override
+    public int hashCode() {
+        return filterExpression.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof HavingClause)) {
+            return false;
+        }
+        HavingClause target = (HavingClause) object;
+        return filterExpression.equals(target.getFilterExpression());
+    }
+
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/Projection.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/Projection.java
index f73ec1e..148cc36 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/Projection.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/Projection.java
@@ -24,6 +24,7 @@
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class Projection implements Clause {
 
@@ -81,4 +82,22 @@
     public String toString() {
         return star ? "*" : (String.valueOf(expr) + (exprStar ? ".*" : (hasName() ? " as " + getName() : "")));
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(expr, exprStar, name, star);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof Projection)) {
+            return false;
+        }
+        Projection target = (Projection) object;
+        return ObjectUtils.equals(expr, target.expr) && ObjectUtils.equals(exprStar, target.exprStar)
+                && ObjectUtils.equals(name, target.name) && ObjectUtils.equals(star, target.star);
+    }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectBlock.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectBlock.java
index f63eced..e297335 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectBlock.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectBlock.java
@@ -19,6 +19,7 @@
 
 package org.apache.asterix.lang.sqlpp.clause;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.asterix.common.exceptions.AsterixException;
@@ -28,15 +29,16 @@
 import org.apache.asterix.lang.common.clause.WhereClause;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class SelectBlock implements Clause {
 
     private SelectClause selectClause;
     private FromClause fromClause;
-    private List<LetClause> letClauses;
+    private List<LetClause> letClauses = new ArrayList<>();
     private WhereClause whereClause;
     private GroupbyClause groupbyClause;
-    private List<LetClause> letClausesAfterGby;
+    private List<LetClause> letClausesAfterGby = new ArrayList<>();
     private HavingClause havingClause;
 
     public SelectBlock(SelectClause selectClause, FromClause fromClause, List<LetClause> letClauses,
@@ -44,11 +46,15 @@
             HavingClause havingClause) {
         this.selectClause = selectClause;
         this.fromClause = fromClause;
-        this.letClauses = letClauses;
+        if (letClauses != null) {
+            this.letClauses.addAll(letClauses);
+        }
         this.whereClause = whereClause;
         this.groupbyClause = groupbyClause;
         this.havingClause = havingClause;
-        this.letClausesAfterGby = letClausesAfterGby;
+        if (letClausesAfterGby != null) {
+            this.letClausesAfterGby.addAll(letClausesAfterGby);
+        }
     }
 
     @Override
@@ -90,7 +96,7 @@
     }
 
     public boolean hasLetClauses() {
-        return letClauses != null && letClauses.size() > 0;
+        return letClauses != null && !letClauses.isEmpty();
     }
 
     public boolean hasWhereClause() {
@@ -102,7 +108,7 @@
     }
 
     public boolean hasLetClausesAfterGroupby() {
-        return letClausesAfterGby != null && letClausesAfterGby.size() > 0;
+        return letClausesAfterGby != null && !letClausesAfterGby.isEmpty();
     }
 
     public List<LetClause> getLetListAfterGroupby() {
@@ -116,4 +122,53 @@
     public void setGroupbyClause(GroupbyClause groupbyClause) {
         this.groupbyClause = groupbyClause;
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(fromClause, groupbyClause, havingClause, letClauses, letClausesAfterGby,
+                selectClause, whereClause);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof SelectBlock)) {
+            return false;
+        }
+        SelectBlock target = (SelectBlock) object;
+        boolean equals = ObjectUtils.equals(fromClause, target.fromClause)
+                && ObjectUtils.equals(groupbyClause, target.groupbyClause)
+                && ObjectUtils.equals(havingClause, target.havingClause)
+                && ObjectUtils.equals(letClauses, target.letClauses);
+        return equals && ObjectUtils.equals(letClausesAfterGby, target.letClausesAfterGby)
+                && ObjectUtils.equals(selectClause, target.selectClause)
+                && ObjectUtils.equals(whereClause, target.whereClause);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(selectClause);
+        if (hasFromClause()) {
+            sb.append(fromClause);
+        }
+        if (hasLetClauses()) {
+            sb.append(letClauses);
+        }
+        if (hasWhereClause()) {
+            sb.append(whereClause);
+        }
+        if (hasGroupbyClause()) {
+            sb.append(groupbyClause);
+        }
+        if (hasLetClausesAfterGroupby()) {
+            sb.append(letClausesAfterGby);
+        }
+        if (hasHavingClause()) {
+            sb.append(havingClause);
+        }
+        return sb.toString();
+    }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectClause.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectClause.java
index 42c8b29..12e501b 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectClause.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectClause.java
@@ -23,6 +23,7 @@
 import org.apache.asterix.lang.common.base.Clause;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class SelectClause implements Clause {
 
@@ -69,6 +70,24 @@
     @Override
     public String toString() {
         return "select " + (distinct ? "distinct " : "")
-                + (selectElement() ? "element " + String.valueOf(selectElement) : String.valueOf(selectRegular));
+                + (selectElement() ? "element " + selectElement : String.valueOf(selectRegular));
+    }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(distinct, selectElement, selectRegular);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof SelectClause)) {
+            return false;
+        }
+        SelectClause target = (SelectClause) object;
+        return distinct == target.distinct && ObjectUtils.equals(selectElement, target.selectElement)
+                && ObjectUtils.equals(selectRegular, target.selectRegular);
     }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectElement.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectElement.java
index 14efd0f..6a630b5 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectElement.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectElement.java
@@ -55,4 +55,21 @@
     public String toString() {
         return String.valueOf(expr);
     }
+
+    @Override
+    public int hashCode() {
+        return expr.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof SelectElement)) {
+            return false;
+        }
+        SelectElement target = (SelectElement) object;
+        return expr.equals(target.expr);
+    }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectRegular.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectRegular.java
index 4703e01..1df1e7a 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectRegular.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectRegular.java
@@ -53,4 +53,21 @@
     public String toString() {
         return projections.stream().map(String::valueOf).collect(Collectors.joining(", "));
     }
+
+    @Override
+    public int hashCode() {
+        return projections.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof SelectRegular)) {
+            return false;
+        }
+        SelectRegular target = (SelectRegular) object;
+        return projections.equals(target.getProjections());
+    }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectSetOperation.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectSetOperation.java
index 3af93a7..6e0fb21 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectSetOperation.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/SelectSetOperation.java
@@ -28,15 +28,18 @@
 import org.apache.asterix.lang.sqlpp.struct.SetOperationInput;
 import org.apache.asterix.lang.sqlpp.struct.SetOperationRight;
 import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class SelectSetOperation implements Clause {
 
     private SetOperationInput leftInput;
-    private List<SetOperationRight> rightInputs;
+    private List<SetOperationRight> rightInputs = new ArrayList<>();
 
     public SelectSetOperation(SetOperationInput leftInput, List<SetOperationRight> rightInputs) {
         this.leftInput = leftInput;
-        this.rightInputs = rightInputs == null ? new ArrayList<SetOperationRight>() : rightInputs;
+        if (rightInputs != null) {
+            this.rightInputs.addAll(rightInputs);
+        }
     }
 
     @Override
@@ -58,7 +61,34 @@
     }
 
     public boolean hasRightInputs() {
-        return rightInputs != null && rightInputs.size() > 0;
+        return rightInputs != null && !rightInputs.isEmpty();
+    }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(leftInput, rightInputs);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof SelectSetOperation)) {
+            return false;
+        }
+        SelectSetOperation target = (SelectSetOperation) object;
+        return ObjectUtils.equals(leftInput, target.leftInput) && ObjectUtils.equals(rightInputs, target.rightInputs);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(leftInput);
+        for (SetOperationRight right : rightInputs) {
+            sb.append(" " + right);
+        }
+        return sb.toString();
     }
 
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/expression/IndependentSubquery.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/expression/IndependentSubquery.java
index 3825092..3b4c1aa 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/expression/IndependentSubquery.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/expression/IndependentSubquery.java
@@ -54,4 +54,21 @@
     public String toString() {
         return String.valueOf(expr);
     }
+
+    @Override
+    public int hashCode() {
+        return expr.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof IndependentSubquery)) {
+            return false;
+        }
+        IndependentSubquery target = (IndependentSubquery) object;
+        return this.expr.equals(target.getExpr());
+    }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/expression/SelectExpression.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/expression/SelectExpression.java
index 7f9daf8..a940c47 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/expression/SelectExpression.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/expression/SelectExpression.java
@@ -18,6 +18,7 @@
  */
 package org.apache.asterix.lang.sqlpp.expression;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.asterix.common.exceptions.AsterixException;
@@ -28,10 +29,11 @@
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
 import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class SelectExpression implements Expression {
 
-    private List<LetClause> letList;
+    private List<LetClause> letList = new ArrayList<>();
     private SelectSetOperation selectSetOperation;
     private OrderbyClause orderbyClause;
     private LimitClause limitClause;
@@ -39,7 +41,9 @@
 
     public SelectExpression(List<LetClause> letList, SelectSetOperation selectSetOperation, OrderbyClause orderbyClause,
             LimitClause limitClause, boolean subquery) {
-        this.letList = letList;
+        if (letList != null) {
+            this.letList.addAll(letList);
+        }
         this.selectSetOperation = selectSetOperation;
         this.orderbyClause = orderbyClause;
         this.limitClause = limitClause;
@@ -92,4 +96,42 @@
         subquery = setSubquery;
     }
 
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(letList, limitClause, orderbyClause, selectSetOperation, subquery);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof SelectExpression)) {
+            return false;
+        }
+        SelectExpression target = (SelectExpression) object;
+        boolean equals =
+                ObjectUtils.equals(letList, target.letList) && ObjectUtils.equals(limitClause, target.limitClause)
+                        && ObjectUtils.equals(orderbyClause, target.orderbyClause)
+                        && ObjectUtils.equals(selectSetOperation, target.selectSetOperation);
+        return equals && subquery == target.subquery;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(subquery ? "(" : "");
+        if (this.hasLetClauses()) {
+            sb.append(letList.toString());
+        }
+        sb.append(selectSetOperation);
+        if (hasOrderby()) {
+            sb.append(orderbyClause);
+        }
+        if (hasLimit()) {
+            sb.append(limitClause);
+        }
+        sb.append(subquery ? ")" : "");
+        return sb.toString();
+    }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppFunctionBodyRewriter.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppFunctionBodyRewriter.java
index 49823e7..6ddfa40 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppFunctionBodyRewriter.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppFunctionBodyRewriter.java
@@ -37,6 +37,12 @@
         // Inlines column aliases.
         inlineColumnAlias();
 
+        // Generates column names.
+        generateColumnNames();
+
+        // Substitutes group-by key expressions.
+        substituteGroupbyKeyExpression();
+
         // Inlines WITH expressions.
         inlineWithExpressions();
 
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppQueryRewriter.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppQueryRewriter.java
index 5a62e1e..d0e2aa3 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppQueryRewriter.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppQueryRewriter.java
@@ -49,6 +49,7 @@
 import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
 import org.apache.asterix.lang.sqlpp.parser.FunctionParser;
 import org.apache.asterix.lang.sqlpp.parser.SqlppParserFactory;
+import org.apache.asterix.lang.sqlpp.rewrites.visitor.GenerateColumnNameVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.InlineColumnAliasVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.InlineWithExpressionVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.OperatorExpressionVisitor;
@@ -56,6 +57,7 @@
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppGlobalAggregationSugarVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppGroupByVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppInlineUdfsVisitor;
+import org.apache.asterix.lang.sqlpp.rewrites.visitor.SubstituteGroupbyExpressionWithVariableVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.VariableCheckAndRewriteVisitor;
 import org.apache.asterix.lang.sqlpp.struct.SetOperationRight;
 import org.apache.asterix.lang.sqlpp.util.FunctionMapUtil;
@@ -97,6 +99,12 @@
         // Inlines column aliases.
         inlineColumnAlias();
 
+        // Generates column names.
+        generateColumnNames();
+
+        // Substitutes group-by key expressions.
+        substituteGroupbyKeyExpression();
+
         // Inlines WITH expressions.
         inlineWithExpressions();
 
@@ -157,10 +165,29 @@
             return;
         }
         // Inlines with expressions.
-        InlineWithExpressionVisitor inlineWithExpressionVisitor = new InlineWithExpressionVisitor(context);
+        InlineWithExpressionVisitor inlineWithExpressionVisitor = new InlineWithExpressionVisitor();
         inlineWithExpressionVisitor.visit(topExpr, null);
     }
 
+    protected void generateColumnNames() throws AsterixException {
+        if (topExpr == null) {
+            return;
+        }
+        // Generate column names if they are missing in the user query.
+        GenerateColumnNameVisitor generateColumnNameVisitor = new GenerateColumnNameVisitor(context);
+        generateColumnNameVisitor.visit(topExpr, null);
+    }
+
+    protected void substituteGroupbyKeyExpression() throws AsterixException {
+        if (topExpr == null) {
+            return;
+        }
+        // Substitute group-by key expressions that appear in the select clause.
+        SubstituteGroupbyExpressionWithVariableVisitor substituteGbyExprVisitor =
+                new SubstituteGroupbyExpressionWithVariableVisitor();
+        substituteGbyExprVisitor.visit(topExpr, null);
+    }
+
     protected void rewriteOperatorExpression() throws AsterixException {
         if (topExpr == null) {
             return;
@@ -183,8 +210,8 @@
         if (topExpr == null) {
             return;
         }
-        VariableCheckAndRewriteVisitor variableCheckAndRewriteVisitor = new VariableCheckAndRewriteVisitor(context,
-                overwrite, metadataProvider);
+        VariableCheckAndRewriteVisitor variableCheckAndRewriteVisitor =
+                new VariableCheckAndRewriteVisitor(context, overwrite, metadataProvider);
         variableCheckAndRewriteVisitor.visit(topExpr, null);
     }
 
@@ -235,8 +262,8 @@
 
             Function function = lookupUserDefinedFunctionDecl(signature);
             if (function == null) {
-                FunctionSignature normalizedSignature = FunctionMapUtil.normalizeBuiltinFunctionSignature(signature,
-                        false);
+                FunctionSignature normalizedSignature =
+                        FunctionMapUtil.normalizeBuiltinFunctionSignature(signature, false);
                 if (AsterixBuiltinFunctions.isBuiltinCompilerFunction(normalizedSignature, includePrivateFunctions)) {
                     continue;
                 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/GenerateColumnNameVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/GenerateColumnNameVisitor.java
new file mode 100644
index 0000000..e2edb3a
--- /dev/null
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/GenerateColumnNameVisitor.java
@@ -0,0 +1,66 @@
+/*
+ * 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.asterix.lang.sqlpp.rewrites.visitor;
+
+import org.apache.asterix.common.exceptions.AsterixException;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.clause.GroupbyClause;
+import org.apache.asterix.lang.common.expression.GbyVariableExpressionPair;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
+import org.apache.asterix.lang.sqlpp.clause.Projection;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppExpressionScopingVisitor;
+
+// Generates implicit column names for projections in a SELECT clause and group-by keys.
+public class GenerateColumnNameVisitor extends AbstractSqlppExpressionScopingVisitor {
+
+    public GenerateColumnNameVisitor(LangRewritingContext context) {
+        super(context);
+    }
+
+    @Override
+    public Expression visit(SelectBlock selectBlock, ILangExpression arg) throws AsterixException {
+        // Visit selectBlock first so that column names starts from $1.
+        selectBlock.getSelectClause().accept(this, arg);
+        return super.visit(selectBlock, arg);
+    }
+
+    @Override
+    public Expression visit(Projection projection, ILangExpression arg) throws AsterixException {
+        if (!projection.star() && projection.getName() == null) {
+            projection.setName(SqlppVariableUtil.variableNameToDisplayedFieldName(context.newVariable().getValue()));
+        }
+        return super.visit(projection, arg);
+    }
+
+    @Override
+    public Expression visit(GroupbyClause groupbyClause, ILangExpression arg) throws AsterixException {
+        for (GbyVariableExpressionPair gbyKeyPair : groupbyClause.getGbyPairList()) {
+            if (gbyKeyPair.getVar() == null) {
+                gbyKeyPair.setVar(new VariableExpr(context.newVariable()));
+            }
+        }
+        return super.visit(groupbyClause, arg);
+    }
+
+}
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/InlineColumnAliasVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/InlineColumnAliasVisitor.java
index 6b74d3f..57cbdc5 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/InlineColumnAliasVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/InlineColumnAliasVisitor.java
@@ -130,12 +130,12 @@
 
     @Override
     public Void visit(Projection projection, Boolean overwriteWithGbyKeyVarRefs) throws AsterixException {
-        if (projection.star()) {
+        if (projection.star() || projection.getName() == null) {
             return null;
         }
         projection.getExpression().accept(this, overwriteWithGbyKeyVarRefs);
-        VariableExpr columnAlias = new VariableExpr(
-                SqlppVariableUtil.toInternalVariableIdentifier(projection.getName()));
+        VariableExpr columnAlias =
+                new VariableExpr(SqlppVariableUtil.toInternalVariableIdentifier(projection.getName()));
         VariableSubstitutionEnvironment env = scopeChecker.getCurrentScope().getVarSubstitutionEnvironment();
         Expression gbyKey = (Expression) SqlppRewriteUtil.deepCopy(env.findSubstitution(columnAlias));
         if (overwriteWithGbyKeyVarRefs) {
@@ -220,26 +220,25 @@
             // We only need to deal with the case that the left expression (for a field name) is
             // a string literal. Otherwise, it is different from a column alias in a projection
             // (e.g., foo.name AS name) in regular SQL SELECT.
-            if (leftExpr.getKind() == Kind.LITERAL_EXPRESSION) {
-                LiteralExpr literalExpr = (LiteralExpr) leftExpr;
-                if (literalExpr.getValue().getLiteralType() == Literal.Type.STRING) {
-                    String fieldName = literalExpr.getValue().getStringValue();
-                    VariableExpr columnAlias = new VariableExpr(
-                            SqlppVariableUtil.toInternalVariableIdentifier(fieldName));
-                    VariableSubstitutionEnvironment env = scopeChecker.getCurrentScope()
-                            .getVarSubstitutionEnvironment();
-                    if (overwriteWithGbyKeyVarRefs) {
-                        // Rewrites the field value expression by the mapped grouping key
-                        // (for the column alias) if there exists such a mapping.
-                        Expression gbyKey = (Expression) SqlppRewriteUtil.deepCopy(env.findSubstitution(columnAlias));
-                        if (gbyKey != null) {
-                            binding.setRightExpr(gbyKey);
-                        }
-                    } else {
-                        // If this is the first pass, map a field name (i.e., column alias) to the field expression.
-                        scopeChecker.getCurrentScope().addSymbolExpressionMappingToScope(columnAlias,
-                                binding.getRightExpr());
+            if (leftExpr.getKind() != Kind.LITERAL_EXPRESSION) {
+                continue;
+            }
+            LiteralExpr literalExpr = (LiteralExpr) leftExpr;
+            if (literalExpr.getValue().getLiteralType() == Literal.Type.STRING) {
+                String fieldName = literalExpr.getValue().getStringValue();
+                VariableExpr columnAlias = new VariableExpr(SqlppVariableUtil.toInternalVariableIdentifier(fieldName));
+                VariableSubstitutionEnvironment env = scopeChecker.getCurrentScope().getVarSubstitutionEnvironment();
+                if (overwriteWithGbyKeyVarRefs) {
+                    // Rewrites the field value expression by the mapped grouping key
+                    // (for the column alias) if there exists such a mapping.
+                    Expression gbyKey = (Expression) SqlppRewriteUtil.deepCopy(env.findSubstitution(columnAlias));
+                    if (gbyKey != null) {
+                        binding.setRightExpr(gbyKey);
                     }
+                } else {
+                    // If this is the first pass, map a field name (i.e., column alias) to the field expression.
+                    scopeChecker.getCurrentScope().addSymbolExpressionMappingToScope(columnAlias,
+                            binding.getRightExpr());
                 }
             }
         }
@@ -324,8 +323,8 @@
         Map<VariableExpr, VariableExpr> oldGbyExprsToNewGbyVarMap = new HashMap<>();
         for (GbyVariableExpressionPair gbyVarExpr : gc.getGbyPairList()) {
             Expression oldGbyExpr = gbyVarExpr.getExpr();
-            Expression newExpr = (Expression) SqlppVariableSubstitutionUtil.substituteVariableWithoutContext(oldGbyExpr,
-                    env);
+            Expression newExpr =
+                    (Expression) SqlppVariableSubstitutionUtil.substituteVariableWithoutContext(oldGbyExpr, env);
             newExpr.accept(this, overwriteWithGbyKeyVarRefs);
             gbyVarExpr.setExpr(newExpr);
             if (oldGbyExpr.getKind() == Kind.VARIABLE_EXPRESSION) {
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/InlineWithExpressionVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/InlineWithExpressionVisitor.java
index bec1523..a01c09f 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/InlineWithExpressionVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/InlineWithExpressionVisitor.java
@@ -28,17 +28,12 @@
 import org.apache.asterix.lang.common.base.ILangExpression;
 import org.apache.asterix.lang.common.clause.LetClause;
 import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
 import org.apache.asterix.lang.sqlpp.expression.IndependentSubquery;
 import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
 import org.apache.asterix.lang.sqlpp.util.SqlppVariableSubstitutionUtil;
-import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppExpressionScopingVisitor;
+import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppSimpleExpressionVisitor;
 
-public class InlineWithExpressionVisitor extends AbstractSqlppExpressionScopingVisitor {
-
-    public InlineWithExpressionVisitor(LangRewritingContext context) {
-        super(context);
-    }
+public class InlineWithExpressionVisitor extends AbstractSqlppSimpleExpressionVisitor {
 
     @Override
     public Expression visit(SelectExpression selectExpression, ILangExpression arg) throws AsterixException {
@@ -66,7 +61,9 @@
 
             // Continues to visit the rewritten select expression.
             return super.visit(newSelectExpression, arg);
+        } else {
+            // Continues to visit inside the select expression.
+            return super.visit(selectExpression, arg);
         }
-        return selectExpression;
     }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SubstituteGroupbyExpressionWithVariableVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SubstituteGroupbyExpressionWithVariableVisitor.java
new file mode 100644
index 0000000..c5cd60e
--- /dev/null
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SubstituteGroupbyExpressionWithVariableVisitor.java
@@ -0,0 +1,89 @@
+/*
+ * 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.asterix.lang.sqlpp.rewrites.visitor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.asterix.common.exceptions.AsterixException;
+import org.apache.asterix.common.functions.FunctionSignature;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.Expression.Kind;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.GbyVariableExpressionPair;
+import org.apache.asterix.lang.common.rewrites.ExpressionSubstitutionEnvironment;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.util.FunctionMapUtil;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.asterix.lang.sqlpp.visitor.SqlppSubstituteExpressionsVisitor;
+import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppSimpleExpressionVisitor;
+
+// Replaces expressions that appear in having/select/order-by/limit clause and are identical to some
+// group by key expression with the group by key expression.
+public class SubstituteGroupbyExpressionWithVariableVisitor extends AbstractSqlppSimpleExpressionVisitor {
+
+    @Override
+    public Expression visit(SelectBlock selectBlock, ILangExpression arg) throws AsterixException {
+        if (selectBlock.hasGroupbyClause()) {
+            Map<Expression, Expression> map = new HashMap<>();
+            for (GbyVariableExpressionPair gbyKeyPair : selectBlock.getGroupbyClause().getGbyPairList()) {
+                Expression gbyKeyExpr = gbyKeyPair.getExpr();
+                if (gbyKeyExpr.getKind() != Kind.VARIABLE_EXPRESSION) {
+                    map.put(gbyKeyExpr, gbyKeyPair.getVar());
+                }
+            }
+
+            // Creates a substitution visitor.
+            ExpressionSubstitutionEnvironment env =
+                    new ExpressionSubstitutionEnvironment(map, SqlppVariableUtil::getFreeVariables);
+            SubstituteGroupbyExpressionVisitor visitor = new SubstituteGroupbyExpressionVisitor();
+
+            // Rewrites having/select/order-by/limit clauses.
+            if (selectBlock.hasHavingClause()) {
+                selectBlock.getHavingClause().accept(visitor, env);
+            }
+            selectBlock.getSelectClause().accept(visitor, env);
+            SelectExpression selectExpression = (SelectExpression) arg;
+            if (selectExpression.hasOrderby()) {
+                selectExpression.getOrderbyClause().accept(visitor, env);
+            }
+            if (selectExpression.hasLimit()) {
+                selectExpression.getLimitClause().accept(visitor, env);
+            }
+        }
+        return super.visit(selectBlock, arg);
+    }
+
+}
+
+class SubstituteGroupbyExpressionVisitor extends SqlppSubstituteExpressionsVisitor {
+
+    @Override
+    public Expression visit(CallExpr callExpr, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        FunctionSignature signature = callExpr.getFunctionSignature();
+        if (FunctionMapUtil.isSql92AggregateFunction(signature)) {
+            return callExpr;
+        } else {
+            return super.visit(callExpr, env);
+        }
+    }
+}
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/struct/SetOperationInput.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/struct/SetOperationInput.java
index da89709..55245fe 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/struct/SetOperationInput.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/struct/SetOperationInput.java
@@ -23,6 +23,7 @@
 import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
 import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
 import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class SetOperationInput {
 
@@ -57,4 +58,26 @@
             return ((ISqlppVisitor<R, T>) visitor).visit(subquery, arg);
         }
     }
+
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(selectBlock, subquery);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof SetOperationInput)) {
+            return false;
+        }
+        SetOperationInput target = (SetOperationInput) object;
+        return ObjectUtils.equals(selectBlock, target.selectBlock) && ObjectUtils.equals(subquery, target.subquery);
+    }
+
+    @Override
+    public String toString() {
+        return selectBlock.toString();
+    }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/struct/SetOperationRight.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/struct/SetOperationRight.java
index 5e061e0..ade4119 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/struct/SetOperationRight.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/struct/SetOperationRight.java
@@ -19,6 +19,7 @@
 package org.apache.asterix.lang.sqlpp.struct;
 
 import org.apache.asterix.lang.sqlpp.optype.SetOpType;
+import org.apache.commons.lang3.ObjectUtils;
 
 public class SetOperationRight {
 
@@ -44,4 +45,30 @@
         return setOperationRightInput;
     }
 
+    @Override
+    public int hashCode() {
+        return ObjectUtils.hashCodeMulti(opType, setOperationRightInput, setSemantics);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof SetOperationRight)) {
+            return false;
+        }
+        SetOperationRight target = (SetOperationRight) object;
+        return ObjectUtils.equals(opType, target.opType)
+                && ObjectUtils.equals(setOperationRightInput, target.setOperationRightInput)
+                && setSemantics == target.setSemantics;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(opType + " " + (setSemantics ? "" : " all "));
+        sb.append(setOperationRightInput);
+        return sb.toString();
+    }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/ExpressionToVariableUtil.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/ExpressionToVariableUtil.java
index 8817d76..93e1828 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/ExpressionToVariableUtil.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/ExpressionToVariableUtil.java
@@ -19,19 +19,23 @@
 
 package org.apache.asterix.lang.sqlpp.util;
 
+import org.apache.asterix.common.exceptions.AsterixException;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.Expression.Kind;
 import org.apache.asterix.lang.common.expression.FieldAccessor;
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.struct.VarIdentifier;
 import org.apache.asterix.lang.sqlpp.parser.ParseException;
+import org.apache.log4j.Logger;
 
 public class ExpressionToVariableUtil {
 
+    private static final Logger LOGGER = Logger.getLogger(ExpressionToVariableUtil.class);
+
     private ExpressionToVariableUtil() {
     }
 
-    public static String getGeneratedIdentifier(Expression expr) throws ParseException {
+    private static String getGeneratedIdentifier(Expression expr) throws ParseException {
         if (expr.getKind() == Kind.VARIABLE_EXPRESSION) {
             VariableExpr bindingVarExpr = (VariableExpr) expr;
             return bindingVarExpr.getVar().getValue();
@@ -39,15 +43,62 @@
             FieldAccessor fa = (FieldAccessor) expr;
             return SqlppVariableUtil.toInternalVariableName(fa.getIdent().getValue());
         } else {
-            throw new ParseException("Need an alias for the enclosed " + expr.getKind() + " expression.");
+            try {
+                throw new ParseException(
+                        "Need an alias for the enclosed expression:\n" + SqlppFormatPrintUtil.toString(expr));
+            } catch (AsterixException e) {
+                LOGGER.error(e.getLocalizedMessage(), e);
+                throw new ParseException(e.getLocalizedMessage());
+            }
         }
     }
 
-    public static VariableExpr getGeneratedVariable(Expression expr) throws ParseException {
-        VarIdentifier var = new VarIdentifier(getGeneratedIdentifier(expr));
-        VariableExpr varExpr = new VariableExpr();
-        varExpr.setVar(var);
-        return varExpr;
+    /**
+     * Generates an identifier according to an expression.
+     *
+     * @param expr
+     *            the input expression.
+     * @param raiseError,
+     *            if it is not possible to generate an identifier from the input expression,
+     *            to raise the error if true, and to return a null if false.
+     * @return the generated identifier.
+     * @throws ParseException
+     */
+    public static String getGeneratedIdentifier(Expression expr, boolean raiseError) throws ParseException {
+        try {
+            return getGeneratedIdentifier(expr);
+        } catch (ParseException e) {
+            if (raiseError) {
+                throw e;
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Generates a variable according to an expression.
+     *
+     * @param expr
+     *            the input expression.
+     * @param raiseError,
+     *            if it is not possible to generate a variable from the input expression,
+     *            to raise the error if true, and to return a null if false.
+     * @return the generated variable.
+     * @throws ParseException
+     */
+    public static VariableExpr getGeneratedVariable(Expression expr, boolean raiseError) throws ParseException {
+        try {
+            String varName = getGeneratedIdentifier(expr);
+            VarIdentifier var = new VarIdentifier(varName);
+            VariableExpr varExpr = new VariableExpr();
+            varExpr.setVar(var);
+            return varExpr;
+        } catch (ParseException e) {
+            if (raiseError) {
+                throw e;
+            }
+            return null;
+        }
     }
 
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppAstPrintUtil.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppAstPrintUtil.java
index 5888194..f700439 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppAstPrintUtil.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppAstPrintUtil.java
@@ -32,7 +32,11 @@
 
 public class SqlppAstPrintUtil {
 
-    private static final IAstPrintVisitorFactory astPrintVisitor = new SqlppAstPrintVisitorFactory();
+    private static final IAstPrintVisitorFactory astPrintVisitorFactory = new SqlppAstPrintVisitorFactory();
+
+    private SqlppAstPrintUtil() {
+
+    }
 
     /**
      * Prints the AST (abstract syntax tree) of an ILangExpression.
@@ -44,7 +48,7 @@
      * @throws AsterixException
      */
     public static void print(ILangExpression expr, PrintWriter output) throws AsterixException {
-        QueryPrintVisitor visitor = astPrintVisitor.createLangVisitor(output);
+        QueryPrintVisitor visitor = astPrintVisitorFactory.createLangVisitor(output);
         expr.accept(visitor, 0);
         output.flush();
     }
@@ -59,7 +63,7 @@
      * @throws AsterixException
      */
     public static void print(List<Statement> statements, PrintWriter output) throws AsterixException {
-        QueryPrintVisitor visitor = astPrintVisitor.createLangVisitor(output);
+        QueryPrintVisitor visitor = astPrintVisitorFactory.createLangVisitor(output);
         for (Statement statement : statements) {
             statement.accept(visitor, 0);
         }
@@ -73,7 +77,7 @@
      * @throws AsterixException
      */
     public static String toString(ILangExpression expr) throws AsterixException {
-        List<ILangExpression> exprs = new ArrayList<ILangExpression>();
+        List<ILangExpression> exprs = new ArrayList<>();
         exprs.add(expr);
         return toString(exprs);
     }
@@ -87,7 +91,7 @@
     public static String toString(List<ILangExpression> exprs) throws AsterixException {
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
         PrintWriter output = new PrintWriter(bos);
-        QueryPrintVisitor visitor = astPrintVisitor.createLangVisitor(output);
+        QueryPrintVisitor visitor = astPrintVisitorFactory.createLangVisitor(output);
         for (ILangExpression expr : exprs) {
             expr.accept(visitor, 0);
         }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppFormatPrintUtil.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppFormatPrintUtil.java
index 41760f5..ff0a8e1 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppFormatPrintUtil.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppFormatPrintUtil.java
@@ -30,6 +30,10 @@
 
 public class SqlppFormatPrintUtil {
 
+    private SqlppFormatPrintUtil() {
+
+    }
+
     /**
      * Prints the formatted output of an ILangExpression.
      *
@@ -67,7 +71,7 @@
      * @throws AsterixException
      */
     public static String toString(ILangExpression expr) throws AsterixException {
-        List<ILangExpression> exprs = new ArrayList<ILangExpression>();
+        List<ILangExpression> exprs = new ArrayList<>();
         exprs.add(expr);
         return toString(exprs);
     }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppRewriteUtil.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppRewriteUtil.java
index 122bde3..4a8c69d 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppRewriteUtil.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppRewriteUtil.java
@@ -40,8 +40,8 @@
     public static Expression rewriteExpressionUsingGroupVariable(VariableExpr groupVar,
             Collection<VariableExpr> targetVarList, Collection<VariableExpr> allVisableVars, ILangExpression expr,
             LangRewritingContext context) throws AsterixException {
-        SqlppGroupBySugarVisitor visitor = new SqlppGroupBySugarVisitor(context, groupVar, targetVarList,
-                allVisableVars);
+        SqlppGroupBySugarVisitor visitor =
+                new SqlppGroupBySugarVisitor(context, groupVar, targetVarList, allVisableVars);
         return expr.accept(visitor, null);
     }
 
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/DeepCopyVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/DeepCopyVisitor.java
index 559183e..6c48c64 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/DeepCopyVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/DeepCopyVisitor.java
@@ -139,7 +139,7 @@
         GroupbyClause gbyClause = null;
         List<LetClause> gbyLetClauses = new ArrayList<>();
         HavingClause havingClause = null;
-        SelectClause selectCluase = null;
+        SelectClause selectCluase;
         // Traverses the select block in the order of "from", "let"s, "where",
         // "group by", "let"s, "having" and "select".
         if (selectBlock.hasFromClause()) {
@@ -201,7 +201,7 @@
     @Override
     public SelectSetOperation visit(SelectSetOperation selectSetOperation, Void arg) throws AsterixException {
         SetOperationInput leftInput = selectSetOperation.getLeftInput();
-        SetOperationInput newLeftInput = null;
+        SetOperationInput newLeftInput;
         if (leftInput.selectBlock()) {
             newLeftInput = new SetOperationInput((SelectBlock) leftInput.accept(this, arg), null);
         } else {
@@ -209,12 +209,12 @@
         }
         List<SetOperationRight> rightInputs = new ArrayList<>();
         for (SetOperationRight right : selectSetOperation.getRightInputs()) {
-            SetOperationInput newRightInput = null;
+            SetOperationInput newRightInput;
             SetOperationInput setOpRightInput = right.getSetOperationRightInput();
             if (setOpRightInput.selectBlock()) {
-                newRightInput = new SetOperationInput((SelectBlock) leftInput.accept(this, arg), null);
+                newRightInput = new SetOperationInput((SelectBlock) setOpRightInput.accept(this, arg), null);
             } else {
-                newRightInput = new SetOperationInput(null, (SelectExpression) leftInput.accept(this, arg));
+                newRightInput = new SetOperationInput(null, (SelectExpression) setOpRightInput.accept(this, arg));
             }
             rightInputs.add(new SetOperationRight(right.getSetOpType(), right.isSetSemantics(), newRightInput));
         }
@@ -244,7 +244,7 @@
 
     @Override
     public OrderbyClause visit(OrderbyClause oc, Void arg) throws AsterixException {
-        List<Expression> newOrderbyList = new ArrayList<Expression>();
+        List<Expression> newOrderbyList = new ArrayList<>();
         for (Expression orderExpr : oc.getOrderbyList()) {
             newOrderbyList.add((Expression) orderExpr.accept(this, arg));
         }
@@ -259,11 +259,13 @@
         VariableExpr groupVarExpr = null;
         List<Pair<Expression, Identifier>> groupFieldList = new ArrayList<>();
         for (GbyVariableExpressionPair gbyVarExpr : gc.getGbyPairList()) {
-            gbyPairList.add(new GbyVariableExpressionPair((VariableExpr) gbyVarExpr.getVar().accept(this, arg),
+            VariableExpr var = gbyVarExpr.getVar();
+            gbyPairList.add(new GbyVariableExpressionPair(var == null ? null : (VariableExpr) var.accept(this, arg),
                     (Expression) gbyVarExpr.getExpr().accept(this, arg)));
         }
         for (GbyVariableExpressionPair gbyVarExpr : gc.getDecorPairList()) {
-            decorPairList.add(new GbyVariableExpressionPair((VariableExpr) gbyVarExpr.getVar().accept(this, arg),
+            VariableExpr var = gbyVarExpr.getVar();
+            decorPairList.add(new GbyVariableExpressionPair(var == null ? null : (VariableExpr) var.accept(this, arg),
                     (Expression) gbyVarExpr.getExpr().accept(this, arg)));
         }
         for (VariableExpr withVar : gc.getWithVarList()) {
@@ -295,7 +297,7 @@
     @Override
     public SelectExpression visit(SelectExpression selectExpression, Void arg) throws AsterixException {
         List<LetClause> lets = new ArrayList<>();
-        SelectSetOperation select = null;
+        SelectSetOperation select;
         OrderbyClause orderby = null;
         LimitClause limit = null;
 
@@ -332,7 +334,7 @@
 
     @Override
     public ListConstructor visit(ListConstructor lc, Void arg) throws AsterixException {
-        List<Expression> newExprList = new ArrayList<Expression>();
+        List<Expression> newExprList = new ArrayList<>();
         for (Expression expr : lc.getExprList()) {
             newExprList.add((Expression) expr.accept(this, arg));
         }
@@ -352,7 +354,7 @@
 
     @Override
     public OperatorExpr visit(OperatorExpr operatorExpr, Void arg) throws AsterixException {
-        List<Expression> newExprList = new ArrayList<Expression>();
+        List<Expression> newExprList = new ArrayList<>();
         for (Expression expr : operatorExpr.getExprList()) {
             newExprList.add((Expression) expr.accept(this, arg));
         }
@@ -382,7 +384,7 @@
 
     @Override
     public CallExpr visit(CallExpr callExpr, Void arg) throws AsterixException {
-        List<Expression> newExprList = new ArrayList<Expression>();
+        List<Expression> newExprList = new ArrayList<>();
         for (Expression expr : callExpr.getExprList()) {
             newExprList.add((Expression) expr.accept(this, arg));
         }
@@ -391,8 +393,8 @@
 
     @Override
     public VariableExpr visit(VariableExpr varExpr, Void arg) throws AsterixException {
-        VariableExpr clonedVar = new VariableExpr(
-                new VarIdentifier(varExpr.getVar().getValue(), varExpr.getVar().getId()));
+        VariableExpr clonedVar =
+                new VariableExpr(new VarIdentifier(varExpr.getVar().getValue(), varExpr.getVar().getId()));
         clonedVar.setIsNewVar(varExpr.getIsNewVar());
         clonedVar.setNamedValueAccess(varExpr.namedValueAccess());
         return clonedVar;
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppCloneAndSubstituteVariablesVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppCloneAndSubstituteVariablesVisitor.java
index 8ec2b04..445c835 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppCloneAndSubstituteVariablesVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppCloneAndSubstituteVariablesVisitor.java
@@ -68,7 +68,7 @@
     public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(FromClause fromClause,
             VariableSubstitutionEnvironment env) throws AsterixException {
         VariableSubstitutionEnvironment currentEnv = new VariableSubstitutionEnvironment(env);
-        List<FromTerm> newFromTerms = new ArrayList<FromTerm>();
+        List<FromTerm> newFromTerms = new ArrayList<>();
         for (FromTerm fromTerm : fromClause.getFromTerms()) {
             Pair<ILangExpression, VariableSubstitutionEnvironment> p = fromTerm.accept(this, currentEnv);
             newFromTerms.add((FromTerm) p.first);
@@ -76,7 +76,7 @@
             // therefore we propagate the substitution environment.
             currentEnv = p.second;
         }
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(new FromClause(newFromTerms), currentEnv);
+        return new Pair<>(new FromClause(newFromTerms), currentEnv);
     }
 
     @Override
@@ -87,7 +87,7 @@
         VariableExpr newLeftPosVar = fromTerm.hasPositionalVariable()
                 ? generateNewVariable(context, fromTerm.getPositionalVariable()) : null;
         Expression newLeftExpr = (Expression) visitUnnesBindingExpression(fromTerm.getLeftExpression(), env).first;
-        List<AbstractBinaryCorrelateClause> newCorrelateClauses = new ArrayList<AbstractBinaryCorrelateClause>();
+        List<AbstractBinaryCorrelateClause> newCorrelateClauses = new ArrayList<>();
 
         VariableSubstitutionEnvironment currentEnv = new VariableSubstitutionEnvironment(env);
         currentEnv.removeSubstitution(newLeftVar);
@@ -108,8 +108,7 @@
                 newCorrelateClauses.add((AbstractBinaryCorrelateClause) correlateClause.accept(this, env).first);
             }
         }
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(
-                new FromTerm(newLeftExpr, newLeftVar, newLeftPosVar, newCorrelateClauses), currentEnv);
+        return new Pair<>(new FromTerm(newLeftExpr, newLeftVar, newLeftPosVar, newCorrelateClauses), currentEnv);
     }
 
     @Override
@@ -132,9 +131,9 @@
         // The condition can refer to the newRightVar and newRightPosVar.
         Expression conditionExpr = (Expression) joinClause.getConditionExpression().accept(this, currentEnv).first;
 
-        JoinClause newJoinClause = new JoinClause(joinClause.getJoinType(), newRightExpr, newRightVar, newRightPosVar,
-                conditionExpr);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(newJoinClause, currentEnv);
+        JoinClause newJoinClause =
+                new JoinClause(joinClause.getJoinType(), newRightExpr, newRightVar, newRightPosVar, conditionExpr);
+        return new Pair<>(newJoinClause, currentEnv);
     }
 
     @Override
@@ -157,9 +156,9 @@
         // The condition can refer to the newRightVar and newRightPosVar.
         Expression conditionExpr = (Expression) nestClause.getConditionExpression().accept(this, currentEnv).first;
 
-        NestClause newJoinClause = new NestClause(nestClause.getJoinType(), rightExpr, newRightVar, newRightPosVar,
-                conditionExpr);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(newJoinClause, currentEnv);
+        NestClause newJoinClause =
+                new NestClause(nestClause.getJoinType(), rightExpr, newRightVar, newRightPosVar, conditionExpr);
+        return new Pair<>(newJoinClause, currentEnv);
     }
 
     @Override
@@ -180,9 +179,9 @@
             currentEnv.removeSubstitution(newRightPosVar);
         }
         // The condition can refer to the newRightVar and newRightPosVar.
-        UnnestClause newJoinClause = new UnnestClause(unnestClause.getJoinType(), rightExpr, newRightVar,
-                newRightPosVar);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(newJoinClause, currentEnv);
+        UnnestClause newJoinClause =
+                new UnnestClause(unnestClause.getJoinType(), rightExpr, newRightVar, newRightPosVar);
+        return new Pair<>(newJoinClause, currentEnv);
     }
 
     @Override
@@ -190,20 +189,20 @@
             VariableSubstitutionEnvironment env) throws AsterixException {
         Projection newProjection = new Projection((Expression) projection.getExpression().accept(this, env).first,
                 projection.getName(), projection.star(), projection.exprStar());
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(newProjection, env);
+        return new Pair<>(newProjection, env);
     }
 
     @Override
     public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(SelectBlock selectBlock,
             VariableSubstitutionEnvironment env) throws AsterixException {
         Pair<ILangExpression, VariableSubstitutionEnvironment> newFrom = null;
-        Pair<ILangExpression, VariableSubstitutionEnvironment> newLet = null;
+        Pair<ILangExpression, VariableSubstitutionEnvironment> newLet;
         Pair<ILangExpression, VariableSubstitutionEnvironment> newWhere = null;
         Pair<ILangExpression, VariableSubstitutionEnvironment> newGroupby = null;
         Pair<ILangExpression, VariableSubstitutionEnvironment> newHaving = null;
-        Pair<ILangExpression, VariableSubstitutionEnvironment> newSelect = null;
-        List<LetClause> newLetClauses = new ArrayList<LetClause>();
-        List<LetClause> newLetClausesAfterGby = new ArrayList<LetClause>();
+        Pair<ILangExpression, VariableSubstitutionEnvironment> newSelect;
+        List<LetClause> newLetClauses = new ArrayList<>();
+        List<LetClause> newLetClausesAfterGby = new ArrayList<>();
         VariableSubstitutionEnvironment currentEnv = new VariableSubstitutionEnvironment(env);
 
         if (selectBlock.hasFromClause()) {
@@ -243,12 +242,12 @@
 
         newSelect = selectBlock.getSelectClause().accept(this, currentEnv);
         currentEnv = newSelect.second;
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(
-                new SelectBlock((SelectClause) newSelect.first, newFrom == null ? null : (FromClause) newFrom.first,
-                        newLetClauses, newWhere == null ? null : (WhereClause) newWhere.first,
-                        newGroupby == null ? null : (GroupbyClause) newGroupby.first, newLetClausesAfterGby,
-                        newHaving == null ? null : (HavingClause) newHaving.first),
-                currentEnv);
+        FromClause fromClause = newFrom == null ? null : (FromClause) newFrom.first;
+        WhereClause whereClause = newWhere == null ? null : (WhereClause) newWhere.first;
+        GroupbyClause groupbyClause = newGroupby == null ? null : (GroupbyClause) newGroupby.first;
+        HavingClause havingClause = newHaving == null ? null : (HavingClause) newHaving.first;
+        return new Pair<>(new SelectBlock((SelectClause) newSelect.first, fromClause, newLetClauses, whereClause,
+                groupbyClause, newLetClausesAfterGby, havingClause), currentEnv);
     }
 
     @Override
@@ -256,42 +255,41 @@
             VariableSubstitutionEnvironment env) throws AsterixException {
         boolean distinct = selectClause.distinct();
         if (selectClause.selectElement()) {
-            Pair<ILangExpression, VariableSubstitutionEnvironment> newSelectElement = selectClause.getSelectElement()
-                    .accept(this, env);
-            return new Pair<ILangExpression, VariableSubstitutionEnvironment>(
-                    new SelectClause((SelectElement) newSelectElement.first, null, distinct), newSelectElement.second);
+            Pair<ILangExpression, VariableSubstitutionEnvironment> newSelectElement =
+                    selectClause.getSelectElement().accept(this, env);
+            return new Pair<>(new SelectClause((SelectElement) newSelectElement.first, null, distinct),
+                    newSelectElement.second);
         } else {
-            Pair<ILangExpression, VariableSubstitutionEnvironment> newSelectRegular = selectClause.getSelectRegular()
-                    .accept(this, env);
-            return new Pair<ILangExpression, VariableSubstitutionEnvironment>(
-                    new SelectClause(null, (SelectRegular) newSelectRegular.first, distinct), newSelectRegular.second);
+            Pair<ILangExpression, VariableSubstitutionEnvironment> newSelectRegular =
+                    selectClause.getSelectRegular().accept(this, env);
+            return new Pair<>(new SelectClause(null, (SelectRegular) newSelectRegular.first, distinct),
+                    newSelectRegular.second);
         }
     }
 
     @Override
     public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(SelectElement selectElement,
             VariableSubstitutionEnvironment env) throws AsterixException {
-        Pair<ILangExpression, VariableSubstitutionEnvironment> newExpr = selectElement.getExpression().accept(this,
-                env);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(new SelectElement((Expression) newExpr.first),
-                newExpr.second);
+        Pair<ILangExpression, VariableSubstitutionEnvironment> newExpr =
+                selectElement.getExpression().accept(this, env);
+        return new Pair<>(new SelectElement((Expression) newExpr.first), newExpr.second);
     }
 
     @Override
     public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(SelectRegular selectRegular,
             VariableSubstitutionEnvironment env) throws AsterixException {
-        List<Projection> newProjections = new ArrayList<Projection>();
+        List<Projection> newProjections = new ArrayList<>();
         for (Projection projection : selectRegular.getProjections()) {
             newProjections.add((Projection) projection.accept(this, env).first);
         }
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(new SelectRegular(newProjections), env);
+        return new Pair<>(new SelectRegular(newProjections), env);
     }
 
     @Override
     public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(SelectSetOperation selectSetOperation,
             VariableSubstitutionEnvironment env) throws AsterixException {
         SetOperationInput leftInput = selectSetOperation.getLeftInput();
-        SetOperationInput newLeftInput = null;
+        SetOperationInput newLeftInput;
 
         // Sets the left input.
         if (leftInput.selectBlock()) {
@@ -303,13 +301,13 @@
         }
 
         // Sets the right input
-        List<SetOperationRight> newRightInputs = new ArrayList<SetOperationRight>();
+        List<SetOperationRight> newRightInputs = new ArrayList<>();
         for (SetOperationRight right : selectSetOperation.getRightInputs()) {
-            SetOperationInput newRightInput = null;
+            SetOperationInput newRightInput;
             SetOperationInput rightInput = right.getSetOperationRightInput();
             if (rightInput.selectBlock()) {
-                Pair<ILangExpression, VariableSubstitutionEnvironment> p = rightInput.getSelectBlock().accept(this,
-                        env);
+                Pair<ILangExpression, VariableSubstitutionEnvironment> p =
+                        rightInput.getSelectBlock().accept(this, env);
                 newRightInput = new SetOperationInput((SelectBlock) p.first, null);
             } else {
                 Pair<ILangExpression, VariableSubstitutionEnvironment> p = rightInput.getSubquery().accept(this, env);
@@ -318,20 +316,20 @@
             newRightInputs.add(new SetOperationRight(right.getSetOpType(), right.isSetSemantics(), newRightInput));
         }
         SelectSetOperation newSelectSetOperation = new SelectSetOperation(newLeftInput, newRightInputs);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(newSelectSetOperation, env);
+        return new Pair<>(newSelectSetOperation, env);
     }
 
     @Override
     public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(SelectExpression selectExpression,
             VariableSubstitutionEnvironment env) throws AsterixException {
         boolean subquery = selectExpression.isSubquery();
-        List<LetClause> newLetList = new ArrayList<LetClause>();
-        SelectSetOperation newSelectSetOperation = null;
+        List<LetClause> newLetList = new ArrayList<>();
+        SelectSetOperation newSelectSetOperation;
         OrderbyClause newOrderbyClause = null;
         LimitClause newLimitClause = null;
 
         VariableSubstitutionEnvironment currentEnv = env;
-        Pair<ILangExpression, VariableSubstitutionEnvironment> p = null;
+        Pair<ILangExpression, VariableSubstitutionEnvironment> p;
         if (selectExpression.hasLetClauses()) {
             for (LetClause letClause : selectExpression.getLetList()) {
                 p = letClause.accept(this, currentEnv);
@@ -355,7 +353,7 @@
             newLimitClause = (LimitClause) p.first;
             currentEnv = p.second;
         }
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(
+        return new Pair<>(
                 new SelectExpression(newLetList, newSelectSetOperation, newOrderbyClause, newLimitClause, subquery),
                 currentEnv);
     }
@@ -365,7 +363,7 @@
             VariableSubstitutionEnvironment env) throws AsterixException {
         Pair<ILangExpression, VariableSubstitutionEnvironment> p = havingClause.getFilterExpression().accept(this, env);
         HavingClause newHavingClause = new HavingClause((Expression) p.first);
-        return new Pair<ILangExpression, VariableSubstitutionEnvironment>(newHavingClause, p.second);
+        return new Pair<>(newHavingClause, p.second);
     }
 
     @Override
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppFormatPrintVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppFormatPrintVisitor.java
index e03d711..2dca532 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppFormatPrintVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppFormatPrintVisitor.java
@@ -127,7 +127,10 @@
     @Override
     public Void visit(Projection projection, Integer step) throws AsterixException {
         projection.getExpression().accept(this, step);
-        out.print(" as " + projection.getName());
+        String name = projection.getName();
+        if (name != null) {
+            out.print(" as " + name);
+        }
         return null;
     }
 
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppSubstituteExpressionsVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppSubstituteExpressionsVisitor.java
new file mode 100644
index 0000000..af160bf
--- /dev/null
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppSubstituteExpressionsVisitor.java
@@ -0,0 +1,272 @@
+/*
+ * 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.asterix.lang.sqlpp.visitor;
+
+import java.util.List;
+
+import org.apache.asterix.common.exceptions.AsterixException;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.clause.GroupbyClause;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.expression.GbyVariableExpressionPair;
+import org.apache.asterix.lang.common.rewrites.ExpressionSubstitutionEnvironment;
+import org.apache.asterix.lang.common.visitor.SubstituteExpressionVisitor;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.FromClause;
+import org.apache.asterix.lang.sqlpp.clause.FromTerm;
+import org.apache.asterix.lang.sqlpp.clause.HavingClause;
+import org.apache.asterix.lang.sqlpp.clause.JoinClause;
+import org.apache.asterix.lang.sqlpp.clause.NestClause;
+import org.apache.asterix.lang.sqlpp.clause.Projection;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.clause.SelectClause;
+import org.apache.asterix.lang.sqlpp.clause.SelectElement;
+import org.apache.asterix.lang.sqlpp.clause.SelectRegular;
+import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
+import org.apache.asterix.lang.sqlpp.clause.UnnestClause;
+import org.apache.asterix.lang.sqlpp.expression.IndependentSubquery;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.struct.SetOperationRight;
+import org.apache.asterix.lang.sqlpp.util.SqlppRewriteUtil;
+import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
+
+public class SqlppSubstituteExpressionsVisitor extends SubstituteExpressionVisitor
+        implements ISqlppVisitor<Expression, ExpressionSubstitutionEnvironment> {
+
+    public SqlppSubstituteExpressionsVisitor() {
+        super(SqlppRewriteUtil::deepCopy);
+    }
+
+    @Override
+    public Expression visit(GroupbyClause gc, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        for (GbyVariableExpressionPair pair : gc.getGbyPairList()) {
+            pair.setExpr(pair.getExpr().accept(this, env));
+        }
+        // Forces from binding variables to exit their scopes, i.e.,
+        // one can still replace from binding variables.
+        env.pop();
+        for (GbyVariableExpressionPair pair : gc.getGbyPairList()) {
+            env.disableVariable(pair.getVar());
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(FromClause fromClause, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        // Marks the states before the from clause, and a consequent
+        // group-by clause will reset env to the state.
+        env.mark();
+        for (FromTerm fromTerm : fromClause.getFromTerms()) {
+            fromTerm.accept(this, env);
+            // From terms are correlated and thus we mask binding variables after
+            // visiting each individual from term.
+            env.disableVariable(fromTerm.getLeftVariable());
+            if (fromTerm.hasPositionalVariable()) {
+                env.disableVariable(fromTerm.getLeftVariable());
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(FromTerm fromTerm, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        fromTerm.setLeftExpression(fromTerm.getLeftExpression().accept(this, env));
+        if (fromTerm.hasCorrelateClauses()) {
+            for (AbstractBinaryCorrelateClause correlateClause : fromTerm.getCorrelateClauses()) {
+                correlateClause.accept(this, env);
+            }
+            // correlate clauses are independent and thus we mask their binding variables
+            // after visiting them.
+            for (AbstractBinaryCorrelateClause correlateClause : fromTerm.getCorrelateClauses()) {
+                env.disableVariable(correlateClause.getRightVariable());
+                if (correlateClause.hasPositionalVariable()) {
+                    env.disableVariable(correlateClause.getPositionalVariable());
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(JoinClause joinClause, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        joinClause.setRightExpression(joinClause.getRightExpression().accept(this, env));
+        // Condition expressions can see the join binding variables, thus we have to mask them for replacement.
+        env.disableVariable(joinClause.getRightVariable());
+        if (joinClause.hasPositionalVariable()) {
+            env.disableVariable(joinClause.getPositionalVariable());
+        }
+        joinClause.setConditionExpression(joinClause.getConditionExpression().accept(this, env));
+        // Re-enable them.
+        env.enableVariable(joinClause.getRightVariable());
+        if (joinClause.hasPositionalVariable()) {
+            env.enableVariable(joinClause.getPositionalVariable());
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(NestClause nestClause, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        nestClause.setRightExpression(nestClause.getRightExpression().accept(this, env));
+        // Condition expressions can see the join binding variables, thus we have to mask them for replacement.
+        env.disableVariable(nestClause.getRightVariable());
+        if (nestClause.hasPositionalVariable()) {
+            env.disableVariable(nestClause.getPositionalVariable());
+        }
+        nestClause.setConditionExpression(nestClause.getConditionExpression().accept(this, env));
+        // Re-enable them.
+        env.enableVariable(nestClause.getRightVariable());
+        if (nestClause.hasPositionalVariable()) {
+            env.enableVariable(nestClause.getPositionalVariable());
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(Projection projection, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        if (!projection.star()) {
+            projection.setExpression(projection.getExpression().accept(this, env));
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(SelectBlock selectBlock, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        // Traverses the select block in the order of "from", "let"s, "where",
+        // "group by", "let"s, "having" and "select".
+        if (selectBlock.hasFromClause()) {
+            selectBlock.getFromClause().accept(this, env);
+        }
+        if (selectBlock.hasLetClauses()) {
+            List<LetClause> letList = selectBlock.getLetList();
+            for (LetClause letClause : letList) {
+                letClause.accept(this, env);
+            }
+        }
+        if (selectBlock.hasWhereClause()) {
+            selectBlock.getWhereClause().accept(this, env);
+        }
+        if (selectBlock.hasGroupbyClause()) {
+            selectBlock.getGroupbyClause().accept(this, env);
+        }
+        if (selectBlock.hasLetClausesAfterGroupby()) {
+            List<LetClause> letListAfterGby = selectBlock.getLetListAfterGroupby();
+            for (LetClause letClauseAfterGby : letListAfterGby) {
+                letClauseAfterGby.accept(this, env);
+            }
+        }
+        if (selectBlock.hasHavingClause()) {
+            selectBlock.getHavingClause().accept(this, env);
+        }
+        selectBlock.getSelectClause().accept(this, env);
+        return null;
+    }
+
+    @Override
+    public Expression visit(SelectClause selectClause, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        if (selectClause.selectElement()) {
+            selectClause.getSelectElement().accept(this, env);
+        } else {
+            selectClause.getSelectRegular().accept(this, env);
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(SelectElement selectElement, ExpressionSubstitutionEnvironment env)
+            throws AsterixException {
+        selectElement.setExpression(selectElement.getExpression().accept(this, env));
+        return null;
+    }
+
+    @Override
+    public Expression visit(SelectRegular selectRegular, ExpressionSubstitutionEnvironment env)
+            throws AsterixException {
+        for (Projection projection : selectRegular.getProjections()) {
+            projection.accept(this, env);
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(SelectSetOperation selectSetOperation, ExpressionSubstitutionEnvironment env)
+            throws AsterixException {
+        selectSetOperation.getLeftInput().accept(this, env);
+        for (SetOperationRight right : selectSetOperation.getRightInputs()) {
+            right.getSetOperationRightInput().accept(this, env);
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(SelectExpression selectExpression, ExpressionSubstitutionEnvironment env)
+            throws AsterixException {
+        // Backups the current states of env and the end of this method will reset to the returned depth.
+        int depth = env.mark();
+        // visit let list
+        if (selectExpression.hasLetClauses()) {
+            for (LetClause letClause : selectExpression.getLetList()) {
+                letClause.accept(this, env);
+            }
+        }
+
+        // visit the main select.
+        selectExpression.getSelectSetOperation().accept(this, env);
+
+        // visit order by.
+        if (selectExpression.hasOrderby()) {
+            selectExpression.getOrderbyClause().accept(this, env);
+        }
+
+        // visit limit
+        if (selectExpression.hasLimit()) {
+            selectExpression.getLimitClause().accept(this, env);
+        }
+
+        // re-enable the replacements all of all binding variables in the current scope.
+        if (selectExpression.hasLetClauses()) {
+            for (LetClause letClause : selectExpression.getLetList()) {
+                env.enableVariable(letClause.getVarExpr());
+            }
+        }
+        // Restores the states of env to be the same as the beginning .
+        env.reset(depth);
+        return selectExpression;
+    }
+
+    @Override
+    public Expression visit(UnnestClause unnestClause, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        unnestClause.setRightExpression(unnestClause.getRightExpression().accept(this, env));
+        return null;
+    }
+
+    @Override
+    public Expression visit(HavingClause havingClause, ExpressionSubstitutionEnvironment env) throws AsterixException {
+        havingClause.setFilterExpression(havingClause.getFilterExpression().accept(this, env));
+        return null;
+    }
+
+    @Override
+    public Expression visit(IndependentSubquery independentSubquery, ExpressionSubstitutionEnvironment env)
+            throws AsterixException {
+        independentSubquery.setExpr(independentSubquery.getExpr().accept(this, env));
+        return null;
+    }
+
+}
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppExpressionScopingVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppExpressionScopingVisitor.java
index dfb15b0..4fbb697 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppExpressionScopingVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppExpressionScopingVisitor.java
@@ -60,8 +60,8 @@
 
 public class AbstractSqlppExpressionScopingVisitor extends AbstractSqlppSimpleExpressionVisitor {
 
-    protected final FunctionSignature resolveFunction = new FunctionSignature(MetadataConstants.METADATA_DATAVERSE_NAME,
-            "resolve", FunctionIdentifier.VARARGS);
+    protected final FunctionSignature resolveFunction =
+            new FunctionSignature(MetadataConstants.METADATA_DATAVERSE_NAME, "resolve", FunctionIdentifier.VARARGS);
     protected final ScopeChecker scopeChecker = new ScopeChecker();
     protected final LangRewritingContext context;
 
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppSimpleExpressionVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppSimpleExpressionVisitor.java
index 2098156..9fd5a6e 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppSimpleExpressionVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppSimpleExpressionVisitor.java
@@ -250,9 +250,7 @@
 
         // visit order by
         if (selectExpression.hasOrderby()) {
-            for (Expression orderExpr : selectExpression.getOrderbyClause().getOrderbyList()) {
-                orderExpr.accept(this, selectExpression);
-            }
+            selectExpression.getOrderbyClause().accept(this, selectExpression);
         }
 
         // visit limit
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index dff69ab..06b067e 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -2376,7 +2376,10 @@
   )
   {
     if(!star && name == null){
-       name = SqlppVariableUtil.toUserDefinedName(ExpressionToVariableUtil.getGeneratedIdentifier(expr));
+       String generatedColumnIdentifier = ExpressionToVariableUtil.getGeneratedIdentifier(expr, false);
+       if(generatedColumnIdentifier != null){
+            name = SqlppVariableUtil.toUserDefinedName(generatedColumnIdentifier);
+       }
     }
     return new Projection(expr, name, star, exprStar);
   }
@@ -2422,7 +2425,7 @@
   )*
   {
     if(leftVar==null){
-        leftVar = ExpressionToVariableUtil.getGeneratedVariable(leftExpr);
+        leftVar = ExpressionToVariableUtil.getGeneratedVariable(leftExpr, true);
     }
     return new FromTerm(leftExpr, leftVar, posVar, correlateClauses);
   }
@@ -2439,7 +2442,7 @@
   <JOIN> rightExpr = Expression() ((<AS>)? rightVar = Variable())? (<AT> posVar = Variable())? <ON> conditionExpr = Expression()
   {
     if(rightVar==null){
-        rightVar = ExpressionToVariableUtil.getGeneratedVariable(rightExpr);
+        rightVar = ExpressionToVariableUtil.getGeneratedVariable(rightExpr, true);
     }
     return new JoinClause(joinType, rightExpr, rightVar, posVar, conditionExpr);
   }
@@ -2455,7 +2458,7 @@
   (<UNNEST>|<CORRELATE>|<FLATTEN>) rightExpr = Expression() ((<AS>)? rightVar = Variable()) (<AT> posVar = Variable())?
   {
     if(rightVar==null){
-        rightVar = ExpressionToVariableUtil.getGeneratedVariable(rightExpr);
+        rightVar = ExpressionToVariableUtil.getGeneratedVariable(rightExpr, true);
     }
     return new UnnestClause(joinType, rightExpr, rightVar, posVar);
   }
@@ -2586,7 +2589,7 @@
         )?
         {
             if(var==null){
-                var = ExpressionToVariableUtil.getGeneratedVariable(expr);
+                var = ExpressionToVariableUtil.getGeneratedVariable(expr, false);
             }
             GbyVariableExpressionPair pair1 = new GbyVariableExpressionPair(var, expr);
             vePairList.add(pair1);
@@ -2601,7 +2604,7 @@
          )?
          {
              if(var==null){
-                var = ExpressionToVariableUtil.getGeneratedVariable(expr);
+                var = ExpressionToVariableUtil.getGeneratedVariable(expr, false);
              }
              GbyVariableExpressionPair pair2 = new GbyVariableExpressionPair(var, expr);
              vePairList.add(pair2);