You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asterixdb.apache.org by dl...@apache.org on 2021/10/11 16:10:47 UTC

[asterixdb] branch master updated: [NO ISSUE][API] Support client-type parameter in QueryServiceServlet

This is an automated email from the ASF dual-hosted git repository.

dlych pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb.git


The following commit(s) were added to refs/heads/master by this push:
     new f547e1f  [NO ISSUE][API] Support client-type parameter in QueryServiceServlet
f547e1f is described below

commit f547e1f6f85959c9a166b30c98117e085c567a97
Author: Dmitry Lychagin <dm...@couchbase.com>
AuthorDate: Fri Oct 8 14:36:34 2021 -0700

    [NO ISSUE][API] Support client-type parameter in QueryServiceServlet
    
    - user model changes: no
    - storage format changes: no
    - interface changes: yes
    
    Details:
    - QueryServiceServlet supports 'client-type' parameter that
      indicates whether a client is a generic client or a JDBC driver
    - Print field names and types in result signature if client
      is the JDBC driver
    - Extract common fields from ResultsPrinter and NcResultPrinter
      into AbstractResultsPrinter
    - Add testcases
    
    Change-Id: Ia927091c9dec508b0c799ec8170765e43b847a39
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/13623
    Integration-Tests: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Tested-by: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Reviewed-by: Ali Alsuliman <al...@gmail.com>
---
 .../apache/asterix/translator/ExecutionPlans.java  |  38 +++++-
 .../translator/ExecutionPlansJsonPrintUtil.java    |   9 ++
 .../asterix/translator/IRequestParameters.java     |  10 +-
 .../apache/asterix/translator/SessionConfig.java   |  23 +++-
 .../apache/asterix/api/common/APIFramework.java    |  36 +++++-
 .../api/http/server/NCQueryServiceServlet.java     |   4 +
 .../http/server/QueryServiceRequestParameters.java |  15 +++
 .../api/http/server/QueryServiceServlet.java       |  16 +--
 .../apache/asterix/api/http/server/ResultUtil.java |  43 +++++--
 ...urePrinter.java => AbstractResultsPrinter.java} |  27 +++--
 .../asterix/app/result/fields/NcResultPrinter.java |  15 +--
 .../asterix/app/result/fields/ResultsPrinter.java  |  16 +--
 .../app/result/fields/SignaturePrinter.java        | 130 ++++++++++++++++++++-
 .../asterix/app/translator/RequestParameters.java  |  18 ++-
 .../asterix/test/common/ResultExtractor.java       |   4 +
 .../apache/asterix/test/common/TestExecutor.java   |   5 +
 .../org/apache/asterix/test/common/TestHelper.java |   4 +-
 .../api/compileonly/compileonly.2.plans.sqlpp}     |  30 +----
 .../api/compileonly/compileonly.3.plans.sqlpp}     |  30 +----
 .../api/compileonly/compileonly.4.plans.sqlpp}     |  30 ++---
 .../api/signature/signature.1.signature.sqlpp}     |  27 +----
 .../api/signature/signature.2.signature.sqlpp}     |  47 ++++----
 .../api/signature/signature.3.signature.sqlpp}     |  36 ++----
 .../api/signature/signature.4.signature.sqlpp}     |  35 ++----
 .../api/signature/signature.5.signature.sqlpp}     |  31 ++---
 .../results/api/compileonly/compileonly.2.adm      |   1 +
 .../results/api/compileonly/compileonly.3.adm      |   1 +
 .../results/api/compileonly/compileonly.4.adm      |   1 +
 .../results/api/signature/signature.1.adm          |   1 +
 .../results/api/signature/signature.2.adm          |   1 +
 .../results/api/signature/signature.3.adm          |   1 +
 .../results/api/signature/signature.4.adm          |   1 +
 .../results/api/signature/signature.5.adm          |   1 +
 .../test/resources/runtimets/testsuite_sqlpp.xml   |   5 +
 34 files changed, 423 insertions(+), 269 deletions(-)

diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/ExecutionPlans.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/ExecutionPlans.java
index 511b74c..f33bb01 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/ExecutionPlans.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/ExecutionPlans.java
@@ -21,13 +21,17 @@ package org.apache.asterix.translator;
 import java.io.Serializable;
 
 public class ExecutionPlans implements Serializable {
-    private static final long serialVersionUID = 6853904213354224457L;
+    private static final long serialVersionUID = 6853904213354224458L;
 
     private String expressionTree;
     private String rewrittenExpressionTree;
     private String logicalPlan;
     private String optimizedLogicalPlan;
     private String job;
+    private String signature;
+    private String statementCategory;
+    private String statementParameters;
+    private boolean explainOnly;
 
     public String getExpressionTree() {
         return expressionTree;
@@ -68,4 +72,36 @@ public class ExecutionPlans implements Serializable {
     public void setJob(String job) {
         this.job = job;
     }
+
+    public String getSignature() {
+        return signature;
+    }
+
+    public void setSignature(String signature) {
+        this.signature = signature;
+    }
+
+    public String getStatementCategory() {
+        return statementCategory;
+    }
+
+    public void setStatementCategory(String statementCategory) {
+        this.statementCategory = statementCategory;
+    }
+
+    public String getStatementParameters() {
+        return statementParameters;
+    }
+
+    public void setStatementParameters(String statementParameters) {
+        this.statementParameters = statementParameters;
+    }
+
+    public boolean isExplainOnly() {
+        return explainOnly;
+    }
+
+    public void setExplainOnly(boolean explainOnly) {
+        this.explainOnly = explainOnly;
+    }
 }
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 c83e865..ad89ad6 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
@@ -18,6 +18,7 @@
  */
 package org.apache.asterix.translator;
 
+import static org.apache.asterix.translator.SessionConfig.PlanFormat.JSON;
 import static org.apache.asterix.translator.SessionConfig.PlanFormat.STRING;
 
 import org.apache.hyracks.util.JSONUtil;
@@ -29,6 +30,9 @@ public class ExecutionPlansJsonPrintUtil {
     private static final String REWRITTEN_EXPRESSION_TREE_LBL = "rewrittenExpressionTree";
     public static final String OPTIMIZED_LOGICAL_PLAN_LBL = "optimizedLogicalPlan";
     private static final String JOB_LBL = "job";
+    private static final String STATEMENT_CATEGORY_LBL = "statementCategory";
+    private static final String STATEMENT_PARAMETERS_LBL = "statementParameters";
+    private static final String EXPLAIN_ONLY_LBL = "explainOnly";
 
     private ExecutionPlansJsonPrintUtil() {
     }
@@ -42,6 +46,11 @@ public class ExecutionPlansJsonPrintUtil {
         appendNonNull(output, LOGICAL_PLAN_LBL, plans.getLogicalPlan(), format);
         appendNonNull(output, OPTIMIZED_LOGICAL_PLAN_LBL, plans.getOptimizedLogicalPlan(), format);
         appendNonNull(output, JOB_LBL, plans.getJob(), format);
+        appendNonNull(output, STATEMENT_CATEGORY_LBL, plans.getStatementCategory(), STRING);
+        appendNonNull(output, STATEMENT_PARAMETERS_LBL, plans.getStatementParameters(), JSON);
+        if (plans.isExplainOnly()) {
+            appendNonNull(output, EXPLAIN_ONLY_LBL, Boolean.toString(plans.isExplainOnly()), JSON);
+        }
         appendOutputPostfix(output);
         return output.toString();
     }
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 00342b6..ab1061f 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
@@ -47,7 +47,7 @@ public interface IRequestParameters extends ICommonRequestParameters {
 
     /**
      * @return a reference on which to write properties of executed queries (e.g. what kind of statement was parsed
-     *         by the parser)
+     * by the parser)
      */
     StatementProperties getStatementProperties();
 
@@ -63,8 +63,8 @@ public interface IRequestParameters extends ICommonRequestParameters {
 
     /**
      * @return a bitmask that restricts which statement
-     *   {@link org.apache.asterix.lang.common.base.Statement.Category categories} are permitted for this request,
-     *   {@code 0} if all categories are allowed
+     * {@link org.apache.asterix.lang.common.base.Statement.Category categories} are permitted for this request,
+     * {@code 0} if all categories are allowed
      */
     int getStatementCategoryRestrictionMask();
 
@@ -76,8 +76,10 @@ public interface IRequestParameters extends ICommonRequestParameters {
 
     boolean isSkipAdmissionPolicy();
 
+    boolean isPrintSignature();
+
     /**
      * @return canonical name of the default dataverse for this statement
      */
     String getDefaultDataverseName();
-}
+}
\ No newline at end of file
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 33646f0..4e294ec 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
@@ -21,6 +21,7 @@ package org.apache.asterix.translator;
 import java.io.Serializable;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.Logger;
@@ -39,7 +40,7 @@ import org.apache.logging.log4j.Logger;
  * <li>It allows you to specify output format-specific parameters.
  */
 public class SessionConfig implements Serializable {
-    private static final long serialVersionUID = 1L;
+    private static final long serialVersionUID = 2L;
 
     /**
      * Used to specify the output format for the primary execution.
@@ -73,6 +74,11 @@ public class SessionConfig implements Serializable {
         }
     }
 
+    public enum ClientType {
+        ASTERIX,
+        JDBC
+    }
+
     /**
      * Produce out-of-band output for Hyracks Job.
      */
@@ -128,6 +134,9 @@ public class SessionConfig implements Serializable {
      */
     public static final String FORMAT_QUOTE_RECORD = "quote-record";
 
+    // Client type
+    private ClientType clientType;
+
     // Output format.
     private OutputFormat fmt;
     private PlanFormat planFormat;
@@ -175,6 +184,18 @@ public class SessionConfig implements Serializable {
         this.generateJobSpec = generateJobSpec;
         this.flags = new HashMap<>();
         this.planFormat = planFormat;
+        this.clientType = ClientType.ASTERIX;
+    }
+
+    /**
+     * Retrieve the client type for this execution.
+     */
+    public ClientType getClientType() {
+        return this.clientType;
+    }
+
+    public void setClientType(ClientType clientType) {
+        this.clientType = Objects.requireNonNull(clientType);
     }
 
     /**
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 9292296..53137e6 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
@@ -33,7 +33,9 @@ import java.util.Set;
 
 import org.apache.asterix.algebra.base.ILangExpressionToPlanTranslator;
 import org.apache.asterix.algebra.base.ILangExpressionToPlanTranslatorFactory;
+import org.apache.asterix.api.http.server.ResultUtil;
 import org.apache.asterix.app.result.fields.ExplainOnlyResultsPrinter;
+import org.apache.asterix.app.result.fields.SignaturePrinter;
 import org.apache.asterix.common.api.INodeJobTracker;
 import org.apache.asterix.common.api.IResponsePrinter;
 import org.apache.asterix.common.config.CompilerProperties;
@@ -278,14 +280,34 @@ public class APIFramework {
                 }
             }
         }
+
+        if (conf.getClientType() == SessionConfig.ClientType.JDBC) {
+            executionPlans.setStatementCategory(Statement.Category.toString(getStatementCategory(query, statement)));
+            if (!conf.isExecuteQuery()) {
+                String stmtParams = ResultUtil.ParseOnlyResult.printStatementParameters(externalVars.keySet(), v -> v);
+                executionPlans.setStatementParameters(stmtParams);
+            }
+            if (isExplainOnly) {
+                executionPlans.setExplainOnly(true);
+            } else if (isQuery) {
+                executionPlans.setSignature(SignaturePrinter.generateFlatSignature(resultMetadata));
+            }
+        }
+
+        boolean printSignature = isQuery && requestParameters != null && requestParameters.isPrintSignature();
+
         if (isExplainOnly) {
-            printPlanAsResult(metadataProvider, output, printer);
+            printPlanAsResult(metadataProvider, output, printer, printSignature);
             if (!conf.is(SessionConfig.OOB_OPTIMIZED_LOGICAL_PLAN)) {
                 executionPlans.setOptimizedLogicalPlan(null);
             }
             return null;
         }
 
+        if (printSignature) {
+            printer.addResultPrinter(SignaturePrinter.newInstance(executionPlans));
+        }
+
         if (!conf.isGenerateJobSpec()) {
             return null;
         }
@@ -328,9 +350,12 @@ public class APIFramework {
         return spec;
     }
 
-    private void printPlanAsResult(MetadataProvider metadataProvider, SessionOutput output, IResponsePrinter printer)
-            throws AlgebricksException {
+    private void printPlanAsResult(MetadataProvider metadataProvider, SessionOutput output, IResponsePrinter printer,
+            boolean printSignature) throws AlgebricksException {
         try {
+            if (printSignature) {
+                printer.addResultPrinter(SignaturePrinter.INSTANCE);
+            }
             printer.addResultPrinter(new ExplainOnlyResultsPrinter(metadataProvider.getApplicationContext(),
                     executionPlans.getOptimizedLogicalPlan(), output));
             printer.printResults();
@@ -362,6 +387,11 @@ public class APIFramework {
                 : PlanPrettyPrinter.createStringPlanPrettyPrinter();
     }
 
+    private byte getStatementCategory(Query query, ICompiledDmlStatement statement) {
+        return statement != null ? statement.getCategory()
+                : query != null ? Statement.Category.QUERY : Statement.Category.DDL;
+    }
+
     public void executeJobArray(IHyracksClientConnection hcc, JobSpecification[] specs, PrintWriter out)
             throws Exception {
         for (JobSpecification spec : specs) {
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 68d7b08..e6ba15f 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
@@ -32,6 +32,7 @@ import org.apache.asterix.app.message.ExecuteStatementRequestMessage;
 import org.apache.asterix.app.message.ExecuteStatementResponseMessage;
 import org.apache.asterix.app.result.ResponsePrinter;
 import org.apache.asterix.app.result.fields.NcResultPrinter;
+import org.apache.asterix.app.result.fields.SignaturePrinter;
 import org.apache.asterix.common.api.IApplicationContext;
 import org.apache.asterix.common.api.IRequestReference;
 import org.apache.asterix.common.config.GlobalConfig;
@@ -122,6 +123,9 @@ public class NCQueryServiceServlet extends QueryServiceServlet {
         // if the was no error, we can set the result status to success
         executionState.setStatus(ResultStatus.SUCCESS, HttpResponseStatus.OK);
         updateStatsFromCC(stats, responseMsg);
+        if (param.isSignature() && delivery != IStatementExecutor.ResultDelivery.ASYNC && !param.isParseOnly()) {
+            responsePrinter.addResultPrinter(SignaturePrinter.newInstance(responseMsg.getExecutionPlans()));
+        }
         if (hasResult(responseMsg)) {
             responsePrinter.addResultPrinter(
                     new NcResultPrinter(appCtx, responseMsg, getResultSet(), delivery, sessionOutput, stats));
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 412e5c5..084bca7 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
@@ -33,6 +33,7 @@ import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.exceptions.RuntimeDataException;
 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.OutputFormat;
 import org.apache.asterix.translator.SessionConfig.PlanFormat;
 import org.apache.commons.lang3.StringUtils;
@@ -60,6 +61,7 @@ public class QueryServiceRequestParameters {
         STATEMENT("statement"),
         FORMAT("format"),
         CLIENT_ID("client_context_id"),
+        CLIENT_TYPE("client-type"),
         DATAVERSE("dataverse"),
         PRETTY("pretty"),
         MODE("mode"),
@@ -108,6 +110,8 @@ public class QueryServiceRequestParameters {
 
     private static final Map<String, PlanFormat> planFormats = ImmutableMap.of(HttpUtil.ContentType.JSON,
             PlanFormat.JSON, "clean_json", PlanFormat.JSON, "string", PlanFormat.STRING);
+    private static final Map<String, ClientType> clientTypes =
+            ImmutableMap.of("asterix", ClientType.ASTERIX, "jdbc", ClientType.JDBC);
     private static final Map<String, Boolean> booleanValues =
             ImmutableMap.of(Boolean.TRUE.toString(), Boolean.TRUE, Boolean.FALSE.toString(), Boolean.FALSE);
     private static final Map<String, Boolean> csvHeaderValues =
@@ -119,6 +123,7 @@ public class QueryServiceRequestParameters {
     private String statement;
     private String clientContextID;
     private String dataverse;
+    private ClientType clientType = ClientType.ASTERIX;
     private OutputFormat format = OutputFormat.CLEAN_JSON;
     private ResultDelivery mode = ResultDelivery.IMMEDIATE;
     private PlanFormat planFormat = PlanFormat.JSON;
@@ -202,6 +207,14 @@ public class QueryServiceRequestParameters {
         this.clientContextID = clientContextID;
     }
 
+    public ClientType getClientType() {
+        return clientType;
+    }
+
+    public void setClientType(ClientType clientType) {
+        this.clientType = Objects.requireNonNull(clientType);
+    }
+
     public String getDataverse() {
         return dataverse;
     }
@@ -361,6 +374,7 @@ public class QueryServiceRequestParameters {
         object.put("pretty", pretty);
         object.put("mode", mode.getName());
         object.put("clientContextID", clientContextID);
+        object.put("clientType", clientType.toString());
         object.put("dataverse", dataverse);
         object.put("format", format.toString());
         object.put("timeout", timeout);
@@ -459,6 +473,7 @@ public class QueryServiceRequestParameters {
         setMultiStatement(parseBoolean(req, Parameter.MULTI_STATEMENT.str(), valGetter, isMultiStatement()));
         setJob(parseBoolean(req, Parameter.JOB.str(), valGetter, isJob()));
         setSignature(parseBoolean(req, Parameter.SIGNATURE.str(), valGetter, isSignature()));
+        setClientType(parseIfExists(req, Parameter.CLIENT_TYPE.str(), valGetter, getClientType(), clientTypes::get));
     }
 
     protected void setExtraParams(JsonNode jsonRequest) throws HyracksDataException {
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 26e4b96..b46c299 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
@@ -46,7 +46,6 @@ import org.apache.asterix.app.result.fields.ParseOnlyResultPrinter;
 import org.apache.asterix.app.result.fields.PlansPrinter;
 import org.apache.asterix.app.result.fields.ProfilePrinter;
 import org.apache.asterix.app.result.fields.RequestIdPrinter;
-import org.apache.asterix.app.result.fields.SignaturePrinter;
 import org.apache.asterix.app.result.fields.StatusPrinter;
 import org.apache.asterix.app.result.fields.TypePrinter;
 import org.apache.asterix.app.result.fields.WarningsPrinter;
@@ -297,7 +296,7 @@ public class QueryServiceServlet extends AbstractQueryApiServlet {
                 responsePrinter.addResultPrinter(new ParseOnlyResultPrinter(parseOnlyResult));
             } else {
                 Map<String, byte[]> statementParams = org.apache.asterix.app.translator.RequestParameters
-                        .serializeParameterValues(param.getStatementParams());
+                        .serializeParameterValues(param.getStatementParams(), sessionOutput.config().fmt());
                 setAccessControlHeaders(request, response);
                 stats.setProfileType(param.getProfileType());
                 IStatementExecutor.StatementProperties statementProperties =
@@ -335,9 +334,6 @@ public class QueryServiceServlet extends AbstractQueryApiServlet {
         if (param.getClientContextID() != null && !param.getClientContextID().isEmpty()) {
             responsePrinter.addHeaderPrinter(new ClientContextIdPrinter(param.getClientContextID()));
         }
-        if (param.isSignature() && delivery != ResultDelivery.ASYNC && !param.isParseOnly()) {
-            responsePrinter.addHeaderPrinter(SignaturePrinter.INSTANCE);
-        }
         if (sessionOutput.config().fmt() == SessionConfig.OutputFormat.ADM
                 || sessionOutput.config().fmt() == SessionConfig.OutputFormat.CSV) {
             responsePrinter.addHeaderPrinter(new TypePrinter(sessionOutput.config()));
@@ -484,8 +480,10 @@ public class QueryServiceServlet extends AbstractQueryApiServlet {
         String handleUrl = getHandleUrl(param.getHost(), param.getPath(), delivery);
         sessionOutput.setHandleAppender(ResultUtil.createResultHandleAppender(handleUrl));
         SessionConfig sessionConfig = sessionOutput.config();
+        SessionConfig.ClientType clientType = param.getClientType();
         SessionConfig.OutputFormat format = param.getFormat();
         SessionConfig.PlanFormat planFormat = param.getPlanFormat();
+        sessionConfig.setClientType(clientType);
         sessionConfig.setFmt(format);
         sessionConfig.setPlanFormat(planFormat);
         sessionConfig.setMaxWarnings(param.getMaxWarnings());
@@ -517,9 +515,11 @@ public class QueryServiceServlet extends AbstractQueryApiServlet {
             IRequestReference requestReference, String statementsText, IResultSet resultSet,
             ResultProperties resultProperties, Stats stats, IStatementExecutor.StatementProperties statementProperties,
             Map<String, String> optionalParameters, Map<String, IAObject> stmtParams, int stmtCategoryRestriction) {
-        return new RequestParameters(requestReference, statementsText, resultSet, resultProperties, stats,
-                statementProperties, null, param.getClientContextID(), param.getDataverse(), optionalParameters,
-                stmtParams, param.isMultiStatement(), stmtCategoryRestriction);
+        RequestParameters requestParameters = new RequestParameters(requestReference, statementsText, resultSet,
+                resultProperties, stats, statementProperties, null, param.getClientContextID(), param.getDataverse(),
+                optionalParameters, stmtParams, param.isMultiStatement(), stmtCategoryRestriction);
+        requestParameters.setPrintSignature(param.isSignature());
+        return requestParameters;
     }
 
     protected static boolean isPrintingProfile(IStatementExecutor.Stats stats) {
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ResultUtil.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ResultUtil.java
index 045d64c..cf535d9 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ResultUtil.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ResultUtil.java
@@ -26,9 +26,11 @@ import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
+import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -39,6 +41,7 @@ import org.apache.asterix.app.result.fields.ResultsPrinter;
 import org.apache.asterix.app.result.fields.StatusPrinter;
 import org.apache.asterix.common.api.IApplicationContext;
 import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
 import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
 import org.apache.asterix.om.types.ARecordType;
 import org.apache.asterix.translator.IStatementExecutor.Stats;
@@ -318,7 +321,8 @@ public class ResultUtil {
     }
 
     public static class ParseOnlyResult {
-        private Set<VariableExpr> externalVariables;
+
+        private final Set<VariableExpr> externalVariables;
 
         private static final String STMT_PARAM_LBL = "statement-parameters";
 
@@ -327,13 +331,29 @@ public class ResultUtil {
         }
 
         public String asJson() {
+            StringBuilder output = new StringBuilder();
+            output.append("{\"").append(STMT_PARAM_LBL).append("\":");
+            printStatementParameters(externalVariables, VariableExpr::getVar, output);
+            output.append('}');
+            return output.toString();
+        }
 
-            ArrayList<String> positionalVars = new ArrayList<>();
-            ArrayList<String> namedVars = new ArrayList<>();
+        public static <T> String printStatementParameters(Collection<T> externalVariables,
+                Function<T, VarIdentifier> varIdentAccessor) {
+            StringBuilder output = new StringBuilder(externalVariables.size() * 12);
+            printStatementParameters(externalVariables, varIdentAccessor, output);
+            return output.toString();
+        }
 
-            for (VariableExpr extVarRef : externalVariables) {
-                String varname = extVarRef.getVar().getValue();
-                if (SqlppVariableUtil.isPositionalVariableIdentifier(extVarRef.getVar())) {
+        private static <T> void printStatementParameters(Collection<T> externalVariables,
+                Function<T, VarIdentifier> varIdentAccessor, StringBuilder output) {
+            List<String> positionalVars = new ArrayList<>();
+            List<String> namedVars = new ArrayList<>();
+
+            for (T extVarItem : externalVariables) {
+                VarIdentifier extVar = varIdentAccessor.apply(extVarItem);
+                String varname = extVar.getValue();
+                if (SqlppVariableUtil.isPositionalVariableIdentifier(extVar)) {
                     positionalVars.add(SqlppVariableUtil.toUserDefinedName(varname));
                 } else {
                     namedVars.add(SqlppVariableUtil.toUserDefinedName(varname));
@@ -341,14 +361,14 @@ public class ResultUtil {
             }
             Collections.sort(positionalVars);
             Collections.sort(namedVars);
-            final StringBuilder output = new StringBuilder();
-            output.append("{\"").append(STMT_PARAM_LBL).append("\":[");
+
+            output.append('[');
             boolean first = true;
             for (String posVar : positionalVars) {
                 if (first) {
                     first = false;
                 } else {
-                    output.append(",");
+                    output.append(',');
                 }
                 output.append(posVar);
             }
@@ -358,10 +378,9 @@ public class ResultUtil {
                 } else {
                     output.append(",");
                 }
-                output.append("\"").append(namedVar).append("\"");
+                output.append('"').append(namedVar).append('"');
             }
-            output.append("]}");
-            return output.toString();
+            output.append(']');
         }
     }
 }
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/AbstractResultsPrinter.java
similarity index 60%
copy from asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
copy to asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/AbstractResultsPrinter.java
index fe9d2be..621fde1 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/AbstractResultsPrinter.java
@@ -16,26 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.app.result.fields;
 
-import java.io.PrintWriter;
+package org.apache.asterix.app.result.fields;
 
-import org.apache.asterix.api.http.server.ResultUtil;
+import org.apache.asterix.common.api.IApplicationContext;
 import org.apache.asterix.common.api.IResponseFieldPrinter;
+import org.apache.asterix.translator.IStatementExecutor;
+import org.apache.asterix.translator.SessionOutput;
 
-public class SignaturePrinter implements IResponseFieldPrinter {
+abstract class AbstractResultsPrinter implements IResponseFieldPrinter {
+    public static final String FIELD_NAME = "results";
 
-    public static final SignaturePrinter INSTANCE = new SignaturePrinter();
-    private static final String FIELD_NAME = "signature";
+    protected final IApplicationContext appCtx;
+    protected final IStatementExecutor.Stats stats;
+    protected final SessionOutput sessionOutput;
 
-    @Override
-    public void print(PrintWriter pw) {
-        pw.print("\t\"");
-        pw.print(FIELD_NAME);
-        pw.print("\": {\n");
-        pw.print("\t");
-        ResultUtil.printField(pw, "*", "*", false);
-        pw.print("\n\t}");
+    AbstractResultsPrinter(IApplicationContext appCtx, IStatementExecutor.Stats stats, SessionOutput sessionOutput) {
+        this.appCtx = appCtx;
+        this.stats = stats;
+        this.sessionOutput = sessionOutput;
     }
 
     @Override
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/NcResultPrinter.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/NcResultPrinter.java
index ccf970c..d98472e 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/NcResultPrinter.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/NcResultPrinter.java
@@ -26,7 +26,6 @@ import org.apache.asterix.app.message.ExecuteStatementResponseMessage;
 import org.apache.asterix.app.result.ResponsePrinter;
 import org.apache.asterix.app.result.ResultReader;
 import org.apache.asterix.common.api.IApplicationContext;
-import org.apache.asterix.common.api.IResponseFieldPrinter;
 import org.apache.asterix.om.types.ARecordType;
 import org.apache.asterix.translator.IStatementExecutor;
 import org.apache.asterix.translator.SessionOutput;
@@ -36,24 +35,19 @@ import org.apache.hyracks.api.job.JobId;
 import org.apache.hyracks.api.result.IResultSet;
 import org.apache.hyracks.api.result.ResultSetId;
 
-public class NcResultPrinter implements IResponseFieldPrinter {
+public class NcResultPrinter extends AbstractResultsPrinter {
 
     private final IStatementExecutor.ResultDelivery delivery;
     private final ExecuteStatementResponseMessage responseMsg;
-    private final IApplicationContext appCtx;
     private final IResultSet resultSet;
-    private final SessionOutput sessionOutput;
-    private final IStatementExecutor.Stats stats;
 
     public NcResultPrinter(IApplicationContext appCtx, ExecuteStatementResponseMessage responseMsg,
             IResultSet resultSet, IStatementExecutor.ResultDelivery delivery, SessionOutput sessionOutput,
             IStatementExecutor.Stats stats) {
-        this.appCtx = appCtx;
+        super(appCtx, stats, sessionOutput);
         this.responseMsg = responseMsg;
         this.delivery = delivery;
         this.resultSet = resultSet;
-        this.sessionOutput = sessionOutput;
-        this.stats = stats;
     }
 
     @Override
@@ -73,9 +67,4 @@ public class NcResultPrinter implements IResponseFieldPrinter {
             pw.append(responseMsg.getResult());
         }
     }
-
-    @Override
-    public String getName() {
-        return ResultsPrinter.FIELD_NAME;
-    }
 }
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/ResultsPrinter.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/ResultsPrinter.java
index 52198de..ee2d7d5 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/ResultsPrinter.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/ResultsPrinter.java
@@ -23,37 +23,25 @@ import java.io.PrintWriter;
 import org.apache.asterix.api.http.server.ResultUtil;
 import org.apache.asterix.app.result.ResultReader;
 import org.apache.asterix.common.api.IApplicationContext;
-import org.apache.asterix.common.api.IResponseFieldPrinter;
 import org.apache.asterix.om.types.ARecordType;
 import org.apache.asterix.translator.IStatementExecutor;
 import org.apache.asterix.translator.SessionOutput;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
 
-public class ResultsPrinter implements IResponseFieldPrinter {
+public class ResultsPrinter extends AbstractResultsPrinter {
 
-    public static final String FIELD_NAME = "results";
-    private final IApplicationContext appCtx;
     private final ARecordType recordType;
     private final ResultReader resultReader;
-    private final IStatementExecutor.Stats stats;
-    private final SessionOutput sessionOutput;
 
     public ResultsPrinter(IApplicationContext appCtx, ResultReader resultReader, ARecordType recordType,
             IStatementExecutor.Stats stats, SessionOutput sessionOutput) {
-        this.appCtx = appCtx;
+        super(appCtx, stats, sessionOutput);
         this.recordType = recordType;
         this.resultReader = resultReader;
-        this.stats = stats;
-        this.sessionOutput = sessionOutput;
     }
 
     @Override
     public void print(PrintWriter pw) throws HyracksDataException {
         ResultUtil.printResults(appCtx, resultReader, sessionOutput, stats, recordType);
     }
-
-    @Override
-    public String getName() {
-        return FIELD_NAME;
-    }
 }
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
index fe9d2be..048584c 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
@@ -19,27 +19,147 @@
 package org.apache.asterix.app.result.fields;
 
 import java.io.PrintWriter;
+import java.util.LinkedHashSet;
+import java.util.List;
 
+import org.apache.asterix.api.common.ResultMetadata;
 import org.apache.asterix.api.http.server.ResultUtil;
+import org.apache.asterix.common.annotations.IRecordTypeAnnotation;
+import org.apache.asterix.common.annotations.RecordFieldOrderAnnotation;
 import org.apache.asterix.common.api.IResponseFieldPrinter;
+import org.apache.asterix.om.typecomputer.impl.TypeComputeUtils;
+import org.apache.asterix.om.types.ARecordType;
+import org.apache.asterix.om.types.ATypeTag;
+import org.apache.asterix.om.types.AUnionType;
+import org.apache.asterix.om.types.BuiltinType;
+import org.apache.asterix.om.types.IAType;
+import org.apache.asterix.translator.ExecutionPlans;
+import org.apache.hyracks.algebricks.common.utils.Pair;
+import org.apache.hyracks.util.JSONUtil;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 
 public class SignaturePrinter implements IResponseFieldPrinter {
 
-    public static final SignaturePrinter INSTANCE = new SignaturePrinter();
+    public static final SignaturePrinter INSTANCE = new SignaturePrinter(null);
     private static final String FIELD_NAME = "signature";
+    private static final String NAME_FIELD_NAME = "name";
+    private static final String TYPE_FIELD_NAME = "type";
+    private static final String OPTIONAL_MODIFIER = "?";
+
+    private final String signature;
+
+    public SignaturePrinter(String signature) {
+        this.signature = signature;
+    }
 
     @Override
     public void print(PrintWriter pw) {
         pw.print("\t\"");
         pw.print(FIELD_NAME);
-        pw.print("\": {\n");
-        pw.print("\t");
-        ResultUtil.printField(pw, "*", "*", false);
-        pw.print("\n\t}");
+        pw.print("\": ");
+        if (signature == null) {
+            pw.print("{\n\t");
+            ResultUtil.printField(pw, "*", "*", false);
+            pw.print("\n\t}");
+        } else {
+            pw.print(signature);
+        }
     }
 
     @Override
     public String getName() {
         return FIELD_NAME;
     }
+
+    public static String generateFlatSignature(ResultMetadata resultMetadata) {
+        List<Object> typeInfo = resultMetadata.getOutputTypes();
+        if (typeInfo == null || typeInfo.size() != 1) {
+            return null;
+        }
+        IAType outputType = TypeComputeUtils.getActualType((IAType) typeInfo.get(0));
+        if (outputType.getTypeTag() != ATypeTag.OBJECT) {
+            return null;
+        }
+        ARecordType outputRecordType = (ARecordType) outputType;
+        String[] fieldNames;
+        IAType[] fieldTypes;
+        Pair<String[], IAType[]> p = generateFlatSignatureFromOpenType(outputRecordType);
+        if (p != null) {
+            fieldNames = p.first;
+            fieldTypes = p.second;
+        } else {
+            fieldNames = outputRecordType.getFieldNames();
+            fieldTypes = outputRecordType.getFieldTypes();
+        }
+        if (fieldNames == null || fieldNames.length == 0) {
+            return null;
+        }
+        ObjectNode signatureNode = JSONUtil.createObject();
+        ArrayNode fieldNameArrayNode = signatureNode.putArray(NAME_FIELD_NAME);
+        ArrayNode fieldTypeArrayNode = signatureNode.putArray(TYPE_FIELD_NAME);
+        for (int i = 0, n = fieldNames.length; i < n; i++) {
+            fieldNameArrayNode.add(fieldNames[i]);
+            fieldTypeArrayNode.add(printFieldType(fieldTypes[i]));
+        }
+        return signatureNode.toString();
+    }
+
+    private static Pair<String[], IAType[]> generateFlatSignatureFromOpenType(ARecordType outputRecordType) {
+        if (!outputRecordType.isOpen() || !outputRecordType.knowsAllPossibleAdditonalFieldNames()) {
+            return null;
+        }
+        IRecordTypeAnnotation fieldOrderAnn =
+                outputRecordType.findAnnotation(IRecordTypeAnnotation.Kind.RECORD_FIELD_ORDER);
+        if (fieldOrderAnn == null) {
+            return null;
+        }
+        LinkedHashSet<String> fieldNamesOrdered = ((RecordFieldOrderAnnotation) fieldOrderAnn).getFieldNames();
+        int numFields = fieldNamesOrdered.size();
+
+        String[] fieldNames = new String[numFields];
+        IAType[] fieldTypes = new IAType[numFields];
+        int i = 0;
+        for (String fieldName : fieldNamesOrdered) {
+            IAType fieldType;
+            if (outputRecordType.isClosedField(fieldName)) {
+                fieldType = outputRecordType.getFieldType(fieldName);
+            } else if (outputRecordType.getAllPossibleAdditonalFieldNames().contains(fieldName)) {
+                fieldType = BuiltinType.ANY;
+            } else {
+                return null;
+            }
+            fieldNames[i] = fieldName;
+            fieldTypes[i] = fieldType;
+            i++;
+        }
+        return new Pair<>(fieldNames, fieldTypes);
+    }
+
+    private static String printFieldType(IAType type) {
+        boolean optional = false;
+        if (type.getTypeTag() == ATypeTag.UNION) {
+            AUnionType fieldTypeUnion = (AUnionType) type;
+            optional = fieldTypeUnion.isNullableType() || fieldTypeUnion.isMissableType();
+            type = fieldTypeUnion.getActualType();
+        }
+        String typeName;
+        switch (type.getTypeTag()) {
+            case OBJECT:
+            case ARRAY:
+            case MULTISET:
+                typeName = type.getDisplayName();
+                break;
+            default:
+                typeName = type.getTypeName();
+                break;
+        }
+        return optional ? typeName + OPTIONAL_MODIFIER : typeName;
+    }
+
+    public static SignaturePrinter newInstance(ExecutionPlans executionPlans) {
+        String signature = executionPlans.getSignature();
+        return signature != null ? new SignaturePrinter(signature) : INSTANCE;
+    }
 }
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 e7af36b..7b634bd 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
@@ -25,6 +25,7 @@ import java.util.Map;
 
 import org.apache.asterix.common.api.IRequestReference;
 import org.apache.asterix.external.parser.JSONDataParser;
+import org.apache.asterix.external.parser.LosslessADMJSONDataParser;
 import org.apache.asterix.formats.nontagged.SerializerDeserializerProvider;
 import org.apache.asterix.lang.common.base.Statement;
 import org.apache.asterix.om.base.IAObject;
@@ -34,6 +35,7 @@ import org.apache.asterix.translator.IStatementExecutor;
 import org.apache.asterix.translator.IStatementExecutor.StatementProperties;
 import org.apache.asterix.translator.IStatementExecutor.Stats;
 import org.apache.asterix.translator.ResultProperties;
+import org.apache.asterix.translator.SessionConfig;
 import org.apache.hyracks.api.dataflow.value.ISerializerDeserializer;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
 import org.apache.hyracks.api.result.IResultSet;
@@ -61,6 +63,7 @@ public class RequestParameters implements IRequestParameters {
     private final String defaultDataverseName;
     private final boolean forceDropDataset;
     private final boolean skipAdmissionPolicy;
+    private boolean printSignature;
 
     public RequestParameters(IRequestReference requestReference, String statement, IResultSet resultSet,
             ResultProperties resultProperties, Stats stats, StatementProperties statementProperties,
@@ -188,12 +191,21 @@ public class RequestParameters implements IRequestParameters {
         return defaultDataverseName;
     }
 
-    public static Map<String, byte[]> serializeParameterValues(Map<String, JsonNode> inParams)
-            throws HyracksDataException {
+    public boolean isPrintSignature() {
+        return printSignature;
+    }
+
+    public void setPrintSignature(boolean printSignature) {
+        this.printSignature = printSignature;
+    }
+
+    public static Map<String, byte[]> serializeParameterValues(Map<String, JsonNode> inParams,
+            SessionConfig.OutputFormat format) throws HyracksDataException {
         if (inParams == null || inParams.isEmpty()) {
             return null;
         }
-        JSONDataParser parser = new JSONDataParser(null, null);
+        JSONDataParser parser = format == SessionConfig.OutputFormat.LOSSLESS_ADM_JSON
+                ? new LosslessADMJSONDataParser(null) : new JSONDataParser(null, null);
         ByteArrayAccessibleOutputStream buffer = new ByteArrayAccessibleOutputStream();
         DataOutputStream bufferDataOutput = new DataOutputStream(buffer);
         Map<String, byte[]> m = new HashMap<>();
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/ResultExtractor.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/ResultExtractor.java
index 5ab5b01..f83ddb2 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/ResultExtractor.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/ResultExtractor.java
@@ -173,6 +173,10 @@ public class ResultExtractor {
         return extract(resultStream, EnumSet.of(ResultField.PLANS), resultCharset, OutputFormat.ADM, plans).getResult();
     }
 
+    public static InputStream extractSignature(InputStream resultStream, Charset resultCharset) throws Exception {
+        return extract(resultStream, EnumSet.of(ResultField.SIGNATURE), resultCharset).getResult();
+    }
+
     public static InputStream extractStatus(InputStream resultStream, Charset resultCharset) throws Exception {
         return extract(resultStream, EnumSet.of(ResultField.STATUS), resultCharset).getResult();
     }
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 bd618d5..95b77cc 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
@@ -238,6 +238,7 @@ public class TestExecutor {
     private static final String METRICS_QUERY_TYPE = "metrics";
     private static final String PROFILE_QUERY_TYPE = "profile";
     private static final String PLANS_QUERY_TYPE = "plans";
+    private static final String SIGNATURE_QUERY_TYPE = "signature";
 
     private static final HashMap<Integer, ITestServer> runningTestServers = new HashMap<>();
     private static Map<String, InetSocketAddress> ncEndPoints;
@@ -1202,6 +1203,7 @@ public class TestExecutor {
             case "metrics":
             case "profile":
             case "plans":
+            case "signature":
                 // isDmlRecoveryTest: insert Crash and Recovery
                 if (isDmlRecoveryTest) {
                     executeScript(pb, pb.environment().get("SCRIPT_HOME") + File.separator + "dml_recovery"
@@ -1577,6 +1579,9 @@ public class TestExecutor {
                     String[] plans = plans(statement);
                     resultStream = ResultExtractor.extractPlans(resultStream, responseCharset, plans);
                     break;
+                case SIGNATURE_QUERY_TYPE:
+                    resultStream = ResultExtractor.extractSignature(resultStream, responseCharset);
+                    break;
                 default:
                     extractedResult = ResultExtractor.extract(resultStream, responseCharset, fmt);
                     resultStream = extractedResult.getResult();
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestHelper.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestHelper.java
index 24ca072..fcab213 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestHelper.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestHelper.java
@@ -41,6 +41,7 @@ import org.apache.asterix.app.translator.RequestParameters;
 import org.apache.asterix.om.base.IAObject;
 import org.apache.asterix.testframework.xml.ParameterTypeEnum;
 import org.apache.asterix.testframework.xml.TestCase;
+import org.apache.asterix.translator.SessionConfig;
 import org.apache.commons.compress.utils.IOUtils;
 import org.apache.commons.io.FileUtils;
 import org.apache.hyracks.util.file.FileUtil;
@@ -164,7 +165,8 @@ public final class TestHelper {
             }
         }
 
-        return RequestParameters.deserializeParameterValues(RequestParameters.serializeParameterValues(stmtParams));
+        return RequestParameters.deserializeParameterValues(
+                RequestParameters.serializeParameterValues(stmtParams, SessionConfig.OutputFormat.CLEAN_JSON));
     }
 
     public static boolean equalJson(JsonNode expectedJson, JsonNode actualJson, boolean compareUnorderedArray,
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.2.plans.sqlpp
similarity index 53%
copy from asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.2.plans.sqlpp
index fe9d2be..2a4952f 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.2.plans.sqlpp
@@ -16,30 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.app.result.fields;
 
-import java.io.PrintWriter;
-
-import org.apache.asterix.api.http.server.ResultUtil;
-import org.apache.asterix.common.api.IResponseFieldPrinter;
-
-public class SignaturePrinter implements IResponseFieldPrinter {
-
-    public static final SignaturePrinter INSTANCE = new SignaturePrinter();
-    private static final String FIELD_NAME = "signature";
+/*
+ * Test additional information returned when client-type=jdbc
+ */
 
-    @Override
-    public void print(PrintWriter pw) {
-        pw.print("\t\"");
-        pw.print(FIELD_NAME);
-        pw.print("\": {\n");
-        pw.print("\t");
-        ResultUtil.printField(pw, "*", "*", false);
-        pw.print("\n\t}");
-    }
+-- param client-type:string=jdbc
+-- param compile-only:string=true
 
-    @Override
-    public String getName() {
-        return FIELD_NAME;
-    }
-}
+select v from range(1,2) v where v between ? and ? ;
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.3.plans.sqlpp
similarity index 53%
copy from asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.3.plans.sqlpp
index fe9d2be..d7217a4 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.3.plans.sqlpp
@@ -16,30 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.app.result.fields;
 
-import java.io.PrintWriter;
-
-import org.apache.asterix.api.http.server.ResultUtil;
-import org.apache.asterix.common.api.IResponseFieldPrinter;
-
-public class SignaturePrinter implements IResponseFieldPrinter {
-
-    public static final SignaturePrinter INSTANCE = new SignaturePrinter();
-    private static final String FIELD_NAME = "signature";
+/*
+ * Test additional information returned when client-type=jdbc (with explain)
+ */
 
-    @Override
-    public void print(PrintWriter pw) {
-        pw.print("\t\"");
-        pw.print(FIELD_NAME);
-        pw.print("\": {\n");
-        pw.print("\t");
-        ResultUtil.printField(pw, "*", "*", false);
-        pw.print("\n\t}");
-    }
+-- param client-type:string=jdbc
+-- param compile-only:string=true
 
-    @Override
-    public String getName() {
-        return FIELD_NAME;
-    }
-}
+explain select v from range(1,2) v where v between ? and ? ;
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.4.plans.sqlpp
similarity index 53%
copy from asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.4.plans.sqlpp
index fe9d2be..8d2bd74 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/compileonly/compileonly.4.plans.sqlpp
@@ -16,30 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.app.result.fields;
 
-import java.io.PrintWriter;
+/*
+ * Test additional information returned when client-type=jdbc (update statement)
+ */
 
-import org.apache.asterix.api.http.server.ResultUtil;
-import org.apache.asterix.common.api.IResponseFieldPrinter;
+-- param client-type:string=jdbc
+-- param compile-only:string=true
 
-public class SignaturePrinter implements IResponseFieldPrinter {
+drop dataverse test1 if exists;
 
-    public static final SignaturePrinter INSTANCE = new SignaturePrinter();
-    private static final String FIELD_NAME = "signature";
+create dataverse test1;
 
-    @Override
-    public void print(PrintWriter pw) {
-        pw.print("\t\"");
-        pw.print(FIELD_NAME);
-        pw.print("\": {\n");
-        pw.print("\t");
-        ResultUtil.printField(pw, "*", "*", false);
-        pw.print("\n\t}");
-    }
+create dataset test1.t1(c1 int not unknown, c2 int) primary key c1;
 
-    @Override
-    public String getName() {
-        return FIELD_NAME;
-    }
-}
+insert into test1.t1 [{"c1": 1, "c2": ? }, {"c1": 3, "c2": ? }];
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/signature/signature.1.signature.sqlpp
similarity index 53%
copy from asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/signature/signature.1.signature.sqlpp
index fe9d2be..3620bde 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/signature/signature.1.signature.sqlpp
@@ -16,30 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.app.result.fields;
 
-import java.io.PrintWriter;
+-- param compile-only:string=true
 
-import org.apache.asterix.api.http.server.ResultUtil;
-import org.apache.asterix.common.api.IResponseFieldPrinter;
-
-public class SignaturePrinter implements IResponseFieldPrinter {
-
-    public static final SignaturePrinter INSTANCE = new SignaturePrinter();
-    private static final String FIELD_NAME = "signature";
-
-    @Override
-    public void print(PrintWriter pw) {
-        pw.print("\t\"");
-        pw.print(FIELD_NAME);
-        pw.print("\": {\n");
-        pw.print("\t");
-        ResultUtil.printField(pw, "*", "*", false);
-        pw.print("\n\t}");
-    }
-
-    @Override
-    public String getName() {
-        return FIELD_NAME;
-    }
-}
+select v v1, v v2 from range(1,2) v where v > ?;
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/signature/signature.2.signature.sqlpp
similarity index 53%
copy from asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/signature/signature.2.signature.sqlpp
index fe9d2be..37eee90 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/signature/signature.2.signature.sqlpp
@@ -16,30 +16,29 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.app.result.fields;
 
-import java.io.PrintWriter;
-
-import org.apache.asterix.api.http.server.ResultUtil;
-import org.apache.asterix.common.api.IResponseFieldPrinter;
-
-public class SignaturePrinter implements IResponseFieldPrinter {
-
-    public static final SignaturePrinter INSTANCE = new SignaturePrinter();
-    private static final String FIELD_NAME = "signature";
+/*
+ * Test returned signature when client-type=jdbc (primitive types)
+ */
 
-    @Override
-    public void print(PrintWriter pw) {
-        pw.print("\t\"");
-        pw.print(FIELD_NAME);
-        pw.print("\": {\n");
-        pw.print("\t");
-        ResultUtil.printField(pw, "*", "*", false);
-        pw.print("\n\t}");
-    }
+-- param client-type:string=jdbc
+-- param compile-only:string=true
 
-    @Override
-    public String getName() {
-        return FIELD_NAME;
-    }
-}
+select
+  int8(v) c_int8,
+  int16(v) c_int16,
+  int32(v) c_int32,
+  int64(v) c_int64,
+  float(v) c_float,
+  double(v) c_double,
+  string(v) c_string,
+  boolean(v) c_boolean,
+  datetime_from_unix_time_in_ms(v) c_datetime,
+  date_from_unix_time_in_days(v) c_date,
+  time_from_unix_time_in_ms(v) c_time,
+  duration_from_months(v) c_duration,
+  year_month_duration(duration_from_months(v)) c_year_month_duration,
+  day_time_duration(duration_from_ms(v)) c_day_time_month_duration,
+  null c_null
+from range(1,2) v
+where v > ?;
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/signature/signature.3.signature.sqlpp
similarity index 53%
copy from asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/signature/signature.3.signature.sqlpp
index fe9d2be..07cc798 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/signature/signature.3.signature.sqlpp
@@ -16,30 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.app.result.fields;
 
-import java.io.PrintWriter;
-
-import org.apache.asterix.api.http.server.ResultUtil;
-import org.apache.asterix.common.api.IResponseFieldPrinter;
-
-public class SignaturePrinter implements IResponseFieldPrinter {
-
-    public static final SignaturePrinter INSTANCE = new SignaturePrinter();
-    private static final String FIELD_NAME = "signature";
+/*
+ * Test returned signature when client-type=jdbc (complex types)
+ */
 
-    @Override
-    public void print(PrintWriter pw) {
-        pw.print("\t\"");
-        pw.print(FIELD_NAME);
-        pw.print("\": {\n");
-        pw.print("\t");
-        ResultUtil.printField(pw, "*", "*", false);
-        pw.print("\n\t}");
-    }
+-- param client-type:string=jdbc
+-- param compile-only:string=true
 
-    @Override
-    public String getName() {
-        return FIELD_NAME;
-    }
-}
+select
+  v c_number,
+  { "f": v } c_record,
+  [ v, v ] c_array,
+  {{ v, v }} c_multiset
+from range(1,2) v
+where v > ?;
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/signature/signature.4.signature.sqlpp
similarity index 53%
copy from asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/signature/signature.4.signature.sqlpp
index fe9d2be..3e3d170 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/signature/signature.4.signature.sqlpp
@@ -16,30 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.app.result.fields;
 
-import java.io.PrintWriter;
-
-import org.apache.asterix.api.http.server.ResultUtil;
-import org.apache.asterix.common.api.IResponseFieldPrinter;
-
-public class SignaturePrinter implements IResponseFieldPrinter {
-
-    public static final SignaturePrinter INSTANCE = new SignaturePrinter();
-    private static final String FIELD_NAME = "signature";
+/*
+ * Test returned signature when client-type=jdbc (unknown types)
+ */
 
-    @Override
-    public void print(PrintWriter pw) {
-        pw.print("\t\"");
-        pw.print(FIELD_NAME);
-        pw.print("\": {\n");
-        pw.print("\t");
-        ResultUtil.printField(pw, "*", "*", false);
-        pw.print("\n\t}");
-    }
+-- param client-type:string=jdbc
+-- param compile-only:string=true
 
-    @Override
-    public String getName() {
-        return FIELD_NAME;
-    }
-}
+select
+  v c_number,
+  case when v = 1 then { "f": v } else [ v, v ] end c_case,
+  missing c_missing
+from range(1,2) v
+where v > ?;
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/signature/signature.5.signature.sqlpp
similarity index 53%
copy from asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/signature/signature.5.signature.sqlpp
index fe9d2be..c776f31 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/SignaturePrinter.java
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/api/signature/signature.5.signature.sqlpp
@@ -16,30 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.app.result.fields;
 
-import java.io.PrintWriter;
-
-import org.apache.asterix.api.http.server.ResultUtil;
-import org.apache.asterix.common.api.IResponseFieldPrinter;
-
-public class SignaturePrinter implements IResponseFieldPrinter {
-
-    public static final SignaturePrinter INSTANCE = new SignaturePrinter();
-    private static final String FIELD_NAME = "signature";
+/*
+ * Test returned signature when client-type=jdbc (types from a dataset)
+ */
 
-    @Override
-    public void print(PrintWriter pw) {
-        pw.print("\t\"");
-        pw.print(FIELD_NAME);
-        pw.print("\": {\n");
-        pw.print("\t");
-        ResultUtil.printField(pw, "*", "*", false);
-        pw.print("\n\t}");
-    }
+-- param client-type:string=jdbc
+-- param compile-only:string=true
 
-    @Override
-    public String getName() {
-        return FIELD_NAME;
-    }
-}
+select DataverseName, DatasetName, ViewDetails
+from Metadata.`Dataset`;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.2.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.2.adm
new file mode 100644
index 0000000..1ced420
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.2.adm
@@ -0,0 +1 @@
+{"statementCategory":"query","statementParameters":[1,2]}
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.3.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.3.adm
new file mode 100644
index 0000000..83b47f4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.3.adm
@@ -0,0 +1 @@
+{"statementCategory":"query","statementParameters":[1,2],"explainOnly":true}
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.4.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.4.adm
new file mode 100644
index 0000000..63c482a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.4.adm
@@ -0,0 +1 @@
+{"statementCategory":"update","statementParameters":[1,2]}
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/signature/signature.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/signature/signature.1.adm
new file mode 100644
index 0000000..a10e23e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/signature/signature.1.adm
@@ -0,0 +1 @@
+{"*":"*"}
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/signature/signature.2.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/signature/signature.2.adm
new file mode 100644
index 0000000..fae3584
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/signature/signature.2.adm
@@ -0,0 +1 @@
+{"name":["c_int8","c_int16","c_int32","c_int64","c_float","c_double","c_string","c_boolean","c_datetime","c_date","c_time","c_duration","c_year_month_duration","c_day_time_month_duration","c_null"],"type":["int8","int16","int32","int64","float","double","string","boolean","datetime","date","time","duration","year-month-duration","day-time-duration","null"]}
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/signature/signature.3.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/signature/signature.3.adm
new file mode 100644
index 0000000..86234be
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/signature/signature.3.adm
@@ -0,0 +1 @@
+{"name":["c_number","c_record","c_array","c_multiset"],"type":["int64","object","array","multiset"]}
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/signature/signature.4.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/signature/signature.4.adm
new file mode 100644
index 0000000..5c5ffd0
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/signature/signature.4.adm
@@ -0,0 +1 @@
+{"name":["c_number","c_case","c_missing"],"type":["int64","any","any"]}
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/signature/signature.5.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/signature/signature.5.adm
new file mode 100644
index 0000000..48710ba
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/signature/signature.5.adm
@@ -0,0 +1 @@
+{"name":["DataverseName","DatasetName","ViewDetails"],"type":["string","string","any"]}
\ No newline at end of file
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 65a676c..6450a1c 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -145,6 +145,11 @@
       </compilation-unit>
     </test-case>
     <test-case FilePath="api">
+      <compilation-unit name="signature">
+        <output-dir compare="Text">signature</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="api">
       <compilation-unit name="ignore-body-for-get">
         <output-dir compare="Text">ignore-body-for-get</output-dir>
       </compilation-unit>