[ASTERIXDB-3224] Added support for printing logical plan in DOT format

 - Added support in UI and API(s)

Change-Id: Ia6e37080a581292744ddd9030b294936513c15ac
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/19390
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Ian Maxon <imaxon@apache.org>
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/ExecutionPlansJsonPrintUtil.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/ExecutionPlansJsonPrintUtil.java
index ad89ad6..bf010c5 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/ExecutionPlansJsonPrintUtil.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/ExecutionPlansJsonPrintUtil.java
@@ -66,6 +66,9 @@
                 case STRING:
                     JSONUtil.quoteAndEscape(builder, value);
                     break;
+                case DOT:
+                    JSONUtil.quoteAndEscape(builder, value);
+                    break;
                 default:
                     throw new IllegalStateException("Unrecognized plan format: " + format);
             }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SessionConfig.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SessionConfig.java
index 4e294ec..b7eb98b 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SessionConfig.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SessionConfig.java
@@ -59,7 +59,8 @@
 
     public enum PlanFormat {
         JSON,
-        STRING;
+        STRING,
+        DOT;
         public static PlanFormat get(String fmtString, String label, PlanFormat defaultFmt, Logger logger) {
             try {
                 if (fmtString != null) {
@@ -80,6 +81,24 @@
     }
 
     /**
+     * Used to specify the format for Hyracks Job
+     */
+    public enum HyracksJobFormat {
+        JSON,
+        DOT;
+        public static HyracksJobFormat get(String fmtString, String label, HyracksJobFormat defaultFmt, Logger logger) {
+            try {
+                if (fmtString != null) {
+                    return HyracksJobFormat.valueOf(fmtString.toUpperCase());
+                }
+            } catch (IllegalArgumentException e) {
+                logger.log(Level.INFO, fmtString + ": unsupported " + label + ", using " + defaultFmt + "instead", e);
+            }
+            return defaultFmt;
+        }
+    }
+
+    /**
      * Produce out-of-band output for Hyracks Job.
      */
     public static final String OOB_HYRACKS_JOB = "oob-hyracks-job";
@@ -140,6 +159,7 @@
     // Output format.
     private OutputFormat fmt;
     private PlanFormat planFormat;
+    private HyracksJobFormat hyracksJobFormat;
 
     // Standard execution flags.
     private boolean executeQuery;
@@ -155,7 +175,11 @@
     }
 
     public SessionConfig(OutputFormat fmt, PlanFormat planFormat) {
-        this(fmt, true, true, true, planFormat);
+        this(fmt, true, true, true, planFormat, HyracksJobFormat.JSON);
+    }
+
+    public SessionConfig(OutputFormat fmt, PlanFormat planFormat, HyracksJobFormat jobFormat) {
+        this(fmt, true, true, true, planFormat, jobFormat);
     }
 
     /**
@@ -173,11 +197,11 @@
      *            Whether to generate the Hyracks job specification (if
      */
     public SessionConfig(OutputFormat fmt, boolean optimize, boolean executeQuery, boolean generateJobSpec) {
-        this(fmt, optimize, executeQuery, generateJobSpec, PlanFormat.STRING);
+        this(fmt, optimize, executeQuery, generateJobSpec, PlanFormat.STRING, HyracksJobFormat.DOT);
     }
 
     public SessionConfig(OutputFormat fmt, boolean optimize, boolean executeQuery, boolean generateJobSpec,
-            PlanFormat planFormat) {
+            PlanFormat planFormat, HyracksJobFormat jobFormat) {
         this.fmt = fmt;
         this.optimize = optimize;
         this.executeQuery = executeQuery;
@@ -185,6 +209,7 @@
         this.flags = new HashMap<>();
         this.planFormat = planFormat;
         this.clientType = ClientType.ASTERIX;
+        this.hyracksJobFormat = jobFormat;
     }
 
     /**
@@ -221,6 +246,17 @@
     }
 
     /**
+     * Retrieve the HyracksJobFormat for this execution.
+     */
+    public HyracksJobFormat getHyracksJobFormat() {
+        return this.hyracksJobFormat;
+    }
+
+    public void setHyracksJobFormat(HyracksJobFormat hyracksJobFormat) {
+        this.hyracksJobFormat = hyracksJobFormat;
+    }
+
+    /**
      * Retrieve the maximum number of warnings to be reported.
      */
     public long getMaxWarnings() {
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 c5fc395..06c8c5d 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
@@ -98,6 +98,7 @@
 import org.apache.hyracks.algebricks.core.algebra.prettyprint.PlanPrettyPrinter;
 import org.apache.hyracks.algebricks.core.rewriter.base.IOptimizationContextFactory;
 import org.apache.hyracks.algebricks.core.rewriter.base.PhysicalOptimizationConfig;
+import org.apache.hyracks.algebricks.core.utils.DotFormatGenerator;
 import org.apache.hyracks.algebricks.data.IPrinterFactoryProvider;
 import org.apache.hyracks.algebricks.runtime.serializer.ResultSerializerFactoryProvider;
 import org.apache.hyracks.algebricks.runtime.writers.PrinterBasedWriterFactory;
@@ -388,7 +389,7 @@
             }
 
             if (isQuery && conf.is(SessionConfig.OOB_HYRACKS_JOB)) {
-                generateJob(spec);
+                generateJob(spec, output.config().getHyracksJobFormat());
             }
             return spec;
 
@@ -580,12 +581,22 @@
 
     private void generateLogicalPlan(ILogicalPlan plan, SessionConfig.PlanFormat format,
             boolean printOptimizerEstimates) throws AlgebricksException {
+        if (format.equals(SessionConfig.PlanFormat.DOT)) {
+            DotFormatGenerator planGenerator = new DotFormatGenerator();
+            executionPlans.setLogicalPlan(planGenerator.generate(plan, true));
+            return;
+        }
         executionPlans
                 .setLogicalPlan(getPrettyPrintVisitor(format).printPlan(plan, printOptimizerEstimates).toString());
     }
 
     private void generateOptimizedLogicalPlan(ILogicalPlan plan, Map<Object, String> log2phys,
             SessionConfig.PlanFormat format, boolean printOptimizerEstimates) throws AlgebricksException {
+        if (format.equals(SessionConfig.PlanFormat.DOT)) {
+            DotFormatGenerator planGenerator = new DotFormatGenerator();
+            executionPlans.setOptimizedLogicalPlan(planGenerator.generate(plan, true));
+            return;
+        }
         executionPlans.setOptimizedLogicalPlan(
                 getPrettyPrintVisitor(format).printPlan(plan, log2phys, printOptimizerEstimates).toString());
     }
@@ -606,11 +617,20 @@
 
     private void generateOptimizedLogicalPlan(ILogicalPlan plan, SessionConfig.PlanFormat format,
             boolean printOptimizerEstimates) throws AlgebricksException {
+        if (format.equals(SessionConfig.PlanFormat.DOT)) {
+            DotFormatGenerator planGenerator = new DotFormatGenerator();
+            executionPlans.setOptimizedLogicalPlan(planGenerator.generate(plan, true));
+            return;
+        }
         executionPlans.setOptimizedLogicalPlan(
                 getPrettyPrintVisitor(format).printPlan(plan, printOptimizerEstimates).toString());
     }
 
-    private void generateJob(JobSpecification spec) {
+    private void generateJob(JobSpecification spec, SessionConfig.HyracksJobFormat format) {
+        if (format.equals(SessionConfig.HyracksJobFormat.DOT)) {
+            executionPlans.setJob(org.apache.hyracks.api.util.DotFormatGenerator.generate(spec));
+            return;
+        }
         final StringWriter stringWriter = new StringWriter();
         try (PrintWriter writer = new PrintWriter(stringWriter)) {
             writer.println(OBJECT_WRITER.writeValueAsString(spec.toJSON()));
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 56ad88e..53abe1b 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
@@ -50,6 +50,7 @@
 import org.apache.asterix.translator.IStatementExecutorFactory;
 import org.apache.asterix.translator.ResultProperties;
 import org.apache.asterix.translator.SessionConfig;
+import org.apache.asterix.translator.SessionConfig.HyracksJobFormat;
 import org.apache.asterix.translator.SessionConfig.OutputFormat;
 import org.apache.asterix.translator.SessionConfig.PlanFormat;
 import org.apache.asterix.translator.SessionOutput;
@@ -121,7 +122,8 @@
         }
         PlanFormat planFormat =
                 PlanFormat.get(request.getParameter("plan-format"), "plan format", PlanFormat.STRING, LOGGER);
-
+        HyracksJobFormat hyracksJobFormat = HyracksJobFormat.get(request.getParameter("hyracks-job-format"),
+                "hyracks-job-format", HyracksJobFormat.JSON, LOGGER);
         String query = request.getParameter("query");
         String wrapperArray = request.getParameter("wrapper-array");
         String printExprParam = request.getParameter("print-expr-tree");
@@ -137,7 +139,8 @@
             IResultSet resultSet = ServletUtil.getResultSet(appCtx, ctx);
             IParser parser = parserFactory.createParser(query);
             List<Statement> statements = parser.parse();
-            SessionConfig sessionConfig = new SessionConfig(format, true, isSet(executeQuery), true, planFormat);
+            SessionConfig sessionConfig =
+                    new SessionConfig(format, true, isSet(executeQuery), true, planFormat, hyracksJobFormat);
             sessionConfig.set(SessionConfig.FORMAT_HTML, true);
             sessionConfig.set(SessionConfig.FORMAT_CSV_HEADER, csvAndHeader);
             sessionConfig.set(SessionConfig.FORMAT_WRAPPER_ARRAY, isSet(wrapperArray));
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryResultApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryResultApiServlet.java
index 77846d5..3b0e031 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryResultApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryResultApiServlet.java
@@ -146,10 +146,13 @@
         }
         SessionConfig.PlanFormat planFormat = SessionConfig.PlanFormat.get(request.getParameter("plan-format"),
                 "plan format", SessionConfig.PlanFormat.STRING, LOGGER);
+        SessionConfig.HyracksJobFormat hyracksJobFormat =
+                SessionConfig.HyracksJobFormat.get(request.getParameter("hyracks-job-format"), "hyracks-job-format",
+                        SessionConfig.HyracksJobFormat.JSON, LOGGER);
 
         SessionOutput.ResultAppender appendHandle = (app, handle) -> app.append("{ \"").append("handle")
                 .append("\":" + " \"").append(handle).append("\" }");
-        SessionConfig sessionConfig = new SessionConfig(format, planFormat);
+        SessionConfig sessionConfig = new SessionConfig(format, planFormat, hyracksJobFormat);
 
         // If it's JSON or ADM, check for the "wrapper-array" flag. Default is
         // "true" for JSON and "false" for ADM. (Not applicable for CSV.)
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
index 563f498..e8cb86d 100644
--- 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
@@ -36,6 +36,7 @@
 import org.apache.asterix.translator.IStatementExecutor.ResultDelivery;
 import org.apache.asterix.translator.IStatementExecutor.Stats.ProfileType;
 import org.apache.asterix.translator.SessionConfig.ClientType;
+import org.apache.asterix.translator.SessionConfig.HyracksJobFormat;
 import org.apache.asterix.translator.SessionConfig.OutputFormat;
 import org.apache.asterix.translator.SessionConfig.PlanFormat;
 import org.apache.commons.lang3.StringUtils;
@@ -70,6 +71,7 @@
         MODE("mode"),
         TIMEOUT("timeout"),
         PLAN_FORMAT("plan-format"),
+        HYRACKS_JOB_FORMAT("hyracks-job-format"),
         MAX_RESULT_READS("max-result-reads"),
         EXPRESSION_TREE("expression-tree"),
         REWRITTEN_EXPRESSION_TREE("rewritten-expression-tree"),
@@ -114,7 +116,9 @@
     }
 
     private static final Map<String, PlanFormat> planFormats = ImmutableMap.of(HttpUtil.ContentType.JSON,
-            PlanFormat.JSON, "clean_json", PlanFormat.JSON, "string", PlanFormat.STRING);
+            PlanFormat.JSON, "clean_json", PlanFormat.JSON, "string", PlanFormat.STRING, "dot", PlanFormat.DOT);
+    private static final Map<String, HyracksJobFormat> hyracksJobFormats =
+            ImmutableMap.of(HttpUtil.ContentType.JSON, HyracksJobFormat.JSON, "dot", HyracksJobFormat.DOT);
     private static final Map<String, ClientType> clientTypes =
             ImmutableMap.of("asterix", ClientType.ASTERIX, "jdbc", ClientType.JDBC);
     private static final Map<String, Boolean> booleanValues =
@@ -134,6 +138,7 @@
     private OutputFormat format = OutputFormat.CLEAN_JSON;
     private ResultDelivery mode = ResultDelivery.IMMEDIATE;
     private PlanFormat planFormat = PlanFormat.JSON;
+    private HyracksJobFormat hyracksJobFormat = HyracksJobFormat.JSON;
     private ProfileType profileType = ProfileType.COUNTS;
     private Map<String, String> optionalParams = null;
     private Map<String, JsonNode> statementParams = null;
@@ -265,6 +270,15 @@
         this.planFormat = planFormat;
     }
 
+    public HyracksJobFormat getHyracksJobFormat() {
+        return hyracksJobFormat;
+    }
+
+    public void setHyracksJobFormat(HyracksJobFormat hyracksJobFormat) {
+        Objects.requireNonNull(hyracksJobFormat);
+        this.hyracksJobFormat = hyracksJobFormat;
+    }
+
     public Map<String, String> getOptionalParams() {
         return optionalParams;
     }
@@ -489,6 +503,9 @@
         setFormatIfExists(req, acceptHeader, Parameter.FORMAT.str(), valGetter);
         setMode(parseIfExists(req, Parameter.MODE.str(), valGetter, getMode(), ResultDelivery::fromName));
         setPlanFormat(parseIfExists(req, Parameter.PLAN_FORMAT.str(), valGetter, getPlanFormat(), planFormats::get));
+        setHyracksJobFormat(parseIfExists(req, Parameter.HYRACKS_JOB_FORMAT.str(), valGetter, getHyracksJobFormat(),
+                hyracksJobFormats::get));
+
         setProfileType(parseIfExists(req, Parameter.PROFILE.str(), valGetter, getProfileType(), ProfileType::fromName));
 
         setTimeout(parseTime(req, Parameter.TIMEOUT.str(), valGetter, getTimeout()));
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 07d9632..12859fb 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
@@ -492,9 +492,11 @@
         SessionConfig.ClientType clientType = param.getClientType();
         SessionConfig.OutputFormat format = param.getFormat();
         SessionConfig.PlanFormat planFormat = param.getPlanFormat();
+        SessionConfig.HyracksJobFormat hyracksJobFormat = param.getHyracksJobFormat();
         sessionConfig.setClientType(clientType);
         sessionConfig.setFmt(format);
         sessionConfig.setPlanFormat(planFormat);
+        sessionConfig.setHyracksJobFormat(hyracksJobFormat);
         sessionConfig.setMaxWarnings(param.getMaxWarnings());
         sessionConfig.setExecuteQuery(!param.isCompileOnly());
         sessionConfig.set(SessionConfig.FORMAT_WRAPPER_ARRAY, true);
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 4b70f95..81dcac0 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
@@ -124,7 +124,8 @@
         List<Statement> statements = parser.parse();
         MetadataManager.INSTANCE.init();
 
-        SessionConfig conf = new SessionConfig(OutputFormat.ADM, optimize, true, generateBinaryRuntime, pformat);
+        SessionConfig conf = new SessionConfig(OutputFormat.ADM, optimize, true, generateBinaryRuntime, pformat,
+                SessionConfig.HyracksJobFormat.JSON);
         conf.setOOBData(false, printRewrittenExpressions, printLogicalPlan, printOptimizedPlan, printJob);
         if (printPhysicalOpsOnly) {
             conf.set(SessionConfig.FORMAT_ONLY_PHYSICAL_OPS, true);
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/PlansPrinter.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/PlansPrinter.java
index 4021956..1935d3f 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/PlansPrinter.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/PlansPrinter.java
@@ -42,6 +42,7 @@
         pw.print(FIELD_NAME);
         pw.print("\":");
         switch (planFormat) {
+            case DOT:
             case JSON:
             case STRING:
                 pw.print(ExecutionPlansJsonPrintUtil.asJson(executionPlans, planFormat));
diff --git a/asterixdb/asterix-app/src/main/resources/webui/querytemplate.html b/asterixdb/asterix-app/src/main/resources/webui/querytemplate.html
index cd803cf..e212f11 100644
--- a/asterixdb/asterix-app/src/main/resources/webui/querytemplate.html
+++ b/asterixdb/asterix-app/src/main/resources/webui/querytemplate.html
@@ -305,6 +305,7 @@
                                 <option selected value="JSON">JSON</option>
                                 <option value="CLEAN_JSON">JSON (formatted)</option>
                                 <option value="STRING">String</option>
+                                <option value="DOT">DOT</option>
                             </select>
                         </label>
                         <label class="optlabel"><input type="checkbox" name="wrapper-array" value="true"/> Wrap results
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.6.plans.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.6.plans.sqlpp
new file mode 100644
index 0000000..409cf06
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.6.plans.sqlpp
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/*
+ * Test additional information returned when client-type=jdbc (update statement)
+ */
+
+-- param compile-only:string=true
+-- param logical-plan:string=true
+-- param plan-format:string=DOT
+select value v from range(1,2) v where v > ?;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.7.plans.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.7.plans.sqlpp
new file mode 100644
index 0000000..949cc09
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.7.plans.sqlpp
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/*
+ * Test additional information returned when client-type=jdbc (update statement)
+ */
+
+-- param compile-only:string=true
+-- param job:string=true
+-- param hyracks-job-format:string=DOT
+select value v from range(1,2) v where v > ?;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.8.plans.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.8.plans.sqlpp
new file mode 100644
index 0000000..7c6b730
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.8.plans.sqlpp
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/*
+ * Test additional information returned when client-type=jdbc (update statement)
+ */
+
+-- param compile-only:string=true
+-- param optimized-logical-plan:string=true
+-- param plan-format:string=DOT
+select value v from range(1,2) v where v > ?;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.6.regexjson b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.6.regexjson
new file mode 100644
index 0000000..0cf39ff
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.6.regexjson
@@ -0,0 +1,3 @@
+{
+  "logicalPlan":"R{(?s).*digraph.*\\{.*distribute result.*\\}}"
+}
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.7.regexjson b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.7.regexjson
new file mode 100644
index 0000000..532708a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.7.regexjson
@@ -0,0 +1,3 @@
+{
+  "job": "R{(?s).*digraph \"JobSpecification\" \\{.*ResultWriterOperatorDescriptor@[a-f0-9]+-ResultWriterOperatorDescriptor.*RangeDescriptor\\$1@[a-f0-9]+.*GreaterThanDescriptor\\$2@[a-f0-9]+.*}"
+}
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.8.regex b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.8.regex
new file mode 100644
index 0000000..e791a9f
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.8.regex
@@ -0,0 +1 @@
+/(?s)digraph\s+"Plan"\s*\{.*?ONE_TO_ONE_EXCHANGE.*?\}/
\ No newline at end of file
diff --git a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/input.component.html b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/input.component.html
index 549615f..db86574 100755
--- a/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/input.component.html
+++ b/asterixdb/asterix-dashboard/src/node/src/app/dashboard/query/input.component.html
@@ -37,6 +37,7 @@
                         <mat-select id="plan-format" placeholder="PLAN FORMAT" [(ngModel)]="formatOptions">
                             <mat-option value="JSON">JSON</mat-option>
                             <mat-option value="STRING">STRING</mat-option>
+                            <mat-option value="DOT">DOT</mat-option>
                         </mat-select>
                     </mat-form-field>
                 </div>