[ASTERIXDB-3287][SQL++] Allow PATH to have multiple expressions

- user model changes: yes
- storage format changes: no
- interface changes: no

Details:
- Allow users to sepcify multiple expressions in the
  PATH clause in COPY TO
- The result of the multiple expressions will be
  concatenated by a sperator char

Change-Id: I62a4616a4fc12bdb57fe5a673efff0aa87dbdc46
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/17892
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Wail Alkowaileet <wael.y.k@gmail.com>
Reviewed-by: Murtadha Hubail <mhubail@apache.org>
Tested-by: Wail Alkowaileet <wael.y.k@gmail.com>
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/CopyToStatement.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/CopyToStatement.java
index 52e3570..5f13960 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/CopyToStatement.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/CopyToStatement.java
@@ -30,7 +30,9 @@
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.IReturningStatement;
 import org.apache.asterix.lang.common.clause.OrderbyClause;
+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.visitor.base.ILangVisitor;
 
 public class CopyToStatement extends AbstractStatement implements IReturningStatement {
@@ -43,29 +45,34 @@
     private final List<OrderbyClause.NullOrderModifier> orderbyNullModifierList;
 
     private Query query;
-    private Expression pathExpression;
+    private List<Expression> pathExpressions;
 
     private List<Expression> partitionExpressions;
     private List<Expression> orderbyList;
     private int varCounter;
 
     public CopyToStatement(Namespace namespace, String datasetName, Query query, VariableExpr sourceVariable,
-            ExternalDetailsDecl externalDetailsDecl, Expression pathExpression, List<Expression> partitionExpressions,
-            Map<Integer, VariableExpr> partitionsVariables, List<Expression> orderbyList,
-            List<OrderbyClause.OrderModifier> orderbyModifiers,
+            ExternalDetailsDecl externalDetailsDecl, List<Expression> pathExpressions,
+            List<Expression> partitionExpressions, Map<Integer, VariableExpr> partitionsVariables,
+            List<Expression> orderbyList, List<OrderbyClause.OrderModifier> orderbyModifiers,
             List<OrderbyClause.NullOrderModifier> orderbyNullModifierList, int varCounter) {
         this.namespace = namespace;
         this.datasetName = datasetName;
         this.query = query;
         this.sourceVariable = sourceVariable;
         this.externalDetailsDecl = externalDetailsDecl;
-        this.pathExpression = pathExpression;
+        this.pathExpressions = pathExpressions;
         this.partitionExpressions = partitionExpressions;
         this.partitionsVariables = partitionsVariables;
         this.orderbyList = orderbyList;
         this.orderbyModifiers = orderbyModifiers;
         this.orderbyNullModifierList = orderbyNullModifierList;
         this.varCounter = varCounter;
+
+        if (pathExpressions.isEmpty()) {
+            // Ensure path expressions to have at least an empty string
+            pathExpressions.add(new LiteralExpr(new StringLiteral("")));
+        }
     }
 
     @Override
@@ -107,12 +114,15 @@
         return externalDetailsDecl;
     }
 
-    public Expression getPathExpression() {
-        return pathExpression;
+    public List<Expression> getPathExpressions() {
+        return pathExpressions;
     }
 
-    public void setPathExpression(Expression pathExpression) {
-        this.pathExpression = pathExpression;
+    public void setPathExpressions(List<Expression> pathExpressions) {
+        if (pathExpressions.isEmpty()) {
+            pathExpressions.add(new LiteralExpr(new StringLiteral("")));
+        }
+        this.pathExpressions = pathExpressions;
     }
 
     public List<Expression> getPartitionExpressions() {
@@ -170,7 +180,7 @@
     public List<Expression> getDirectlyEnclosedExpressions() {
         List<Expression> topLevelExpressions = new ArrayList<>();
         topLevelExpressions.add(query.getBody());
-        topLevelExpressions.add(pathExpression);
+        topLevelExpressions.addAll(pathExpressions);
         topLevelExpressions.addAll(partitionExpressions);
         topLevelExpressions.addAll(orderbyList);
         return topLevelExpressions;
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/AbstractInlineUdfsVisitor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/AbstractInlineUdfsVisitor.java
index d91873c..22c011a 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/AbstractInlineUdfsVisitor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/AbstractInlineUdfsVisitor.java
@@ -290,9 +290,9 @@
         changed |= queryBody.first;
         stmtCopy.setBody(queryBody.second);
 
-        Pair<Boolean, Expression> path = inlineUdfsAndViewsInExpr(stmtCopy.getPathExpression());
+        Pair<Boolean, List<Expression>> path = inlineUdfsInExprList(stmtCopy.getPathExpressions());
         changed |= path.first;
-        stmtCopy.setPathExpression(path.second);
+        stmtCopy.setPathExpressions(path.second);
 
         Pair<Boolean, List<Expression>> part = inlineUdfsInExprList(stmtCopy.getPartitionExpressions());
         changed |= part.first;
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/FormatPrintVisitor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/FormatPrintVisitor.java
index 7ac9967..9c904da 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/FormatPrintVisitor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/FormatPrintVisitor.java
@@ -574,7 +574,7 @@
         out.println();
 
         out.print("path (");
-        cto.getPathExpression().accept(this, step + 1);
+        printDelimitedExpressions(cto.getPathExpressions(), COMMA, step + 1);
         out.print(")");
 
         out.println();
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/GatherFunctionCallsVisitor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/GatherFunctionCallsVisitor.java
index 4ea7789..3373495 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/GatherFunctionCallsVisitor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/GatherFunctionCallsVisitor.java
@@ -252,7 +252,7 @@
     @Override
     public Void visit(CopyToStatement stmtCopy, Void arg) throws CompilationException {
         stmtCopy.getQuery().accept(this, arg);
-        stmtCopy.getPathExpression().accept(this, arg);
+        acceptList(stmtCopy.getPathExpressions(), arg);
         acceptList(stmtCopy.getPartitionExpressions(), arg);
         acceptList(stmtCopy.getOrderbyList(), arg);
         return null;
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/CheckSubqueryVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/CheckSubqueryVisitor.java
index 387b7f7..abae3ee 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/CheckSubqueryVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/CheckSubqueryVisitor.java
@@ -315,7 +315,7 @@
 
     @Override
     public Boolean visit(CopyToStatement stmtCopy, ILangExpression arg) throws CompilationException {
-        return stmtCopy.getQuery().accept(this, arg) || stmtCopy.getPathExpression().accept(this, arg)
+        return stmtCopy.getQuery().accept(this, arg) || visitExprList(stmtCopy.getPathExpressions(), arg)
                 || visitExprList(stmtCopy.getPartitionExpressions(), arg)
                 || visitExprList(stmtCopy.getOrderbyList(), arg);
     }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/FreeVariableVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/FreeVariableVisitor.java
index c1c124a..45fd4bf 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/FreeVariableVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/FreeVariableVisitor.java
@@ -516,7 +516,7 @@
     @Override
     public Void visit(CopyToStatement stmtCopy, Collection<VariableExpr> freeVars) throws CompilationException {
         stmtCopy.getBody().accept(this, freeVars);
-        stmtCopy.getPathExpression().accept(this, freeVars);
+        visit(stmtCopy.getPathExpressions(), freeVars);
         visit(stmtCopy.getPartitionExpressions(), freeVars);
         visit(stmtCopy.getOrderbyList(), freeVars);
         return null;
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppContainsExpressionVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppContainsExpressionVisitor.java
index 368cf1d..39fe905a 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppContainsExpressionVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppContainsExpressionVisitor.java
@@ -310,7 +310,7 @@
 
     @Override
     public Boolean visit(CopyToStatement stmtCopy, T arg) throws CompilationException {
-        return stmtCopy.getQuery().accept(this, arg) || stmtCopy.getPathExpression().accept(this, arg)
+        return stmtCopy.accept(this, arg) || visitExprList(stmtCopy.getPathExpressions(), arg)
                 || visitExprList(stmtCopy.getPartitionExpressions(), arg)
                 || visitExprList(stmtCopy.getOrderbyList(), arg);
     }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppExpressionScopingVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppExpressionScopingVisitor.java
index f2519f1..1ecc3a2 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppExpressionScopingVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppExpressionScopingVisitor.java
@@ -434,8 +434,8 @@
         // Visit order by
         stmtCopy.setOrderbyList(visit(stmtCopy.getOrderbyList(), stmtCopy));
 
-        // Visit path expr
-        stmtCopy.setPathExpression(stmtCopy.getPathExpression().accept(this, stmtCopy));
+        // Visit path exprs
+        stmtCopy.setPathExpressions(visit(stmtCopy.getPathExpressions(), stmtCopy));
 
         return null;
     }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppSimpleExpressionVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppSimpleExpressionVisitor.java
index 16fe67a..9b2501d 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppSimpleExpressionVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppSimpleExpressionVisitor.java
@@ -361,7 +361,7 @@
     @Override
     public Expression visit(CopyToStatement stmtCopy, ILangExpression arg) throws CompilationException {
         stmtCopy.setBody(stmtCopy.getBody().accept(this, arg));
-        stmtCopy.setPathExpression(stmtCopy.getPathExpression().accept(this, arg));
+        stmtCopy.setPathExpressions(visit(stmtCopy.getPathExpressions(), arg));
         stmtCopy.setPartitionExpressions(visit(stmtCopy.getPartitionExpressions(), arg));
         stmtCopy.setOrderbyList(visit(stmtCopy.getOrderbyList(), arg));
         return null;
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index f2dc18e..a665015 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -2860,24 +2860,24 @@
 
 Statement CopyStatement() throws ParseException:
 {
- Token startToken = null;
- Pair<Namespace,Identifier> nameComponents = null;
- Query query = null;
- Statement stmt = null;
- TypeExpression typeExpr = null;
- VariableExpr alias = null;
+  Token startToken = null;
+  Pair<Namespace,Identifier> nameComponents = null;
+  Query query = null;
+  Statement stmt = null;
+  TypeExpression typeExpr = null;
+  VariableExpr alias = null;
 }
 {
-   <COPY>
-   ( LOOKAHEAD(1) <INTO> { startToken = token; } nameComponents = QualifiedName() stmt = CopyFromStatement(startToken, nameComponents, typeExpr)
-     | LOOKAHEAD(1) <LEFTPAREN> { startToken = token; } query = Query() <RIGHTPAREN> (<AS>)? alias = Variable()  stmt = CopyToStatement(startToken, nameComponents, query, alias)
-     | { startToken = token; } nameComponents = QualifiedName()
-       (<AS>)? (typeExpr = DatasetTypeSpecification(RecordTypeDefinition.RecordKind.OPEN) | alias = Variable())?
-       (stmt = CopyFromStatement(startToken, nameComponents, typeExpr) | stmt = CopyToStatement(startToken, nameComponents, query, alias))
-   )
-   {
-      return stmt;
-   }
+  <COPY>
+  ( LOOKAHEAD(1) <INTO> { startToken = token; } nameComponents = QualifiedName() stmt = CopyFromStatement(startToken, nameComponents, typeExpr)
+    | LOOKAHEAD(1) <LEFTPAREN> { startToken = token; } query = Query() <RIGHTPAREN> (<AS>)? alias = Variable()  stmt = CopyToStatement(startToken, nameComponents, query, alias)
+    | { startToken = token; } nameComponents = QualifiedName()
+      (<AS>)? (typeExpr = DatasetTypeSpecification(RecordTypeDefinition.RecordKind.OPEN) | alias = Variable())?
+      (stmt = CopyFromStatement(startToken, nameComponents, typeExpr) | stmt = CopyToStatement(startToken, nameComponents, query, alias))
+  )
+  {
+    return stmt;
+  }
 }
 
 CopyFromStatement CopyFromStatement(Token startToken, Pair<Namespace, Identifier> nameComponents, TypeExpression typeExpr) throws ParseException:
@@ -2913,7 +2913,7 @@
   RecordConstructor withRecord;
   Namespace namespace = nameComponents == null ? null : nameComponents.first;
   String datasetName = nameComponents == null ? null : nameComponents.second.getValue();
-  Expression pathExpr = null;
+  List<Expression> pathExprs;
 
   List<Expression> partitionExprs = new ArrayList<Expression>();
   Map<Integer, VariableExpr> partitionVarExprs = new HashMap<Integer, VariableExpr>();
@@ -2923,7 +2923,7 @@
 }
 {
   <TO> adapterName = AdapterName()
-  <PATH> <LEFTPAREN> pathExpr = Expression() <RIGHTPAREN>
+  <PATH> <LEFTPAREN> pathExprs = ExpressionList() <RIGHTPAREN>
   (CopyToOverClause(partitionExprs, partitionVarExprs, orderbyList, orderbyModifierList, orderbyNullModifierList))?
   <WITH> withRecord = RecordConstructor()
     {
@@ -2939,7 +2939,7 @@
           usedAlias = new VariableExpr(SqlppVariableUtil.toInternalVariableIdentifier(datasetName));
        }
 
-       CopyToStatement stmt = new CopyToStatement(namespace, datasetName, query, usedAlias, edd, pathExpr,
+       CopyToStatement stmt = new CopyToStatement(namespace, datasetName, query, usedAlias, edd, pathExprs,
             partitionExprs, partitionVarExprs, orderbyList, orderbyModifierList, orderbyNullModifierList, getVarCounter());
        return addSourceLocation(stmt, startToken);
     }