[NO ISSUE][API] Add Multi-Statement Parameter to HTTP API
- user model changes: no
- storage format changes: no
- interface changes: no
Details:
- Add new query service http API parameter to
enable or disble multiple statements and default
it to enabled.
- When multi-statement is disabled, only the following
statements are allowed to appear multiple times:
USE, SET, DECLARE, and WRITE.
- Extract RequestParameters class out of
QueryServiceServlet.
- Clean test cases left dataverses one at a time
in test framework.
Change-Id: I5bf2d9fc5fec351565b09cfef504539d5a938af5
Reviewed-on: https://asterix-gerrit.ics.uci.edu/2736
Reviewed-by: Murtadha Hubail <mhubail@apache.org>
Sonar-Qube: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
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-algebra/src/main/java/org/apache/asterix/translator/IRequestParameters.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/IRequestParameters.java
index d58d761..0dbd3aa 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/IRequestParameters.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/IRequestParameters.java
@@ -62,4 +62,9 @@
* @return Statement parameters
*/
Map<String, IAObject> getStatementParameters();
+
+ /**
+ * @return true if the request accepts multiple statements. Otherwise, false.
+ */
+ boolean isMultiStatement();
}
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ApiServlet.java
index 86cac25..466757e 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ApiServlet.java
@@ -164,7 +164,7 @@
long startTime = System.currentTimeMillis();
final IRequestParameters requestParameters =
new RequestParameters(hds, new ResultProperties(IStatementExecutor.ResultDelivery.IMMEDIATE),
- new IStatementExecutor.Stats(), null, null, null, null);
+ new IStatementExecutor.Stats(), null, null, null, null, true);
translator.compileAndExecute(hcc, null, requestParameters);
long endTime = System.currentTimeMillis();
duration = (endTime - startTime) / 1000.00;
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NCQueryServiceServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NCQueryServiceServlet.java
index 9655f57..3150cb2 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NCQueryServiceServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NCQueryServiceServlet.java
@@ -70,7 +70,7 @@
@Override
protected void executeStatement(String statementsText, SessionOutput sessionOutput,
- ResultProperties resultProperties, IStatementExecutor.Stats stats, RequestParameters param,
+ ResultProperties resultProperties, IStatementExecutor.Stats stats, QueryServiceRequestParameters param,
RequestExecutionState execution, Map<String, String> optionalParameters,
Map<String, byte[]> statementParameters) throws Exception {
// Running on NC -> send 'execute' message to CC
@@ -79,31 +79,31 @@
final IStatementExecutor.ResultDelivery delivery = resultProperties.getDelivery();
ExecuteStatementResponseMessage responseMsg;
MessageFuture responseFuture = ncMb.registerMessageFuture();
- final String handleUrl = getHandleUrl(param.host, param.path, delivery);
+ final String handleUrl = getHandleUrl(param.getHost(), param.getPath(), delivery);
try {
- if (param.clientContextID == null) {
- param.clientContextID = UUID.randomUUID().toString();
+ if (param.getClientContextID() == null) {
+ param.setClientContextID(UUID.randomUUID().toString());
}
long timeout = ExecuteStatementRequestMessage.DEFAULT_NC_TIMEOUT_MILLIS;
- if (param.timeout != null && !param.timeout.trim().isEmpty()) {
- timeout = TimeUnit.NANOSECONDS.toMillis(Duration.parseDurationStringToNanos(param.timeout));
+ if (param.getTimeout() != null && !param.getTimeout().trim().isEmpty()) {
+ timeout = TimeUnit.NANOSECONDS.toMillis(Duration.parseDurationStringToNanos(param.getTimeout()));
}
- ExecuteStatementRequestMessage requestMsg =
- new ExecuteStatementRequestMessage(ncCtx.getNodeId(), responseFuture.getFutureId(), queryLanguage,
- statementsText, sessionOutput.config(), resultProperties.getNcToCcResultProperties(),
- param.clientContextID, handleUrl, optionalParameters, statementParameters);
+ ExecuteStatementRequestMessage requestMsg = new ExecuteStatementRequestMessage(ncCtx.getNodeId(),
+ responseFuture.getFutureId(), queryLanguage, statementsText, sessionOutput.config(),
+ resultProperties.getNcToCcResultProperties(), param.getClientContextID(), handleUrl,
+ optionalParameters, statementParameters, param.isMultiStatement());
execution.start();
ncMb.sendMessageToPrimaryCC(requestMsg);
try {
responseMsg = (ExecuteStatementResponseMessage) responseFuture.get(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
- cancelQuery(ncMb, ncCtx.getNodeId(), param.clientContextID, e, false);
+ cancelQuery(ncMb, ncCtx.getNodeId(), param.getClientContextID(), e, false);
throw e;
} catch (TimeoutException exception) {
RuntimeDataException hde = new RuntimeDataException(ErrorCode.QUERY_TIMEOUT);
hde.addSuppressed(exception);
// cancel query
- cancelQuery(ncMb, ncCtx.getNodeId(), param.clientContextID, hde, true);
+ cancelQuery(ncMb, ncCtx.getNodeId(), param.getClientContextID(), hde, true);
throw hde;
}
execution.end();
@@ -157,7 +157,8 @@
}
@Override
- protected void handleExecuteStatementException(Throwable t, RequestExecutionState state, RequestParameters param) {
+ protected void handleExecuteStatementException(Throwable t, RequestExecutionState state,
+ QueryServiceRequestParameters param) {
if (t instanceof TimeoutException // TODO(mblow): I don't think t can ever been an instance of TimeoutException
|| ExceptionUtils.matchingCause(t, candidate -> candidate instanceof IPCException)) {
GlobalConfig.ASTERIX_LOGGER.log(Level.WARN, t.toString(), t);
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceRequestParameters.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceRequestParameters.java
new file mode 100644
index 0000000..d0c59b5
--- /dev/null
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceRequestParameters.java
@@ -0,0 +1,230 @@
+/*
+ * 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.api.http.server;
+
+import java.util.Map;
+
+import org.apache.hyracks.util.JSONUtil;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class QueryServiceRequestParameters {
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ private String host;
+ private String path;
+ private String statement;
+ private String format;
+ private String timeout;
+ private boolean pretty;
+ private String clientContextID;
+ private String mode;
+ private String maxResultReads;
+ private String planFormat;
+ private Map<String, JsonNode> statementParams;
+ private boolean expressionTree;
+ private boolean rewrittenExpressionTree;
+ private boolean logicalPlan;
+ private boolean optimizedLogicalPlan;
+ private boolean job;
+ private boolean signature;
+ private boolean multiStatement;
+
+ public String getHost() {
+ return host;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public String getStatement() {
+ return statement;
+ }
+
+ public void setStatement(String statement) {
+ this.statement = statement;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+
+ public void setFormat(String format) {
+ this.format = format;
+ }
+
+ public String getTimeout() {
+ return timeout;
+ }
+
+ public void setTimeout(String timeout) {
+ this.timeout = timeout;
+ }
+
+ public boolean isPretty() {
+ return pretty;
+ }
+
+ public void setPretty(boolean pretty) {
+ this.pretty = pretty;
+ }
+
+ public String getClientContextID() {
+ return clientContextID;
+ }
+
+ public void setClientContextID(String clientContextID) {
+ this.clientContextID = clientContextID;
+ }
+
+ public String getMode() {
+ return mode;
+ }
+
+ public void setMode(String mode) {
+ this.mode = mode;
+ }
+
+ public String getMaxResultReads() {
+ return maxResultReads;
+ }
+
+ public void setMaxResultReads(String maxResultReads) {
+ this.maxResultReads = maxResultReads;
+ }
+
+ public String getPlanFormat() {
+ return planFormat;
+ }
+
+ public void setPlanFormat(String planFormat) {
+ this.planFormat = planFormat;
+ }
+
+ public Map<String, JsonNode> getStatementParams() {
+ return statementParams;
+ }
+
+ public void setStatementParams(Map<String, JsonNode> statementParams) {
+ this.statementParams = statementParams;
+ }
+
+ public boolean isExpressionTree() {
+ return expressionTree;
+ }
+
+ public void setExpressionTree(boolean expressionTree) {
+ this.expressionTree = expressionTree;
+ }
+
+ public boolean isRewrittenExpressionTree() {
+ return rewrittenExpressionTree;
+ }
+
+ public void setRewrittenExpressionTree(boolean rewrittenExpressionTree) {
+ this.rewrittenExpressionTree = rewrittenExpressionTree;
+ }
+
+ public boolean isLogicalPlan() {
+ return logicalPlan;
+ }
+
+ public void setLogicalPlan(boolean logicalPlan) {
+ this.logicalPlan = logicalPlan;
+ }
+
+ public boolean isOptimizedLogicalPlan() {
+ return optimizedLogicalPlan;
+ }
+
+ public void setOptimizedLogicalPlan(boolean optimizedLogicalPlan) {
+ this.optimizedLogicalPlan = optimizedLogicalPlan;
+ }
+
+ public boolean isJob() {
+ return job;
+ }
+
+ public void setJob(boolean job) {
+ this.job = job;
+ }
+
+ public boolean isSignature() {
+ return signature;
+ }
+
+ public void setSignature(boolean signature) {
+ this.signature = signature;
+ }
+
+ public boolean isMultiStatement() {
+ return multiStatement;
+ }
+
+ public void setMultiStatement(boolean multiStatement) {
+ this.multiStatement = multiStatement;
+ }
+
+ @Override
+ public String toString() {
+ try {
+ ObjectNode on = OBJECT_MAPPER.createObjectNode();
+ on.put("host", host);
+ on.put("path", path);
+ on.put("statement", JSONUtil.escape(new StringBuilder(), statement).toString());
+ on.put("pretty", pretty);
+ on.put("mode", mode);
+ on.put("clientContextID", clientContextID);
+ on.put("format", format);
+ on.put("timeout", timeout);
+ on.put("maxResultReads", maxResultReads);
+ on.put("planFormat", planFormat);
+ on.put("expressionTree", expressionTree);
+ on.put("rewrittenExpressionTree", rewrittenExpressionTree);
+ on.put("logicalPlan", logicalPlan);
+ on.put("optimizedLogicalPlan", optimizedLogicalPlan);
+ on.put("job", job);
+ on.put("signature", signature);
+ on.put("multiStatement", multiStatement);
+ if (statementParams != null) {
+ for (Map.Entry<String, JsonNode> statementParam : statementParams.entrySet()) {
+ on.set('$' + statementParam.getKey(), statementParam.getValue());
+ }
+ }
+ return OBJECT_MAPPER.writeValueAsString(on);
+ } catch (JsonProcessingException e) {
+ QueryServiceServlet.LOGGER.debug("unexpected exception marshalling {} instance to json", getClass(), e);
+ return e.toString();
+ }
+ }
+}
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
index a52973c..3d1175c 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
@@ -67,18 +67,14 @@
import org.apache.hyracks.http.api.IServletRequest;
import org.apache.hyracks.http.api.IServletResponse;
import org.apache.hyracks.http.server.utils.HttpUtil;
-import org.apache.hyracks.util.JSONUtil;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.fasterxml.jackson.core.JsonParseException;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.util.MinimalPrettyPrinter;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ObjectNode;
+
import io.netty.handler.codec.http.HttpResponseStatus;
public class QueryServiceServlet extends AbstractQueryApiServlet {
@@ -154,7 +150,8 @@
LOGICAL_PLAN("logical-plan"),
OPTIMIZED_LOGICAL_PLAN("optimized-logical-plan"),
JOB("job"),
- SIGNATURE("signature");
+ SIGNATURE("signature"),
+ MULTI_STATEMENT("multi-statement");
private final String str;
@@ -201,59 +198,6 @@
}
}
- protected static class RequestParameters {
- String host;
- String path;
- String statement;
- String format;
- String timeout;
- boolean pretty;
- String clientContextID;
- String mode;
- String maxResultReads;
- String planFormat;
- Map<String, JsonNode> statementParams;
- boolean expressionTree;
- boolean rewrittenExpressionTree;
- boolean logicalPlan;
- boolean optimizedLogicalPlan;
- boolean job;
- boolean signature;
-
- @Override
- public String toString() {
- try {
- ObjectMapper om = new ObjectMapper();
- ObjectNode on = om.createObjectNode();
- on.put("host", host);
- on.put("path", path);
- on.put("statement", JSONUtil.escape(new StringBuilder(), statement).toString());
- on.put("pretty", pretty);
- on.put("mode", mode);
- on.put("clientContextID", clientContextID);
- on.put("format", format);
- on.put("timeout", timeout);
- on.put("maxResultReads", maxResultReads);
- on.put("planFormat", planFormat);
- on.put("expressionTree", expressionTree);
- on.put("rewrittenExpressionTree", rewrittenExpressionTree);
- on.put("logicalPlan", logicalPlan);
- on.put("optimizedLogicalPlan", optimizedLogicalPlan);
- on.put("job", job);
- on.put("signature", signature);
- if (statementParams != null) {
- for (Map.Entry<String, JsonNode> statementParam : statementParams.entrySet()) {
- on.set('$' + statementParam.getKey(), statementParam.getValue());
- }
- }
- return om.writer(new MinimalPrettyPrinter()).writeValueAsString(on);
- } catch (JsonProcessingException e) { // NOSONAR
- LOGGER.debug("unexpected exception marshalling {} instance to json", getClass(), e);
- return e.toString();
- }
- }
- }
-
protected static final class RequestExecutionState {
private long execStart = -1;
private long execEnd = -1;
@@ -332,39 +276,39 @@
return SessionConfig.OutputFormat.CLEAN_JSON;
}
- private static SessionOutput createSessionOutput(RequestParameters param, String handleUrl,
+ private static SessionOutput createSessionOutput(QueryServiceRequestParameters param, String handleUrl,
PrintWriter resultWriter) {
SessionOutput.ResultDecorator resultPrefix = ResultUtil.createPreResultDecorator();
SessionOutput.ResultDecorator resultPostfix = ResultUtil.createPostResultDecorator();
SessionOutput.ResultAppender appendHandle = ResultUtil.createResultHandleAppender(handleUrl);
SessionOutput.ResultAppender appendStatus = ResultUtil.createResultStatusAppender();
- SessionConfig.OutputFormat format = getFormat(param.format);
- final SessionConfig.PlanFormat planFormat =
- SessionConfig.PlanFormat.get(param.planFormat, param.planFormat, SessionConfig.PlanFormat.JSON, LOGGER);
+ SessionConfig.OutputFormat format = getFormat(param.getFormat());
+ final SessionConfig.PlanFormat planFormat = SessionConfig.PlanFormat.get(param.getPlanFormat(),
+ param.getPlanFormat(), SessionConfig.PlanFormat.JSON, LOGGER);
SessionConfig sessionConfig = new SessionConfig(format, planFormat);
sessionConfig.set(SessionConfig.FORMAT_WRAPPER_ARRAY, true);
- sessionConfig.set(SessionConfig.OOB_EXPR_TREE, param.expressionTree);
- sessionConfig.set(SessionConfig.OOB_REWRITTEN_EXPR_TREE, param.rewrittenExpressionTree);
- sessionConfig.set(SessionConfig.OOB_LOGICAL_PLAN, param.logicalPlan);
- sessionConfig.set(SessionConfig.OOB_OPTIMIZED_LOGICAL_PLAN, param.optimizedLogicalPlan);
- sessionConfig.set(SessionConfig.OOB_HYRACKS_JOB, param.job);
- sessionConfig.set(SessionConfig.FORMAT_INDENT_JSON, param.pretty);
+ sessionConfig.set(SessionConfig.OOB_EXPR_TREE, param.isExpressionTree());
+ sessionConfig.set(SessionConfig.OOB_REWRITTEN_EXPR_TREE, param.isRewrittenExpressionTree());
+ sessionConfig.set(SessionConfig.OOB_LOGICAL_PLAN, param.isLogicalPlan());
+ sessionConfig.set(SessionConfig.OOB_OPTIMIZED_LOGICAL_PLAN, param.isOptimizedLogicalPlan());
+ sessionConfig.set(SessionConfig.OOB_HYRACKS_JOB, param.isJob());
+ sessionConfig.set(SessionConfig.FORMAT_INDENT_JSON, param.isPretty());
sessionConfig.set(SessionConfig.FORMAT_QUOTE_RECORD,
format != SessionConfig.OutputFormat.CLEAN_JSON && format != SessionConfig.OutputFormat.LOSSLESS_JSON);
sessionConfig.set(SessionConfig.FORMAT_CSV_HEADER, format == SessionConfig.OutputFormat.CSV
- && "present".equals(getParameterValue(param.format, Attribute.HEADER.str())));
+ && "present".equals(getParameterValue(param.getFormat(), Attribute.HEADER.str())));
return new SessionOutput(sessionConfig, resultWriter, resultPrefix, resultPostfix, appendHandle, appendStatus);
}
- private static void printClientContextID(PrintWriter pw, RequestParameters params) {
- if (params.clientContextID != null && !params.clientContextID.isEmpty()) {
- ResultUtil.printField(pw, ResultFields.CLIENT_ID.str(), params.clientContextID);
+ private static void printClientContextID(PrintWriter pw, QueryServiceRequestParameters params) {
+ if (params.getClientContextID() != null && !params.getClientContextID().isEmpty()) {
+ ResultUtil.printField(pw, ResultFields.CLIENT_ID.str(), params.getClientContextID());
}
}
- private static void printSignature(PrintWriter pw, RequestParameters param) {
- if (param.signature) {
+ private static void printSignature(PrintWriter pw, QueryServiceRequestParameters param) {
+ if (param.isSignature()) {
pw.print("\t\"");
pw.print(ResultFields.SIGNATURE.str());
pw.print("\": {\n");
@@ -457,50 +401,54 @@
return result;
}
- private RequestParameters getRequestParameters(IServletRequest request) throws IOException {
+ private QueryServiceRequestParameters getRequestParameters(IServletRequest request) throws IOException {
final String contentType = HttpUtil.getContentTypeOnly(request);
- RequestParameters param = new RequestParameters();
- param.host = host(request);
- param.path = servletPath(request);
+ QueryServiceRequestParameters param = new QueryServiceRequestParameters();
+ param.setHost(host(request));
+ param.setPath(servletPath(request));
if (HttpUtil.ContentType.APPLICATION_JSON.equals(contentType)) {
try {
JsonNode jsonRequest = OBJECT_MAPPER.readTree(HttpUtil.getRequestBody(request));
- param.statement = jsonRequest.get(Parameter.STATEMENT.str()).asText();
- param.format = toLower(getOptText(jsonRequest, Parameter.FORMAT.str()));
- param.pretty = getOptBoolean(jsonRequest, Parameter.PRETTY.str(), false);
- param.mode = toLower(getOptText(jsonRequest, Parameter.MODE.str()));
- param.clientContextID = getOptText(jsonRequest, Parameter.CLIENT_ID.str());
- param.timeout = getOptText(jsonRequest, Parameter.TIMEOUT.str());
- param.maxResultReads = getOptText(jsonRequest, Parameter.MAX_RESULT_READS.str());
- param.planFormat = getOptText(jsonRequest, Parameter.PLAN_FORMAT.str());
- param.expressionTree = getOptBoolean(jsonRequest, Parameter.EXPRESSION_TREE.str(), false);
- param.rewrittenExpressionTree =
- getOptBoolean(jsonRequest, Parameter.REWRITTEN_EXPRESSION_TREE.str(), false);
- param.logicalPlan = getOptBoolean(jsonRequest, Parameter.LOGICAL_PLAN.str(), false);
- param.optimizedLogicalPlan = getOptBoolean(jsonRequest, Parameter.OPTIMIZED_LOGICAL_PLAN.str(), false);
- param.job = getOptBoolean(jsonRequest, Parameter.JOB.str(), false);
- param.signature = getOptBoolean(jsonRequest, Parameter.SIGNATURE.str(), true);
- param.statementParams =
- getOptStatementParameters(jsonRequest, jsonRequest.fieldNames(), JsonNode::get, v -> v);
+ param.setStatement(jsonRequest.get(Parameter.STATEMENT.str()).asText());
+ param.setFormat(toLower(getOptText(jsonRequest, Parameter.FORMAT.str())));
+ param.setPretty(getOptBoolean(jsonRequest, Parameter.PRETTY.str(), false));
+ param.setMode(toLower(getOptText(jsonRequest, Parameter.MODE.str())));
+ param.setClientContextID(getOptText(jsonRequest, Parameter.CLIENT_ID.str()));
+ param.setTimeout(getOptText(jsonRequest, Parameter.TIMEOUT.str()));
+ param.setMaxResultReads(getOptText(jsonRequest, Parameter.MAX_RESULT_READS.str()));
+ param.setPlanFormat(getOptText(jsonRequest, Parameter.PLAN_FORMAT.str()));
+ param.setExpressionTree(getOptBoolean(jsonRequest, Parameter.EXPRESSION_TREE.str(), false));
+ param.setRewrittenExpressionTree(
+ getOptBoolean(jsonRequest, Parameter.REWRITTEN_EXPRESSION_TREE.str(), false));
+ param.setLogicalPlan(getOptBoolean(jsonRequest, Parameter.LOGICAL_PLAN.str(), false));
+ param.setOptimizedLogicalPlan(
+ getOptBoolean(jsonRequest, Parameter.OPTIMIZED_LOGICAL_PLAN.str(), false));
+ param.setJob(getOptBoolean(jsonRequest, Parameter.JOB.str(), false));
+ param.setSignature(getOptBoolean(jsonRequest, Parameter.SIGNATURE.str(), true));
+ param.setStatementParams(
+ getOptStatementParameters(jsonRequest, jsonRequest.fieldNames(), JsonNode::get, v -> v));
+ param.setMultiStatement(getOptBoolean(jsonRequest, Parameter.MULTI_STATEMENT.str(), true));
} catch (JsonParseException | JsonMappingException e) {
// if the JSON parsing fails, the statement is empty and we get an empty statement error
GlobalConfig.ASTERIX_LOGGER.log(Level.ERROR, e.getMessage(), e);
}
} else {
- param.statement = request.getParameter(Parameter.STATEMENT.str());
- if (param.statement == null) {
- param.statement = HttpUtil.getRequestBody(request);
+ param.setStatement(request.getParameter(Parameter.STATEMENT.str()));
+ if (param.getStatement() == null) {
+ param.setStatement(HttpUtil.getRequestBody(request));
}
- param.format = toLower(request.getParameter(Parameter.FORMAT.str()));
- param.pretty = Boolean.parseBoolean(request.getParameter(Parameter.PRETTY.str()));
- param.mode = toLower(request.getParameter(Parameter.MODE.str()));
- param.clientContextID = request.getParameter(Parameter.CLIENT_ID.str());
- param.timeout = request.getParameter(Parameter.TIMEOUT.str());
- param.maxResultReads = request.getParameter(Parameter.MAX_RESULT_READS.str());
- param.planFormat = request.getParameter(Parameter.PLAN_FORMAT.str());
+ param.setFormat(toLower(request.getParameter(Parameter.FORMAT.str())));
+ param.setPretty(Boolean.parseBoolean(request.getParameter(Parameter.PRETTY.str())));
+ param.setMode(toLower(request.getParameter(Parameter.MODE.str())));
+ param.setClientContextID(request.getParameter(Parameter.CLIENT_ID.str()));
+ param.setTimeout(request.getParameter(Parameter.TIMEOUT.str()));
+ param.setMaxResultReads(request.getParameter(Parameter.MAX_RESULT_READS.str()));
+ param.setPlanFormat(request.getParameter(Parameter.PLAN_FORMAT.str()));
+ final String multiStatementParam = request.getParameter(Parameter.MULTI_STATEMENT.str());
+ param.setMultiStatement(multiStatementParam == null || Boolean.parseBoolean(multiStatementParam));
try {
- param.statementParams = getOptStatementParameters(request, request.getParameterNames().iterator(),
- IServletRequest::getParameter, OBJECT_MAPPER::readTree);
+ param.setStatementParams(getOptStatementParameters(request, request.getParameterNames().iterator(),
+ IServletRequest::getParameter, OBJECT_MAPPER::readTree));
} catch (JsonParseException | JsonMappingException e) {
GlobalConfig.ASTERIX_LOGGER.log(Level.ERROR, e.getMessage(), e);
}
@@ -548,17 +496,17 @@
}
private void handleRequest(IServletRequest request, IServletResponse response) throws IOException {
- RequestParameters param = getRequestParameters(request);
+ QueryServiceRequestParameters param = getRequestParameters(request);
LOGGER.info("handleRequest: {}", param);
long elapsedStart = System.nanoTime();
final PrintWriter httpWriter = response.writer();
- ResultDelivery delivery = parseResultDelivery(param.mode);
+ ResultDelivery delivery = parseResultDelivery(param.getMode());
- final ResultProperties resultProperties = param.maxResultReads == null ? new ResultProperties(delivery)
- : new ResultProperties(delivery, Long.parseLong(param.maxResultReads));
+ final ResultProperties resultProperties = param.getMaxResultReads() == null ? new ResultProperties(delivery)
+ : new ResultProperties(delivery, Long.parseLong(param.getMaxResultReads()));
- String handleUrl = getHandleUrl(param.host, param.path, delivery);
+ String handleUrl = getHandleUrl(param.getHost(), param.getPath(), delivery);
SessionOutput sessionOutput = createSessionOutput(param, handleUrl, httpWriter);
SessionConfig sessionConfig = sessionOutput.config();
HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, HttpUtil.Encoding.UTF8);
@@ -575,16 +523,16 @@
printType(sessionOutput.out(), sessionConfig);
long errorCount = 1; // so far we just return 1 error
try {
- if (param.statement == null || param.statement.isEmpty()) {
+ if (param.getStatement() == null || param.getStatement().isEmpty()) {
throw new AsterixException("Empty request, no statement provided");
}
- String statementsText = param.statement + ";";
+ String statementsText = param.getStatement() + ";";
Map<String, String> optionalParams = null;
if (optionalParamProvider != null) {
optionalParams = optionalParamProvider.apply(request);
}
- Map<String, byte[]> statementParams =
- org.apache.asterix.app.translator.RequestParameters.serializeParameterValues(param.statementParams);
+ Map<String, byte[]> statementParams = org.apache.asterix.app.translator.RequestParameters
+ .serializeParameterValues(param.getStatementParams());
// CORS
response.setHeader("Access-Control-Allow-Origin",
"http://" + hostName + ":" + appCtx.getExternalProperties().getQueryWebInterfacePort());
@@ -616,8 +564,9 @@
}
protected void executeStatement(String statementsText, SessionOutput sessionOutput,
- ResultProperties resultProperties, Stats stats, RequestParameters param, RequestExecutionState execution,
- Map<String, String> optionalParameters, Map<String, byte[]> statementParameters) throws Exception {
+ ResultProperties resultProperties, Stats stats, QueryServiceRequestParameters param,
+ RequestExecutionState execution, Map<String, String> optionalParameters,
+ Map<String, byte[]> statementParameters) throws Exception {
IClusterManagementWork.ClusterState clusterState =
((ICcApplicationContext) appCtx).getClusterStateManager().getState();
if (clusterState != IClusterManagementWork.ClusterState.ACTIVE) {
@@ -634,13 +583,14 @@
org.apache.asterix.app.translator.RequestParameters.deserializeParameterValues(statementParameters);
IRequestParameters requestParameters =
new org.apache.asterix.app.translator.RequestParameters(getHyracksDataset(), resultProperties, stats,
- null, param.clientContextID, optionalParameters, stmtParams);
+ null, param.getClientContextID(), optionalParameters, stmtParams, param.isMultiStatement());
translator.compileAndExecute(getHyracksClientConnection(), queryCtx, requestParameters);
execution.end();
printExecutionPlans(sessionOutput, translator.getExecutionPlans());
}
- protected void handleExecuteStatementException(Throwable t, RequestExecutionState state, RequestParameters param) {
+ protected void handleExecuteStatementException(Throwable t, RequestExecutionState state,
+ QueryServiceRequestParameters param) {
if (t instanceof org.apache.asterix.aqlplus.parser.TokenMgrError || t instanceof TokenMgrError
|| t instanceof AlgebricksException) {
if (LOGGER.isDebugEnabled()) {
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RestApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RestApiServlet.java
index 40095d7..3c58bc6 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RestApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RestApiServlet.java
@@ -210,7 +210,7 @@
IStatementExecutor translator = statementExecutorFactory.create(appCtx, aqlStatements, sessionOutput,
compilationProvider, componentProvider);
final IRequestParameters requestParameters = new RequestParameters(hds,
- new ResultProperties(resultDelivery), new IStatementExecutor.Stats(), null, null, null, null);
+ new ResultProperties(resultDelivery), new IStatementExecutor.Stats(), null, null, null, null, true);
translator.compileAndExecute(hcc, null, requestParameters);
} catch (AsterixException | TokenMgrError | org.apache.asterix.aqlplus.parser.TokenMgrError pe) {
response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/java/AsterixJavaClient.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/java/AsterixJavaClient.java
index 2d3a2f6..71b4b81 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/java/AsterixJavaClient.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/java/AsterixJavaClient.java
@@ -128,7 +128,7 @@
storageComponentProvider);
final IRequestParameters requestParameters =
new RequestParameters(null, new ResultProperties(IStatementExecutor.ResultDelivery.IMMEDIATE),
- new IStatementExecutor.Stats(), null, null, null, statementParams);
+ new IStatementExecutor.Stats(), null, null, null, statementParams, true);
translator.compileAndExecute(hcc, null, requestParameters);
writer.flush();
}
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/message/ExecuteStatementRequestMessage.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/message/ExecuteStatementRequestMessage.java
index ce259a2..88b5da8 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/message/ExecuteStatementRequestMessage.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/message/ExecuteStatementRequestMessage.java
@@ -78,11 +78,12 @@
private final String handleUrl;
private final Map<String, String> optionalParameters;
private final Map<String, byte[]> statementParameters;
+ private final boolean multiStatement;
public ExecuteStatementRequestMessage(String requestNodeId, long requestMessageId, ILangExtension.Language lang,
String statementsText, SessionConfig sessionConfig, ResultProperties resultProperties,
String clientContextID, String handleUrl, Map<String, String> optionalParameters,
- Map<String, byte[]> statementParameters) {
+ Map<String, byte[]> statementParameters, boolean multiStatement) {
this.requestNodeId = requestNodeId;
this.requestMessageId = requestMessageId;
this.lang = lang;
@@ -93,6 +94,7 @@
this.handleUrl = handleUrl;
this.optionalParameters = optionalParameters;
this.statementParameters = statementParameters;
+ this.multiStatement = multiStatement;
}
@Override
@@ -130,7 +132,7 @@
final IStatementExecutor.Stats stats = new IStatementExecutor.Stats();
Map<String, IAObject> stmtParams = RequestParameters.deserializeParameterValues(statementParameters);
final IRequestParameters requestParameters = new RequestParameters(null, resultProperties, stats,
- outMetadata, clientContextID, optionalParameters, stmtParams);
+ outMetadata, clientContextID, optionalParameters, stmtParams, multiStatement);
translator.compileAndExecute(ccApp.getHcc(), statementExecutorContext, requestParameters);
outPrinter.close();
responseMsg.setResult(outWriter.toString());
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
index 4d71715..469a2dd 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
@@ -65,6 +65,7 @@
import org.apache.asterix.common.exceptions.CompilationException;
import org.apache.asterix.common.exceptions.ErrorCode;
import org.apache.asterix.common.exceptions.MetadataException;
+import org.apache.asterix.common.exceptions.RuntimeDataException;
import org.apache.asterix.common.functions.FunctionSignature;
import org.apache.asterix.common.utils.JobUtils;
import org.apache.asterix.common.utils.JobUtils.ProgressState;
@@ -263,6 +264,9 @@
@Override
public void compileAndExecute(IHyracksClientConnection hcc, IStatementExecutorContext ctx,
IRequestParameters requestParameters) throws Exception {
+ if (!requestParameters.isMultiStatement()) {
+ validateStatements(statements);
+ }
int resultSetIdCounter = 0;
FileSplit outputFile = null;
IAWriterFactory writerFactory = PrinterBasedWriterFactory.INSTANCE;
@@ -2940,6 +2944,24 @@
}
}
+ protected void validateStatements(List<Statement> statements) throws RuntimeDataException {
+ if (statements.stream().filter(this::isNotAllowedMultiStatement).count() > 1) {
+ throw new RuntimeDataException(ErrorCode.UNSUPPORTED_MULTIPLE_STATEMENTS);
+ }
+ }
+
+ protected boolean isNotAllowedMultiStatement(Statement statement) {
+ switch (statement.getKind()) {
+ case DATAVERSE_DECL:
+ case FUNCTION_DECL:
+ case SET:
+ case WRITE:
+ return false;
+ default:
+ return true;
+ }
+ }
+
private Map<VarIdentifier, IAObject> createExternalVariables(Map<String, IAObject> stmtParams,
IStatementRewriter stmtRewriter) {
if (stmtParams == null || stmtParams.isEmpty()) {
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/RequestParameters.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/RequestParameters.java
index 0655285..ad12125 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/RequestParameters.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/RequestParameters.java
@@ -48,10 +48,11 @@
private final IStatementExecutor.ResultMetadata outMetadata;
private final String clientContextId;
private final Map<String, IAObject> statementParameters;
+ private final boolean multiStatement;
public RequestParameters(IHyracksDataset hdc, ResultProperties resultProperties, Stats stats,
IStatementExecutor.ResultMetadata outMetadata, String clientContextId,
- Map<String, String> optionalParameters, Map<String, IAObject> statementParameters) {
+ Map<String, String> optionalParameters, Map<String, IAObject> statementParameters, boolean multiStatement) {
this.hdc = hdc;
this.resultProperties = resultProperties;
this.stats = stats;
@@ -59,6 +60,7 @@
this.clientContextId = clientContextId;
this.optionalParameters = optionalParameters;
this.statementParameters = statementParameters;
+ this.multiStatement = multiStatement;
}
@Override
@@ -92,6 +94,11 @@
}
@Override
+ public boolean isMultiStatement() {
+ return multiStatement;
+ }
+
+ @Override
public Map<String, IAObject> getStatementParameters() {
return statementParameters;
}
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java
index 2281238..3c51775 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java
@@ -1786,13 +1786,14 @@
LOGGER.info("Last test left some garbage. Dropping dataverses: " + StringUtils.join(toBeDropped, ','));
StringBuilder dropStatement = new StringBuilder();
for (String dv : toBeDropped) {
+ dropStatement.setLength(0);
dropStatement.append("drop dataverse ");
dropStatement.append(dv);
dropStatement.append(";\n");
+ resultStream = executeQueryService(dropStatement.toString(), getEndpoint(Servlets.QUERY_SERVICE),
+ OutputFormat.CLEAN_JSON);
+ ResultExtractor.extract(resultStream);
}
- resultStream = executeQueryService(dropStatement.toString(), getEndpoint(Servlets.QUERY_SERVICE),
- OutputFormat.CLEAN_JSON);
- ResultExtractor.extract(resultStream);
}
} catch (Throwable th) {
th.printStackTrace();
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 d919d0e..e0c0d78 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
@@ -75,6 +75,7 @@
public static final int REJECT_BAD_CLUSTER_STATE = 32;
public static final int REJECT_NODE_UNREGISTERED = 33;
public static final int DIVISION_BY_ZERO = 34;
+ public static final int UNSUPPORTED_MULTIPLE_STATEMENTS = 35;
public static final int UNSUPPORTED_JRE = 100;
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 ac51570..d600762 100644
--- a/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
+++ b/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
@@ -68,6 +68,7 @@
32 = Cannot execute request, cluster is %1$s
33 = Node is not registered with the CC
34 = Division by Zero.
+35 = Unsupported multiple statements.
100 = Unsupported JRE: %1$s