ASTERIXDB-1536: supports fully qualified dataset path in SQL++.

Change-Id: I93c7187b3a363a82dbfa225eb67ab526e04aa2dd
Reviewed-on: https://asterix-gerrit.ics.uci.edu/1008
Sonar-Qube: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Till Westmann <tillw@apache.org>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/ResolveVariableRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/ResolveVariableRule.java
index 4965197..d407a7b 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/ResolveVariableRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/ResolveVariableRule.java
@@ -38,6 +38,7 @@
 import org.apache.commons.lang3.mutable.MutableObject;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.common.utils.Pair;
+import org.apache.hyracks.algebricks.common.utils.Triple;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
 import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
@@ -74,7 +75,8 @@
         }
         // Populates the latest type information, e.g., resolved path sugars.
         context.computeAndSetTypeEnvironmentForOperator(op);
-        if (op.acceptExpressionTransform(exprRef -> rewriteExpressionReference(op, exprRef, context))) {
+        if (op.acceptExpressionTransform(
+                exprRef -> rewriteExpressionReference(op, exprRef, new Triple<>(false, null, null), null, context))) {
             // Generates the up-to-date type information.
             context.computeAndSetTypeEnvironmentForOperator(op);
             return true;
@@ -84,40 +86,45 @@
 
     // Recursively rewrites for an expression within an operator.
     private boolean rewriteExpressionReference(ILogicalOperator op, Mutable<ILogicalExpression> exprRef,
-            IOptimizationContext context) throws AlgebricksException {
+            Triple<Boolean, String, String> fullyQualifiedDatasetPathCandidateFromParent,
+            Mutable<ILogicalExpression> parentFuncRef, IOptimizationContext context) throws AlgebricksException {
         ILogicalExpression expr = exprRef.getValue();
         if (expr.getExpressionTag() != LogicalExpressionTag.FUNCTION_CALL) {
             return false;
         }
         boolean changed = false;
         AbstractFunctionCallExpression funcExpr = (AbstractFunctionCallExpression) expr;
+        Triple<Boolean, String, String> fullyQualifiedDatasetPathCandidate =
+                resolveFullyQualifiedPath(funcExpr, context);
         for (Mutable<ILogicalExpression> funcArgRef : funcExpr.getArguments()) {
-            if (rewriteExpressionReference(op, funcArgRef, context)) {
-                context.computeAndSetTypeEnvironmentForOperator(op);
+            if (rewriteExpressionReference(op, funcArgRef, fullyQualifiedDatasetPathCandidate, exprRef, context)) {
                 changed = true;
             }
         }
 
         // Cleans up extra scan-collections if there is.
-        cleanupScanCollectionForDataset(funcExpr);
+        if (changed) {
+            cleanupScanCollectionForDataset(funcExpr);
+        }
 
         // Does the actual resolution.
-        return changed || resolve(op, context, exprRef);
+        return changed || resolve(op, context, exprRef, fullyQualifiedDatasetPathCandidateFromParent, parentFuncRef);
     }
 
     // Resolves a "resolve" function call expression to a fully qualified variable/field-access path or
     // a dataset.
-    private boolean resolve(ILogicalOperator op, IOptimizationContext context, Mutable<ILogicalExpression> exprRef)
-            throws AlgebricksException {
+    private boolean resolve(ILogicalOperator op, IOptimizationContext context, Mutable<ILogicalExpression> exprRef,
+            Triple<Boolean, String, String> fullyQualifiedDatasetPathCandidateFromParent,
+            Mutable<ILogicalExpression> parentFuncRef) throws AlgebricksException {
         AbstractFunctionCallExpression funcExpr = (AbstractFunctionCallExpression) exprRef.getValue();
         if (funcExpr.getFunctionIdentifier() != AsterixBuiltinFunctions.RESOLVE) {
             return false;
         }
         ILogicalExpression arg = funcExpr.getArguments().get(0).getValue();
-        String unresolvedVarName = extractVariableName(arg);
-        return resolveInternal(exprRef, hasMatchedDataset(unresolvedVarName, context),
+        String unresolvedVarName = extractConstantString(arg);
+        return resolveInternal(exprRef, hasMatchedDatasetForVariableName(unresolvedVarName, context),
                 findCandidatePaths(op, extractPossibleVariables(funcExpr.getArguments()), unresolvedVarName, context),
-                unresolvedVarName);
+                unresolvedVarName, fullyQualifiedDatasetPathCandidateFromParent, parentFuncRef);
     }
 
     // Extracts all possible variables from the arguments of the "resolve" function.
@@ -134,41 +141,70 @@
     // Resolves an undefined name to a dataset or a fully qualified variable/field-access path
     // based on the given information of dataset matches and candidate paths.
     private boolean resolveInternal(Mutable<ILogicalExpression> funcRef, boolean hasMatchedDataset,
-            Collection<Pair<LogicalVariable, List<String>>> varAccessCandidates, String unresolvedVarName)
-            throws AlgebricksException {
+            Collection<Pair<LogicalVariable, List<String>>> varAccessCandidates, String unresolvedVarName,
+            Triple<Boolean, String, String> fullyQualifiedDatasetPathCandidateFromParent,
+            Mutable<ILogicalExpression> parentFuncRef) throws AlgebricksException {
         AbstractFunctionCallExpression func = (AbstractFunctionCallExpression) funcRef.getValue();
         int numVarCandidates = varAccessCandidates.size();
-        if (numVarCandidates > 1 || (numVarCandidates == 1 && hasMatchedDataset)) {
+        boolean hasAmbiguity =
+                hasAmbiguity(hasMatchedDataset, fullyQualifiedDatasetPathCandidateFromParent, numVarCandidates);
+        if (hasAmbiguity) {
+            // More than one possibilities.
             throw new AlgebricksException(
                     "Cannot resolve ambiguous alias (variable) reference for identifier " + unresolvedVarName);
-        } else if (numVarCandidates <= 0) {
-            if (!hasMatchedDataset) {
-                throw new AlgebricksException(
-                        "Undefined alias (variable) reference for identifier " + unresolvedVarName);
-            }
-            // Rewrites the "resolve" function to a "dataset" function.
+        } else if (hasMatchedDataset) {
+            // Rewrites the "resolve" function to a "dataset" function and only keep the dataset name argument.
             func.setFunctionInfo(FunctionUtil.getFunctionInfo(AsterixBuiltinFunctions.DATASET));
+            Mutable<ILogicalExpression> datasetNameExpression = func.getArguments().get(0);
+            func.getArguments().clear();
+            func.getArguments().add(datasetNameExpression);
+        } else if (fullyQualifiedDatasetPathCandidateFromParent.first) {
+            // Rewrites the parent "field-access" function to a "dataset" function.
+            AbstractFunctionCallExpression parentFunc = (AbstractFunctionCallExpression) parentFuncRef.getValue();
+            parentFunc.setFunctionInfo(FunctionUtil.getFunctionInfo(AsterixBuiltinFunctions.DATASET));
+            parentFunc.getArguments().clear();
+            parentFunc.getArguments()
+                    .add(new MutableObject<ILogicalExpression>(new ConstantExpression(
+                            new AsterixConstantValue(new AString(fullyQualifiedDatasetPathCandidateFromParent.second
+                                    + "." + fullyQualifiedDatasetPathCandidateFromParent.third)))));
+        } else if (numVarCandidates == 1) {
+            resolveAsFieldAccess(funcRef, varAccessCandidates.iterator().next());
         } else {
-            // Rewrites to field-access-by-names.
-            Pair<LogicalVariable, List<String>> varAndPath = varAccessCandidates.iterator().next();
-            LogicalVariable var = varAndPath.first;
-            List<String> path = varAndPath.second;
-            Mutable<ILogicalExpression> firstArgRef = new MutableObject<>(new VariableReferenceExpression(var));
-            ILogicalExpression newFunc = null;
-            for (String fieldName : path) {
-                List<Mutable<ILogicalExpression>> args = new ArrayList<>();
-                args.add(firstArgRef);
-                args.add(new MutableObject<ILogicalExpression>(
-                        new ConstantExpression(new AsterixConstantValue(new AString(fieldName)))));
-                newFunc = new ScalarFunctionCallExpression(
-                        FunctionUtil.getFunctionInfo(AsterixBuiltinFunctions.FIELD_ACCESS_BY_NAME), args);
-                firstArgRef = new MutableObject<>(newFunc);
-            }
-            funcRef.setValue(newFunc);
+            // Cannot find any resolution.
+            throw new AlgebricksException("Undefined alias (variable) reference for identifier " + unresolvedVarName);
         }
         return true;
     }
 
+    // Check whether it is possible to have multiple resolutions for a "resolve" function.
+    private boolean hasAmbiguity(boolean hasMatchedDataset,
+            Triple<Boolean, String, String> fullyQualifiedDatasetPathCandidateFromParent, int numVarCandidates) {
+        boolean hasAmbiguity = numVarCandidates > 1 || (numVarCandidates == 1 && hasMatchedDataset);
+        hasAmbiguity = hasAmbiguity || (numVarCandidates == 1 && fullyQualifiedDatasetPathCandidateFromParent.first);
+        hasAmbiguity = hasAmbiguity || (hasMatchedDataset && fullyQualifiedDatasetPathCandidateFromParent.first);
+        return hasAmbiguity;
+    }
+
+    // Resolves a "resolve" function call as a field access.
+    private void resolveAsFieldAccess(Mutable<ILogicalExpression> funcRef,
+            Pair<LogicalVariable, List<String>> varAndPath) {
+        // Rewrites to field-access-by-names.
+        LogicalVariable var = varAndPath.first;
+        List<String> path = varAndPath.second;
+        Mutable<ILogicalExpression> firstArgRef = new MutableObject<>(new VariableReferenceExpression(var));
+        ILogicalExpression newFunc = null;
+        for (String fieldName : path) {
+            List<Mutable<ILogicalExpression>> args = new ArrayList<>();
+            args.add(firstArgRef);
+            args.add(new MutableObject<ILogicalExpression>(
+                    new ConstantExpression(new AsterixConstantValue(new AString(fieldName)))));
+            newFunc = new ScalarFunctionCallExpression(
+                    FunctionUtil.getFunctionInfo(AsterixBuiltinFunctions.FIELD_ACCESS_BY_NAME), args);
+            firstArgRef = new MutableObject<>(newFunc);
+        }
+        funcRef.setValue(newFunc);
+    }
+
     // Finds all candidate fully qualified variable/field-access paths.
     private Set<Pair<LogicalVariable, List<String>>> findCandidatePaths(ILogicalOperator op,
             Collection<LogicalVariable> inputLiveVars, String unresolvedVarName, IOptimizationContext context)
@@ -218,11 +254,51 @@
         return varAccessCandidates;
     }
 
-    // Checks whether the name matches a dataset.
-    private boolean hasMatchedDataset(String name, IOptimizationContext context) throws AlgebricksException {
+    // Try to resolve the expression like resolve("x").foo as x.foo.
+    private Triple<Boolean, String, String> resolveFullyQualifiedPath(AbstractFunctionCallExpression funcExpr,
+            IOptimizationContext context) throws AlgebricksException {
+        if (!funcExpr.getFunctionIdentifier().equals(AsterixBuiltinFunctions.FIELD_ACCESS_BY_NAME)) {
+            return new Triple<>(false, null, null);
+        }
+        List<Mutable<ILogicalExpression>> args = funcExpr.getArguments();
+        ILogicalExpression firstExpr = args.get(0).getValue();
+        ILogicalExpression secondExpr = args.get(1).getValue();
+        if (firstExpr.getExpressionTag() != LogicalExpressionTag.FUNCTION_CALL) {
+            return new Triple<>(false, null, null);
+        }
+        if (secondExpr.getExpressionTag() != LogicalExpressionTag.CONSTANT) {
+            return new Triple<>(false, null, null);
+        }
+        AbstractFunctionCallExpression firstFuncExpr = (AbstractFunctionCallExpression) firstExpr;
+        if (!firstFuncExpr.getFunctionIdentifier().equals(AsterixBuiltinFunctions.RESOLVE)) {
+            return new Triple<>(false, null, null);
+        }
+        ILogicalExpression dataverseNameExpr = firstFuncExpr.getArguments().get(0).getValue();
+        String dataverseName = extractConstantString(dataverseNameExpr);
+        String datasetName = extractConstantString(secondExpr);
+        return new Triple<>(hasMatchedDataverseDataset(dataverseName, datasetName, context), dataverseName,
+                datasetName);
+    }
+
+    // Checks whether the dataverse name and dataset name matche a dataset.
+    private boolean hasMatchedDataverseDataset(String dataverseName, String datasetName, IOptimizationContext context)
+            throws AlgebricksException {
         AqlMetadataProvider mdp = (AqlMetadataProvider) context.getMetadataProvider();
-        if (name.contains(".")) {
-            String[] path = name.split("\\.");
+        if (mdp.findDataset(dataverseName, datasetName) != null) {
+            return true;
+        }
+        return false;
+    }
+
+    // Checks whether the name matches a dataset.
+    private boolean hasMatchedDatasetForVariableName(String varName, IOptimizationContext context)
+            throws AlgebricksException {
+        AqlMetadataProvider mdp = (AqlMetadataProvider) context.getMetadataProvider();
+        if (mdp.findDataset(mdp.getDefaultDataverseName(), varName) != null) {
+            return true;
+        }
+        if (varName.contains(".")) {
+            String[] path = varName.split("\\.");
             if (path.length != 2) {
                 return false;
             }
@@ -230,9 +306,6 @@
                 return true;
             }
         }
-        if (mdp.findDataset(mdp.getDefaultDataverseName(), name) != null) {
-            return true;
-        }
         return false;
     }
 
@@ -267,7 +340,7 @@
     }
 
     // Extracts the name of an undefined variable.
-    private String extractVariableName(ILogicalExpression arg) throws AlgebricksException {
+    private String extractConstantString(ILogicalExpression arg) throws AlgebricksException {
         if (arg.getExpressionTag() != LogicalExpressionTag.CONSTANT) {
             throw new AlgebricksException("The argument is expected to be a constant value.");
         }
diff --git a/asterixdb/asterix-app/src/test/resources/parserts/queries_sqlpp/columnalias2.sqlpp b/asterixdb/asterix-app/src/test/resources/parserts/queries_sqlpp/columnalias2.sqlpp
index 8eeea85..f74cf47 100644
--- a/asterixdb/asterix-app/src/test/resources/parserts/queries_sqlpp/columnalias2.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/parserts/queries_sqlpp/columnalias2.sqlpp
@@ -21,4 +21,4 @@
 GROUP BY root.id
 WITH u AS root.time
 HAVING root.orders > 0
-ORDER BY u; 
\ No newline at end of file
+ORDER BY u;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/columnalias2.ast b/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/columnalias2.ast
index 0b7ccd2..89e3b4b 100644
--- a/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/columnalias2.ast
+++ b/asterixdb/asterix-app/src/test/resources/parserts/results_parser_sqlpp/columnalias2.ast
@@ -2,18 +2,12 @@
 SELECT [
 FunctionCall null.SQRT@1[
   OperatorExpr [
-    FieldAccessor [
-      FunctionCall Metadata.dataset@1[
-        LiteralExpr [STRING] [t]
-      ]
-      Field=a
+    FunctionCall Metadata.dataset@1[
+      LiteralExpr [STRING] [t.a]
     ]
     *
-    FieldAccessor [
-      FunctionCall Metadata.dataset@1[
-        LiteralExpr [STRING] [t]
-      ]
-      Field=b
+    FunctionCall Metadata.dataset@1[
+      LiteralExpr [STRING] [t.b]
     ]
   ]
 ]
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/custord/join_q_08/join_q_08.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/custord/join_q_08/join_q_08.1.ddl.sqlpp
new file mode 100644
index 0000000..193d6b4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/custord/join_q_08/join_q_08.1.ddl.sqlpp
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+drop  database test if exists;
+create  database test;
+
+use test;
+
+
+create type test.AddressType as
+{
+  number : int64,
+  street : string,
+  city : string
+}
+
+create type test.CustomerType as
+ closed {
+  cid : int64,
+  name : string,
+  cashBack : int64,
+  age : int64?,
+  address : AddressType?,
+  lastorder : {
+      oid : int64,
+      total : float
+  }
+
+}
+
+create type test.OrderType as
+{
+  oid : int64,
+  cid : int64,
+  orderstatus : string,
+  orderpriority : string,
+  clerk : string,
+  total : float,
+  items : [int64]
+}
+
+create external table Customers(CustomerType) using localfs((`path`=`asterix_nc1://data/nontagged/customerData.json`),(`format`=`adm`));
+
+create external table Orders(OrderType) using localfs((`path`=`asterix_nc1://data/nontagged/orderData.json`),(`format`=`adm`));
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/custord/join_q_08/join_q_08.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/custord/join_q_08/join_q_08.2.update.sqlpp
new file mode 100644
index 0000000..6c98c1e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/custord/join_q_08/join_q_08.2.update.sqlpp
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/custord/join_q_08/join_q_08.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/custord/join_q_08/join_q_08.3.query.sqlpp
new file mode 100644
index 0000000..13fd8f9
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/custord/join_q_08/join_q_08.3.query.sqlpp
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+/** This query is to test fully qualified path for datasets. */
+
+SELECT c.name AS cust_name,
+       c.age AS cust_age,
+       o.total AS order_total,
+       [o.oid,o.cid] AS orderList
+FROM test.Customers c, test.Orders o
+WHERE c.cid = o.cid
+ORDER BY c.name,o.total
+;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/custord/join_q_09/join_q_09.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/custord/join_q_09/join_q_09.1.ddl.sqlpp
new file mode 100644
index 0000000..193d6b4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/custord/join_q_09/join_q_09.1.ddl.sqlpp
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+drop  database test if exists;
+create  database test;
+
+use test;
+
+
+create type test.AddressType as
+{
+  number : int64,
+  street : string,
+  city : string
+}
+
+create type test.CustomerType as
+ closed {
+  cid : int64,
+  name : string,
+  cashBack : int64,
+  age : int64?,
+  address : AddressType?,
+  lastorder : {
+      oid : int64,
+      total : float
+  }
+
+}
+
+create type test.OrderType as
+{
+  oid : int64,
+  cid : int64,
+  orderstatus : string,
+  orderpriority : string,
+  clerk : string,
+  total : float,
+  items : [int64]
+}
+
+create external table Customers(CustomerType) using localfs((`path`=`asterix_nc1://data/nontagged/customerData.json`),(`format`=`adm`));
+
+create external table Orders(OrderType) using localfs((`path`=`asterix_nc1://data/nontagged/orderData.json`),(`format`=`adm`));
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/custord/join_q_09/join_q_09.2.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/custord/join_q_09/join_q_09.2.query.sqlpp
new file mode 100644
index 0000000..49aa67f
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/custord/join_q_09/join_q_09.2.query.sqlpp
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+/** This query is a negative test for ambiguous alias reference. */
+
+SELECT c.name AS cust_name,
+       age AS cust_age,
+       o.total AS order_total,
+       [o.oid,o.cid] AS orderList
+FROM test.Customers c, test.Orders o
+WHERE c.cid = o.cid
+ORDER BY c.name,o.total
+;
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 e3d3102..2d254da 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -1349,7 +1349,18 @@
     <test-case FilePath="custord">
       <compilation-unit name="join_q_07">
         <output-dir compare="Text">join_q_06</output-dir>
-        <expected-error>Undefined alias (variable) reference for identifier c</expected-error>
+        <expected-error>Cannot find dataset c in dataverse test nor an alias with name c</expected-error>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="custord">
+      <compilation-unit name="join_q_08">
+        <output-dir compare="Text">join_q_01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="custord">
+      <compilation-unit name="join_q_09">
+        <output-dir compare="Text">join_q_01</output-dir>
+        <expected-error>Cannot resolve ambiguous alias (variable) reference for identifier age</expected-error>
       </compilation-unit>
     </test-case>
     <test-case FilePath="custord">
@@ -2855,7 +2866,7 @@
         <output-dir compare="Text">partition-by-nonexistent-field</output-dir>
         <expected-error>java.lang.NullPointerException</expected-error>
         <expected-error>Cannot find dataset</expected-error>
-        <expected-error>Undefined alias (variable) reference for identifier testds</expected-error>
+        <expected-error>Cannot find dataset testds in dataverse test nor an alias with name testds</expected-error>
       </compilation-unit>
     </test-case>
     <test-case FilePath="misc">
@@ -6666,7 +6677,7 @@
     <test-case FilePath="user-defined-functions">
       <compilation-unit name="udf30">
         <output-dir compare="Text">udf30</output-dir>
-        <expected-error>Undefined alias (variable) reference for identifier y</expected-error>
+        <expected-error>Cannot find dataset y because there is no dataverse declared, nor an alias with name y</expected-error>
       </compilation-unit>
     </test-case>
     <test-case FilePath="user-defined-functions">
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/VariableCheckAndRewriteVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/VariableCheckAndRewriteVisitor.java
index 7082c25..d250e08 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/VariableCheckAndRewriteVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/VariableCheckAndRewriteVisitor.java
@@ -26,8 +26,10 @@
 import org.apache.asterix.common.exceptions.AsterixException;
 import org.apache.asterix.common.functions.FunctionSignature;
 import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.Expression.Kind;
 import org.apache.asterix.lang.common.base.ILangExpression;
 import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.FieldAccessor;
 import org.apache.asterix.lang.common.expression.LiteralExpr;
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.literal.StringLiteral;
@@ -41,8 +43,8 @@
 
 public class VariableCheckAndRewriteVisitor extends AbstractSqlppExpressionScopingVisitor {
 
-    protected final FunctionSignature datasetFunction = new FunctionSignature(MetadataConstants.METADATA_DATAVERSE_NAME,
-            "dataset", 1);
+    protected final FunctionSignature datasetFunction =
+            new FunctionSignature(MetadataConstants.METADATA_DATAVERSE_NAME, "dataset", 1);
     protected final boolean overwrite;
     protected final AqlMetadataProvider metadataProvider;
 
@@ -62,23 +64,70 @@
     }
 
     @Override
+    public Expression visit(FieldAccessor fa, ILangExpression arg) throws AsterixException {
+        Expression leadingExpr = fa.getExpr();
+        if (leadingExpr.getKind() != Kind.VARIABLE_EXPRESSION) {
+            fa.setExpr(leadingExpr.accept(this, fa));
+            return fa;
+        } else {
+            VariableExpr varExpr = (VariableExpr) leadingExpr;
+            String lastIdentifier = fa.getIdent().getValue();
+            Expression resolvedExpr = resolve(varExpr,
+                    /** Resolves within the dataverse that has the same name as the variable name. */
+                    SqlppVariableUtil.toUserDefinedVariableName(varExpr.getVar().getValue()).getValue(), lastIdentifier,
+                    arg);
+            if (resolvedExpr.getKind() == Kind.CALL_EXPRESSION) {
+                CallExpr callExpr = (CallExpr) resolvedExpr;
+                if (callExpr.getFunctionSignature().equals(datasetFunction)) {
+                    // The field access is resolved to be a dataset access in the form of "dataverse.dataset".
+                    return resolvedExpr;
+                }
+            }
+            fa.setExpr(resolvedExpr);
+            return fa;
+        }
+    }
+
+    @Override
     public Expression visit(VariableExpr varExpr, ILangExpression arg) throws AsterixException {
+        return resolve(varExpr, null /** Resolves within the default dataverse. */
+                , SqlppVariableUtil.toUserDefinedVariableName(varExpr.getVar().getValue()).getValue(), arg);
+    }
+
+    // Resolve a variable expression with dataverse name and dataset name.
+    private Expression resolve(VariableExpr varExpr, String dataverseName, String datasetName, ILangExpression arg)
+            throws AsterixException {
         String varName = varExpr.getVar().getValue();
+        checkError(varName);
+        if (!rewriteNeeded(varExpr)) {
+            return varExpr;
+        }
+        Set<VariableExpr> liveVars = SqlppVariableUtil.getLiveUserDefinedVariables(scopeChecker.getCurrentScope());
+        boolean resolveAsDataset = resolveDatasetFirst(arg) && datasetExists(dataverseName, datasetName);
+        if (resolveAsDataset) {
+            return wrapWithDatasetFunction(dataverseName, datasetName);
+        } else if (liveVars.isEmpty()) {
+            String defaultDataverseName = metadataProvider.getDefaultDataverseName();
+            if (dataverseName == null && defaultDataverseName == null) {
+                throw new AsterixException("Cannot find dataset " + datasetName
+                        + " because there is no dataverse declared, nor an alias with name " + datasetName + "!");
+            }
+            //If no available dataset nor in-scope variable to resolve to, we throw an error.
+            throw new AsterixException("Cannot find dataset " + datasetName + " in dataverse "
+                    + (dataverseName == null ? defaultDataverseName : dataverseName) + " nor an alias with name "
+                    + datasetName + "!");
+        }
+        return wrapWithResolveFunction(varExpr, liveVars);
+    }
+
+    // Checks whether we need to error the variable reference, e.g., the variable is referred
+    // in a LIMIT clause.
+    private void checkError(String varName) throws AsterixException {
         if (scopeChecker.isInForbiddenScopes(varName)) {
             throw new AsterixException(
                     "Inside limit clauses, it is disallowed to reference a variable having the same name"
                             + " as any variable bound in the same scope as the limit clause.");
         }
-        if (!rewriteNeeded(varExpr)) {
-            return varExpr;
-        }
-        boolean resolveAsDataset = resolveDatasetFirst(arg)
-                && datasetExists(SqlppVariableUtil.toUserDefinedVariableName(varName).getValue());
-        if (resolveAsDataset) {
-            return wrapWithDatasetFunction(varExpr);
-        }
-        Set<VariableExpr> liveVars = SqlppVariableUtil.getLiveUserDefinedVariables(scopeChecker.getCurrentScope());
-        return wrapWithResolveFunction(varExpr, liveVars);
     }
 
     // For From/Join/UNNEST/NEST, we resolve the undefined identifier reference as dataset reference first.
@@ -101,26 +150,25 @@
         }
     }
 
-    private Expression wrapWithDatasetFunction(VariableExpr expr) throws AsterixException {
+    private Expression wrapWithDatasetFunction(String dataverseName, String datasetName) throws AsterixException {
+        String fullyQualifiedName = dataverseName == null ? datasetName : dataverseName + "." + datasetName;
         List<Expression> argList = new ArrayList<>();
-        //Ignore the parser-generated prefix "$" for a dataset.
-        String varName = SqlppVariableUtil.toUserDefinedVariableName(expr.getVar()).getValue();
-        argList.add(new LiteralExpr(new StringLiteral(varName)));
+        argList.add(new LiteralExpr(new StringLiteral(fullyQualifiedName)));
         return new CallExpr(datasetFunction, argList);
     }
 
-    private boolean datasetExists(String name) throws AsterixException {
+    private boolean datasetExists(String dataverseName, String datasetName) throws AsterixException {
         try {
-            if (metadataProvider.findDataset(null, name) != null) {
+            if (metadataProvider.findDataset(dataverseName, datasetName) != null) {
                 return true;
             }
-            return pathDatasetExists(name);
+            return fullyQualifiedDatasetNameExists(datasetName);
         } catch (AlgebricksException e) {
             throw new AsterixException(e);
         }
     }
 
-    private boolean pathDatasetExists(String name) throws AlgebricksException {
+    private boolean fullyQualifiedDatasetNameExists(String name) throws AlgebricksException {
         if (!name.contains(".")) {
             return false;
         }