Till Westmann has uploaded a new change for review.
https://asterix-gerrit.ics.uci.edu/1020
Change subject: Implement EXPLAIN for SQL++
......................................................................
Implement EXPLAIN for SQL++
- move some code from static methods in ResultUtils to a stateful
ResultPrinter to facilitate reuse (we create one ResultWriter per request)
Change-Id: I7b7028fb243d494150cac525c73b2d77b0068646
---
M
asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
M
asterixdb/asterix-app/src/main/java/org/apache/asterix/aql/translator/QueryTranslator.java
A
asterixdb/asterix-app/src/main/java/org/apache/asterix/result/ResultPrinter.java
M asterixdb/asterix-app/src/main/java/org/apache/asterix/result/ResultUtils.java
A
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/explain/explain_simple/explain_simple.1.query.sqlpp
A
asterixdb/asterix-app/src/test/resources/runtimets/results/explain/explain_simple/explain_simple.1.adm
M asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
M
asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/Query.java
M asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
9 files changed, 271 insertions(+), 126 deletions(-)
git pull ssh://asterix-gerrit.ics.uci.edu:29418/asterixdb
refs/changes/20/1020/1
diff --git
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
index af17c05..d6864c1 100644
---
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
+++
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
@@ -18,6 +18,7 @@
*/
package org.apache.asterix.api.common;
+import java.io.IOException;
import java.io.PrintWriter;
import java.rmi.RemoteException;
import java.util.ArrayList;
@@ -48,6 +49,7 @@
import org.apache.asterix.metadata.declared.AqlMetadataProvider;
import org.apache.asterix.om.util.AsterixAppContextInfo;
import org.apache.asterix.optimizer.base.RuleCollections;
+import org.apache.asterix.result.ResultUtils;
import org.apache.asterix.runtime.job.listener.JobEventListenerFactory;
import
org.apache.asterix.transaction.management.service.transaction.JobIdFactory;
import org.apache.asterix.translator.CompiledStatements.ICompiledDmlStatement;
@@ -279,6 +281,16 @@
}
}
}
+ if (rwQ != null && rwQ.isExplain()) {
+ try {
+ LogicalOperatorPrettyPrintVisitor pvisitor = new
LogicalOperatorPrettyPrintVisitor();
+ PlanPrettyPrinter.printPlan(plan, pvisitor, 0);
+ ResultUtils.displayResults(pvisitor.get().toString(), conf,
new ResultUtils.Stats(), null);
+ return null;
+ } catch (IOException e) {
+ throw new AlgebricksException(e);
+ }
+ }
if (!conf.isGenerateJobSpec()) {
return null;
diff --git
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/aql/translator/QueryTranslator.java
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/aql/translator/QueryTranslator.java
index 05d9b3d..d6065fb 100644
---
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/aql/translator/QueryTranslator.java
+++
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/aql/translator/QueryTranslator.java
@@ -2529,14 +2529,16 @@
boolean bActiveTxn = true;
metadataProvider.setMetadataTxnContext(mdTxnCtx);
MetadataLockManager.INSTANCE.queryBegin(activeDefaultDataverse,
query.getDataverses(), query.getDatasets());
- JobSpecification compiled = null;
try {
- compiled = rewriteCompileQuery(metadataProvider, query, null);
+ JobSpecification compiled = rewriteCompileQuery(metadataProvider,
query, null);
MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
bActiveTxn = false;
- if (sessionConfig.isExecuteQuery() && compiled != null) {
+ if (query.isExplain()) {
+ sessionConfig.out().flush();
+ return;
+ } else if (sessionConfig.isExecuteQuery() && compiled != null) {
GlobalConfig.ASTERIX_LOGGER.info(compiled.toJSON().toString(1));
JobId jobId = JobUtils.runJob(hcc, compiled, false);
diff --git
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/result/ResultPrinter.java
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/result/ResultPrinter.java
new file mode 100644
index 0000000..c2dda75
--- /dev/null
+++
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/result/ResultPrinter.java
@@ -0,0 +1,188 @@
+/*
+ * 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.result;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.ByteBuffer;
+
+import org.apache.asterix.api.common.SessionConfig;
+import org.apache.asterix.common.utils.JSONUtil;
+import org.apache.asterix.om.types.ARecordType;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import
org.apache.hyracks.algebricks.core.algebra.prettyprint.AlgebricksAppendable;
+import org.apache.hyracks.api.comm.IFrame;
+import org.apache.hyracks.api.comm.IFrameTupleAccessor;
+import org.apache.hyracks.api.comm.VSizeFrame;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.control.nc.resources.memory.FrameManager;
+
+public class ResultPrinter {
+
+ // TODO(tillw): Should this be static?
+ private static FrameManager resultDisplayFrameMgr = new
FrameManager(ResultReader.FRAME_SIZE);
+
+ private final SessionConfig conf;
+ private final ResultUtils.Stats stats;
+ private final ARecordType recordType;
+
+ private boolean indentJSON;
+ private boolean quoteRecord;
+
+ // Whether we are wrapping the output sequence in an array
+ private boolean wrap_array = false;
+ // Whether this is the first instance being output
+ private boolean notfirst = false;
+
+ public ResultPrinter(SessionConfig conf, ResultUtils.Stats stats,
ARecordType recordType) {
+ this.conf = conf;
+ this.stats = stats;
+ this.recordType = recordType;
+ this.indentJSON = conf.is(SessionConfig.FORMAT_INDENT_JSON);
+ this.quoteRecord = conf.is(SessionConfig.FORMAT_QUOTE_RECORD);
+ }
+
+ private static void appendCSVHeader(Appendable app, ARecordType
recordType) throws HyracksDataException {
+ try {
+ String[] fieldNames = recordType.getFieldNames();
+ boolean notfirst = false;
+ for (String name : fieldNames) {
+ if (notfirst) {
+ app.append(',');
+ }
+ notfirst = true;
+ app.append('"').append(name.replace("\"", "\"\"")).append('"');
+ }
+ app.append("\r\n");
+ } catch (IOException e) {
+ throw new HyracksDataException(e);
+ }
+ }
+
+ private void printPrefix() throws HyracksDataException {
+ // If we're outputting CSV with a header, the HTML header was already
+ // output by displayCSVHeader(), so skip it here
+ if (conf.is(SessionConfig.FORMAT_HTML)) {
+ conf.out().println("<h4>Results:</h4>");
+ conf.out().println("<pre>");
+ }
+
+ try {
+ conf.resultPrefix(new AlgebricksAppendable(conf.out()));
+ } catch (AlgebricksException e) {
+ throw new HyracksDataException(e);
+ }
+
+ if (conf.is(SessionConfig.FORMAT_WRAPPER_ARRAY)) {
+ conf.out().print("[ ");
+ wrap_array = true;
+ }
+
+ if (conf.fmt() == SessionConfig.OutputFormat.CSV &&
conf.is(SessionConfig.FORMAT_CSV_HEADER)) {
+ if (recordType == null) {
+ throw new HyracksDataException("Cannot print CSV with header
without specifying output-record-type");
+ }
+ if (quoteRecord) {
+ StringWriter sw = new StringWriter();
+ appendCSVHeader(sw, recordType);
+ conf.out().print(JSONUtil.quoteAndEscape(sw.toString()));
+ conf.out().print("\n");
+ notfirst = true;
+ } else {
+ appendCSVHeader(conf.out(), recordType);
+ }
+ }
+ }
+
+ private void printPostfix() throws HyracksDataException {
+ conf.out().flush();
+ if (wrap_array) {
+ conf.out().println(" ]");
+ }
+ try {
+ conf.resultPostfix(new AlgebricksAppendable(conf.out()));
+ } catch (AlgebricksException e) {
+ throw new HyracksDataException(e);
+ }
+ if (conf.is(SessionConfig.FORMAT_HTML)) {
+ conf.out().println("</pre>");
+ }
+ }
+
+ private void displayRecord(String record) {
+ if (indentJSON) {
+ // TODO(tillw): this is inefficient - do this during record
generation
+ record = JSONUtil.indent(record, 2);
+ }
+ if (conf.fmt() == SessionConfig.OutputFormat.CSV) {
+ // TODO(tillw): this is inefficient as well
+ record = record + "\r\n";
+ }
+ if (quoteRecord) {
+ // TODO(tillw): this is inefficient as well
+ record = JSONUtil.quoteAndEscape(record);
+ }
+ conf.out().print(record);
+ ++stats.count;
+ // TODO(tillw) fix this approximation
+ stats.size += record.length();
+ }
+
+ public void print(String record) throws HyracksDataException {
+ printPrefix();
+ // TODO(tillw) evil hack
+ quoteRecord = true;
+ displayRecord(record);
+ printPostfix();
+ }
+
+ public void print(ResultReader resultReader) throws HyracksDataException {
+ printPrefix();
+
+ final IFrameTupleAccessor fta = resultReader.getFrameTupleAccessor();
+ final IFrame frame = new VSizeFrame(resultDisplayFrameMgr);
+
+ while (resultReader.read(frame) > 0) {
+ final ByteBuffer frameBuffer = frame.getBuffer();
+ final byte[] frameBytes = frameBuffer.array();
+ fta.reset(frameBuffer);
+ final int last = fta.getTupleCount();
+ for (int tIndex = 0; tIndex < last; tIndex++) {
+ final int start = fta.getTupleStartOffset(tIndex);
+ int length = fta.getTupleEndOffset(tIndex) - start;
+ if (conf.fmt() == SessionConfig.OutputFormat.CSV) {
+ if ((length > 0) && (frameBytes[start + length - 1] ==
'\n')) {
+ length--;
+ }
+ }
+ String result = new String(frameBytes, start, length, UTF_8);
+ if (wrap_array && notfirst) {
+ conf.out().print(", ");
+ }
+ notfirst = true;
+ displayRecord(result);
+ }
+ frameBuffer.clear();
+ }
+
+ printPostfix();
+ }
+}
diff --git
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/result/ResultUtils.java
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/result/ResultUtils.java
index 8c3ccfc..3c0013c 100644
---
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/result/ResultUtils.java
+++
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/result/ResultUtils.java
@@ -24,7 +24,6 @@
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
@@ -32,18 +31,11 @@
import java.util.regex.Pattern;
import org.apache.asterix.api.common.SessionConfig;
-import org.apache.asterix.api.common.SessionConfig.OutputFormat;
import org.apache.asterix.api.http.servlet.APIServlet;
-import org.apache.asterix.common.utils.JSONUtil;
import org.apache.asterix.om.types.ARecordType;
import org.apache.http.ParseException;
import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
-import
org.apache.hyracks.algebricks.core.algebra.prettyprint.AlgebricksAppendable;
-import org.apache.hyracks.api.comm.IFrame;
-import org.apache.hyracks.api.comm.IFrameTupleAccessor;
-import org.apache.hyracks.api.comm.VSizeFrame;
import org.apache.hyracks.api.exceptions.HyracksDataException;
-import org.apache.hyracks.control.nc.resources.memory.FrameManager;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -74,124 +66,14 @@
return s;
}
- private static void printCSVHeader(ARecordType recordType, PrintWriter
out) {
- String[] fieldNames = recordType.getFieldNames();
- boolean notfirst = false;
- for (String name : fieldNames) {
- if (notfirst) {
- out.print(',');
- }
- notfirst = true;
- out.print('"');
- out.print(name.replace("\"", "\"\""));
- out.print('"');
- }
- out.print("\r\n");
- }
-
- public static FrameManager resultDisplayFrameMgr = new
FrameManager(ResultReader.FRAME_SIZE);
-
public static void displayResults(ResultReader resultReader, SessionConfig
conf, Stats stats,
ARecordType recordType) throws HyracksDataException {
- // Whether we are wrapping the output sequence in an array
- boolean wrap_array = false;
- // Whether this is the first instance being output
- boolean notfirst = false;
+ new ResultPrinter(conf, stats, recordType).print(resultReader);
+ }
- // If we're outputting CSV with a header, the HTML header was already
- // output by displayCSVHeader(), so skip it here
- if (conf.is(SessionConfig.FORMAT_HTML)) {
- conf.out().println("<h4>Results:</h4>");
- conf.out().println("<pre>");
- }
-
- try {
- conf.resultPrefix(new AlgebricksAppendable(conf.out()));
- } catch (AlgebricksException e) {
- throw new HyracksDataException(e);
- }
-
- if (conf.is(SessionConfig.FORMAT_WRAPPER_ARRAY)) {
- conf.out().print("[ ");
- wrap_array = true;
- }
-
- final boolean indentJSON = conf.is(SessionConfig.FORMAT_INDENT_JSON);
- final boolean quoteRecord = conf.is(SessionConfig.FORMAT_QUOTE_RECORD);
-
- if (conf.fmt() == OutputFormat.CSV &&
conf.is(SessionConfig.FORMAT_CSV_HEADER)) {
- if (recordType == null) {
- throw new HyracksDataException("Cannot print CSV with header
without specifying output-record-type");
- }
- if (quoteRecord) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- printCSVHeader(recordType, pw);
- pw.close();
- conf.out().print(JSONUtil.quoteAndEscape(sw.toString()));
- conf.out().print("\n");
- notfirst = true;
- } else {
- printCSVHeader(recordType, conf.out());
- }
- }
-
- final IFrameTupleAccessor fta = resultReader.getFrameTupleAccessor();
- final IFrame frame = new VSizeFrame(resultDisplayFrameMgr);
-
- while (resultReader.read(frame) > 0) {
- final ByteBuffer frameBuffer = frame.getBuffer();
- final byte[] frameBytes = frameBuffer.array();
- fta.reset(frameBuffer);
- final int last = fta.getTupleCount();
- for (int tIndex = 0; tIndex < last; tIndex++) {
- final int start = fta.getTupleStartOffset(tIndex);
- int length = fta.getTupleEndOffset(tIndex) - start;
- if (conf.fmt() == OutputFormat.CSV) {
- if ((length > 0) && (frameBytes[start + length - 1] ==
'\n')) {
- length--;
- }
- }
- String result = new String(frameBytes, start, length, UTF_8);
- if (wrap_array && notfirst) {
- conf.out().print(", ");
- }
- notfirst = true;
- if (indentJSON) {
- // TODO(tillw): this is inefficient - do this during
result generation
- result = JSONUtil.indent(result, 2);
- }
- if (conf.fmt() == OutputFormat.CSV) {
- // TODO(tillw): this is inefficient as well
- result = result + "\r\n";
- }
- if (quoteRecord) {
- // TODO(tillw): this is inefficient as well
- result = JSONUtil.quoteAndEscape(result);
- }
- conf.out().print(result);
- ++stats.count;
- // TODO(tillw) fix this approximation
- stats.size += result.length();
- }
- frameBuffer.clear();
- }
-
- conf.out().flush();
-
- if (wrap_array) {
- conf.out().println(" ]");
- }
-
- try {
- conf.resultPostfix(new AlgebricksAppendable(conf.out()));
- } catch (AlgebricksException e) {
- throw new HyracksDataException(e);
- }
-
- if (conf.is(SessionConfig.FORMAT_HTML)) {
- conf.out().println("</pre>");
- }
+ public static void displayResults(String record, SessionConfig conf, Stats
stats, ARecordType recordType)
+ throws HyracksDataException {
+ new ResultPrinter(conf, stats, recordType).print(record);
}
public static JSONObject getErrorResponse(int errorCode, String
errorMessage, String errorSummary,
diff --git
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/explain/explain_simple/explain_simple.1.query.sqlpp
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/explain/explain_simple/explain_simple.1.query.sqlpp
new file mode 100644
index 0000000..6860087
--- /dev/null
+++
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/explain/explain_simple/explain_simple.1.query.sqlpp
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+/*
+* Description : EXPLAIN a plan for a very simple query
+* Expected Res : Success
+* Date : Jul 25, 2016
+*/
+explain select value 1+1;
diff --git
a/asterixdb/asterix-app/src/test/resources/runtimets/results/explain/explain_simple/explain_simple.1.adm
b/asterixdb/asterix-app/src/test/resources/runtimets/results/explain/explain_simple/explain_simple.1.adm
new file mode 100644
index 0000000..4398d63
--- /dev/null
+++
b/asterixdb/asterix-app/src/test/resources/runtimets/results/explain/explain_simple/explain_simple.1.adm
@@ -0,0 +1,9 @@
+distribute result [%0->$$2]
+-- DISTRIBUTE_RESULT |UNPARTITIONED|
+ exchange
+ -- ONE_TO_ONE_EXCHANGE |UNPARTITIONED|
+ assign [$$2] <- [AInt64: {2}]
+ -- ASSIGN |UNPARTITIONED|
+ empty-tuple-source
+ -- EMPTY_TUPLE_SOURCE |UNPARTITIONED|
+
diff --git
a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
index e1d728d..f16703d 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -67,6 +67,13 @@
</compilation-unit>
</test-case>
</test-group>
+ <test-group name="explain">
+ <test-case FilePath="explain">
+ <compilation-unit name="explain_simple">
+ <output-dir compare="Text">explain_simple</output-dir>
+ </compilation-unit>
+ </test-case>
+ </test-group>
<!--
<test-group name="union">
<test-case FilePath="union">
diff --git
a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/Query.java
b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/Query.java
index 80853c8..f1019b4 100644
---
a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/Query.java
+++
b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/Query.java
@@ -29,6 +29,7 @@
public class Query implements Statement {
private boolean topLevel = true;
+ private boolean explain = false;
private Expression body;
private int varCounter;
private List<String> dataverses = new ArrayList<>();
@@ -70,6 +71,14 @@
return topLevel;
}
+ public void setExplain(boolean explain) {
+ this.explain = explain;
+ }
+
+ public boolean isExplain() {
+ return explain;
+ }
+
@Override
public byte getKind() {
return Statement.Kind.QUERY;
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index 99b875a..83d3674 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -314,6 +314,7 @@
| stmt = UpdateStatement()
| stmt = FeedStatement()
| stmt = CompactStatement()
+ | stmt = ExplainStatement()
| stmt = Query() <SEMICOLON>
| stmt = RefreshExternalDatasetStatement()
| stmt = RunStatement()
@@ -1556,6 +1557,17 @@
}
}
+Query ExplainStatement() throws ParseException:
+{
+ Query query;
+}
+{
+ "explain" query = Query()
+ {
+ query.setExplain(true);
+ return query;
+ }
+}
Query Query() throws ParseException:
{
--
To view, visit https://asterix-gerrit.ics.uci.edu/1020
To unsubscribe, visit https://asterix-gerrit.ics.uci.edu/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I7b7028fb243d494150cac525c73b2d77b0068646
Gerrit-PatchSet: 1
Gerrit-Project: asterixdb
Gerrit-Branch: master
Gerrit-Owner: Till Westmann <[email protected]>