[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">
}