[ASTERIXDB-2401][SQLPP] Support parameterized queries

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

Details:
- Support statement parameters: named ($name) and positional ($1 or ?)
- Enhance query service API to accept these parameters in the request
- Remove [?] index accessor from SQL++ grammar because it conflicts
  with positional parameters ([0] can be used instead)
- Add testcases for parameterized queries

Change-Id: Ia612f731cd2370fccd54c4796bd9787fbea16766
Reviewed-on: https://asterix-gerrit.ics.uci.edu/2707
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Contrib: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Till Westmann <tillw@apache.org>
diff --git a/asterixdb/asterix-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 7858e58..0c0ebd6 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
@@ -18,22 +18,24 @@
  */
 package org.apache.asterix.lang.sqlpp.rewrites;
 
+import java.util.Collection;
 import java.util.List;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.lang.common.base.IReturningStatement;
 import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
 import org.apache.asterix.lang.common.statement.FunctionDecl;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
 import org.apache.asterix.metadata.declared.MetadataProvider;
 
 class SqlppFunctionBodyRewriter extends SqlppQueryRewriter {
 
     @Override
     public void rewrite(List<FunctionDecl> declaredFunctions, IReturningStatement topStatement,
-            MetadataProvider metadataProvider, LangRewritingContext context, boolean inlineUdfs)
-            throws CompilationException {
+            MetadataProvider metadataProvider, LangRewritingContext context, boolean inlineUdfs,
+            Collection<VarIdentifier> externalVars) throws CompilationException {
         // Sets up parameters.
-        setup(declaredFunctions, topStatement, metadataProvider, context);
+        setup(declaredFunctions, topStatement, metadataProvider, context, externalVars);
 
         // Inlines column aliases.
         inlineColumnAlias();
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 647b50e..09a9f90 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
@@ -19,6 +19,7 @@
 package org.apache.asterix.lang.sqlpp.rewrites;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 
@@ -31,6 +32,7 @@
 import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
 import org.apache.asterix.lang.common.statement.FunctionDecl;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
 import org.apache.asterix.lang.common.util.FunctionUtil;
 import org.apache.asterix.lang.common.visitor.GatherFunctionCallsVisitor;
 import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
@@ -76,25 +78,27 @@
     private List<FunctionDecl> declaredFunctions;
     private LangRewritingContext context;
     private MetadataProvider metadataProvider;
+    private Collection<VarIdentifier> externalVars;
 
     protected void setup(List<FunctionDecl> declaredFunctions, IReturningStatement topExpr,
-            MetadataProvider metadataProvider, LangRewritingContext context) {
+            MetadataProvider metadataProvider, LangRewritingContext context, Collection<VarIdentifier> externalVars) {
         this.topExpr = topExpr;
         this.context = context;
         this.declaredFunctions = declaredFunctions;
         this.metadataProvider = metadataProvider;
+        this.externalVars = externalVars;
     }
 
     @Override
     public void rewrite(List<FunctionDecl> declaredFunctions, IReturningStatement topStatement,
-            MetadataProvider metadataProvider, LangRewritingContext context, boolean inlineUdfs)
-            throws CompilationException {
+            MetadataProvider metadataProvider, LangRewritingContext context, boolean inlineUdfs,
+            Collection<VarIdentifier> externalVars) throws CompilationException {
         if (topStatement == null) {
             return;
         }
 
         // Sets up parameters.
-        setup(declaredFunctions, topStatement, metadataProvider, context);
+        setup(declaredFunctions, topStatement, metadataProvider, context, externalVars);
 
         // Inlines column aliases.
         inlineColumnAlias();
@@ -206,7 +210,7 @@
 
     protected void variableCheckAndRewrite() throws CompilationException {
         VariableCheckAndRewriteVisitor variableCheckAndRewriteVisitor =
-                new VariableCheckAndRewriteVisitor(context, metadataProvider, topExpr.getExternalVars());
+                new VariableCheckAndRewriteVisitor(context, metadataProvider, externalVars);
         topExpr.accept(variableCheckAndRewriteVisitor, null);
     }
 
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppStatementRewriter.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppStatementRewriter.java
index 5667415..7908636 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppStatementRewriter.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppStatementRewriter.java
@@ -21,6 +21,7 @@
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.lang.common.base.IStatementRewriter;
 import org.apache.asterix.lang.common.base.Statement;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
 import org.apache.asterix.lang.sqlpp.visitor.SqlppDeleteRewriteVisitor;
 
 class SqlppStatementRewriter implements IStatementRewriter {
@@ -36,4 +37,9 @@
             stmt.accept(visitor, null);
         }
     }
+
+    @Override
+    public String toExternalVariableName(String statementParameterName) {
+        return SqlppVariableUtil.toExternalVariableName(statementParameterName);
+    }
 }
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 5af284b..20071e3 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
@@ -19,6 +19,7 @@
 package org.apache.asterix.lang.sqlpp.rewrites.visitor;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 
@@ -56,7 +57,7 @@
      * @param context, manages ids of variables and guarantees uniqueness of variables.
      */
     public VariableCheckAndRewriteVisitor(LangRewritingContext context, MetadataProvider metadataProvider,
-            List<VarIdentifier> externalVars) {
+            Collection<VarIdentifier> externalVars) {
         super(context, externalVars);
         this.metadataProvider = metadataProvider;
     }
@@ -96,9 +97,9 @@
     private Expression resolve(VariableExpr varExpr, String dataverseName, String datasetName,
             Expression originalExprWithUndefinedIdentifier, ILangExpression parent) throws CompilationException {
 
+        VarIdentifier varId = varExpr.getVar();
+        String varName = varId.getValue();
         SourceLocation sourceLoc = varExpr.getSourceLocation();
-
-        String varName = varExpr.getVar().getValue();
         VarIdentifier var = lookupVariable(varName, sourceLoc);
         if (var != null) {
             // Exists such an identifier
@@ -107,6 +108,11 @@
             return varExpr;
         }
 
+        if (SqlppVariableUtil.isExternalVariableIdentifier(varId)) {
+            throw new CompilationException(ErrorCode.PARAMETER_NO_VALUE, sourceLoc,
+                    SqlppVariableUtil.variableNameToDisplayedFieldName(varId.getValue()));
+        }
+
         boolean resolveToDatasetOnly = resolveToDatasetOnly(originalExprWithUndefinedIdentifier, parent);
         if (resolveToDatasetOnly) {
             return resolveAsDataset(dataverseName, datasetName, sourceLoc);
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/ExpressionToVariableUtil.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/ExpressionToVariableUtil.java
index c0eb2d9..4842026 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/ExpressionToVariableUtil.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/ExpressionToVariableUtil.java
@@ -40,7 +40,8 @@
     private static String getGeneratedIdentifier(Expression expr) throws ParseException {
         if (expr.getKind() == Kind.VARIABLE_EXPRESSION) {
             VariableExpr bindingVarExpr = (VariableExpr) expr;
-            return bindingVarExpr.getVar().getValue();
+            VarIdentifier var = bindingVarExpr.getVar();
+            return SqlppVariableUtil.isExternalVariableIdentifier(var) ? null : var.getValue();
         } else if (expr.getKind() == Kind.FIELD_ACCESSOR_EXPRESSION) {
             FieldAccessor fa = (FieldAccessor) expr;
             return SqlppVariableUtil.toInternalVariableName(fa.getIdent().getValue());
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppVariableUtil.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppVariableUtil.java
index 519627c..3dbdde5 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppVariableUtil.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppVariableUtil.java
@@ -40,6 +40,8 @@
 
     private static final String USER_VAR_PREFIX = "$";
 
+    private static final String EXTERNAL_VAR_PREFIX = "?";
+
     private SqlppVariableUtil() {
     }
 
@@ -79,7 +81,15 @@
     }
 
     public static VarIdentifier toInternalVariableIdentifier(String idName) {
-        return new VarIdentifier(USER_VAR_PREFIX + idName);
+        return new VarIdentifier(toInternalVariableName(idName));
+    }
+
+    public static String toExternalVariableName(String varName) {
+        return EXTERNAL_VAR_PREFIX + varName;
+    }
+
+    public static boolean isExternalVariableIdentifier(VarIdentifier varId) {
+        return varId.getValue().startsWith(EXTERNAL_VAR_PREFIX);
     }
 
     public static Collection<VariableExpr> getFreeVariables(ILangExpression langExpr) throws CompilationException {
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 6635a0e..fbe9eb5 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
@@ -256,8 +256,8 @@
 
     @Override
     public Query visit(Query q, Void arg) throws CompilationException {
-        Query copy = new Query(q.isExplain(), q.isTopLevel(), (Expression) q.getBody().accept(this, arg),
-                q.getVarCounter(), q.getExternalVars());
+        Query copy =
+                new Query(q.isExplain(), q.isTopLevel(), (Expression) q.getBody().accept(this, arg), q.getVarCounter());
         copy.setSourceLocation(q.getSourceLocation());
         return copy;
     }
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 81b21c8..6d9430b 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
@@ -18,8 +18,8 @@
  */
 package org.apache.asterix.lang.sqlpp.visitor.base;
 
+import java.util.Collection;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -76,7 +76,7 @@
      * @param externalVars
      *            pre-defined (external) variables that must be added to the initial scope
      */
-    public AbstractSqlppExpressionScopingVisitor(LangRewritingContext context, List<VarIdentifier> externalVars) {
+    public AbstractSqlppExpressionScopingVisitor(LangRewritingContext context, Collection<VarIdentifier> externalVars) {
         this.context = context;
         this.scopeChecker.setVarCounter(context.getVarCounter());
         if (externalVars != null) {
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index 6d9976a..e2a8759 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -209,6 +209,8 @@
     // error configuration
     protected static final boolean REPORT_EXPECTED_TOKENS = false;
 
+    private int externalVarCounter;
+
     private static class IndexParams {
       public IndexType type;
       public int gramLength;
@@ -2325,9 +2327,6 @@
             }
         }
     }
-
-  | <QUES> // ANY
-
   )
 
   <RIGHTBRACKET>
@@ -2347,6 +2346,7 @@
   | expr = CaseExpr()
   | expr = Literal()
   | expr = VariableRef()
+  | expr = ExternalVariableRef()
   | expr = ListConstructor()
   | expr = RecordConstructor()
   | expr = ParenthesizedExpression()
@@ -2458,6 +2458,33 @@
     }
 }
 
+VariableExpr ExternalVariableRef() throws ParseException:
+{
+  String name = null;
+}
+{
+  (
+    (
+      <DOLLAR>
+      (
+        <INTEGER_LITERAL> { name = token.image; } |
+        <IDENTIFIER> { name = token.image; } |
+        name = QuotedString()
+      )
+    )
+    |
+    (
+      <QUES> { name = String.valueOf(++externalVarCounter); }
+    )
+  )
+  {
+     String idName = SqlppVariableUtil.toExternalVariableName(name);
+     VarIdentifier id = new VarIdentifier(idName);
+     VariableExpr varExp = new VariableExpr(id);
+     return addSourceLocation(varExp, token);
+  }
+}
+
 Expression ListConstructor() throws ParseException:
 {
     Expression expr = null;
@@ -3410,6 +3437,7 @@
   | <ATT : "@">
   | <COLON : ":">
   | <COMMA : ",">
+  | <DOLLAR: "$">
   | <DOT : ".">
   | <PERCENT: "%">
   | <QUES : "?">
@@ -3459,8 +3487,8 @@
 <DEFAULT,IN_DBL_BRACE>
 TOKEN [IGNORE_CASE]:
 {
-  <MISSING : "missing">
-  |  <NULL : "null">
+    <MISSING : "missing">
+  | <NULL : "null">
   | <TRUE : "true">
   | <FALSE : "false">
 }