[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 <ali.al.solaiman@gmail.com>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
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.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.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.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 @@
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 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 @@
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 @@
}
}
+ 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 @@
// 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 (groupbyClause.hasDecorList()) {
- for (GbyVariableExpressionPair pair : groupbyClause.getDecorPairList()) {
- VariableExpr var = pair.getVar();
+ 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 (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 @@
// 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 @@
this.groupByClause = groupByClause;
}
+ @BeforeClass
+ public static void setUp() throws Exception {
+ setUpBeforeClass();
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ tearDownAfterClass();
+ }
+
@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));
- }
-
- boolean eq = TestHelper.equalJson(sqlResult, sqlppResult, false);
- if (!eq) {
- File sqlResultFile = writeResult(sqlResult, "sql");
- File sqlppResultFile = writeResult(sqlppResult, "sqlpp");
-
- Assert.fail(String.format("Results do not match.\n%s\n%s", sqlResultFile.getCanonicalPath(),
- sqlppResultFile.getCanonicalPath()));
- }
+ runTestCase(testcaseId, groupByClause, sqlQuery, sqlppQuery);
}
private static TestQuery generateQuery(int testcaseId, Random random) {
@@ -343,267 +240,6 @@
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/SqlppRQGTestBase.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGTestBase.java
new file mode 100644
index 0000000..44af727
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGTestBase.java
@@ -0,0 +1,416 @@
+/*
+ * 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.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.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Random;
+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.Assert;
+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
+
+public abstract class SqlppRQGTestBase {
+
+ private static final Logger LOGGER = LogManager.getLogger(SqlppRQGTestBase.class);
+
+ protected static final String TESTCONTAINERS_RYUK_DISABLED = "TESTCONTAINERS_RYUK_DISABLED";
+
+ protected static final String TEST_CONFIG_FILE_NAME = "src/main/resources/cc.conf";
+
+ protected static final String POSTGRES_IMAGE = "postgres:12.2";
+
+ protected static final String TABLE_NAME = "tenk";
+
+ protected static final Path TABLE_FILE = Paths.get("data", "tenk.tbl");
+
+ protected static final char TABLE_FILE_COLUMN_SEPARATOR = '|';
+
+ protected static final Path RESULT_OUTPUT_DIR = Paths.get("target", SqlppRQGGroupingSetsIT.class.getSimpleName());
+
+ 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";
+
+ protected static final LinkedHashMap<String, JDBCType> TABLE_SCHEMA = createTableSchema();
+
+ protected static final ObjectReader JSON_NODE_READER = new ObjectMapper().readerFor(JsonNode.class);
+
+ protected static TestExecutor testExecutor;
+
+ protected static PostgreSQLContainer<?> postgres;
+
+ protected static Connection conn;
+
+ protected static Statement stmt;
+
+ public static void setUpBeforeClass() throws Exception {
+ startAsterix();
+ startPostgres();
+ FileUtils.forceMkdir(RESULT_OUTPUT_DIR.toFile());
+ }
+
+ public static void tearDownAfterClass() throws Exception {
+ stopPostgres();
+ stopAsterix();
+ }
+
+ 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;
+ }
+
+ 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);
+ 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));
+ }
+
+ 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, 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()));
+ }
+ }
+
+ protected 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;
+ }
+
+ protected 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);
+ }
+
+ 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(",")));
+
+ 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();
+ }
+ }
+
+ protected 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;
+ }
+
+ protected static String getSQLType(JDBCType type) {
+ String suffix = "";
+ if (type == JDBCType.VARCHAR) {
+ suffix = "(256)";
+ }
+ return type.getName() + suffix;
+ }
+
+ protected static IAType getAsterixType(JDBCType type) {
+ switch (type) {
+ case INTEGER:
+ return BuiltinType.AINT32;
+ case VARCHAR:
+ return BuiltinType.ASTRING;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ protected 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());
+ }
+ }
+
+ 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;
+ }
+
+ protected static String getConfigurationPropertyName(Class<?> testClass, String propertyName) {
+ return String.format("%s.%s", testClass.getSimpleName(), propertyName);
+ }
+
+ protected 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;
+ }
+ }
+
+ protected static void startAsterix() throws Exception {
+ testExecutor = new TestExecutor();
+ LangExecutionUtil.setUp(TEST_CONFIG_FILE_NAME, testExecutor);
+ loadAsterixData();
+ }
+
+ protected static void stopAsterix() throws Exception {
+ LangExecutionUtil.tearDown();
+ }
+
+ 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));
+ }
+ LOGGER.info("Starting Postgres");
+ postgres = new PostgreSQLContainer<>(POSTGRES_IMAGE);
+ postgres.start();
+ conn = DriverManager.getConnection(postgres.getJdbcUrl(), postgres.getUsername(), postgres.getPassword());
+ stmt = conn.createStatement();
+ loadSQLData();
+ }
+
+ protected 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();
+ }
+ }
+ }
+}
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-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.1.ddl.sqlpp
new file mode 100644
index 0000000..97e2a5e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.1.ddl.sqlpp
@@ -0,0 +1,48 @@
+/*
+ * 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;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.2.update.sqlpp
new file mode 100644
index 0000000..2d7e768
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.2.update.sqlpp
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use test;
+
+load dataset tenk using localfs ((`path`=`asterix_nc1://data/tenk.tbl`),(`format`=`delimited-text`),(`delimiter`=`|`));
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.3.query.sqlpp
new file mode 100644
index 0000000..04c6b89
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.3.query.sqlpp
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+/* RIGHT;c */
+
+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 < 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/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.4.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.4.query.sqlpp
new file mode 100644
index 0000000..c74d919
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.4.query.sqlpp
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+/* RIGHT,RIGHT;c */
+
+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
+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/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.5.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.5.query.sqlpp
new file mode 100644
index 0000000..75be058
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.5.query.sqlpp
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+/* RIGHT,RIGHT,RIGHT;c */
+
+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,
+ 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-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.6.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.6.query.sqlpp
new file mode 100644
index 0000000..832a8ae
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.6.query.sqlpp
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+/* 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;
+
+
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.7.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.7.query.sqlpp
new file mode 100644
index 0000000..d7ab2a6
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-01-core/roj-01-core.7.query.sqlpp
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+/* RIGHT,LEFT;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
+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-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.10.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.10.query.sqlpp
new file mode 100644
index 0000000..a80b614
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.10.query.sqlpp
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* SELECT v.*; RIGHT,RIGHT,RIGHT; */
+
+use test;
+
+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-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.2.update.sqlpp
new file mode 100644
index 0000000..e135a7b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.2.update.sqlpp
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use test;
+
+load dataset tenk using localfs ((`path`=`asterix_nc1://data/tenk.tbl`),(`format`=`delimited-text`),(`delimiter`=`|`));
+
+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-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.3.query.sqlpp
new file mode 100644
index 0000000..2062cd8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.3.query.sqlpp
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use test;
+
+/* UNNEST, RIGHT JOIN */
+
+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-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.4.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.4.query.sqlpp
new file mode 100644
index 0000000..4fbec97
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.4.query.sqlpp
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use test;
+
+/* RIGHT JOIN, LEFT UNNEST */
+
+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-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.5.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.5.query.sqlpp
new file mode 100644
index 0000000..bc79bba
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.5.query.sqlpp
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* Use statement parameters */
+
+// requesttype=application/json
+
+// 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-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.6.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.6.ddl.sqlpp
new file mode 100644
index 0000000..1fe402c
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.6.ddl.sqlpp
@@ -0,0 +1,36 @@
+/*
+ * 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
+ */
+
+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-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.7.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.7.query.sqlpp
new file mode 100644
index 0000000..bb12774
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.7.query.sqlpp
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use test;
+
+SELECT t0_unique1, t1_unique1
+FROM f1(2, 4) t
+ORDER BY t0_unique1, t1_unique1;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.8.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.8.query.sqlpp
new file mode 100644
index 0000000..fa56a4d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.8.query.sqlpp
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* SELECT *; RIGHT,RIGHT,RIGHT; */
+
+use test;
+
+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-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.9.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.9.query.sqlpp
new file mode 100644
index 0000000..ef0d112
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-02-core/roj-02-core.9.query.sqlpp
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* SELECT t0,t1,t2,t3; RIGHT,RIGHT,RIGHT; */
+
+use test;
+
+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-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-03-negative/roj-03-negative.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-03-negative/roj-03-negative.1.ddl.sqlpp
new file mode 100644
index 0000000..4c066b5
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-03-negative/roj-03-negative.1.ddl.sqlpp
@@ -0,0 +1,54 @@
+/*
+ * 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;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-03-negative/roj-03-negative.2.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-03-negative/roj-03-negative.2.query.sqlpp
new file mode 100644
index 0000000..6b4d1ee
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/rightouterjoin/roj-03-negative/roj-03-negative.2.query.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+/*
+ * Expected result: Failure. Cannot use RIGHT JOIN if it's left branch has free variables.
+ */
+
+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 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/annotation/ExcludeFromSelectStarAnnotation.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/annotation/ExcludeFromSelectStarAnnotation.java
new file mode 100644
index 0000000..a430ae6
--- /dev/null
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/annotation/ExcludeFromSelectStarAnnotation.java
@@ -0,0 +1,33 @@
+/*
+ * 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.annotation;
+
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractExpressionAnnotation;
+import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
+
+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 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 @@
@Override
public int hashCode() {
- return Objects.hash(joinType, rightExpr, rightPosVar, rightVar);
+ return Objects.hash(rightExpr, rightPosVar, rightVar);
}
@Override
@@ -79,8 +71,8 @@
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 @@
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 @@
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 @@
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 @@
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.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 @@
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.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 @@
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/UnnestType.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/UnnestType.java
new file mode 100644
index 0000000..819e842
--- /dev/null
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/optype/UnnestType.java
@@ -0,0 +1,25 @@
+/*
+ * 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.optype;
+
+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 @@
// 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.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 @@
// 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 @@
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 @@
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 @@
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 @@
* 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 @@
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 @@
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 @@
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 @@
@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 @@
@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 @@
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 @@
}
// 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 @@
@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 @@
@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.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 @@
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 @@
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 @@
}
}
+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 @@
{
<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 @@
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 @@
| <REFRESH : "refresh">
| <RETURN : "return">
| <RETURNING : "returning">
+ | <RIGHT : "right">
| <RTREE : "rtree">
| <RUN : "run">
| <SATISFIES : "satisfies">