You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asterixdb.apache.org by dl...@apache.org on 2020/10/14 21:16:45 UTC

[asterixdb] branch master updated: [NO ISSUE][COMP] Support RIGHT OUTER JOIN

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

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


The following commit(s) were added to refs/heads/master by this push:
     new d4ce84c  [NO ISSUE][COMP] Support RIGHT OUTER JOIN
d4ce84c is described below

commit d4ce84c8aee927434fe1abfaa157cee08928f466
Author: Dmitry Lychagin <dm...@couchbase.com>
AuthorDate: Tue Oct 13 16:24:37 2020 -0700

    [NO ISSUE][COMP] Support RIGHT OUTER JOIN
    
    - user model changes: yes
    - storage format changes: no
    - interface changes: no
    
    Details:
    - Add support for RIGHT OUTER JOIN
    - Refactor reusable code from SqlppRQGGroupingSetsIT
      into SqlppRQGTestBase
    - Add SqlppRQGJoinsIT for join testing
    
    Change-Id: I975db4476a0a6423372a4bc472e3d8bf31c78529
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/8403
    Reviewed-by: Ali Alsuliman <al...@gmail.com>
    Integration-Tests: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Tested-by: Jenkins <je...@fulliautomatix.ics.uci.edu>
---
 .../SqlppExpressionToPlanTranslator.java           |  79 +++--
 .../test/runtime/SqlppRQGGroupingSetsIT.java       | 394 +--------------------
 .../asterix/test/runtime/SqlppRQGJoinsIT.java      | 235 ++++++++++++
 ...QGGroupingSetsIT.java => SqlppRQGTestBase.java} | 384 +++++---------------
 .../queries/rightouterjoin/roj-01-core.1.sqlpp     |  60 ++++
 .../queries/rightouterjoin/roj-01-core.2.sqlpp     |  63 ++++
 .../results/rightouterjoin/roj-01-core.1.plan      |  28 ++
 .../results/rightouterjoin/roj-01-core.2.plan      |  44 +++
 .../roj-01-core/roj-01-core.1.ddl.sqlpp}           |  31 +-
 .../roj-01-core/roj-01-core.2.update.sqlpp}        |   9 +-
 .../roj-01-core/roj-01-core.3.query.sqlpp}         |  18 +-
 .../roj-01-core/roj-01-core.4.query.sqlpp}         |  22 +-
 .../roj-01-core/roj-01-core.5.query.sqlpp}         |  25 +-
 .../roj-01-core/roj-01-core.6.query.sqlpp}         |  23 +-
 .../roj-01-core/roj-01-core.7.query.sqlpp}         |  22 +-
 .../roj-02-core/roj-02-core.1.ddl.sqlpp            |  60 ++++
 .../roj-02-core/roj-02-core.10.query.sqlpp}        |  21 +-
 .../roj-02-core/roj-02-core.2.update.sqlpp}        |  19 +-
 .../roj-02-core/roj-02-core.3.query.sqlpp}         |  18 +-
 .../roj-02-core/roj-02-core.4.query.sqlpp}         |  23 +-
 .../roj-02-core/roj-02-core.5.query.sqlpp}         |  21 +-
 .../roj-02-core/roj-02-core.6.ddl.sqlpp}           |  21 +-
 .../roj-02-core/roj-02-core.7.query.sqlpp}         |  11 +-
 .../roj-02-core/roj-02-core.8.query.sqlpp}         |  21 +-
 .../roj-02-core/roj-02-core.9.query.sqlpp}         |  21 +-
 .../roj-03-negative/roj-03-negative.1.ddl.sqlpp}   |  37 +-
 .../roj-03-negative/roj-03-negative.2.query.sqlpp} |  19 +-
 .../rightouterjoin/roj-01-core/roj-01-core.3.adm   |   4 +
 .../rightouterjoin/roj-01-core/roj-01-core.4.adm   |   6 +
 .../rightouterjoin/roj-01-core/roj-01-core.5.adm   |   8 +
 .../rightouterjoin/roj-01-core/roj-01-core.6.adm   |   6 +
 .../rightouterjoin/roj-01-core/roj-01-core.7.adm   |   4 +
 .../rightouterjoin/roj-02-core/roj-02-core.10.adm  |   8 +
 .../rightouterjoin/roj-02-core/roj-02-core.3.adm   |   4 +
 .../rightouterjoin/roj-02-core/roj-02-core.4.adm   |   6 +
 .../rightouterjoin/roj-02-core/roj-02-core.5.adm   |   4 +
 .../rightouterjoin/roj-02-core/roj-02-core.7.adm   |   4 +
 .../rightouterjoin/roj-02-core/roj-02-core.8.adm   |   8 +
 .../rightouterjoin/roj-02-core/roj-02-core.9.adm   |   8 +
 .../test/resources/runtimets/testsuite_sqlpp.xml   |  18 +
 .../asterix/common/exceptions/ErrorCode.java       |   1 +
 .../src/main/resources/asx_errormsg/en.properties  |   1 +
 .../ExcludeFromSelectStarAnnotation.java}          |  14 +-
 .../clause/AbstractBinaryCorrelateClause.java      |  16 +-
 ...AbstractBinaryCorrelateWithConditionClause.java |   6 +-
 .../asterix/lang/sqlpp/clause/JoinClause.java      |  25 +-
 .../asterix/lang/sqlpp/clause/NestClause.java      |  29 +-
 .../asterix/lang/sqlpp/clause/UnnestClause.java    |  29 +-
 .../apache/asterix/lang/sqlpp/optype/JoinType.java |   5 +-
 .../optype/{JoinType.java => UnnestType.java}      |   4 +-
 .../sqlpp/rewrites/SqlppFunctionBodyRewriter.java  |   3 +
 .../lang/sqlpp/rewrites/SqlppQueryRewriter.java    |  10 +
 .../rewrites/visitor/SqlppGroupingSetsVisitor.java |  13 +-
 .../visitor/SqlppRightJoinRewriteVisitor.java      | 381 ++++++++++++++++++++
 .../asterix/lang/sqlpp/util/SqlppRewriteUtil.java  |  20 +-
 .../lang/sqlpp/visitor/DeepCopyVisitor.java        |   4 +-
 .../lang/sqlpp/visitor/SqlppAstPrintVisitor.java   |   4 +-
 .../SqlppCloneAndSubstituteVariablesVisitor.java   |   4 +-
 .../sqlpp/visitor/SqlppFormatPrintVisitor.java     |   4 +-
 .../asterix-lang-sqlpp/src/main/javacc/SQLPP.jj    |  47 +--
 60 files changed, 1569 insertions(+), 868 deletions(-)

diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SqlppExpressionToPlanTranslator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SqlppExpressionToPlanTranslator.java
index c77774c..8eaf52a 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SqlppExpressionToPlanTranslator.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SqlppExpressionToPlanTranslator.java
@@ -27,6 +27,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Predicate;
 
 import org.apache.asterix.algebra.base.ILangExpressionToPlanTranslator;
 import org.apache.asterix.common.exceptions.CompilationException;
@@ -58,6 +59,7 @@ import org.apache.asterix.lang.common.struct.OperatorType;
 import org.apache.asterix.lang.common.struct.QuantifiedPair;
 import org.apache.asterix.lang.common.struct.VarIdentifier;
 import org.apache.asterix.lang.common.util.FunctionUtil;
+import org.apache.asterix.lang.sqlpp.annotation.ExcludeFromSelectStarAnnotation;
 import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
 import org.apache.asterix.lang.sqlpp.clause.FromClause;
 import org.apache.asterix.lang.sqlpp.clause.FromTerm;
@@ -76,6 +78,7 @@ import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
 import org.apache.asterix.lang.sqlpp.expression.WindowExpression;
 import org.apache.asterix.lang.sqlpp.optype.JoinType;
 import org.apache.asterix.lang.sqlpp.optype.SetOpType;
+import org.apache.asterix.lang.sqlpp.optype.UnnestType;
 import org.apache.asterix.lang.sqlpp.struct.SetOperationInput;
 import org.apache.asterix.lang.sqlpp.struct.SetOperationRight;
 import org.apache.asterix.lang.sqlpp.util.SqlppRewriteUtil;
@@ -494,6 +497,9 @@ public class SqlppExpressionToPlanTranslator extends LangExpressionToPlanTransla
                 context.setVar(joinClause.getRightVariable(), outerUnnestVar);
             }
             return new Pair<>(currentTopOp, null);
+        } else if (joinClause.getJoinType() == JoinType.RIGHTOUTER) {
+            // Fail if RIGHT OUTER JOIN was not rewritten into LEFT OUTER JOIN
+            throw new CompilationException(ErrorCode.ILLEGAL_RIGHT_OUTER_JOIN, joinClause.getSourceLocation());
         } else {
             throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, joinClause.getSourceLocation(),
                     String.valueOf(joinClause.getJoinType().toString()));
@@ -511,7 +517,7 @@ public class SqlppExpressionToPlanTranslator extends LangExpressionToPlanTransla
     public Pair<ILogicalOperator, LogicalVariable> visit(UnnestClause unnestClause,
             Mutable<ILogicalOperator> inputOpRef) throws CompilationException {
         return generateUnnestForBinaryCorrelateRightBranch(unnestClause, inputOpRef,
-                unnestClause.getJoinType() == JoinType.INNER);
+                unnestClause.getUnnestType() == UnnestType.INNER);
     }
 
     @Override
@@ -790,17 +796,22 @@ public class SqlppExpressionToPlanTranslator extends LangExpressionToPlanTransla
                 recordExprs.add(ifMissingOrNullExpr);
             } else if (projection.star()) {
                 if (selectBlock.hasGroupbyClause()) {
-                    getGroupBindings(selectBlock.getGroupbyClause(), fieldBindings, fieldNames);
+                    getGroupBindings(selectBlock.getGroupbyClause(), fieldBindings, fieldNames,
+                            SqlppExpressionToPlanTranslator::includeInSelectStar);
                     if (selectBlock.hasLetHavingClausesAfterGroupby()) {
-                        getLetBindings(selectBlock.getLetHavingListAfterGroupby(), fieldBindings, fieldNames);
+                        getLetBindings(selectBlock.getLetHavingListAfterGroupby(), fieldBindings, fieldNames,
+                                SqlppExpressionToPlanTranslator::includeInSelectStar);
                     }
                 } else if (selectBlock.hasFromClause()) {
-                    getFromBindings(selectBlock.getFromClause(), fieldBindings, fieldNames);
+                    getFromBindings(selectBlock.getFromClause(), fieldBindings, fieldNames,
+                            SqlppExpressionToPlanTranslator::includeInSelectStar);
                     if (selectBlock.hasLetWhereClauses()) {
-                        getLetBindings(selectBlock.getLetWhereList(), fieldBindings, fieldNames);
+                        getLetBindings(selectBlock.getLetWhereList(), fieldBindings, fieldNames,
+                                SqlppExpressionToPlanTranslator::includeInSelectStar);
                     }
                 } else if (selectBlock.hasLetWhereClauses()) {
-                    getLetBindings(selectBlock.getLetWhereList(), fieldBindings, fieldNames);
+                    getLetBindings(selectBlock.getLetWhereList(), fieldBindings, fieldNames,
+                            SqlppExpressionToPlanTranslator::includeInSelectStar);
                 }
             } else if (projection.hasName()) {
                 fieldBindings.add(getFieldBinding(projection, fieldNames));
@@ -824,21 +835,39 @@ public class SqlppExpressionToPlanTranslator extends LangExpressionToPlanTransla
         }
     }
 
+    private static boolean includeInSelectStar(VariableExpr varExpr) {
+        boolean excludeFromSelectStar =
+                varExpr.hasHints() && varExpr.getHints().contains(ExcludeFromSelectStarAnnotation.INSTANCE);
+        return !excludeFromSelectStar;
+    }
+
     // Generates all field bindings according to the from clause.
-    private void getFromBindings(FromClause fromClause, List<FieldBinding> outFieldBindings, Set<String> outFieldNames)
-            throws CompilationException {
+    private void getFromBindings(FromClause fromClause, List<FieldBinding> outFieldBindings, Set<String> outFieldNames,
+            Predicate<VariableExpr> varTest) throws CompilationException {
         for (FromTerm fromTerm : fromClause.getFromTerms()) {
-            outFieldBindings.add(getFieldBinding(fromTerm.getLeftVariable(), outFieldNames));
+            VariableExpr leftVar = fromTerm.getLeftVariable();
+            if (varTest == null || varTest.test(leftVar)) {
+                outFieldBindings.add(getFieldBinding(leftVar, outFieldNames));
+            }
             if (fromTerm.hasPositionalVariable()) {
-                outFieldBindings.add(getFieldBinding(fromTerm.getPositionalVariable(), outFieldNames));
+                VariableExpr leftPosVar = fromTerm.getPositionalVariable();
+                if (varTest == null || varTest.test(leftPosVar)) {
+                    outFieldBindings.add(getFieldBinding(leftPosVar, outFieldNames));
+                }
             }
             if (!fromTerm.hasCorrelateClauses()) {
                 continue;
             }
             for (AbstractBinaryCorrelateClause correlateClause : fromTerm.getCorrelateClauses()) {
-                outFieldBindings.add(getFieldBinding(correlateClause.getRightVariable(), outFieldNames));
+                VariableExpr rightVar = correlateClause.getRightVariable();
+                if (varTest == null || varTest.test(rightVar)) {
+                    outFieldBindings.add(getFieldBinding(rightVar, outFieldNames));
+                }
                 if (correlateClause.hasPositionalVariable()) {
-                    outFieldBindings.add(getFieldBinding(correlateClause.getPositionalVariable(), outFieldNames));
+                    VariableExpr rightPosVar = correlateClause.getPositionalVariable();
+                    if (varTest == null || varTest.test(rightPosVar)) {
+                        outFieldBindings.add(getFieldBinding(rightPosVar, outFieldNames));
+                    }
                 }
             }
         }
@@ -846,25 +875,32 @@ public class SqlppExpressionToPlanTranslator extends LangExpressionToPlanTransla
 
     // Generates all field bindings according to the from clause.
     private void getGroupBindings(GroupbyClause groupbyClause, List<FieldBinding> outFieldBindings,
-            Set<String> outFieldNames) throws CompilationException {
+            Set<String> outFieldNames, Predicate<VariableExpr> varTest) throws CompilationException {
         Set<VariableExpr> gbyKeyVars = new HashSet<>();
         List<GbyVariableExpressionPair> groupingSet = getSingleGroupingSet(groupbyClause);
         for (GbyVariableExpressionPair pair : groupingSet) {
             VariableExpr var = pair.getVar();
-            if (gbyKeyVars.add(var)) {
-                outFieldBindings.add(getFieldBinding(var, outFieldNames));
+            if (varTest == null || varTest.test(var)) {
+                if (gbyKeyVars.add(var)) {
+                    outFieldBindings.add(getFieldBinding(var, outFieldNames));
+                }
             }
         }
         if (groupbyClause.hasDecorList()) {
             for (GbyVariableExpressionPair pair : groupbyClause.getDecorPairList()) {
                 VariableExpr var = pair.getVar();
-                if (gbyKeyVars.add(var)) {
-                    outFieldBindings.add(getFieldBinding(var, outFieldNames));
+                if (varTest == null || varTest.test(var)) {
+                    if (gbyKeyVars.add(var)) {
+                        outFieldBindings.add(getFieldBinding(var, outFieldNames));
+                    }
                 }
             }
         }
         if (groupbyClause.hasGroupVar()) {
-            outFieldBindings.add(getFieldBinding(groupbyClause.getGroupVar(), outFieldNames));
+            VariableExpr var = groupbyClause.getGroupVar();
+            if (varTest == null || varTest.test(var)) {
+                outFieldBindings.add(getFieldBinding(var, outFieldNames));
+            }
         }
         if (groupbyClause.hasWithMap()) {
             // no WITH in SQLPP
@@ -875,11 +911,14 @@ public class SqlppExpressionToPlanTranslator extends LangExpressionToPlanTransla
 
     // Generates all field bindings according to the let clause.
     private void getLetBindings(List<AbstractClause> clauses, List<FieldBinding> outFieldBindings,
-            Set<String> outFieldNames) throws CompilationException {
+            Set<String> outFieldNames, Predicate<VariableExpr> varTest) throws CompilationException {
         for (AbstractClause clause : clauses) {
             if (clause.getClauseType() == ClauseType.LET_CLAUSE) {
                 LetClause letClause = (LetClause) clause;
-                outFieldBindings.add(getFieldBinding(letClause.getVarExpr(), outFieldNames));
+                VariableExpr letVar = letClause.getVarExpr();
+                if (varTest == null || varTest.test(letVar)) {
+                    outFieldBindings.add(getFieldBinding(letVar, outFieldNames));
+                }
             }
         }
     }
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGGroupingSetsIT.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGGroupingSetsIT.java
index b1125a5..2ccab70 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGGroupingSetsIT.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGGroupingSetsIT.java
@@ -19,130 +19,43 @@
 
 package org.apache.asterix.test.runtime;
 
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.sql.JDBCType;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.ResultSetMetaData;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.sql.Types;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Random;
 import java.util.Set;
 import java.util.stream.Collectors;
 
-import org.apache.asterix.common.utils.Servlets;
-import org.apache.asterix.om.types.BuiltinType;
-import org.apache.asterix.om.types.IAType;
-import org.apache.asterix.test.common.ExtractedResult;
-import org.apache.asterix.test.common.ResultExtractor;
-import org.apache.asterix.test.common.TestExecutor;
-import org.apache.asterix.test.common.TestHelper;
-import org.apache.asterix.testframework.context.TestCaseContext;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.junit.AfterClass;
-import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
-import org.testcontainers.containers.PostgreSQLContainer;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.ObjectReader;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-
-// Prerequisite:
-// setenv TESTCONTAINERS_RYUK_DISABLED true
 
 @RunWith(Parameterized.class)
-public class SqlppRQGGroupingSetsIT {
+public final class SqlppRQGGroupingSetsIT extends SqlppRQGTestBase {
 
-    private static final String CONF_PROPERTY_SEED = getConfigurationPropertyName("seed");
+    private static final Logger LOGGER = LogManager.getLogger(SqlppRQGGroupingSetsIT.class);
 
+    private static final String CONF_PROPERTY_SEED = getConfigurationPropertyName(SqlppRQGTestBase.class, "seed");
     private static final long CONF_PROPERTY_SEED_DEFAULT = System.currentTimeMillis();
 
-    private static final String CONF_PROPERTY_LIMIT = getConfigurationPropertyName("limit");
-
+    private static final String CONF_PROPERTY_LIMIT = getConfigurationPropertyName(SqlppRQGTestBase.class, "limit");
     private static final int CONF_PROPERTY_LIMIT_DEFAULT = 100;
 
-    private static final String TESTCONTAINERS_RYUK_DISABLED = "TESTCONTAINERS_RYUK_DISABLED";
-
-    private static final String TEST_CONFIG_FILE_NAME = "src/main/resources/cc.conf";
-
-    private static final String POSTGRES_IMAGE = "postgres:12.2";
-
-    private static final String TABLE_NAME = "tenk";
-
-    private static final Path TABLE_FILE = Paths.get("data", "tenk.tbl");
-
-    private static final char TABLE_FILE_COLUMN_SEPARATOR = '|';
-
-    private static final Path RESULT_OUTPUT_DIR = Paths.get("target", SqlppRQGGroupingSetsIT.class.getSimpleName());
-
     private static final int ROLLUP_ELEMENT_LIMIT = 2;
     private static final int CUBE_ELEMENT_LIMIT = 1;
     private static final int GROUPING_SETS_ELEMENT_LIMIT = 2;
     private static final int MULTI_ELEMENT_LIMIT = 2;
 
-    private static final String UNIQUE_1 = "unique1";
-    private static final String UNIQUE_2 = "unique2";
-    private static final String TWO = "two";
-    private static final String FOUR = "four";
-    private static final String TEN = "ten";
-    private static final String TWENTY = "twenty";
-    private static final String HUNDRED = "hundred";
-    private static final String THOUSAND = "thousand";
-    private static final String TWOTHOUSAND = "twothousand";
-    private static final String FIVETHOUS = "fivethous";
-    private static final String TENTHOUS = "tenthous";
-    private static final String ODD100 = "odd100";
-    private static final String EVEN100 = "even100";
-    private static final String STRINGU1 = "stringu1";
-    private static final String STRINGU2 = "stringu2";
-    private static final String STRING4 = "string4";
-
     private static final List<String> GROUPBY_COLUMNS = Arrays.asList(TWO, FOUR, TEN, TWENTY, HUNDRED, ODD100, EVEN100);
 
-    private static final LinkedHashMap<String, JDBCType> TABLE_SCHEMA = createTableSchema();
-
-    private static final ObjectReader JSON_NODE_READER = new ObjectMapper().readerFor(JsonNode.class);
-
-    private static final Logger LOGGER = LogManager.getLogger(SqlppRQGGroupingSetsIT.class);
-
-    private static TestExecutor testExecutor;
-
-    private static PostgreSQLContainer<?> postgres;
-
-    private static Connection conn;
-
-    private static Statement stmt;
-
     private final int testcaseId;
 
     private final String sqlQuery;
@@ -177,35 +90,19 @@ public class SqlppRQGGroupingSetsIT {
         this.groupByClause = groupByClause;
     }
 
-    @Test
-    public void test() throws Exception {
-        LOGGER.info(String.format("Starting testcase #%d: %s", testcaseId, groupByClause));
-
-        LOGGER.info("Running SQL");
-        LOGGER.info(sqlQuery);
-        stmt.execute(sqlQuery);
-        ArrayNode sqlResult;
-        try (ResultSet rs = stmt.getResultSet()) {
-            sqlResult = asJson(rs);
-        }
-
-        LOGGER.info("Running SQL++");
-        LOGGER.info(sqlppQuery);
-        ArrayNode sqlppResult;
-        try (InputStream resultStream = testExecutor.executeQueryService(sqlppQuery,
-                testExecutor.getEndpoint(Servlets.QUERY_SERVICE), TestCaseContext.OutputFormat.ADM)) {
-            sqlppResult = asJson(
-                    ResultExtractor.extract(resultStream, StandardCharsets.UTF_8, TestCaseContext.OutputFormat.ADM));
-        }
+    @BeforeClass
+    public static void setUp() throws Exception {
+        setUpBeforeClass();
+    }
 
-        boolean eq = TestHelper.equalJson(sqlResult, sqlppResult, false);
-        if (!eq) {
-            File sqlResultFile = writeResult(sqlResult, "sql");
-            File sqlppResultFile = writeResult(sqlppResult, "sqlpp");
+    @AfterClass
+    public static void tearDown() throws Exception {
+        tearDownAfterClass();
+    }
 
-            Assert.fail(String.format("Results do not match.\n%s\n%s", sqlResultFile.getCanonicalPath(),
-                    sqlppResultFile.getCanonicalPath()));
-        }
+    @Test
+    public void test() throws Exception {
+        runTestCase(testcaseId, groupByClause, sqlQuery, sqlppQuery);
     }
 
     private static TestQuery generateQuery(int testcaseId, Random random) {
@@ -343,267 +240,6 @@ public class SqlppRQGGroupingSetsIT {
         sublist.add(s);
     }
 
-    private ArrayNode asJson(ExtractedResult aresult) throws IOException {
-        ArrayNode result = (ArrayNode) JSON_NODE_READER.createArrayNode();
-        try (BufferedReader reader =
-                new BufferedReader(new InputStreamReader(aresult.getResult(), StandardCharsets.UTF_8))) {
-            reader.lines().forEachOrdered(l -> {
-                try {
-                    result.add(JSON_NODE_READER.readTree(l));
-                } catch (JsonProcessingException e) {
-                    throw new RuntimeException(e);
-                }
-            });
-        }
-        return result;
-    }
-
-    private ArrayNode asJson(ResultSet rs) throws SQLException {
-        ResultSetMetaData rsmd = rs.getMetaData();
-        int rsColumnCount = rsmd.getColumnCount();
-        ArrayNode result = (ArrayNode) JSON_NODE_READER.createArrayNode();
-        while (rs.next()) {
-            ObjectNode row = (ObjectNode) JSON_NODE_READER.createObjectNode();
-            for (int i = 0; i < rsColumnCount; i++) {
-                int jdbcColumnIdx = i + 1;
-                String columnName = rsmd.getColumnName(jdbcColumnIdx);
-                switch (rsmd.getColumnType(jdbcColumnIdx)) {
-                    case Types.INTEGER:
-                        int intValue = rs.getInt(jdbcColumnIdx);
-                        if (rs.wasNull()) {
-                            row.putNull(columnName);
-                        } else {
-                            row.put(columnName, intValue);
-                        }
-                        break;
-                    case Types.BIGINT:
-                        long longValue = rs.getLong(jdbcColumnIdx);
-                        if (rs.wasNull()) {
-                            row.putNull(columnName);
-                        } else {
-                            row.put(columnName, longValue);
-                        }
-                        break;
-                    case Types.VARCHAR:
-                        String stringValue = rs.getString(jdbcColumnIdx);
-                        if (rs.wasNull()) {
-                            row.putNull(columnName);
-                        } else {
-                            row.put(columnName, stringValue);
-                        }
-                        break;
-                    default:
-                        throw new UnsupportedOperationException();
-                }
-            }
-            result.add(row);
-        }
-        return result;
-    }
-
-    private static void loadAsterixData() throws Exception {
-        String tableTypeName = TABLE_NAME + "Type";
-        String createTypeStmtText =
-                String.format("CREATE TYPE %s AS CLOSED { %s }", tableTypeName,
-                        TABLE_SCHEMA.entrySet().stream()
-                                .map(e -> e.getKey() + ':' + getAsterixType(e.getValue()).getTypeName())
-                                .collect(Collectors.joining(",")));
-
-        LOGGER.debug(createTypeStmtText);
-        testExecutor.executeSqlppUpdateOrDdl(createTypeStmtText, TestCaseContext.OutputFormat.ADM);
-
-        String createDatasetStmtText =
-                String.format("CREATE DATASET %s(%s) PRIMARY KEY %s", TABLE_NAME, tableTypeName, UNIQUE_2);
-        LOGGER.debug(createDatasetStmtText);
-        testExecutor.executeSqlppUpdateOrDdl(createDatasetStmtText, TestCaseContext.OutputFormat.ADM);
-
-        String loadStmtText =
-                String.format("LOAD DATASET %s USING localfs ((`path`=`%s`),(`format`=`%s`),(`delimiter`=`%s`))",
-                        TABLE_NAME, "asterix_nc1://" + TABLE_FILE, "delimited-text", "|");
-        LOGGER.debug(loadStmtText);
-        testExecutor.executeSqlppUpdateOrDdl(loadStmtText, TestCaseContext.OutputFormat.ADM);
-    }
-
-    private static void loadSQLData() throws SQLException, IOException {
-        String createTableStmtText = String.format("CREATE TEMPORARY TABLE %s (%s)", TABLE_NAME, TABLE_SCHEMA.entrySet()
-                .stream().map(e -> e.getKey() + ' ' + getSQLType(e.getValue())).collect(Collectors.joining(",")));
-
-        stmt.execute(createTableStmtText);
-
-        String insertStmtText = String.format("INSERT INTO %s VALUES (%s)", TABLE_NAME,
-                StringUtils.repeat("?", ",", TABLE_SCHEMA.size()));
-
-        try (PreparedStatement insertStmt = conn.prepareStatement(insertStmtText)) {
-            Files.lines(TABLE_FILE).forEachOrdered(line -> {
-                String[] values = StringUtils.split(line, TABLE_FILE_COLUMN_SEPARATOR);
-                try {
-                    insertStmt.clearParameters();
-                    int i = 0;
-                    for (JDBCType type : TABLE_SCHEMA.values()) {
-                        setColumnValue(insertStmt, i + 1, type, values[i]);
-                        i++;
-                    }
-                    insertStmt.addBatch();
-                } catch (SQLException e) {
-                    throw new RuntimeException(e);
-                }
-            });
-            insertStmt.executeBatch();
-        }
-    }
-
-    private static LinkedHashMap<String, JDBCType> createTableSchema() {
-        LinkedHashMap<String, JDBCType> schema = new LinkedHashMap<>();
-        schema.put(UNIQUE_1, JDBCType.INTEGER);
-        schema.put(UNIQUE_2, JDBCType.INTEGER);
-        schema.put(TWO, JDBCType.INTEGER);
-        schema.put(FOUR, JDBCType.INTEGER);
-        schema.put(TEN, JDBCType.INTEGER);
-        schema.put(TWENTY, JDBCType.INTEGER);
-        schema.put(HUNDRED, JDBCType.INTEGER);
-        schema.put(THOUSAND, JDBCType.INTEGER);
-        schema.put(TWOTHOUSAND, JDBCType.INTEGER);
-        schema.put(FIVETHOUS, JDBCType.INTEGER);
-        schema.put(TENTHOUS, JDBCType.INTEGER);
-        schema.put(ODD100, JDBCType.INTEGER);
-        schema.put(EVEN100, JDBCType.INTEGER);
-        schema.put(STRINGU1, JDBCType.VARCHAR);
-        schema.put(STRINGU2, JDBCType.VARCHAR);
-        schema.put(STRING4, JDBCType.VARCHAR);
-        return schema;
-    }
-
-    private static String getSQLType(JDBCType type) {
-        String suffix = "";
-        if (type == JDBCType.VARCHAR) {
-            suffix = "(256)";
-        }
-        return type.getName() + suffix;
-    }
-
-    private static IAType getAsterixType(JDBCType type) {
-        switch (type) {
-            case INTEGER:
-                return BuiltinType.AINT32;
-            case VARCHAR:
-                return BuiltinType.ASTRING;
-            default:
-                throw new UnsupportedOperationException();
-        }
-    }
-
-    private static void setColumnValue(PreparedStatement stmt, int jdbcParamIdx, JDBCType type, String value)
-            throws SQLException {
-        switch (type) {
-            case INTEGER:
-                stmt.setInt(jdbcParamIdx, Integer.parseInt(value));
-                break;
-            case VARCHAR:
-                stmt.setString(jdbcParamIdx, value);
-                break;
-            default:
-                throw new UnsupportedOperationException(type.getName());
-        }
-    }
-
-    private static <T> List<T> randomize(Collection<T> input, Random random) {
-        List<T> output = new ArrayList<>(input);
-        Collections.shuffle(output, random);
-        return output;
-    }
-
-    private static String getConfigurationPropertyName(String propertyName) {
-        return String.format("%s.%s", SqlppRQGGroupingSetsIT.class.getSimpleName(), propertyName);
-    }
-
-    private static long getLongConfigurationProperty(String propertyName, long defValue) {
-        String textValue = System.getProperty(propertyName);
-        if (textValue == null) {
-            return defValue;
-        }
-        try {
-            return Long.parseLong(textValue);
-        } catch (NumberFormatException e) {
-            LOGGER.warn(String.format("Cannot parse configuration property: %s. Will use default value: %d",
-                    propertyName, defValue));
-            return defValue;
-        }
-    }
-
-    private File writeResult(ArrayNode result, String resultKind) throws IOException {
-        String outFileName = String.format("%d.%s.txt", testcaseId, resultKind);
-        File outFile = new File(RESULT_OUTPUT_DIR.toFile(), outFileName);
-        try (PrintWriter pw = new PrintWriter(outFile, StandardCharsets.UTF_8.name())) {
-            pw.print("---");
-            pw.println(groupByClause);
-            for (int i = 0, ln = result.size(); i < ln; i++) {
-                pw.println(ResultExtractor.prettyPrint(result.get(i)));
-            }
-        }
-        return outFile;
-    }
-
-    @BeforeClass
-    public static void setUp() throws Exception {
-        startAsterix();
-        startPostgres();
-        FileUtils.forceMkdir(RESULT_OUTPUT_DIR.toFile());
-    }
-
-    @AfterClass
-    public static void tearDown() throws Exception {
-        stopPostgres();
-        stopAsterix();
-    }
-
-    private static void startAsterix() throws Exception {
-        testExecutor = new TestExecutor();
-        LangExecutionUtil.setUp(TEST_CONFIG_FILE_NAME, testExecutor);
-        loadAsterixData();
-    }
-
-    private static void stopAsterix() throws Exception {
-        LangExecutionUtil.tearDown();
-    }
-
-    private static void startPostgres() throws SQLException, IOException {
-        if (!Boolean.parseBoolean(System.getenv(TESTCONTAINERS_RYUK_DISABLED))) {
-            throw new IllegalStateException(
-                    String.format("Set environment variable %s=%s", TESTCONTAINERS_RYUK_DISABLED, true));
-        }
-        LOGGER.info("Starting Postgres");
-        postgres = new PostgreSQLContainer<>(POSTGRES_IMAGE);
-        postgres.start();
-        conn = DriverManager.getConnection(postgres.getJdbcUrl(), postgres.getUsername(), postgres.getPassword());
-        stmt = conn.createStatement();
-        loadSQLData();
-    }
-
-    private static void stopPostgres() {
-        LOGGER.info("Stopping Postgres");
-        if (stmt != null) {
-            try {
-                stmt.close();
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-        }
-        if (conn != null) {
-            try {
-                conn.close();
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-        }
-        if (postgres != null) {
-            try {
-                postgres.close();
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-        }
-    }
-
     private static class TestQuery {
         final String sqlQuery;
         final String sqlppQuery;
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGJoinsIT.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGJoinsIT.java
new file mode 100644
index 0000000..6f16400
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGJoinsIT.java
@@ -0,0 +1,235 @@
+/*
+ * 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.test.runtime;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.IntUnaryOperator;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SqlppRQGJoinsIT extends SqlppRQGTestBase {
+
+    private final int testcaseId;
+
+    private final String sqlQuery;
+
+    private final String sqlppQuery;
+
+    private final String desc;
+
+    static final String PROJECT_FIELD = "unique1";
+
+    static final String JOIN_FIELD = "unique2";
+
+    static final String FILTER_FIELD = JOIN_FIELD;
+
+    static final char[] SHAPES = new char[] { 'c', 's' }; //TODO: 'q'
+
+    @Parameterized.Parameters(name = "SqlppRQGJoinsIT {index}: {3}")
+    public static Collection<Object[]> tests() {
+        List<Object[]> testCases = new ArrayList<>();
+
+        IntUnaryOperator filterComputer = i -> 2 * (i + 1);
+
+        String[] allJoinKinds = new String[] { "INNER", "LEFT", "RIGHT" };
+        String[] queryJoinKinds = new String[3];
+        int id = 0;
+
+        for (String jk0 : allJoinKinds) {
+            queryJoinKinds[0] = jk0;
+            TestQuery q1 = generateQuery(queryJoinKinds, 1, filterComputer, SHAPES[0]);
+            testCases.add(new Object[] { id++, q1.sqlQuery, q1.sqlppQuery, q1.summary });
+
+            for (char s : SHAPES) {
+                for (String jk1 : allJoinKinds) {
+                    queryJoinKinds[1] = jk1;
+                    TestQuery q2 = generateQuery(queryJoinKinds, 2, filterComputer, s);
+                    testCases.add(new Object[] { id++, q2.sqlQuery, q2.sqlppQuery, q2.summary });
+
+                    for (String jk2 : allJoinKinds) {
+                        queryJoinKinds[2] = jk2;
+                        TestQuery q3 = generateQuery(queryJoinKinds, 3, filterComputer, s);
+                        testCases.add(new Object[] { id++, q3.sqlQuery, q3.sqlppQuery, q3.summary });
+                    }
+                }
+            }
+        }
+
+        return testCases;
+    }
+
+    private static TestQuery generateQuery(String[] joinKinds, int joinKindsSize, IntUnaryOperator filterComputer,
+            char shape) {
+        int tCount = joinKindsSize + 1;
+        List<String> tDefs = new ArrayList<>(tCount);
+        for (int i = 0; i < tCount; i++) {
+            int filterValue = filterComputer.applyAsInt(i);
+            String tDef = String.format("SELECT %s, %s FROM %s WHERE %s < %d", PROJECT_FIELD, JOIN_FIELD, TABLE_NAME,
+                    FILTER_FIELD, filterValue);
+            tDefs.add(tDef);
+        }
+
+        StringBuilder joinCondBuilderSql = new StringBuilder(128);
+        StringBuilder joinCondBuilderSqlpp = new StringBuilder(128);
+        StringBuilder selectClauseSql = new StringBuilder(128);
+        StringBuilder selectClauseSqlpp = new StringBuilder(128);
+        StringBuilder fromClauseSql = new StringBuilder(128);
+        StringBuilder fromClauseSqlpp = new StringBuilder(128);
+        StringBuilder orderbyClauseSql = new StringBuilder(128);
+        StringBuilder orderbyClauseSqlpp = new StringBuilder(128);
+        StringBuilder summary = new StringBuilder(128);
+
+        String fieldExprFormat = "%s.%s";
+
+        for (int i = 0; i < tCount; i++) {
+            String tThis = "t" + i;
+            String clause = i == 0 ? "FROM" : joinKinds[i - 1] + " JOIN";
+
+            String joinConditionSql, joinConditionSqlpp;
+            if (i == 0) {
+                joinConditionSqlpp = joinConditionSql = "";
+            } else {
+                String joinFieldThisExprSql = String.format(fieldExprFormat, tThis, JOIN_FIELD);
+                String joinFieldThisExprSqlpp = joinFieldThisExprSql; //missing2Null(joinFieldThisExprSql);
+                String joinConditionFormat;
+                switch (shape) {
+                    case 'c':
+                        String tPrev = "t" + (i - 1);
+                        String joinFieldPrevExprSql = String.format(fieldExprFormat, tPrev, JOIN_FIELD);
+                        String joinFieldPrevExprSqlpp = joinFieldPrevExprSql; // missing2Null(joinFieldPrevExprSql);
+                        joinConditionFormat = "ON %s = %s ";
+                        joinConditionSql =
+                                String.format(joinConditionFormat, joinFieldPrevExprSql, joinFieldThisExprSql);
+                        joinConditionSqlpp =
+                                String.format(joinConditionFormat, joinFieldPrevExprSqlpp, joinFieldThisExprSqlpp);
+                        break;
+                    case 's':
+                        String t0 = "t" + 0;
+                        String joinField0ExprSql = String.format(fieldExprFormat, t0, JOIN_FIELD);
+                        String joinField0ExprSqlpp = joinField0ExprSql; // missing2Null(joinField0ExprSql);
+                        joinConditionFormat = "ON %s = %s ";
+                        joinConditionSql = String.format(joinConditionFormat, joinField0ExprSql, joinFieldThisExprSql);
+                        joinConditionSqlpp =
+                                String.format(joinConditionFormat, joinField0ExprSqlpp, joinFieldThisExprSqlpp);
+                        break;
+                    case 'q':
+                        joinCondBuilderSql.setLength(0);
+                        joinCondBuilderSqlpp.setLength(0);
+                        joinConditionFormat = "%s %s = %s ";
+                        for (int j = 0; j < i; j++) {
+                            String kwj = j == 0 ? "ON" : "AND";
+                            String tj = "t" + j;
+                            String joinFieldJExprSql = String.format(fieldExprFormat, tj, JOIN_FIELD);
+                            String joinFieldJExprSqlpp = joinFieldJExprSql; //missing2Null(joinFieldJExprSql);
+                            String joinCondPartSql =
+                                    String.format(joinConditionFormat, kwj, joinFieldJExprSql, joinFieldThisExprSql);
+                            String joinCondPartSqlpp = String.format(joinConditionFormat, kwj, joinFieldJExprSqlpp,
+                                    joinFieldThisExprSqlpp);
+                            joinCondBuilderSql.append(joinCondPartSql);
+                            joinCondBuilderSqlpp.append(joinCondPartSqlpp);
+                        }
+                        joinConditionSql = joinCondBuilderSql.toString();
+                        joinConditionSqlpp = joinCondBuilderSqlpp.toString();
+                        break;
+                    default:
+                        throw new IllegalArgumentException(String.valueOf(shape));
+                }
+            }
+
+            String fromClauseFormat = "%s (%s) %s %s";
+            fromClauseSql.append(String.format(fromClauseFormat, clause, tDefs.get(i), tThis, joinConditionSql));
+            fromClauseSqlpp.append(String.format(fromClauseFormat, clause, tDefs.get(i), tThis, joinConditionSqlpp));
+
+            if (i > 0) {
+                selectClauseSql.append(", ");
+                selectClauseSqlpp.append(", ");
+                orderbyClauseSql.append(", ");
+                orderbyClauseSqlpp.append(", ");
+                if (i > 1) {
+                    summary.append(',');
+                }
+                summary.append(joinKinds[i - 1]);
+            }
+            String projectFieldExprSql = String.format(fieldExprFormat, tThis, PROJECT_FIELD);
+            String projectFieldExprSqlpp = missing2Null(projectFieldExprSql);
+            String projectFieldAlias = String.format("%s_%s", tThis, PROJECT_FIELD);
+            String projectFormat = "%s AS %s";
+            selectClauseSql.append(String.format(projectFormat, projectFieldExprSql, projectFieldAlias));
+            selectClauseSqlpp.append(String.format(projectFormat, projectFieldExprSqlpp, projectFieldAlias));
+            orderbyClauseSql.append(String.format("%s NULLS FIRST", projectFieldAlias));
+            orderbyClauseSqlpp.append(projectFieldAlias);
+        }
+
+        if (tCount > 1) {
+            summary.append(';').append(shape);
+        }
+
+        String queryFormat = "SELECT %s %sORDER BY %s";
+        String sqlQuery = String.format(queryFormat, selectClauseSql, fromClauseSql, orderbyClauseSql);
+        String sqlppQuery = String.format(queryFormat, selectClauseSqlpp, fromClauseSqlpp, orderbyClauseSqlpp);
+
+        return new TestQuery(sqlQuery, sqlppQuery, summary.toString());
+    }
+
+    private static String missing2Null(String expr) {
+        return String.format("if_missing(%s, null)", expr);
+    }
+
+    public SqlppRQGJoinsIT(int testcaseId, String sqlQuery, String sqlppQuery, String desc) {
+        this.testcaseId = testcaseId;
+        this.sqlQuery = sqlQuery;
+        this.sqlppQuery = sqlppQuery;
+        this.desc = desc;
+    }
+
+    @BeforeClass
+    public static void setUp() throws Exception {
+        setUpBeforeClass();
+    }
+
+    @AfterClass
+    public static void tearDown() throws Exception {
+        tearDownAfterClass();
+    }
+
+    @Test
+    public void test() throws Exception {
+        runTestCase(testcaseId, desc, sqlQuery, sqlppQuery);
+    }
+
+    private static class TestQuery {
+        final String sqlQuery;
+        final String sqlppQuery;
+        final String summary;
+
+        TestQuery(String sqlQuery, String sqlppQuery, String summary) {
+            this.sqlQuery = sqlQuery;
+            this.sqlppQuery = sqlppQuery;
+            this.summary = summary;
+        }
+    }
+}
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGGroupingSetsIT.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGTestBase.java
similarity index 50%
copy from asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGGroupingSetsIT.java
copy to asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGTestBase.java
index b1125a5..44af727 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGGroupingSetsIT.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGTestBase.java
@@ -39,14 +39,11 @@ import java.sql.SQLException;
 import java.sql.Statement;
 import java.sql.Types;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Random;
-import java.util.Set;
 import java.util.stream.Collectors;
 
 import org.apache.asterix.common.utils.Servlets;
@@ -61,13 +58,7 @@ import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-import org.junit.AfterClass;
 import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 import org.testcontainers.containers.PostgreSQLContainer;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -80,106 +71,82 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
 // Prerequisite:
 // setenv TESTCONTAINERS_RYUK_DISABLED true
 
-@RunWith(Parameterized.class)
-public class SqlppRQGGroupingSetsIT {
+public abstract class SqlppRQGTestBase {
 
-    private static final String CONF_PROPERTY_SEED = getConfigurationPropertyName("seed");
+    private static final Logger LOGGER = LogManager.getLogger(SqlppRQGTestBase.class);
 
-    private static final long CONF_PROPERTY_SEED_DEFAULT = System.currentTimeMillis();
+    protected static final String TESTCONTAINERS_RYUK_DISABLED = "TESTCONTAINERS_RYUK_DISABLED";
 
-    private static final String CONF_PROPERTY_LIMIT = getConfigurationPropertyName("limit");
+    protected static final String TEST_CONFIG_FILE_NAME = "src/main/resources/cc.conf";
 
-    private static final int CONF_PROPERTY_LIMIT_DEFAULT = 100;
+    protected static final String POSTGRES_IMAGE = "postgres:12.2";
 
-    private static final String TESTCONTAINERS_RYUK_DISABLED = "TESTCONTAINERS_RYUK_DISABLED";
+    protected static final String TABLE_NAME = "tenk";
 
-    private static final String TEST_CONFIG_FILE_NAME = "src/main/resources/cc.conf";
+    protected static final Path TABLE_FILE = Paths.get("data", "tenk.tbl");
 
-    private static final String POSTGRES_IMAGE = "postgres:12.2";
+    protected static final char TABLE_FILE_COLUMN_SEPARATOR = '|';
 
-    private static final String TABLE_NAME = "tenk";
+    protected static final Path RESULT_OUTPUT_DIR = Paths.get("target", SqlppRQGGroupingSetsIT.class.getSimpleName());
 
-    private static final Path TABLE_FILE = Paths.get("data", "tenk.tbl");
+    protected static final String UNIQUE_1 = "unique1";
+    protected static final String UNIQUE_2 = "unique2";
+    protected static final String TWO = "two";
+    protected static final String FOUR = "four";
+    protected static final String TEN = "ten";
+    protected static final String TWENTY = "twenty";
+    protected static final String HUNDRED = "hundred";
+    protected static final String THOUSAND = "thousand";
+    protected static final String TWOTHOUSAND = "twothousand";
+    protected static final String FIVETHOUS = "fivethous";
+    protected static final String TENTHOUS = "tenthous";
+    protected static final String ODD100 = "odd100";
+    protected static final String EVEN100 = "even100";
+    protected static final String STRINGU1 = "stringu1";
+    protected static final String STRINGU2 = "stringu2";
+    protected static final String STRING4 = "string4";
 
-    private static final char TABLE_FILE_COLUMN_SEPARATOR = '|';
+    protected static final LinkedHashMap<String, JDBCType> TABLE_SCHEMA = createTableSchema();
 
-    private static final Path RESULT_OUTPUT_DIR = Paths.get("target", SqlppRQGGroupingSetsIT.class.getSimpleName());
+    protected static final ObjectReader JSON_NODE_READER = new ObjectMapper().readerFor(JsonNode.class);
 
-    private static final int ROLLUP_ELEMENT_LIMIT = 2;
-    private static final int CUBE_ELEMENT_LIMIT = 1;
-    private static final int GROUPING_SETS_ELEMENT_LIMIT = 2;
-    private static final int MULTI_ELEMENT_LIMIT = 2;
+    protected static TestExecutor testExecutor;
 
-    private static final String UNIQUE_1 = "unique1";
-    private static final String UNIQUE_2 = "unique2";
-    private static final String TWO = "two";
-    private static final String FOUR = "four";
-    private static final String TEN = "ten";
-    private static final String TWENTY = "twenty";
-    private static final String HUNDRED = "hundred";
-    private static final String THOUSAND = "thousand";
-    private static final String TWOTHOUSAND = "twothousand";
-    private static final String FIVETHOUS = "fivethous";
-    private static final String TENTHOUS = "tenthous";
-    private static final String ODD100 = "odd100";
-    private static final String EVEN100 = "even100";
-    private static final String STRINGU1 = "stringu1";
-    private static final String STRINGU2 = "stringu2";
-    private static final String STRING4 = "string4";
+    protected static PostgreSQLContainer<?> postgres;
 
-    private static final List<String> GROUPBY_COLUMNS = Arrays.asList(TWO, FOUR, TEN, TWENTY, HUNDRED, ODD100, EVEN100);
+    protected static Connection conn;
 
-    private static final LinkedHashMap<String, JDBCType> TABLE_SCHEMA = createTableSchema();
+    protected static Statement stmt;
 
-    private static final ObjectReader JSON_NODE_READER = new ObjectMapper().readerFor(JsonNode.class);
-
-    private static final Logger LOGGER = LogManager.getLogger(SqlppRQGGroupingSetsIT.class);
-
-    private static TestExecutor testExecutor;
-
-    private static PostgreSQLContainer<?> postgres;
-
-    private static Connection conn;
-
-    private static Statement stmt;
-
-    private final int testcaseId;
-
-    private final String sqlQuery;
-
-    private final String sqlppQuery;
-
-    private final String groupByClause;
-
-    @Parameters(name = "SqlppRQGGroupingSetsIT {index}: {3}")
-    public static Collection<Object[]> tests() {
-        List<Object[]> testCases = new ArrayList<>();
-
-        long seed = getLongConfigurationProperty(CONF_PROPERTY_SEED, CONF_PROPERTY_SEED_DEFAULT);
-        int limit = (int) getLongConfigurationProperty(CONF_PROPERTY_LIMIT, CONF_PROPERTY_LIMIT_DEFAULT);
-
-        LOGGER.info(String.format("Testsuite configuration: -D%s=%d -D%s=%d", CONF_PROPERTY_SEED, seed,
-                CONF_PROPERTY_LIMIT, limit));
-
-        Random random = new Random(seed);
-        for (int i = 0; i < limit; i++) {
-            TestQuery query = generateQuery(i, random);
-            testCases.add(new Object[] { i, query.sqlQuery, query.sqlppQuery, query.groupbyClause });
-        }
+    public static void setUpBeforeClass() throws Exception {
+        startAsterix();
+        startPostgres();
+        FileUtils.forceMkdir(RESULT_OUTPUT_DIR.toFile());
+    }
 
-        return testCases;
+    public static void tearDownAfterClass() throws Exception {
+        stopPostgres();
+        stopAsterix();
     }
 
-    public SqlppRQGGroupingSetsIT(int testcaseId, String sqlQuery, String sqlppQuery, String groupByClause) {
-        this.testcaseId = testcaseId;
-        this.sqlQuery = sqlQuery;
-        this.sqlppQuery = sqlppQuery;
-        this.groupByClause = groupByClause;
+    protected ArrayNode asJson(ExtractedResult aresult) throws IOException {
+        ArrayNode result = (ArrayNode) JSON_NODE_READER.createArrayNode();
+        try (BufferedReader reader =
+                new BufferedReader(new InputStreamReader(aresult.getResult(), StandardCharsets.UTF_8))) {
+            reader.lines().forEachOrdered(l -> {
+                try {
+                    result.add(JSON_NODE_READER.readTree(l));
+                } catch (JsonProcessingException e) {
+                    throw new RuntimeException(e);
+                }
+            });
+        }
+        return result;
     }
 
-    @Test
-    public void test() throws Exception {
-        LOGGER.info(String.format("Starting testcase #%d: %s", testcaseId, groupByClause));
+    protected void runTestCase(int testcaseId, String testcaseDescription, String sqlQuery, String sqlppQuery)
+            throws Exception {
+        LOGGER.info(String.format("Starting testcase #%d: %s", testcaseId, testcaseDescription));
 
         LOGGER.info("Running SQL");
         LOGGER.info(sqlQuery);
@@ -199,166 +166,22 @@ public class SqlppRQGGroupingSetsIT {
         }
 
         boolean eq = TestHelper.equalJson(sqlResult, sqlppResult, false);
+
+        File sqlResultFile = writeResult(sqlResult, testcaseId, "sql", testcaseDescription);
+        File sqlppResultFile = writeResult(sqlppResult, testcaseId, "sqlpp", testcaseDescription);
+
         if (!eq) {
-            File sqlResultFile = writeResult(sqlResult, "sql");
-            File sqlppResultFile = writeResult(sqlppResult, "sqlpp");
+            /*
+            File sqlResultFile = writeResult(sqlResult, testcaseId, "sql", testcaseDescription);
+            File sqlppResultFile = writeResult(sqlppResult, testcaseId, "sqlpp", testcaseDescription);
+            */
 
             Assert.fail(String.format("Results do not match.\n%s\n%s", sqlResultFile.getCanonicalPath(),
                     sqlppResultFile.getCanonicalPath()));
         }
     }
 
-    private static TestQuery generateQuery(int testcaseId, Random random) {
-        Set<String> allColumns = new LinkedHashSet<>();
-        List<String> groupingElements = new ArrayList<>();
-        int nElements = 1 + random.nextInt(3);
-
-        int rollupCount = 0, cubeCount = 0, groupingSetsCount = 0;
-        for (int i = 0; i < nElements; i++) {
-            String prefix;
-            int minItems, maxItems;
-            boolean allowSimpleSubgroup = false, allowComplexSubgroup = false;
-
-            switch (random.nextInt(6)) {
-                case 0:
-                case 1:
-                    prefix = "";
-                    minItems = 1;
-                    maxItems = 4;
-                    break;
-                case 2:
-                case 3:
-                    if (isSingleElementLimitReached(rollupCount, ROLLUP_ELEMENT_LIMIT)
-                            || isMultiElementLimitReached(rollupCount, cubeCount, groupingSetsCount)) {
-                        // skip this element
-                        nElements++;
-                        continue;
-                    }
-                    prefix = "ROLLUP";
-                    minItems = 1;
-                    maxItems = 4;
-                    allowSimpleSubgroup = true;
-                    rollupCount++;
-                    break;
-                case 4:
-                    if (isSingleElementLimitReached(cubeCount, CUBE_ELEMENT_LIMIT)
-                            || isMultiElementLimitReached(rollupCount, cubeCount, groupingSetsCount)) {
-                        // skip this element
-                        nElements++;
-                        continue;
-                    }
-                    prefix = "CUBE";
-                    minItems = 2;
-                    maxItems = 2;
-                    allowSimpleSubgroup = true; // allowed, not actually used, because we set maxItems to 2
-                    cubeCount++;
-                    break;
-                case 5:
-                    if (isSingleElementLimitReached(groupingSetsCount, GROUPING_SETS_ELEMENT_LIMIT)
-                            || isMultiElementLimitReached(rollupCount, cubeCount, groupingSetsCount)) {
-                        // skip this element
-                        nElements++;
-                        continue;
-                    }
-                    prefix = "GROUPING SETS";
-                    minItems = 0;
-                    maxItems = 3;
-                    allowSimpleSubgroup = allowComplexSubgroup = true;
-                    groupingSetsCount++;
-                    break;
-                default:
-                    throw new IllegalStateException();
-            }
-            int nItems = minItems + random.nextInt(maxItems - minItems + 1);
-            List<String> elementItems =
-                    nItems == 0 ? Collections.emptyList() : randomize(GROUPBY_COLUMNS, random).subList(0, nItems);
-            allColumns.addAll(elementItems);
-            if (allowSimpleSubgroup && nItems >= 3 && random.nextInt(2) == 0) {
-                makeSubgroup(elementItems, random, allowComplexSubgroup);
-            }
-            String elementItemsText = elementItems.isEmpty() ? "()" : String.join(",", elementItems);
-            String element = String.format("%s(%s)", prefix, elementItemsText);
-            groupingElements.add(element);
-        }
-
-        StringBuilder selectClause = new StringBuilder();
-        for (String col : allColumns) {
-            selectClause.append(col).append(',');
-        }
-        for (String col : allColumns) {
-            selectClause.append(String.format("GROUPING(%s) AS grp_%s", col, col)).append(',');
-        }
-        if (allColumns.size() > 1) {
-            selectClause.append(String.format("GROUPING(%s) AS grp", String.join(",", randomize(allColumns, random))))
-                    .append(',');
-        }
-        String agg = String.format("SUM(%s)", UNIQUE_1);
-        if (random.nextInt(3) == 0) {
-            int filterLimit = 1 + random.nextInt(9999);
-            agg = String.format("%s FILTER(WHERE %s < %d)", agg, UNIQUE_2, filterLimit);
-        }
-        selectClause.append(String.format("%s AS agg_sum", agg));
-
-        String groupingElementText = groupingElements.isEmpty() ? "()" : String.join(",", groupingElements);
-        String groupbyClause = String.format("GROUP BY %s", groupingElementText);
-
-        String orderbyClauseSql = generateOrderBy(allColumns, true);
-        String orderbyClauseSqlpp = generateOrderBy(allColumns, false);
-
-        String queryTemplate = "SELECT %s FROM %s %s %s";
-        String sqlQuery = String.format(queryTemplate, selectClause, TABLE_NAME, groupbyClause, orderbyClauseSql);
-        String sqlppQuery = String.format(queryTemplate, selectClause, TABLE_NAME, groupbyClause, orderbyClauseSqlpp);
-
-        LOGGER.info(String.format("Testcase #%d: %s", testcaseId, groupbyClause));
-
-        return new TestQuery(sqlQuery, sqlppQuery, groupbyClause);
-    }
-
-    private static boolean isSingleElementLimitReached(int elementCount, int limit) {
-        return elementCount >= limit;
-    }
-
-    private static boolean isMultiElementLimitReached(int elementCount1, int elementCount2, int elementCount3) {
-        return elementCount1 + elementCount2 + elementCount3 >= MULTI_ELEMENT_LIMIT;
-    }
-
-    private static String generateOrderBy(Set<String> allColumns, boolean insertNullsFirst) {
-        if (allColumns.isEmpty()) {
-            return "";
-        }
-        return "ORDER BY " + allColumns.stream().map(c -> c + (insertNullsFirst ? " NULLS FIRST" : ""))
-                .collect(Collectors.joining(", "));
-    }
-
-    private static void makeSubgroup(List<String> elementColumns, Random random, boolean allowComplexSubgroup) {
-        // rewrite (a, b, c, ... ) into (a,(b,c), ...) or (a,ROLLUP(b,c), ...)
-        String subgroupSpecifier = "";
-        if (allowComplexSubgroup && random.nextInt(2) == 0) {
-            subgroupSpecifier = "ROLLUP";
-        }
-        int start = random.nextInt(elementColumns.size() - 1);
-        List<String> sublist = elementColumns.subList(start, start + 2);
-        String s = String.format("%s(%s)", subgroupSpecifier, String.join(",", sublist));
-        sublist.clear();
-        sublist.add(s);
-    }
-
-    private ArrayNode asJson(ExtractedResult aresult) throws IOException {
-        ArrayNode result = (ArrayNode) JSON_NODE_READER.createArrayNode();
-        try (BufferedReader reader =
-                new BufferedReader(new InputStreamReader(aresult.getResult(), StandardCharsets.UTF_8))) {
-            reader.lines().forEachOrdered(l -> {
-                try {
-                    result.add(JSON_NODE_READER.readTree(l));
-                } catch (JsonProcessingException e) {
-                    throw new RuntimeException(e);
-                }
-            });
-        }
-        return result;
-    }
-
-    private ArrayNode asJson(ResultSet rs) throws SQLException {
+    protected ArrayNode asJson(ResultSet rs) throws SQLException {
         ResultSetMetaData rsmd = rs.getMetaData();
         int rsColumnCount = rsmd.getColumnCount();
         ArrayNode result = (ArrayNode) JSON_NODE_READER.createArrayNode();
@@ -401,7 +224,7 @@ public class SqlppRQGGroupingSetsIT {
         return result;
     }
 
-    private static void loadAsterixData() throws Exception {
+    protected static void loadAsterixData() throws Exception {
         String tableTypeName = TABLE_NAME + "Type";
         String createTypeStmtText =
                 String.format("CREATE TYPE %s AS CLOSED { %s }", tableTypeName,
@@ -424,7 +247,7 @@ public class SqlppRQGGroupingSetsIT {
         testExecutor.executeSqlppUpdateOrDdl(loadStmtText, TestCaseContext.OutputFormat.ADM);
     }
 
-    private static void loadSQLData() throws SQLException, IOException {
+    protected static void loadSQLData() throws SQLException, IOException {
         String createTableStmtText = String.format("CREATE TEMPORARY TABLE %s (%s)", TABLE_NAME, TABLE_SCHEMA.entrySet()
                 .stream().map(e -> e.getKey() + ' ' + getSQLType(e.getValue())).collect(Collectors.joining(",")));
 
@@ -452,7 +275,7 @@ public class SqlppRQGGroupingSetsIT {
         }
     }
 
-    private static LinkedHashMap<String, JDBCType> createTableSchema() {
+    protected static LinkedHashMap<String, JDBCType> createTableSchema() {
         LinkedHashMap<String, JDBCType> schema = new LinkedHashMap<>();
         schema.put(UNIQUE_1, JDBCType.INTEGER);
         schema.put(UNIQUE_2, JDBCType.INTEGER);
@@ -473,7 +296,7 @@ public class SqlppRQGGroupingSetsIT {
         return schema;
     }
 
-    private static String getSQLType(JDBCType type) {
+    protected static String getSQLType(JDBCType type) {
         String suffix = "";
         if (type == JDBCType.VARCHAR) {
             suffix = "(256)";
@@ -481,7 +304,7 @@ public class SqlppRQGGroupingSetsIT {
         return type.getName() + suffix;
     }
 
-    private static IAType getAsterixType(JDBCType type) {
+    protected static IAType getAsterixType(JDBCType type) {
         switch (type) {
             case INTEGER:
                 return BuiltinType.AINT32;
@@ -492,7 +315,7 @@ public class SqlppRQGGroupingSetsIT {
         }
     }
 
-    private static void setColumnValue(PreparedStatement stmt, int jdbcParamIdx, JDBCType type, String value)
+    protected static void setColumnValue(PreparedStatement stmt, int jdbcParamIdx, JDBCType type, String value)
             throws SQLException {
         switch (type) {
             case INTEGER:
@@ -506,17 +329,30 @@ public class SqlppRQGGroupingSetsIT {
         }
     }
 
-    private static <T> List<T> randomize(Collection<T> input, Random random) {
+    protected File writeResult(ArrayNode result, int testcaseId, String resultKind, String comment) throws IOException {
+        String outFileName = String.format("%d.%s.txt", testcaseId, resultKind);
+        File outFile = new File(RESULT_OUTPUT_DIR.toFile(), outFileName);
+        try (PrintWriter pw = new PrintWriter(outFile, StandardCharsets.UTF_8.name())) {
+            pw.print("---");
+            pw.println(comment);
+            for (int i = 0, ln = result.size(); i < ln; i++) {
+                pw.println(ResultExtractor.prettyPrint(result.get(i)));
+            }
+        }
+        return outFile;
+    }
+
+    protected static <T> List<T> randomize(Collection<T> input, Random random) {
         List<T> output = new ArrayList<>(input);
         Collections.shuffle(output, random);
         return output;
     }
 
-    private static String getConfigurationPropertyName(String propertyName) {
-        return String.format("%s.%s", SqlppRQGGroupingSetsIT.class.getSimpleName(), propertyName);
+    protected static String getConfigurationPropertyName(Class<?> testClass, String propertyName) {
+        return String.format("%s.%s", testClass.getSimpleName(), propertyName);
     }
 
-    private static long getLongConfigurationProperty(String propertyName, long defValue) {
+    protected static long getLongConfigurationProperty(String propertyName, long defValue) {
         String textValue = System.getProperty(propertyName);
         if (textValue == null) {
             return defValue;
@@ -530,43 +366,17 @@ public class SqlppRQGGroupingSetsIT {
         }
     }
 
-    private File writeResult(ArrayNode result, String resultKind) throws IOException {
-        String outFileName = String.format("%d.%s.txt", testcaseId, resultKind);
-        File outFile = new File(RESULT_OUTPUT_DIR.toFile(), outFileName);
-        try (PrintWriter pw = new PrintWriter(outFile, StandardCharsets.UTF_8.name())) {
-            pw.print("---");
-            pw.println(groupByClause);
-            for (int i = 0, ln = result.size(); i < ln; i++) {
-                pw.println(ResultExtractor.prettyPrint(result.get(i)));
-            }
-        }
-        return outFile;
-    }
-
-    @BeforeClass
-    public static void setUp() throws Exception {
-        startAsterix();
-        startPostgres();
-        FileUtils.forceMkdir(RESULT_OUTPUT_DIR.toFile());
-    }
-
-    @AfterClass
-    public static void tearDown() throws Exception {
-        stopPostgres();
-        stopAsterix();
-    }
-
-    private static void startAsterix() throws Exception {
+    protected static void startAsterix() throws Exception {
         testExecutor = new TestExecutor();
         LangExecutionUtil.setUp(TEST_CONFIG_FILE_NAME, testExecutor);
         loadAsterixData();
     }
 
-    private static void stopAsterix() throws Exception {
+    protected static void stopAsterix() throws Exception {
         LangExecutionUtil.tearDown();
     }
 
-    private static void startPostgres() throws SQLException, IOException {
+    protected static void startPostgres() throws SQLException, IOException {
         if (!Boolean.parseBoolean(System.getenv(TESTCONTAINERS_RYUK_DISABLED))) {
             throw new IllegalStateException(
                     String.format("Set environment variable %s=%s", TESTCONTAINERS_RYUK_DISABLED, true));
@@ -579,7 +389,7 @@ public class SqlppRQGGroupingSetsIT {
         loadSQLData();
     }
 
-    private static void stopPostgres() {
+    protected static void stopPostgres() {
         LOGGER.info("Stopping Postgres");
         if (stmt != null) {
             try {
@@ -603,16 +413,4 @@ public class SqlppRQGGroupingSetsIT {
             }
         }
     }
-
-    private static class TestQuery {
-        final String sqlQuery;
-        final String sqlppQuery;
-        final String groupbyClause;
-
-        TestQuery(String sqlQuery, String sqlppQuery, String groupbyClause) {
-            this.sqlQuery = sqlQuery;
-            this.sqlppQuery = sqlppQuery;
-            this.groupbyClause = groupbyClause;
-        }
-    }
 }
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/rightouterjoin/roj-01-core.1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/rightouterjoin/roj-01-core.1.sqlpp
new file mode 100644
index 0000000..a766ad0
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/rightouterjoin/roj-01-core.1.sqlpp
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+/*
+ * Test basic RIGHT OUTER JOIN cases
+ */
+
+drop  dataverse test if exists;
+create  dataverse test;
+
+use test;
+
+create type tenkType as closed {
+  unique1         : integer,
+  unique2         : integer,
+  two             : integer,
+  four            : integer,
+  ten             : integer,
+  twenty          : integer,
+  hundred         : integer,
+  thousand        : integer,
+  twothousand     : integer,
+  fivethous       : integer,
+  tenthous        : integer,
+  odd100          : integer,
+  even100         : integer,
+  stringu1        : string,
+  stringu2        : string,
+  string4         : string
+};
+
+create dataset tenk(tenkType) primary key unique2;
+
+SELECT
+  if_missing(t0.unique1, null) AS t0_unique1,
+  if_missing(t1.unique1, null) AS t1_unique1
+FROM (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 2
+) t0
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 4
+) t1 ON t0.unique2 = t1.unique2
+ORDER BY t0_unique1, t1_unique1;
+
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/rightouterjoin/roj-01-core.2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/rightouterjoin/roj-01-core.2.sqlpp
new file mode 100644
index 0000000..03ca3cd
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/rightouterjoin/roj-01-core.2.sqlpp
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+/*
+ * Test basic RIGHT OUTER JOIN cases
+ */
+
+drop  dataverse test if exists;
+create  dataverse test;
+
+use test;
+
+create type tenkType as closed {
+  unique1         : integer,
+  unique2         : integer,
+  two             : integer,
+  four            : integer,
+  ten             : integer,
+  twenty          : integer,
+  hundred         : integer,
+  thousand        : integer,
+  twothousand     : integer,
+  fivethous       : integer,
+  tenthous        : integer,
+  odd100          : integer,
+  even100         : integer,
+  stringu1        : string,
+  stringu2        : string,
+  string4         : string
+};
+
+create dataset tenk(tenkType) primary key unique2;
+
+SELECT
+  if_missing(t0.unique1, null) AS t0_unique1,
+  if_missing(t1.unique1, null) AS t1_unique1,
+  if_missing(t2.unique1, null) AS t2_unique1
+FROM (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 2
+) t0
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 4
+) t1 ON t0.unique2 = t1.unique2
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 6
+) t2 ON t1.unique2 = t2.unique2
+ORDER BY t0_unique1, t1_unique1, t2_unique1;
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/rightouterjoin/roj-01-core.1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/rightouterjoin/roj-01-core.1.plan
new file mode 100644
index 0000000..274b951
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/rightouterjoin/roj-01-core.1.plan
@@ -0,0 +1,28 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- SORT_MERGE_EXCHANGE [$#1(ASC), $#2(ASC) ]  |PARTITIONED|
+          -- STABLE_SORT [$#1(ASC), $#2(ASC)]  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ASSIGN  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- HYBRID_HASH_JOIN [$$91][$$92]  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ASSIGN  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- BTREE_SEARCH  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- ASSIGN  |PARTITIONED|
+                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ASSIGN  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- BTREE_SEARCH  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- ASSIGN  |PARTITIONED|
+                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/rightouterjoin/roj-01-core.2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/rightouterjoin/roj-01-core.2.plan
new file mode 100644
index 0000000..2061d8d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/rightouterjoin/roj-01-core.2.plan
@@ -0,0 +1,44 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- ASSIGN  |PARTITIONED|
+        -- SORT_MERGE_EXCHANGE [$#1(ASC), $#2(ASC), $#3(ASC) ]  |PARTITIONED|
+          -- STABLE_SORT [$#1(ASC), $#2(ASC), $#3(ASC)]  |PARTITIONED|
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              -- STREAM_PROJECT  |PARTITIONED|
+                -- ASSIGN  |PARTITIONED|
+                  -- STREAM_PROJECT  |PARTITIONED|
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      -- HYBRID_HASH_JOIN [$$154][$$155]  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ASSIGN  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- BTREE_SEARCH  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- ASSIGN  |PARTITIONED|
+                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ASSIGN  |PARTITIONED|
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                -- ASSIGN  |PARTITIONED|
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- HYBRID_HASH_JOIN [$$155][$$156]  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- STREAM_PROJECT  |PARTITIONED|
+                                            -- ASSIGN  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- BTREE_SEARCH  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- ASSIGN  |PARTITIONED|
+                                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- STREAM_PROJECT  |PARTITIONED|
+                                            -- ASSIGN  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- BTREE_SEARCH  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- ASSIGN  |PARTITIONED|
+                                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.1.ddl.sqlpp
similarity index 55%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.1.ddl.sqlpp
index f4b84f5..97e2a5e 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.1.ddl.sqlpp
@@ -17,11 +17,32 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+/*
+ * Test basic RIGHT OUTER JOIN cases
+ */
+
+drop  dataverse test if exists;
+create  dataverse test;
 
-public enum JoinType {
+use test;
 
-    INNER,
-    LEFTOUTER
+create type tenkType as closed {
+  unique1         : integer,
+  unique2         : integer,
+  two             : integer,
+  four            : integer,
+  ten             : integer,
+  twenty          : integer,
+  hundred         : integer,
+  thousand        : integer,
+  twothousand     : integer,
+  fivethous       : integer,
+  tenthous        : integer,
+  odd100          : integer,
+  even100         : integer,
+  stringu1        : string,
+  stringu2        : string,
+  string4         : string
+};
 
-}
+create dataset tenk(tenkType) primary key unique2;
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.2.update.sqlpp
similarity index 86%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.2.update.sqlpp
index f4b84f5..2d7e768 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.2.update.sqlpp
@@ -17,11 +17,6 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+use test;
 
-public enum JoinType {
-
-    INNER,
-    LEFTOUTER
-
-}
+load  dataset tenk using localfs ((`path`=`asterix_nc1://data/tenk.tbl`),(`format`=`delimited-text`),(`delimiter`=`|`));
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.3.query.sqlpp
similarity index 71%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.3.query.sqlpp
index f4b84f5..04c6b89 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.3.query.sqlpp
@@ -17,11 +17,17 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+/* RIGHT;c */
 
-public enum JoinType {
+use test;
 
-    INNER,
-    LEFTOUTER
-
-}
+SELECT
+  if_missing(t0.unique1, null) AS t0_unique1,
+  if_missing(t1.unique1, null) AS t1_unique1
+FROM (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 2
+) t0
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 4
+) t1 ON t0.unique2 = t1.unique2
+ORDER BY t0_unique1, t1_unique1;
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.4.query.sqlpp
similarity index 62%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.4.query.sqlpp
index f4b84f5..c74d919 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.4.query.sqlpp
@@ -17,11 +17,21 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+/* RIGHT,RIGHT;c */
 
-public enum JoinType {
+use test;
 
-    INNER,
-    LEFTOUTER
-
-}
+SELECT
+  if_missing(t0.unique1, null) AS t0_unique1,
+  if_missing(t1.unique1, null) AS t1_unique1,
+  if_missing(t2.unique1, null) AS t2_unique1
+FROM (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 2
+) t0
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 4
+) t1 ON t0.unique2 = t1.unique2
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 6
+) t2 ON t1.unique2 = t2.unique2
+ORDER BY t0_unique1, t1_unique1, t2_unique1;
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.5.query.sqlpp
similarity index 55%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.5.query.sqlpp
index f4b84f5..75be058 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.5.query.sqlpp
@@ -17,11 +17,26 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+/* RIGHT,RIGHT,RIGHT;c */
 
-public enum JoinType {
+use test;
 
-    INNER,
-    LEFTOUTER
+SELECT
+  if_missing(t0.unique1, null) AS t0_unique1,
+  if_missing(t1.unique1, null) AS t1_unique1,
+  if_missing(t2.unique1, null) AS t2_unique1,
+  if_missing(t3.unique1, null) AS t3_unique1
+FROM (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 2
+) t0
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 4
+) t1 ON t0.unique2 = t1.unique2
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 6
+) t2 ON t1.unique2 = t2.unique2
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 8
+) t3 ON t2.unique2 = t3.unique2
+ORDER BY t0_unique1, t1_unique1, t2_unique1, t3_unique1;
 
-}
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.6.query.sqlpp
similarity index 62%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.6.query.sqlpp
index f4b84f5..832a8ae 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.6.query.sqlpp
@@ -17,11 +17,24 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+/* LEFT,RIGHT;s */
+
+use test;
+
+SELECT
+  if_missing(t0.unique1, null) AS t0_unique1,
+  if_missing(t1.unique1, null) AS t1_unique1,
+  if_missing(t2.unique1, null) AS t2_unique1
+FROM (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 2
+) t0
+LEFT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 4
+) t1 ON t0.unique2 = t1.unique2
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 6
+) t2 ON t0.unique2 = t2.unique2
+ORDER BY t0_unique1, t1_unique1, t2_unique1;
 
-public enum JoinType {
 
-    INNER,
-    LEFTOUTER
 
-}
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.7.query.sqlpp
similarity index 62%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.7.query.sqlpp
index f4b84f5..d7ab2a6 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.7.query.sqlpp
@@ -17,11 +17,21 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+/* RIGHT,LEFT;s */
 
-public enum JoinType {
+use test;
 
-    INNER,
-    LEFTOUTER
-
-}
+SELECT
+  if_missing(t0.unique1, null) AS t0_unique1,
+  if_missing(t1.unique1, null) AS t1_unique1,
+  if_missing(t2.unique1, null) AS t2_unique1
+FROM (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 2
+) t0
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 4
+) t1 ON t0.unique2 = t1.unique2
+LEFT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 6
+) t2 ON t0.unique2 = t2.unique2
+ORDER BY t0_unique1, t1_unique1, t2_unique1;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.1.ddl.sqlpp
new file mode 100644
index 0000000..93f35eb
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.1.ddl.sqlpp
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+/*
+ * Test basic RIGHT OUTER JOIN cases
+ */
+
+drop  dataverse test if exists;
+create  dataverse test;
+
+use test;
+
+create type tenkType as closed {
+  unique1         : integer,
+  unique2         : integer,
+  two             : integer,
+  four            : integer,
+  ten             : integer,
+  twenty          : integer,
+  hundred         : integer,
+  thousand        : integer,
+  twothousand     : integer,
+  fivethous       : integer,
+  tenthous        : integer,
+  odd100          : integer,
+  even100         : integer,
+  stringu1        : string,
+  stringu2        : string,
+  string4         : string
+};
+
+create dataset tenk(tenkType) primary key unique2;
+
+create type tenkTypeNested1000 as open {
+  thousand        : integer
+};
+
+create dataset tenkNested1000(tenkTypeNested1000) primary key thousand;
+
+create type tenkTypeNested5000 as open {
+  fivethous        : integer
+};
+
+create dataset tenkNested5000(tenkTypeNested5000) primary key fivethous;
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.10.query.sqlpp
similarity index 55%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.10.query.sqlpp
index f4b84f5..a80b614 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.10.query.sqlpp
@@ -17,11 +17,22 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+/* SELECT v.*; RIGHT,RIGHT,RIGHT; */
 
-public enum JoinType {
+use test;
 
-    INNER,
-    LEFTOUTER
+SELECT t0.*, t1.*, t2.*, t3.*
+FROM (
+  SELECT unique1 as t0_unique1, unique2 as t0_unique2 FROM tenk WHERE unique2 < 2
+) t0
+RIGHT JOIN (
+  SELECT unique1 as t1_unique1, unique2 as t1_unique2 FROM tenk WHERE unique2 < 4
+) t1 ON t0.t0_unique2 = t1.t1_unique2
+RIGHT JOIN (
+  SELECT unique1 as t2_unique1, unique2 as t2_unique2 FROM tenk WHERE unique2 < 6
+) t2 ON t1.t1_unique2 = t2.t2_unique2
+RIGHT JOIN (
+  SELECT unique1 as t3_unique1, unique2 as t3_unique2 FROM tenk WHERE unique2 < 8
+) t3 ON t2.t2_unique2 = t3.t3_unique2
+ORDER BY t0.t0_unique1, t1.t1_unique1, t2.t2_unique1, t3.t3_unique1;
 
-}
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.2.update.sqlpp
similarity index 64%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.2.update.sqlpp
index f4b84f5..e135a7b 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.2.update.sqlpp
@@ -17,11 +17,20 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+use test;
 
-public enum JoinType {
+load  dataset tenk using localfs ((`path`=`asterix_nc1://data/tenk.tbl`),(`format`=`delimited-text`),(`delimiter`=`|`));
 
-    INNER,
-    LEFTOUTER
+insert into tenkNested1000 ((
+  select thousand, g
+  from tenk
+  where unique2 > 1
+  group by thousand group as g(unique1 as unique1, unique2 as unique2)
+));
 
-}
+insert into tenkNested5000 ((
+  select fivethous, g
+  from tenk
+  where unique2 > 1
+  group by fivethous group as g(unique1 as unique1, unique2 as unique2)
+));
\ No newline at end of file
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.3.query.sqlpp
similarity index 72%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.3.query.sqlpp
index f4b84f5..2062cd8 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.3.query.sqlpp
@@ -17,11 +17,19 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+use test;
 
-public enum JoinType {
+/* UNNEST, RIGHT JOIN */
 
-    INNER,
-    LEFTOUTER
+use test;
 
-}
+SELECT
+  if_missing(t0.unique1, null) AS t0_unique1,
+  if_missing(t1.unique1, null) AS t1_unique1
+
+FROM tenkNested1000 tn
+UNNEST tn.g t0
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 4
+) t1 ON t0.unique2 = t1.unique2
+ORDER BY t0_unique1, t1_unique1;
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.4.query.sqlpp
similarity index 61%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.4.query.sqlpp
index f4b84f5..4fbec97 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.4.query.sqlpp
@@ -17,11 +17,24 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+use test;
 
-public enum JoinType {
+/* RIGHT JOIN, LEFT UNNEST */
 
-    INNER,
-    LEFTOUTER
+use test;
 
-}
+SELECT
+    if_missing(t0.fivethous, null) AS t0_fivethous,
+    if_missing(t0_n.unique1, null) AS t0_n_unique1,
+    if_missing(t1.fivethous, null) AS t1_fivethous,
+    if_missing(t1.unique1, null) AS t1_unique1
+FROM (
+  SELECT fivethous, g
+  FROM tenkNested5000
+  WHERE fivethous < 1
+) t0
+RIGHT JOIN (
+  SELECT fivethous, unique1 FROM tenk WHERE fivethous < 2
+) t1 ON t0.fivethous = t1.fivethous
+LEFT UNNEST t0.g AS t0_n
+ORDER BY t1_fivethous, t1_unique1, t0_n_unique1;
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.5.query.sqlpp
similarity index 66%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.5.query.sqlpp
index f4b84f5..bc79bba 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.5.query.sqlpp
@@ -17,11 +17,22 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+/* Use statement parameters */
 
-public enum JoinType {
+// requesttype=application/json
 
-    INNER,
-    LEFTOUTER
+// param $p2:json=2
+// param $p4:json=4
 
-}
+use test;
+
+SELECT
+  if_missing(t0.unique1, null) AS t0_unique1,
+  if_missing(t1.unique1, null) AS t1_unique1
+FROM (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < $p2
+) t0
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < $p4
+) t1 ON t0.unique2 = t1.unique2
+ORDER BY t0_unique1, t1_unique1;
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.6.ddl.sqlpp
similarity index 68%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.6.ddl.sqlpp
index f4b84f5..1fe402c 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.6.ddl.sqlpp
@@ -17,11 +17,20 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
-
-public enum JoinType {
+/*
+ * Test basic RIGHT OUTER JOIN cases
+ */
 
-    INNER,
-    LEFTOUTER
+use test;
 
-}
+create function f1(p2, p4) {
+  SELECT
+    if_missing(t0.unique1, null) AS t0_unique1,
+    if_missing(t1.unique1, null) AS t1_unique1
+  FROM (
+    SELECT unique1, unique2 FROM tenk WHERE unique2 < p2
+  ) t0
+  RIGHT JOIN (
+    SELECT unique1, unique2 FROM tenk WHERE unique2 < p4
+  ) t1 ON t0.unique2 = t1.unique2
+};
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.7.query.sqlpp
similarity index 89%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.7.query.sqlpp
index f4b84f5..bb12774 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.7.query.sqlpp
@@ -17,11 +17,8 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+use test;
 
-public enum JoinType {
-
-    INNER,
-    LEFTOUTER
-
-}
+SELECT t0_unique1, t1_unique1
+FROM f1(2, 4) t
+ORDER BY t0_unique1, t1_unique1;
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.8.query.sqlpp
similarity index 63%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.8.query.sqlpp
index f4b84f5..fa56a4d 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.8.query.sqlpp
@@ -17,11 +17,22 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+/* SELECT *; RIGHT,RIGHT,RIGHT; */
 
-public enum JoinType {
+use test;
 
-    INNER,
-    LEFTOUTER
+SELECT *
+FROM (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 2
+) t0
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 4
+) t1 ON t0.unique2 = t1.unique2
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 6
+) t2 ON t1.unique2 = t2.unique2
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 8
+) t3 ON t2.unique2 = t3.unique2
+ORDER BY t0.unique1, t1.unique1, t2.unique1, t3.unique1;
 
-}
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.9.query.sqlpp
similarity index 62%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.9.query.sqlpp
index f4b84f5..ef0d112 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.9.query.sqlpp
@@ -17,11 +17,22 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+/* SELECT t0,t1,t2,t3; RIGHT,RIGHT,RIGHT; */
 
-public enum JoinType {
+use test;
 
-    INNER,
-    LEFTOUTER
+SELECT t0, t1, t2, t3
+FROM (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 2
+) t0
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 4
+) t1 ON t0.unique2 = t1.unique2
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 6
+) t2 ON t1.unique2 = t2.unique2
+RIGHT JOIN (
+  SELECT unique1, unique2 FROM tenk WHERE unique2 < 8
+) t3 ON t2.unique2 = t3.unique2
+ORDER BY t0.unique1, t1.unique1, t2.unique1, t3.unique1;
 
-}
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-03-negative/roj-03-negative.1.ddl.sqlpp
similarity index 50%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-03-negative/roj-03-negative.1.ddl.sqlpp
index f4b84f5..4c066b5 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-03-negative/roj-03-negative.1.ddl.sqlpp
@@ -17,11 +17,38 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+/*
+ * Test basic RIGHT OUTER JOIN cases
+ */
+
+drop  dataverse test if exists;
+create  dataverse test;
+
+use test;
+
+create type tenkType as closed {
+  unique1         : integer,
+  unique2         : integer,
+  two             : integer,
+  four            : integer,
+  ten             : integer,
+  twenty          : integer,
+  hundred         : integer,
+  thousand        : integer,
+  twothousand     : integer,
+  fivethous       : integer,
+  tenthous        : integer,
+  odd100          : integer,
+  even100         : integer,
+  stringu1        : string,
+  stringu2        : string,
+  string4         : string
+};
 
-public enum JoinType {
+create dataset tenk(tenkType) primary key unique2;
 
-    INNER,
-    LEFTOUTER
+create type tenkTypeNested1000 as open {
+  thousand        : integer
+};
 
-}
+create dataset tenkNested1000(tenkTypeNested1000) primary key thousand;
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-03-negative/roj-03-negative.2.query.sqlpp
similarity index 72%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-03-negative/roj-03-negative.2.query.sqlpp
index f4b84f5..6b4d1ee 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-03-negative/roj-03-negative.2.query.sqlpp
@@ -17,11 +17,18 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
-
-public enum JoinType {
+/*
+ * Expected result: Failure. Cannot use RIGHT JOIN if it's left branch has free variables.
+ */
 
-    INNER,
-    LEFTOUTER
+use test;
 
-}
+SELECT tn, x
+FROM tenkNested1000 tn
+LET x = (
+  SELECT t0.unique1, t1.unique1
+  FROM tn.g t0
+  RIGHT JOIN (
+    SELECT unique1, unique2 FROM tenk WHERE unique2 < 4
+  ) t1 ON t0.unique2 = t1.unique2
+);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-01-core/roj-01-core.3.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-01-core/roj-01-core.3.adm
new file mode 100644
index 0000000..e7f5f55
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-01-core/roj-01-core.3.adm
@@ -0,0 +1,4 @@
+{ "t0_unique1": null, "t1_unique1": 3420 }
+{ "t0_unique1": null, "t1_unique1": 9850 }
+{ "t0_unique1": 1891, "t1_unique1": 1891 }
+{ "t0_unique1": 8800, "t1_unique1": 8800 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-01-core/roj-01-core.4.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-01-core/roj-01-core.4.adm
new file mode 100644
index 0000000..d3b4a40
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-01-core/roj-01-core.4.adm
@@ -0,0 +1,6 @@
+{ "t0_unique1": null, "t1_unique1": null, "t2_unique1": 7164 }
+{ "t0_unique1": null, "t1_unique1": null, "t2_unique1": 8009 }
+{ "t0_unique1": null, "t1_unique1": 3420, "t2_unique1": 3420 }
+{ "t0_unique1": null, "t1_unique1": 9850, "t2_unique1": 9850 }
+{ "t0_unique1": 1891, "t1_unique1": 1891, "t2_unique1": 1891 }
+{ "t0_unique1": 8800, "t1_unique1": 8800, "t2_unique1": 8800 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-01-core/roj-01-core.5.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-01-core/roj-01-core.5.adm
new file mode 100644
index 0000000..37c7036
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-01-core/roj-01-core.5.adm
@@ -0,0 +1,8 @@
+{ "t0_unique1": null, "t1_unique1": null, "t2_unique1": null, "t3_unique1": 5057 }
+{ "t0_unique1": null, "t1_unique1": null, "t2_unique1": null, "t3_unique1": 6701 }
+{ "t0_unique1": null, "t1_unique1": null, "t2_unique1": 7164, "t3_unique1": 7164 }
+{ "t0_unique1": null, "t1_unique1": null, "t2_unique1": 8009, "t3_unique1": 8009 }
+{ "t0_unique1": null, "t1_unique1": 3420, "t2_unique1": 3420, "t3_unique1": 3420 }
+{ "t0_unique1": null, "t1_unique1": 9850, "t2_unique1": 9850, "t3_unique1": 9850 }
+{ "t0_unique1": 1891, "t1_unique1": 1891, "t2_unique1": 1891, "t3_unique1": 1891 }
+{ "t0_unique1": 8800, "t1_unique1": 8800, "t2_unique1": 8800, "t3_unique1": 8800 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-01-core/roj-01-core.6.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-01-core/roj-01-core.6.adm
new file mode 100644
index 0000000..ed96b75
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-01-core/roj-01-core.6.adm
@@ -0,0 +1,6 @@
+{ "t0_unique1": null, "t1_unique1": null, "t2_unique1": 3420 }
+{ "t0_unique1": null, "t1_unique1": null, "t2_unique1": 7164 }
+{ "t0_unique1": null, "t1_unique1": null, "t2_unique1": 8009 }
+{ "t0_unique1": null, "t1_unique1": null, "t2_unique1": 9850 }
+{ "t0_unique1": 1891, "t1_unique1": 1891, "t2_unique1": 1891 }
+{ "t0_unique1": 8800, "t1_unique1": 8800, "t2_unique1": 8800 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-01-core/roj-01-core.7.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-01-core/roj-01-core.7.adm
new file mode 100644
index 0000000..5984a21
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-01-core/roj-01-core.7.adm
@@ -0,0 +1,4 @@
+{ "t0_unique1": null, "t1_unique1": 3420, "t2_unique1": null }
+{ "t0_unique1": null, "t1_unique1": 9850, "t2_unique1": null }
+{ "t0_unique1": 1891, "t1_unique1": 1891, "t2_unique1": 1891 }
+{ "t0_unique1": 8800, "t1_unique1": 8800, "t2_unique1": 8800 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.10.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.10.adm
new file mode 100644
index 0000000..edbaaf0
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.10.adm
@@ -0,0 +1,8 @@
+{ "t3_unique1": 5057, "t3_unique2": 6 }
+{ "t3_unique1": 6701, "t3_unique2": 7 }
+{ "t3_unique1": 7164, "t3_unique2": 4, "t2_unique1": 7164, "t2_unique2": 4 }
+{ "t3_unique1": 8009, "t3_unique2": 5, "t2_unique1": 8009, "t2_unique2": 5 }
+{ "t3_unique1": 3420, "t3_unique2": 2, "t2_unique1": 3420, "t2_unique2": 2, "t1_unique1": 3420, "t1_unique2": 2 }
+{ "t3_unique1": 9850, "t3_unique2": 3, "t2_unique1": 9850, "t2_unique2": 3, "t1_unique1": 9850, "t1_unique2": 3 }
+{ "t3_unique1": 1891, "t3_unique2": 1, "t2_unique1": 1891, "t2_unique2": 1, "t1_unique1": 1891, "t1_unique2": 1, "t0_unique1": 1891, "t0_unique2": 1 }
+{ "t3_unique1": 8800, "t3_unique2": 0, "t2_unique1": 8800, "t2_unique2": 0, "t1_unique1": 8800, "t1_unique2": 0, "t0_unique1": 8800, "t0_unique2": 0 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.3.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.3.adm
new file mode 100644
index 0000000..9571ef0
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.3.adm
@@ -0,0 +1,4 @@
+{ "t1_unique1": 1891, "t0_unique1": null }
+{ "t1_unique1": 8800, "t0_unique1": null }
+{ "t1_unique1": 3420, "t0_unique1": 3420 }
+{ "t1_unique1": 9850, "t0_unique1": 9850 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.4.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.4.adm
new file mode 100644
index 0000000..39f7696
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.4.adm
@@ -0,0 +1,6 @@
+{ "t0_fivethous": 0, "t1_fivethous": 0, "t1_unique1": 0, "t0_n_unique1": 0 }
+{ "t0_fivethous": 0, "t1_fivethous": 0, "t1_unique1": 0, "t0_n_unique1": 5000 }
+{ "t0_fivethous": 0, "t1_fivethous": 0, "t1_unique1": 5000, "t0_n_unique1": 0 }
+{ "t0_fivethous": 0, "t1_fivethous": 0, "t1_unique1": 5000, "t0_n_unique1": 5000 }
+{ "t0_fivethous": null, "t1_fivethous": 1, "t1_unique1": 1, "t0_n_unique1": null }
+{ "t0_fivethous": null, "t1_fivethous": 1, "t1_unique1": 5001, "t0_n_unique1": null }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.5.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.5.adm
new file mode 100644
index 0000000..e7f5f55
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.5.adm
@@ -0,0 +1,4 @@
+{ "t0_unique1": null, "t1_unique1": 3420 }
+{ "t0_unique1": null, "t1_unique1": 9850 }
+{ "t0_unique1": 1891, "t1_unique1": 1891 }
+{ "t0_unique1": 8800, "t1_unique1": 8800 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.7.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.7.adm
new file mode 100644
index 0000000..e7f5f55
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.7.adm
@@ -0,0 +1,4 @@
+{ "t0_unique1": null, "t1_unique1": 3420 }
+{ "t0_unique1": null, "t1_unique1": 9850 }
+{ "t0_unique1": 1891, "t1_unique1": 1891 }
+{ "t0_unique1": 8800, "t1_unique1": 8800 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.8.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.8.adm
new file mode 100644
index 0000000..1872bb7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.8.adm
@@ -0,0 +1,8 @@
+{ "t3": { "unique1": 5057, "unique2": 6 } }
+{ "t3": { "unique1": 6701, "unique2": 7 } }
+{ "t3": { "unique1": 7164, "unique2": 4 }, "t2": { "unique1": 7164, "unique2": 4 } }
+{ "t3": { "unique1": 8009, "unique2": 5 }, "t2": { "unique1": 8009, "unique2": 5 } }
+{ "t3": { "unique1": 3420, "unique2": 2 }, "t1": { "unique1": 3420, "unique2": 2 }, "t2": { "unique1": 3420, "unique2": 2 } }
+{ "t3": { "unique1": 9850, "unique2": 3 }, "t1": { "unique1": 9850, "unique2": 3 }, "t2": { "unique1": 9850, "unique2": 3 } }
+{ "t3": { "unique1": 1891, "unique2": 1 }, "t1": { "unique1": 1891, "unique2": 1 }, "t0": { "unique1": 1891, "unique2": 1 }, "t2": { "unique1": 1891, "unique2": 1 } }
+{ "t3": { "unique1": 8800, "unique2": 0 }, "t1": { "unique1": 8800, "unique2": 0 }, "t0": { "unique1": 8800, "unique2": 0 }, "t2": { "unique1": 8800, "unique2": 0 } }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.9.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.9.adm
new file mode 100644
index 0000000..663291c
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/rightouterjoin/roj-02-core/roj-02-core.9.adm
@@ -0,0 +1,8 @@
+{ "t3": { "unique1": 5057, "unique2": 6 } }
+{ "t3": { "unique1": 6701, "unique2": 7 } }
+{ "t2": { "unique1": 7164, "unique2": 4 }, "t3": { "unique1": 7164, "unique2": 4 } }
+{ "t2": { "unique1": 8009, "unique2": 5 }, "t3": { "unique1": 8009, "unique2": 5 } }
+{ "t1": { "unique1": 3420, "unique2": 2 }, "t2": { "unique1": 3420, "unique2": 2 }, "t3": { "unique1": 3420, "unique2": 2 } }
+{ "t1": { "unique1": 9850, "unique2": 3 }, "t2": { "unique1": 9850, "unique2": 3 }, "t3": { "unique1": 9850, "unique2": 3 } }
+{ "t0": { "unique1": 1891, "unique2": 1 }, "t1": { "unique1": 1891, "unique2": 1 }, "t2": { "unique1": 1891, "unique2": 1 }, "t3": { "unique1": 1891, "unique2": 1 } }
+{ "t0": { "unique1": 8800, "unique2": 0 }, "t1": { "unique1": 8800, "unique2": 0 }, "t2": { "unique1": 8800, "unique2": 0 }, "t3": { "unique1": 8800, "unique2": 0 } }
\ No newline at end of file
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 66cb684..04093a3 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -12847,6 +12847,24 @@
       </compilation-unit>
     </test-case>
   </test-group>
+  <test-group name="rightouterjoin">
+    <test-case FilePath="rightouterjoin">
+      <compilation-unit name="roj-01-core">
+        <output-dir compare="Text">roj-01-core</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="rightouterjoin">
+      <compilation-unit name="roj-02-core">
+        <output-dir compare="Text">roj-02-core</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="rightouterjoin">
+      <compilation-unit name="roj-03-negative">
+        <output-dir compare="Text">none</output-dir>
+        <expected-error>ASX1130: Illegal use of RIGHT OUTER JOIN</expected-error>
+      </compilation-unit>
+    </test-case>
+  </test-group>
   <test-group name="tinysocial">
     <test-case FilePath="tinysocial">
       <compilation-unit name="tinysocial-suite">
diff --git a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
index 431d5b3..3aae2e1 100644
--- a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
+++ b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
@@ -218,6 +218,7 @@ public class ErrorCode {
     public static final int UNSUPPORTED_ADAPTER_LANGUAGE = 1127;
     public static final int INCONSISTENT_FILTER_INDICATOR = 1128;
     public static final int UNSUPPORTED_GBY_OBY_SELECT_COMBO = 1129;
+    public static final int ILLEGAL_RIGHT_OUTER_JOIN = 1130;
 
     // Feed errors
     public static final int DATAFLOW_ILLEGAL_STATE = 3001;
diff --git a/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties b/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
index 127ec04..40caeb8 100644
--- a/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
+++ b/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
@@ -215,6 +215,7 @@
 1127 = Unsupported adapter language: %1$s
 1128 = Filter field is not defined properly
 1129 = Cannot compile SELECT variable.* with GROUP BY GROUPING SETS/ROLLUP/CUBE followed by ORDER BY/LIMIT
+1130 = Illegal use of RIGHT OUTER JOIN
 
 # Feed Errors
 3001 = Illegal state.
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/annotation/ExcludeFromSelectStarAnnotation.java
similarity index 61%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/annotation/ExcludeFromSelectStarAnnotation.java
index f4b84f5..a430ae6 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/annotation/ExcludeFromSelectStarAnnotation.java
@@ -17,11 +17,17 @@
  * under the License.
  */
 
-package org.apache.asterix.lang.sqlpp.optype;
+package org.apache.asterix.lang.sqlpp.annotation;
 
-public enum JoinType {
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractExpressionAnnotation;
+import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
 
-    INNER,
-    LEFTOUTER
+public final class ExcludeFromSelectStarAnnotation extends AbstractExpressionAnnotation {
 
+    public static final ExcludeFromSelectStarAnnotation INSTANCE = new ExcludeFromSelectStarAnnotation();
+
+    @Override
+    public IExpressionAnnotation copy() {
+        return this;
+    }
 }
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 ba1c498..21770c3 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
@@ -24,27 +24,19 @@ import java.util.Objects;
 import org.apache.asterix.lang.common.base.AbstractClause;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.sqlpp.optype.JoinType;
 
 public abstract class AbstractBinaryCorrelateClause extends AbstractClause {
 
-    private JoinType joinType;
     private Expression rightExpr;
     private VariableExpr rightVar;
     private VariableExpr rightPosVar;
 
-    public AbstractBinaryCorrelateClause(JoinType joinType, Expression rightExpr, VariableExpr rightVar,
-            VariableExpr rightPosVar) {
-        this.joinType = joinType;
+    public AbstractBinaryCorrelateClause(Expression rightExpr, VariableExpr rightVar, VariableExpr rightPosVar) {
         this.rightExpr = rightExpr;
         this.rightVar = rightVar;
         this.rightPosVar = rightPosVar;
     }
 
-    public JoinType getJoinType() {
-        return joinType;
-    }
-
     public Expression getRightExpression() {
         return rightExpr;
     }
@@ -67,7 +59,7 @@ public abstract class AbstractBinaryCorrelateClause extends AbstractClause {
 
     @Override
     public int hashCode() {
-        return Objects.hash(joinType, rightExpr, rightPosVar, rightVar);
+        return Objects.hash(rightExpr, rightPosVar, rightVar);
     }
 
     @Override
@@ -79,8 +71,8 @@ public abstract class AbstractBinaryCorrelateClause extends AbstractClause {
             return false;
         }
         AbstractBinaryCorrelateClause target = (AbstractBinaryCorrelateClause) object;
-        return Objects.equals(joinType, target.joinType) && Objects.equals(rightExpr, target.rightExpr)
-                && Objects.equals(rightPosVar, target.rightPosVar) && Objects.equals(rightVar, target.rightVar);
+        return Objects.equals(rightExpr, target.rightExpr) && Objects.equals(rightPosVar, target.rightPosVar)
+                && Objects.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 3773ea9..581efdd 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
@@ -21,15 +21,14 @@ package org.apache.asterix.lang.sqlpp.clause;
 
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.sqlpp.optype.JoinType;
 
 public abstract class AbstractBinaryCorrelateWithConditionClause extends AbstractBinaryCorrelateClause {
 
     private Expression conditionExpr;
 
-    public AbstractBinaryCorrelateWithConditionClause(JoinType joinType, Expression rightExpr, VariableExpr rightVar,
+    public AbstractBinaryCorrelateWithConditionClause(Expression rightExpr, VariableExpr rightVar,
             VariableExpr rightPosVar, Expression conditionExpr) {
-        super(joinType, rightExpr, rightVar, rightPosVar);
+        super(rightExpr, rightVar, rightPosVar);
         this.conditionExpr = conditionExpr;
     }
 
@@ -57,5 +56,4 @@ public abstract class AbstractBinaryCorrelateWithConditionClause extends Abstrac
         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/JoinClause.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/JoinClause.java
index 0b2a346..18b7a24 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/JoinClause.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/JoinClause.java
@@ -28,9 +28,12 @@ import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
 
 public class JoinClause extends AbstractBinaryCorrelateWithConditionClause {
 
+    private final JoinType joinType;
+
     public JoinClause(JoinType joinType, Expression rightExpr, VariableExpr rightVar, VariableExpr rightPosVar,
             Expression conditionExpr) {
-        super(joinType, rightExpr, rightVar, rightPosVar, conditionExpr);
+        super(rightExpr, rightVar, rightPosVar, conditionExpr);
+        this.joinType = joinType;
     }
 
     @Override
@@ -43,4 +46,24 @@ public class JoinClause extends AbstractBinaryCorrelateWithConditionClause {
         return ClauseType.JOIN_CLAUSE;
     }
 
+    public JoinType getJoinType() {
+        return joinType;
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() + joinType.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof JoinClause)) {
+            return false;
+        }
+        JoinClause target = (JoinClause) object;
+        return super.equals(target) && joinType.equals(target.getJoinType());
+    }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/NestClause.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/NestClause.java
index a2c27f0..5f0afb0 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/NestClause.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/NestClause.java
@@ -23,14 +23,17 @@ import org.apache.asterix.common.exceptions.CompilationException;
 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.asterix.lang.sqlpp.optype.JoinType;
+import org.apache.asterix.lang.sqlpp.optype.UnnestType;
 import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
 
 public class NestClause extends AbstractBinaryCorrelateWithConditionClause {
 
-    public NestClause(JoinType joinType, Expression rightExpr, VariableExpr rightVar, VariableExpr rightPosVar,
+    private final UnnestType nestType;
+
+    public NestClause(UnnestType nestType, Expression rightExpr, VariableExpr rightVar, VariableExpr rightPosVar,
             Expression conditionExpr) {
-        super(joinType, rightExpr, rightVar, rightPosVar, conditionExpr);
+        super(rightExpr, rightVar, rightPosVar, conditionExpr);
+        this.nestType = nestType;
     }
 
     @Override
@@ -43,4 +46,24 @@ public class NestClause extends AbstractBinaryCorrelateWithConditionClause {
         return ClauseType.NEST_CLAUSE;
     }
 
+    public UnnestType getNestType() {
+        return nestType;
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() + nestType.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof NestClause)) {
+            return false;
+        }
+        NestClause target = (NestClause) object;
+        return super.equals(target) && nestType.equals(target.getNestType());
+    }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/UnnestClause.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/UnnestClause.java
index f1a3964..f035be4 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/UnnestClause.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/clause/UnnestClause.java
@@ -23,13 +23,16 @@ import org.apache.asterix.common.exceptions.CompilationException;
 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.asterix.lang.sqlpp.optype.JoinType;
+import org.apache.asterix.lang.sqlpp.optype.UnnestType;
 import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
 
 public class UnnestClause extends AbstractBinaryCorrelateClause {
 
-    public UnnestClause(JoinType joinType, Expression rightExpr, VariableExpr rightVar, VariableExpr rightPosVar) {
-        super(joinType, rightExpr, rightVar, rightPosVar);
+    private final UnnestType unnestType;
+
+    public UnnestClause(UnnestType unnestType, Expression rightExpr, VariableExpr rightVar, VariableExpr rightPosVar) {
+        super(rightExpr, rightVar, rightPosVar);
+        this.unnestType = unnestType;
     }
 
     @Override
@@ -42,4 +45,24 @@ public class UnnestClause extends AbstractBinaryCorrelateClause {
         return ClauseType.UNNEST_CLAUSE;
     }
 
+    public UnnestType getUnnestType() {
+        return unnestType;
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() + unnestType.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof UnnestClause)) {
+            return false;
+        }
+        UnnestClause target = (UnnestClause) object;
+        return super.equals(target) && unnestType.equals(target.getUnnestType());
+    }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
index f4b84f5..352db3c 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
@@ -20,8 +20,7 @@
 package org.apache.asterix.lang.sqlpp.optype;
 
 public enum JoinType {
-
     INNER,
-    LEFTOUTER
-
+    LEFTOUTER,
+    RIGHTOUTER
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/UnnestType.java
similarity index 97%
copy from asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
copy to asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/UnnestType.java
index f4b84f5..819e842 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/JoinType.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/UnnestType.java
@@ -19,9 +19,7 @@
 
 package org.apache.asterix.lang.sqlpp.optype;
 
-public enum JoinType {
-
+public enum UnnestType {
     INNER,
     LEFTOUTER
-
 }
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 a89ca5b..356a683 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
@@ -91,6 +91,9 @@ class SqlppFunctionBodyRewriter extends SqlppQueryRewriter {
         // Rewrites several variable-arg functions into their corresponding internal list-input functions.
         rewriteListInputFunctions();
 
+        // Rewrites RIGHT OUTER JOINs into LEFT OUTER JOINs if possible
+        rewriteRightJoins();
+
         // Inlines functions recursively.
         inlineDeclaredUdfs(inlineUdfs);
     }
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 3f93dd0..365c42e 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
@@ -72,6 +72,7 @@ import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppGroupByVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppGroupingSetsVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppInlineUdfsVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppListInputFunctionRewriteVisitor;
+import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppRightJoinRewriteVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppSpecialFunctionNameRewriteVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppWindowAggregationSugarVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppWindowRewriteVisitor;
@@ -176,6 +177,9 @@ public class SqlppQueryRewriter implements IQueryRewriter {
         // Rewrites several variable-arg functions into their corresponding internal list-input functions.
         rewriteListInputFunctions();
 
+        // Rewrites RIGHT OUTER JOINs into LEFT OUTER JOINs if possible
+        rewriteRightJoins();
+
         // Inlines functions.
         inlineDeclaredUdfs(inlineUdfs);
 
@@ -297,6 +301,12 @@ public class SqlppQueryRewriter implements IQueryRewriter {
         rewriteTopExpr(visitor, null);
     }
 
+    protected void rewriteRightJoins() throws CompilationException {
+        // Rewrites RIGHT OUTER JOINs into LEFT OUTER JOINs if possible
+        SqlppRightJoinRewriteVisitor visitor = new SqlppRightJoinRewriteVisitor(context, externalVars);
+        rewriteTopExpr(visitor, null);
+    }
+
     protected void inlineDeclaredUdfs(boolean inlineUdfs) throws CompilationException {
         List<FunctionSignature> funIds = new ArrayList<FunctionSignature>();
         for (FunctionDecl fdecl : declaredFunctions) {
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppGroupingSetsVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppGroupingSetsVisitor.java
index c474ee3..791bfe1 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppGroupingSetsVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppGroupingSetsVisitor.java
@@ -369,7 +369,7 @@ public final class SqlppGroupingSetsVisitor extends AbstractSqlppExpressionScopi
 
         return selectElement
                 ? SetOperationVisitor.createSelectBlock(newSelectExpr, distinct,
-                        selectElementConvertToRegular ? SqlppGroupingSetsVisitor::getFieldByName : null,
+                        selectElementConvertToRegular ? SqlppRewriteUtil::getFieldByName : null,
                         selectElementConvertToRegular ? selectElementMainProjectionName : null, context)
                 : SetOperationVisitor.createSelectBlock(newSelectExpr, distinct,
                         SqlppGroupingSetsVisitor::removeFieldsByName, extraProjections.values(), context);
@@ -435,17 +435,6 @@ public final class SqlppGroupingSetsVisitor extends AbstractSqlppExpressionScopi
         return resultExpr;
     }
 
-    private static Expression getFieldByName(Expression inExpr, String fieldName) {
-        LiteralExpr fieldNameExpr = new LiteralExpr(new StringLiteral(fieldName));
-        fieldNameExpr.setSourceLocation(inExpr.getSourceLocation());
-        List<Expression> argList = new ArrayList<>(2);
-        argList.add(inExpr);
-        argList.add(fieldNameExpr);
-        CallExpr callExpr = new CallExpr(new FunctionSignature(BuiltinFunctions.FIELD_ACCESS_BY_NAME), argList);
-        callExpr.setSourceLocation(inExpr.getSourceLocation());
-        return callExpr;
-    }
-
     private String generateProjectionName(List<Projection> projectionList) {
         String projectionName;
         do {
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppRightJoinRewriteVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppRightJoinRewriteVisitor.java
new file mode 100644
index 0000000..a8db06f
--- /dev/null
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppRightJoinRewriteVisitor.java
@@ -0,0 +1,381 @@
+/*
+ * 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.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.lang.common.base.Clause;
+import org.apache.asterix.lang.common.base.Expression;
+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.common.struct.VarIdentifier;
+import org.apache.asterix.lang.sqlpp.annotation.ExcludeFromSelectStarAnnotation;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateWithConditionClause;
+import org.apache.asterix.lang.sqlpp.clause.FromClause;
+import org.apache.asterix.lang.sqlpp.clause.FromTerm;
+import org.apache.asterix.lang.sqlpp.clause.JoinClause;
+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.SelectRegular;
+import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
+import org.apache.asterix.lang.sqlpp.clause.UnnestClause;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.optype.JoinType;
+import org.apache.asterix.lang.sqlpp.struct.SetOperationInput;
+import org.apache.asterix.lang.sqlpp.util.SqlppRewriteUtil;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppSimpleExpressionVisitor;
+import org.apache.hyracks.algebricks.common.utils.Pair;
+import org.apache.hyracks.api.exceptions.SourceLocation;
+
+/**
+ * Rewrites RIGHT OUTER JOIN into LEFT OUTER JOIN as follows:
+ * <p>
+ * Single preceding clause:
+ * <pre>
+ * FROM x RIGHT OUTER JOIN y
+ * -->
+ * FROM y LEFT OUTER JOIN x
+ * </pre>
+ * Multiple preceding clauses:
+ * <pre>
+ * FROM x JOIN y RIGHT OUTER JOIN z
+ * -->
+ * FROM z LEFT OUTER JOIN ( FROM x JOIN y SELECT ... )
+ * </pre>
+ * Note, clauses preceding to the RIGHT JOIN clause should not have any non-external free variables.
+ * If they do then this rewriting fails and the query would fail later during logical plan generation.
+ * E.g.
+ * <pre>
+ * FROM x
+ * LET v = ( FROM x.nested RIGHT OUTER JOIN y ... )
+ * ...
+ * </pre>
+ */
+public final class SqlppRightJoinRewriteVisitor extends AbstractSqlppSimpleExpressionVisitor {
+
+    private final LangRewritingContext context;
+
+    private final Collection<VarIdentifier> externalVars;
+
+    public SqlppRightJoinRewriteVisitor(LangRewritingContext context, Collection<VarIdentifier> externalVars) {
+        this.context = context;
+        this.externalVars = externalVars;
+    }
+
+    @Override
+    public Expression visit(SelectBlock selectBlock, ILangExpression arg) throws CompilationException {
+        super.visit(selectBlock, arg);
+
+        if (selectBlock.hasFromClause()) {
+            // we only support a FromClause with a single FromTerm for now
+            List<FromTerm> fromTerms = selectBlock.getFromClause().getFromTerms();
+            if (fromTerms.size() == 1) {
+                FromTerm fromTerm = fromTerms.get(0);
+                if (canRewriteFromTerm(fromTerm)) {
+                    Pair<FromTerm, List<LetClause>> newFromLetPair = rewriteFromTerm(fromTerm);
+                    fromTerms.set(0, newFromLetPair.first);
+                    selectBlock.getLetWhereList().addAll(0, newFromLetPair.second);
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private boolean canRewriteFromTerm(FromTerm fromTerm) throws CompilationException {
+        boolean hasRightOuterJoin = false;
+        List<AbstractBinaryCorrelateClause> clauseList = fromTerm.getCorrelateClauses();
+        for (AbstractBinaryCorrelateClause correlateClause : clauseList) {
+            if (correlateClause.getClauseType() == Clause.ClauseType.JOIN_CLAUSE) {
+                JoinClause joinClause = (JoinClause) correlateClause;
+                if (joinClause.getJoinType() == JoinType.RIGHTOUTER) {
+                    hasRightOuterJoin = true;
+                    break;
+                }
+            }
+        }
+        if (!hasRightOuterJoin) {
+            // nothing to do
+            return false;
+        }
+
+        Set<VariableExpr> fromExprFreeVars = SqlppVariableUtil.getFreeVariables(fromTerm.getLeftExpression());
+        for (VariableExpr freeVarExpr : fromExprFreeVars) {
+            if (!externalVars.contains(freeVarExpr.getVar())) {
+                // cannot rewrite. will fail later
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private Pair<FromTerm, List<LetClause>> rewriteFromTerm(FromTerm fromTerm) throws CompilationException {
+        Map<VariableExpr, Expression> substMapInner = new HashMap<>();
+        Map<VariableExpr, Expression> substMapOuterTmp = new HashMap<>();
+        Map<VariableExpr, Expression> substMapOuterFinal = new LinkedHashMap<>();
+
+        Expression fromExpr = fromTerm.getLeftExpression();
+        VariableExpr fromVar = fromTerm.getLeftVariable();
+        VariableExpr fromPosVar = fromTerm.getPositionalVariable();
+
+        List<AbstractBinaryCorrelateClause> correlateClauses = fromTerm.getCorrelateClauses();
+        for (int i = 0; i < correlateClauses.size(); i++) {
+            AbstractBinaryCorrelateClause correlateClause = correlateClauses.get(i);
+            if (correlateClause.getClauseType() == Clause.ClauseType.JOIN_CLAUSE
+                    && ((JoinClause) correlateClause).getJoinType() == JoinType.RIGHTOUTER) {
+                JoinClause joinClause = (JoinClause) correlateClause;
+                SourceLocation joinClauseSourceLoc = joinClause.getSourceLocation();
+
+                Expression rightExpr = joinClause.getRightExpression();
+                VariableExpr rightVar = joinClause.getRightVariable();
+                VariableExpr rightPosVar = joinClause.getPositionalVariable();
+                Expression condExpr = joinClause.getConditionExpression();
+                if (i == 0) {
+                    JoinClause newJoinClause =
+                            new JoinClause(JoinType.LEFTOUTER, fromExpr, fromVar, fromPosVar, condExpr);
+                    newJoinClause.setSourceLocation(joinClauseSourceLoc);
+
+                    fromExpr = rightExpr;
+                    fromVar = rightVar;
+                    fromPosVar = rightPosVar;
+                    correlateClauses.set(i, newJoinClause);
+                } else {
+                    VarIdentifier newRightVar = context.newVariable();
+
+                    substMapOuterTmp.clear();
+                    substMapInner.clear();
+                    List<Projection> projectList = new ArrayList<>();
+
+                    SourceLocation fromVarSourceLoc = fromVar.getSourceLocation();
+                    VarIdentifier newFromVar = context.newVariable();
+                    String newFromVarFieldName = generateFieldName();
+                    projectList.add(createProjection(newVariableExpr(newFromVar, fromVarSourceLoc), newFromVarFieldName,
+                            fromVarSourceLoc));
+                    substMapOuterTmp.put(fromVar, SqlppRewriteUtil
+                            .getFieldByName(newVariableExpr(newRightVar, fromVarSourceLoc), newFromVarFieldName));
+                    substMapInner.put(fromVar, newVariableExpr(newFromVar, fromVarSourceLoc));
+
+                    VarIdentifier newFromPosVar = null;
+                    if (fromPosVar != null) {
+                        SourceLocation fromPosVarSourceLoc = fromPosVar.getSourceLocation();
+                        newFromPosVar = context.newVariable();
+                        String newFromPosVarFieldName = generateFieldName();
+                        projectList.add(createProjection(newVariableExpr(newFromPosVar, fromPosVarSourceLoc),
+                                newFromPosVarFieldName, fromPosVarSourceLoc));
+                        substMapOuterTmp.put(fromPosVar, SqlppRewriteUtil.getFieldByName(
+                                newVariableExpr(newRightVar, fromPosVarSourceLoc), newFromPosVarFieldName));
+                        substMapInner.put(fromPosVar, newVariableExpr(newFromPosVar, fromPosVarSourceLoc));
+                    }
+
+                    List<AbstractBinaryCorrelateClause> newPrecedingClauseList = new ArrayList<>(i);
+                    for (int j = 0; j < i; j++) {
+                        AbstractBinaryCorrelateClause precedingClause = correlateClauses.get(j);
+                        SourceLocation precedingClauseSourceLoc = precedingClause.getSourceLocation();
+
+                        VariableExpr precedingClauseRightVar = precedingClause.getRightVariable();
+                        SourceLocation precedingClauseRightVarSourceLoc = precedingClauseRightVar.getSourceLocation();
+                        VarIdentifier newPrecedingClauseRightVar = context.newVariable();
+                        String newPrecedingClauseRightVarFieldName = generateFieldName();
+                        projectList.add(createProjection(
+                                newVariableExpr(newPrecedingClauseRightVar, precedingClauseRightVarSourceLoc),
+                                newPrecedingClauseRightVarFieldName, precedingClauseRightVarSourceLoc));
+                        substMapOuterTmp.put(precedingClauseRightVar,
+                                SqlppRewriteUtil.getFieldByName(
+                                        newVariableExpr(newRightVar, precedingClauseRightVarSourceLoc),
+                                        newPrecedingClauseRightVarFieldName));
+                        substMapInner.put(precedingClauseRightVar,
+                                newVariableExpr(newPrecedingClauseRightVar, precedingClauseRightVarSourceLoc));
+
+                        VariableExpr precedingClauseRightPosVar = precedingClause.getPositionalVariable();
+                        SourceLocation precedingClauseRightPosVarSourceLoc = null;
+                        VarIdentifier newPrecedingClauseRightPosVar = null;
+                        if (precedingClauseRightPosVar != null) {
+                            precedingClauseRightPosVarSourceLoc = precedingClauseRightPosVar.getSourceLocation();
+                            newPrecedingClauseRightPosVar = context.newVariable();
+                            String newPrecedingClauseRightPosVarFieldName = generateFieldName();
+                            projectList.add(createProjection(
+                                    newVariableExpr(newPrecedingClauseRightPosVar, precedingClauseRightPosVarSourceLoc),
+                                    newPrecedingClauseRightPosVarFieldName, precedingClauseRightPosVarSourceLoc));
+                            substMapOuterTmp.put(precedingClauseRightPosVar,
+                                    SqlppRewriteUtil.getFieldByName(
+                                            newVariableExpr(newRightVar, precedingClauseRightPosVarSourceLoc),
+                                            newPrecedingClauseRightPosVarFieldName));
+                            substMapInner.put(precedingClauseRightPosVar, newVariableExpr(newPrecedingClauseRightPosVar,
+                                    precedingClauseRightPosVarSourceLoc));
+                        }
+
+                        AbstractBinaryCorrelateClause newPrecedingClause;
+                        switch (precedingClause.getClauseType()) {
+                            case JOIN_CLAUSE:
+                                JoinClause joinPrecedingClause = (JoinClause) precedingClause;
+                                Expression newCondExpr = (Expression) SqlppRewriteUtil
+                                        .deepCopy(joinPrecedingClause.getConditionExpression());
+                                SqlppRewriteUtil.substituteExpression(newCondExpr, substMapInner, context);
+                                newPrecedingClause = new JoinClause(joinPrecedingClause.getJoinType(),
+                                        joinPrecedingClause.getRightExpression(),
+                                        newVariableExpr(newPrecedingClauseRightVar, precedingClauseRightVarSourceLoc),
+                                        newPrecedingClauseRightPosVar != null
+                                                ? newVariableExpr(newPrecedingClauseRightPosVar,
+                                                        precedingClauseRightPosVarSourceLoc)
+                                                : null,
+                                        newCondExpr);
+                                newPrecedingClause.setSourceLocation(precedingClauseSourceLoc);
+                                break;
+                            case UNNEST_CLAUSE:
+                                UnnestClause unnestPrecedingClause = (UnnestClause) precedingClause;
+                                Expression newRightExpr = (Expression) SqlppRewriteUtil
+                                        .deepCopy(unnestPrecedingClause.getRightExpression());
+                                SqlppRewriteUtil.substituteExpression(newRightExpr, substMapInner, context);
+                                newPrecedingClause = new UnnestClause(unnestPrecedingClause.getUnnestType(),
+                                        newRightExpr,
+                                        newVariableExpr(newPrecedingClauseRightVar, precedingClauseRightVarSourceLoc),
+                                        newPrecedingClauseRightPosVar != null
+                                                ? newVariableExpr(newPrecedingClauseRightPosVar,
+                                                        precedingClauseRightPosVarSourceLoc)
+                                                : null);
+                                newPrecedingClause.setSourceLocation(precedingClauseSourceLoc);
+                                break;
+                            default:
+                                throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                                        String.valueOf(precedingClause.getClauseType()));
+                        }
+                        newPrecedingClauseList.add(newPrecedingClause);
+                    }
+
+                    Expression newRightExpr = createRightSelectExpression(fromExpr, newFromVar, newFromPosVar,
+                            newPrecedingClauseList, projectList, joinClauseSourceLoc);
+
+                    VariableExpr newRightVarExpr = newVariableExpr(newRightVar, rightVar.getSourceLocation());
+                    newRightVarExpr.addHint(ExcludeFromSelectStarAnnotation.INSTANCE);
+
+                    // compose substitution maps
+                    for (VariableExpr varExpr : substMapOuterFinal.keySet()) {
+                        Expression substExpr = substMapOuterFinal.get(varExpr);
+                        Expression newSubstExpr =
+                                SqlppRewriteUtil.substituteExpression(substExpr, substMapOuterTmp, context);
+                        substMapOuterFinal.put(varExpr, newSubstExpr);
+                    }
+                    substMapOuterFinal.putAll(substMapOuterTmp);
+
+                    Expression newCondExpr = SqlppRewriteUtil.substituteExpression(
+                            (Expression) SqlppRewriteUtil.deepCopy(condExpr), substMapOuterFinal, context);
+
+                    JoinClause newJoinClause =
+                            new JoinClause(JoinType.LEFTOUTER, newRightExpr, newRightVarExpr, null, newCondExpr);
+                    newJoinClause.setSourceLocation(joinClauseSourceLoc);
+
+                    fromExpr = rightExpr;
+                    fromVar = rightVar;
+                    fromPosVar = rightPosVar;
+
+                    correlateClauses.subList(0, i).clear();
+                    correlateClauses.set(0, newJoinClause);
+
+                    i = 0;
+                }
+            } else if (!substMapOuterFinal.isEmpty()) {
+                switch (correlateClause.getClauseType()) {
+                    case JOIN_CLAUSE:
+                        AbstractBinaryCorrelateWithConditionClause correlateConditionClause =
+                                (AbstractBinaryCorrelateWithConditionClause) correlateClause;
+                        Expression condExpr = correlateConditionClause.getConditionExpression();
+                        Expression newCondExpr = SqlppRewriteUtil.substituteExpression(
+                                (Expression) SqlppRewriteUtil.deepCopy(condExpr), substMapOuterFinal, context);
+                        correlateConditionClause.setConditionExpression(newCondExpr);
+                        // fall thru to UNNEST_CLAUSE
+                    case UNNEST_CLAUSE:
+                        Expression rightExpr = correlateClause.getRightExpression();
+                        Expression newRightExpr = SqlppRewriteUtil.substituteExpression(
+                                (Expression) SqlppRewriteUtil.deepCopy(rightExpr), substMapOuterFinal, context);
+                        correlateClause.setRightExpression(newRightExpr);
+                        break;
+                    default:
+                        throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                                correlateClause.getSourceLocation(), String.valueOf(correlateClause.getClauseType()));
+                }
+
+            }
+        }
+
+        FromTerm newFromTerm = new FromTerm(fromExpr, fromVar, fromPosVar, correlateClauses);
+        newFromTerm.setSourceLocation(fromTerm.getSourceLocation());
+
+        List<LetClause> newLetClauses = new ArrayList<>(substMapOuterFinal.size());
+        for (Map.Entry<VariableExpr, Expression> me : substMapOuterFinal.entrySet()) {
+            VariableExpr newVarExpr = (VariableExpr) SqlppRewriteUtil.deepCopy(me.getKey());
+            Expression newValueExpr = (Expression) SqlppRewriteUtil.deepCopy(me.getValue());
+            LetClause newLetClause = new LetClause(newVarExpr, newValueExpr);
+            newLetClause.setSourceLocation(newVarExpr.getSourceLocation());
+            newLetClauses.add(newLetClause);
+        }
+
+        return new Pair<>(newFromTerm, newLetClauses);
+    }
+
+    private Expression createRightSelectExpression(Expression fromExpr, VarIdentifier fromVar, VarIdentifier fromPosVar,
+            List<AbstractBinaryCorrelateClause> correlateClauseList, List<Projection> projectList,
+            SourceLocation sourceLoc) {
+        FromTerm newFromTerm = new FromTerm(fromExpr, newVariableExpr(fromVar, sourceLoc),
+                fromPosVar != null ? newVariableExpr(fromPosVar, sourceLoc) : null, correlateClauseList);
+        List<FromTerm> newFromTermList = new ArrayList<>(1);
+        newFromTermList.add(newFromTerm);
+        FromClause newFromClause = new FromClause(newFromTermList);
+        newFromClause.setSourceLocation(sourceLoc);
+        SelectClause newSelectClause = new SelectClause(null, new SelectRegular(projectList), false);
+        newSelectClause.setSourceLocation(sourceLoc);
+        SelectBlock newSelectBlock = new SelectBlock(newSelectClause, newFromClause, null, null, null);
+        newSelectBlock.setSourceLocation(sourceLoc);
+        SelectSetOperation newSelectSetOp = new SelectSetOperation(new SetOperationInput(newSelectBlock, null), null);
+        newSelectSetOp.setSourceLocation(sourceLoc);
+        SelectExpression newSelectExpr = new SelectExpression(null, newSelectSetOp, null, null, true);
+        newSelectExpr.setSourceLocation(sourceLoc);
+        return newSelectExpr;
+    }
+
+    private VariableExpr newVariableExpr(VarIdentifier newFromVar, SourceLocation sourceLoc) {
+        VariableExpr varExpr = new VariableExpr(newFromVar);
+        varExpr.setSourceLocation(sourceLoc);
+        return varExpr;
+    }
+
+    private Projection createProjection(VariableExpr var, String fieldName, SourceLocation sourceLoc) {
+        Projection projection = new Projection(newVariableExpr(var.getVar(), null), fieldName, false, false);
+        projection.setSourceLocation(sourceLoc);
+        return projection;
+    }
+
+    private String generateFieldName() {
+        return SqlppVariableUtil.variableNameToDisplayedFieldName(context.newVariable().getValue());
+    }
+}
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 5a30b42..62acfeb 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
@@ -18,20 +18,27 @@
  */
 package org.apache.asterix.lang.sqlpp.util;
 
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.functions.FunctionSignature;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.LiteralExpr;
 import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.literal.StringLiteral;
 import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
 import org.apache.asterix.lang.common.statement.Query;
 import org.apache.asterix.lang.sqlpp.visitor.CheckSubqueryVisitor;
 import org.apache.asterix.lang.sqlpp.visitor.DeepCopyVisitor;
 import org.apache.asterix.lang.sqlpp.visitor.FreeVariableVisitor;
 import org.apache.asterix.lang.sqlpp.visitor.SqlppSubstituteExpressionVisitor;
+import org.apache.asterix.om.functions.BuiltinFunctions;
 
 public class SqlppRewriteUtil {
 
@@ -75,7 +82,7 @@ public class SqlppRewriteUtil {
      *         replacement expression.
      * @throws CompilationException
      */
-    public static Expression substituteExpression(Expression expression, Map<Expression, Expression> exprMap,
+    public static Expression substituteExpression(Expression expression, Map<? extends Expression, Expression> exprMap,
             LangRewritingContext context) throws CompilationException {
         if (exprMap.isEmpty()) {
             return expression;
@@ -90,4 +97,15 @@ public class SqlppRewriteUtil {
         wrapper.accept(visitor, wrapper);
         return wrapper.getBody();
     }
+
+    public static Expression getFieldByName(Expression inExpr, String fieldName) {
+        LiteralExpr fieldNameExpr = new LiteralExpr(new StringLiteral(fieldName));
+        fieldNameExpr.setSourceLocation(inExpr.getSourceLocation());
+        List<Expression> argList = new ArrayList<>(2);
+        argList.add(inExpr);
+        argList.add(fieldNameExpr);
+        CallExpr callExpr = new CallExpr(new FunctionSignature(BuiltinFunctions.FIELD_ACCESS_BY_NAME), argList);
+        callExpr.setSourceLocation(inExpr.getSourceLocation());
+        return callExpr;
+    }
 }
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 684a738..51bbe98 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
@@ -124,7 +124,7 @@ public class DeepCopyVisitor extends AbstractSqlppQueryExpressionVisitor<ILangEx
         VariableExpr rightPositionVar = nestClause.getPositionalVariable() == null ? null
                 : (VariableExpr) nestClause.getPositionalVariable().accept(this, arg);
         Expression conditionExpresion = (Expression) nestClause.getConditionExpression().accept(this, arg);
-        NestClause copy = new NestClause(nestClause.getJoinType(), rightExpression, rightVar, rightPositionVar,
+        NestClause copy = new NestClause(nestClause.getNestType(), rightExpression, rightVar, rightPositionVar,
                 conditionExpresion);
         copy.setSourceLocation(nestClause.getSourceLocation());
         return copy;
@@ -136,7 +136,7 @@ public class DeepCopyVisitor extends AbstractSqlppQueryExpressionVisitor<ILangEx
         VariableExpr rightVar = (VariableExpr) unnestClause.getRightVariable().accept(this, arg);
         VariableExpr rightPositionVar = unnestClause.getPositionalVariable() == null ? null
                 : (VariableExpr) unnestClause.getPositionalVariable().accept(this, arg);
-        UnnestClause copy = new UnnestClause(unnestClause.getJoinType(), rightExpression, rightVar, rightPositionVar);
+        UnnestClause copy = new UnnestClause(unnestClause.getUnnestType(), rightExpression, rightVar, rightPositionVar);
         copy.setSourceLocation(unnestClause.getSourceLocation());
         return copy;
     }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppAstPrintVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppAstPrintVisitor.java
index 038be3e..083015b 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppAstPrintVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppAstPrintVisitor.java
@@ -115,7 +115,7 @@ public class SqlppAstPrintVisitor extends QueryPrintVisitor implements ISqlppVis
 
     @Override
     public Void visit(NestClause nestClause, Integer step) throws CompilationException {
-        out.println(skip(step) + nestClause.getJoinType() + " NEST");
+        out.println(skip(step) + nestClause.getNestType() + " NEST");
         nestClause.getRightExpression().accept(this, step + 1);
         out.print(skip(step + 1) + "AS ");
         nestClause.getRightVariable().accept(this, 0);
@@ -229,7 +229,7 @@ public class SqlppAstPrintVisitor extends QueryPrintVisitor implements ISqlppVis
 
     @Override
     public Void visit(UnnestClause unnestClause, Integer step) throws CompilationException {
-        out.println(skip(step) + unnestClause.getJoinType() + " UNNEST");
+        out.println(skip(step) + unnestClause.getUnnestType() + " UNNEST");
         unnestClause.getRightExpression().accept(this, step + 1);
         out.print(skip(step + 1) + " AS ");
         unnestClause.getRightVariable().accept(this, 0);
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 418a960..efe1c3d 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
@@ -170,7 +170,7 @@ public class SqlppCloneAndSubstituteVariablesVisitor extends CloneAndSubstituteV
         Expression conditionExpr = (Expression) nestClause.getConditionExpression().accept(this, currentEnv).first;
 
         NestClause newNestClause =
-                new NestClause(nestClause.getJoinType(), rightExpr, newRightVar, newRightPosVar, conditionExpr);
+                new NestClause(nestClause.getNestType(), rightExpr, newRightVar, newRightPosVar, conditionExpr);
         newNestClause.setSourceLocation(nestClause.getSourceLocation());
         return new Pair<>(newNestClause, currentEnv);
     }
@@ -194,7 +194,7 @@ public class SqlppCloneAndSubstituteVariablesVisitor extends CloneAndSubstituteV
         }
         // The condition can refer to the newRightVar and newRightPosVar.
         UnnestClause newUnnestClause =
-                new UnnestClause(unnestClause.getJoinType(), rightExpr, newRightVar, newRightPosVar);
+                new UnnestClause(unnestClause.getUnnestType(), rightExpr, newRightVar, newRightPosVar);
         newUnnestClause.setSourceLocation(unnestClause.getSourceLocation());
         return new Pair<>(newUnnestClause, currentEnv);
     }
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 8798dfa..bdf5c9d 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
@@ -109,7 +109,7 @@ public class SqlppFormatPrintVisitor extends FormatPrintVisitor implements ISqlp
 
     @Override
     public Void visit(NestClause nestClause, Integer step) throws CompilationException {
-        out.print(nestClause.getJoinType());
+        out.print(nestClause.getNestType());
         nestClause.getRightExpression().accept(this, step + 2);
         out.println(skip(step + 1) + " as ");
         nestClause.getRightVariable().accept(this, step + 2);
@@ -233,7 +233,7 @@ public class SqlppFormatPrintVisitor extends FormatPrintVisitor implements ISqlp
 
     @Override
     public Void visit(UnnestClause unnestClause, Integer step) throws CompilationException {
-        out.print(unnestClause.getJoinType());
+        out.print(unnestClause.getUnnestType());
         unnestClause.getRightExpression().accept(this, step + 2);
         out.print(" as ");
         unnestClause.getRightVariable().accept(this, step + 2);
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index b23492a..bc0c1c2 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -180,6 +180,7 @@ import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
 import org.apache.asterix.lang.sqlpp.expression.WindowExpression;
 import org.apache.asterix.lang.sqlpp.optype.JoinType;
 import org.apache.asterix.lang.sqlpp.optype.SetOpType;
+import org.apache.asterix.lang.sqlpp.optype.UnnestType;
 import org.apache.asterix.lang.sqlpp.parser.SqlppGroupingSetsParser;
 import org.apache.asterix.lang.sqlpp.parser.SqlppGroupingSetsParser.GroupingElement;
 import org.apache.asterix.lang.sqlpp.parser.SqlppGroupingSetsParser.GroupingSet;
@@ -389,7 +390,7 @@ class SQLPPParser extends ScopeChecker implements IParser {
             throw new CompilationException(ErrorCode.PARSE_ERROR, LogRedactionUtil.userData(getMessage(e)));
         } catch (Error e) {
             // this is here as the JavaCharStream that's below the lexer sometimes throws Errors that are not handled
-            // by the ANTLR-generated lexer or parser (e.g it does this for invalid backslash u + 4 hex digits escapes)
+            // by the generated lexer or parser (e.g it does this for invalid backslash u + 4 hex digits escapes)
             final String msg = e.getClass().getSimpleName() + (e.getMessage() != null ? ": " + e.getMessage() : "");
             throw new CompilationException(ErrorCode.PARSE_ERROR, LogRedactionUtil.userData(msg));
         } finally {
@@ -4025,18 +4026,17 @@ FromTerm FromTerm() throws ParseException :
   Expression leftExpr = null;
   VariableExpr leftVar = null;
   VariableExpr posVar = null;
+  AbstractBinaryCorrelateClause correlateClause = null;
   List<AbstractBinaryCorrelateClause> correlateClauses = new ArrayList<AbstractBinaryCorrelateClause>();
 }
 {
   leftExpr = Expression() ((<AS>)? leftVar = Variable())? (<AT> posVar = Variable())?
   (
-     {JoinType joinType = JoinType.INNER; }
-     (joinType = JoinType())?
-     {
-       AbstractBinaryCorrelateClause correlateClause = null;
-     }
-     (correlateClause = JoinClause(joinType)
-      | correlateClause = UnnestClause(joinType)
+     (
+      correlateClause = JoinOrUnnestClause(JoinType.INNER, UnnestType.INNER)
+      | ( <INNER> correlateClause = JoinOrUnnestClause(JoinType.INNER, UnnestType.INNER) )
+      | ( <LEFT> ( <OUTER> )? correlateClause = JoinOrUnnestClause(JoinType.LEFTOUTER, UnnestType.LEFTOUTER) )
+      | ( <RIGHT> ( <OUTER> )? correlateClause = JoinClause(JoinType.RIGHTOUTER) )
      )
      {
         correlateClauses.add(correlateClause);
@@ -4052,6 +4052,17 @@ FromTerm FromTerm() throws ParseException :
   }
 }
 
+AbstractBinaryCorrelateClause JoinOrUnnestClause(JoinType joinType, UnnestType unnestType) throws ParseException :
+{
+  AbstractBinaryCorrelateClause correlateClause = null;
+}
+{
+  ( correlateClause = JoinClause(joinType) | correlateClause = UnnestClause(unnestType) )
+  {
+    return correlateClause;
+  }
+}
+
 JoinClause JoinClause(JoinType joinType) throws ParseException :
 {
     Token startToken = null;
@@ -4063,15 +4074,15 @@ JoinClause JoinClause(JoinType joinType) throws ParseException :
 {
   <JOIN> { startToken = token; } rightExpr = Expression() ((<AS>)? rightVar = Variable())? (<AT> posVar = Variable())? <ON> conditionExpr = Expression()
   {
-    if(rightVar==null){
-        rightVar = ExpressionToVariableUtil.getGeneratedVariable(rightExpr, true);
+    if (rightVar == null) {
+      rightVar = ExpressionToVariableUtil.getGeneratedVariable(rightExpr, true);
     }
     JoinClause joinClause = new JoinClause(joinType, rightExpr, rightVar, posVar, conditionExpr);
     return addSourceLocation(joinClause, startToken);
   }
 }
 
-UnnestClause UnnestClause(JoinType joinType) throws ParseException :
+UnnestClause UnnestClause(UnnestType unnestType) throws ParseException :
 {
     Token startToken = null;
     Expression rightExpr;
@@ -4084,22 +4095,11 @@ UnnestClause UnnestClause(JoinType joinType) throws ParseException :
     if (rightVar == null) {
       rightVar = ExpressionToVariableUtil.getGeneratedVariable(rightExpr, true);
     }
-    UnnestClause unnestClause = new UnnestClause(joinType, rightExpr, rightVar, posVar);
+    UnnestClause unnestClause = new UnnestClause(unnestType, rightExpr, rightVar, posVar);
     return addSourceLocation(unnestClause, startToken);
   }
 }
 
-JoinType JoinType() throws ParseException :
-{
-   JoinType joinType = JoinType.INNER;
-}
-{
-     (<INNER>|<LEFT> (<OUTER>)? {joinType = JoinType.LEFTOUTER; })
-     {
-       return joinType;
-     }
-}
-
 List<LetClause> LetClause() throws ParseException:
 {
     List<LetClause> letList = new ArrayList<LetClause>();
@@ -4575,6 +4575,7 @@ TOKEN [IGNORE_CASE]:
   | <REFRESH : "refresh">
   | <RETURN : "return">
   | <RETURNING : "returning">
+  | <RIGHT : "right">
   | <RTREE : "rtree">
   | <RUN : "run">
   | <SATISFIES : "satisfies">