Murtadha Hubail has submitted this change and it was merged. Change subject: [NO ISSUE][API] Add Multi-Statement Parameter to HTTP API ......................................................................
[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 <[email protected]> Sonar-Qube: Jenkins <[email protected]> Tested-by: Jenkins <[email protected]> Contrib: Jenkins <[email protected]> Integration-Tests: Jenkins <[email protected]> Reviewed-by: Till Westmann <[email protected]> --- M asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/IRequestParameters.java M asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ApiServlet.java M asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NCQueryServiceServlet.java A asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceRequestParameters.java M asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java M asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RestApiServlet.java M asterixdb/asterix-app/src/main/java/org/apache/asterix/api/java/AsterixJavaClient.java M asterixdb/asterix-app/src/main/java/org/apache/asterix/app/message/ExecuteStatementRequestMessage.java M asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java M asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/RequestParameters.java M asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java M asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java M asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties 13 files changed, 363 insertions(+), 143 deletions(-) Approvals: Anon. E. Moose #1000171: Till Westmann: Looks good to me, approved Jenkins: Verified; No violations found; ; Verified Murtadha Hubail: Looks good to me, but someone else must approve 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; @@ -198,59 +195,6 @@ public String str() { return str; - } - } - - 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(); - } } } @@ -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 -- To view, visit https://asterix-gerrit.ics.uci.edu/2736 To unsubscribe, visit https://asterix-gerrit.ics.uci.edu/settings Gerrit-MessageType: merged Gerrit-Change-Id: I5bf2d9fc5fec351565b09cfef504539d5a938af5 Gerrit-PatchSet: 5 Gerrit-Project: asterixdb Gerrit-Branch: master Gerrit-Owner: Murtadha Hubail <[email protected]> Gerrit-Reviewer: Anon. E. Moose #1000171 Gerrit-Reviewer: Jenkins <[email protected]> Gerrit-Reviewer: Murtadha Hubail <[email protected]> Gerrit-Reviewer: Till Westmann <[email protected]>
