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 <ti...@apache.org>

Reply via email to