Several CSV, API, HTTP API, and Web interface improvements.
- APIFramework: internal refactoring to consolidate output PrintWriter,
OutputFormat, and all output flags into SessionConfig
- APIFramework: "HTML" is now a flag, rather than an OutputFormat
- HTTP API: Output format can be select via query parameter in
addition to HTTP Accept header
- CSV: default output is now without header, to improve roundtripping
- CSV: header can be requested via Accept header or "header" query
parameter
- Web interface: Added ability to select output format (JSON, CSV or ADM)
Change-Id: I91398bd30dbd6f3b1f69eb51fbf201010d0e5d93
Reviewed-on: http://fulliautomatix.ics.uci.edu:8443/242
Reviewed-by: Chris Hillery <ceej@lambda.nu>
Tested-by: Chris Hillery <ceej@lambda.nu>
diff --git a/asterix-app/src/main/java/edu/uci/ics/asterix/api/common/APIFramework.java b/asterix-app/src/main/java/edu/uci/ics/asterix/api/common/APIFramework.java
index 1ee9a3e..84a01da 100644
--- a/asterix-app/src/main/java/edu/uci/ics/asterix/api/common/APIFramework.java
+++ b/asterix-app/src/main/java/edu/uci/ics/asterix/api/common/APIFramework.java
@@ -153,43 +153,26 @@
}
- /**
- * Used to select the output from the various servlets. Note: "HTML" is
- * primarily intended for use by the built-in web interface. It produces
- * ADM output with various HTML wrappers.
- */
- public enum OutputFormat {
- ADM,
- HTML,
- JSON,
- CSV
- }
-
public static Pair<Query, Integer> reWriteQuery(List<FunctionDecl> declaredFunctions,
- AqlMetadataProvider metadataProvider, Query q, SessionConfig pc, PrintWriter out, OutputFormat pdf)
+ AqlMetadataProvider metadataProvider, Query q, SessionConfig conf)
throws AsterixException {
- if (!pc.isPrintPhysicalOpsOnly() && pc.isPrintExprParam()) {
- out.println();
- switch (pdf) {
- case HTML: {
- out.println("<h4>Expression tree:</h4>");
- out.println("<pre>");
- break;
- }
- default: {
- out.println("----------Expression tree:");
- break;
- }
+ if (conf.is(SessionConfig.FORMAT_ONLY_PHYSICAL_OPS) && conf.is(SessionConfig.OOB_EXPR_TREE)) {
+ conf.out().println();
+
+ if (conf.is(SessionConfig.FORMAT_HTML)) {
+ conf.out().println("<h4>Expression tree:</h4>");
+ conf.out().println("<pre>");
+ } else {
+ conf.out().println("----------Expression tree:");
}
+
if (q != null) {
- q.accept(new AQLPrintVisitor(out), 0);
+ q.accept(new AQLPrintVisitor(conf.out()), 0);
}
- switch (pdf) {
- case HTML: {
- out.println("</pre>");
- break;
- }
+
+ if (conf.is(SessionConfig.FORMAT_HTML)) {
+ conf.out().println("</pre>");
}
}
AqlRewriter rw = new AqlRewriter(declaredFunctions, q, metadataProvider);
@@ -200,35 +183,26 @@
public static JobSpecification compileQuery(List<FunctionDecl> declaredFunctions,
AqlMetadataProvider queryMetadataProvider, Query rwQ, int varCounter, String outputDatasetName,
- SessionConfig pc, PrintWriter out, OutputFormat pdf, ICompiledDmlStatement statement)
+ SessionConfig conf, ICompiledDmlStatement statement)
throws AsterixException, AlgebricksException, JSONException, RemoteException, ACIDException {
- if (!pc.isPrintPhysicalOpsOnly() && pc.isPrintRewrittenExprParam()) {
- out.println();
+ if (conf.is(SessionConfig.FORMAT_ONLY_PHYSICAL_OPS) && conf.is(SessionConfig.OOB_REWRITTEN_EXPR_TREE)) {
+ conf.out().println();
- switch (pdf) {
- case HTML: {
- out.println("<h4>Rewritten expression tree:</h4>");
- out.println("<pre>");
- break;
- }
- default: {
- out.println("----------Rewritten expression:");
- break;
- }
+ if (conf.is(SessionConfig.FORMAT_HTML)) {
+ conf.out().println("<h4>Rewritten expression tree:</h4>");
+ conf.out().println("<pre>");
+ } else {
+ conf.out().println("----------Rewritten expression:");
}
if (rwQ != null) {
- rwQ.accept(new AQLPrintVisitor(out), 0);
+ rwQ.accept(new AQLPrintVisitor(conf.out()), 0);
}
- switch (pdf) {
- case HTML: {
- out.println("</pre>");
- break;
- }
+ if (conf.is(SessionConfig.FORMAT_HTML)) {
+ conf.out().println("</pre>");
}
-
}
edu.uci.ics.asterix.common.transactions.JobId asterixJobId = JobIdFactory.generateJobId();
@@ -246,31 +220,24 @@
boolean isWriteTransaction = queryMetadataProvider.isWriteTransaction();
LogicalOperatorPrettyPrintVisitor pvisitor = new LogicalOperatorPrettyPrintVisitor();
- if (!pc.isPrintPhysicalOpsOnly() && pc.isPrintLogicalPlanParam()) {
+ if (conf.is(SessionConfig.FORMAT_ONLY_PHYSICAL_OPS) && conf.is(SessionConfig.OOB_LOGICAL_PLAN)) {
+ conf.out().println();
- switch (pdf) {
- case HTML: {
- out.println("<h4>Logical plan:</h4>");
- out.println("<pre>");
- break;
- }
- default: {
- out.println("----------Logical plan:");
- break;
- }
+ if (conf.is(SessionConfig.FORMAT_HTML)) {
+ conf.out().println("<h4>Logical plan:</h4>");
+ conf.out().println("<pre>");
+ } else {
+ conf.out().println("----------Logical plan:");
}
if (rwQ != null || statement.getKind() == Kind.LOAD) {
StringBuilder buffer = new StringBuilder();
PlanPrettyPrinter.printPlan(plan, buffer, pvisitor, 0);
- out.print(buffer);
+ conf.out().print(buffer);
}
- switch (pdf) {
- case HTML: {
- out.println("</pre>");
- break;
- }
+ if (conf.is(SessionConfig.FORMAT_HTML)) {
+ conf.out().println("</pre>");
}
}
@@ -305,45 +272,39 @@
builder.setNullableTypeComputer(AqlNullableTypeComputer.INSTANCE);
ICompiler compiler = compilerFactory.createCompiler(plan, queryMetadataProvider, t.getVarCounter());
- if (pc.isOptimize()) {
+ if (conf.isOptimize()) {
compiler.optimize();
//plot optimized logical plan
if (plot)
PlanPlotter.printOptimizedLogicalPlan(plan);
- if (pc.isPrintOptimizedLogicalPlanParam()) {
- if (pc.isPrintPhysicalOpsOnly()) {
+ if (conf.is(SessionConfig.OOB_OPTIMIZED_LOGICAL_PLAN)) {
+ if (conf.is(SessionConfig.FORMAT_ONLY_PHYSICAL_OPS)) {
// For Optimizer tests.
StringBuilder buffer = new StringBuilder();
PlanPrettyPrinter.printPhysicalOps(plan, buffer, 0);
- out.print(buffer);
+ conf.out().print(buffer);
} else {
- switch (pdf) {
- case HTML: {
- out.println("<h4>Optimized logical plan:</h4>");
- out.println("<pre>");
- break;
- }
- default: {
- out.println("----------Optimized logical plan:");
- break;
- }
+ if (conf.is(SessionConfig.FORMAT_HTML)) {
+ conf.out().println("<h4>Optimized logical plan:</h4>");
+ conf.out().println("<pre>");
+ } else {
+ conf.out().println("----------Optimized logical plan:");
}
+
if (rwQ != null || statement.getKind() == Kind.LOAD) {
StringBuilder buffer = new StringBuilder();
PlanPrettyPrinter.printPlan(plan, buffer, pvisitor, 0);
- out.print(buffer);
+ conf.out().print(buffer);
}
- switch (pdf) {
- case HTML: {
- out.println("</pre>");
- break;
- }
+
+ if (conf.is(SessionConfig.FORMAT_HTML)) {
+ conf.out().println("</pre>");
}
}
}
}
- if (!pc.isGenerateJobSpec()) {
+ if (!conf.isGenerateJobSpec()) {
return null;
}
@@ -359,16 +320,18 @@
builder.setNullWriterFactory(format.getNullWriterFactory());
builder.setPredicateEvaluatorFactoryProvider(format.getPredicateEvaluatorFactoryProvider());
- switch (pdf) {
+ switch (conf.fmt()) {
case JSON:
builder.setPrinterProvider(format.getJSONPrinterFactoryProvider());
break;
case CSV:
builder.setPrinterProvider(format.getCSVPrinterFactoryProvider());
break;
- default:
+ case ADM:
builder.setPrinterProvider(format.getPrinterFactoryProvider());
break;
+ default:
+ throw new RuntimeException("Unexpected OutputFormat!");
}
builder.setSerializerDeserializerProvider(format.getSerdeProvider());
@@ -379,34 +342,28 @@
isWriteTransaction);
JobSpecification spec = compiler.createJob(AsterixAppContextInfo.getInstance(), jobEventListenerFactory);
- if (pc.isPrintJob()) {
- switch (pdf) {
- case HTML: {
- out.println("<h4>Hyracks job:</h4>");
- out.println("<pre>");
- break;
- }
- default: {
- out.println("----------Hyracks job:");
- break;
- }
+ if (conf.is(SessionConfig.OOB_HYRACKS_JOB)) {
+ if (conf.is(SessionConfig.FORMAT_HTML)) {
+ conf.out().println("<h4>Hyracks job:</h4>");
+ conf.out().println("<pre>");
+ } else {
+ conf.out().println("----------Hyracks job:");
}
+
if (rwQ != null) {
- out.println(spec.toJSON().toString(1));
- out.println(spec.getUserConstraints());
+ conf.out().println(spec.toJSON().toString(1));
+ conf.out().println(spec.getUserConstraints());
}
- switch (pdf) {
- case HTML: {
- out.println("</pre>");
- break;
- }
+
+ if (conf.is(SessionConfig.FORMAT_HTML)) {
+ conf.out().println("</pre>");
}
}
return spec;
}
- public static void executeJobArray(IHyracksClientConnection hcc, JobSpecification[] specs, PrintWriter out,
- OutputFormat pdf) throws Exception {
+ public static void executeJobArray(IHyracksClientConnection hcc, JobSpecification[] specs, PrintWriter out)
+ throws Exception {
for (int i = 0; i < specs.length; i++) {
specs[i].setMaxReattempts(0);
JobId jobId = hcc.startJob(specs[i]);
@@ -419,7 +376,7 @@
}
- public static void executeJobArray(IHyracksClientConnection hcc, Job[] jobs, PrintWriter out, OutputFormat pdf)
+ public static void executeJobArray(IHyracksClientConnection hcc, Job[] jobs, PrintWriter out)
throws Exception {
for (int i = 0; i < jobs.length; i++) {
jobs[i].getJobSpec().setMaxReattempts(0);
diff --git a/asterix-app/src/main/java/edu/uci/ics/asterix/api/common/SessionConfig.java b/asterix-app/src/main/java/edu/uci/ics/asterix/api/common/SessionConfig.java
index a2f27b4..a82558f 100644
--- a/asterix-app/src/main/java/edu/uci/ics/asterix/api/common/SessionConfig.java
+++ b/asterix-app/src/main/java/edu/uci/ics/asterix/api/common/SessionConfig.java
@@ -14,80 +14,188 @@
*/
package edu.uci.ics.asterix.api.common;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * SessionConfig captures several different parameters for controlling
+ * the execution of an APIFramework call.
+ * <li> It specifies how the execution will proceed (for instance,
+ * whether to optimize, or whether to execute at all).
+ * <li> It allows you specify where the primary execution output will
+ * be sent.
+ * <li> It also allows you to request additional output for optional
+ * out-of-band data about the execution (query plan, etc).
+ * <li> It allows you to specify the output format for the primary
+ * execution output - JSON, CSV, etc.
+ * <li> It allows you to specify output format-specific parameters.
+ */
+
public class SessionConfig {
- private final boolean optimize;
- private final boolean printExprParam;
- private final boolean printRewrittenExprParam;
- private final boolean printLogicalPlanParam;
- private final boolean printOptimizedLogicalPlanParam;
- private final boolean printPhysicalOpsOnly;
- private final boolean executeQuery;
- private final boolean generateJobSpec;
- private final boolean printJob;
+ /**
+ * Used to specify the output format for the primary execution.
+ */
+ public enum OutputFormat {
+ ADM,
+ JSON,
+ CSV
+ };
/**
- * Note: the various "print" options below will cause additional output
- * to be generated when invoking servlet functions. This output is NOT
- * guaranteed to match the OutputFormat (JSON, ADM...) except when the
- * OutputFormat is "HTML". This is primarily for use by the built-in
- * web interface (APIServlet).
- * @param optimize
- * @param printExprParam
- * @param printRewrittenExprParam
- * @param printLogicalPlanParam
- * @param printOptimizedLogicalPlanParam
- * @param printPhysicalOpsOnly
- * @param executeQuery
- * @param generateJobSpec
- * @param printJob
+ * Produce out-of-band output for Hyracks Job.
*/
- public SessionConfig(boolean optimize, boolean printExprParam, boolean printRewrittenExprParam,
- boolean printLogicalPlanParam, boolean printOptimizedLogicalPlanParam, boolean printPhysicalOpsOnly,
- boolean executeQuery, boolean generateJobSpec, boolean printJob) {
+ public static final String OOB_HYRACKS_JOB = "oob-hyracks-job";
+
+ /**
+ * Produce out-of-band output for Expression Tree.
+ */
+ public static final String OOB_EXPR_TREE = "oob-expr-tree";
+
+ /**
+ * Produce out-of-band output for Rewritten Expression Tree.
+ */
+ public static final String OOB_REWRITTEN_EXPR_TREE = "oob-rewritten-expr-tree";
+
+ /**
+ * Produce out-of-band output for Logical Plan.
+ */
+ public static final String OOB_LOGICAL_PLAN = "oob-logical-plan";
+
+ /**
+ * Produce out-of-band output for Optimized Logical Plan.
+ */
+ public static final String OOB_OPTIMIZED_LOGICAL_PLAN = "oob-optimized-logical-plan";
+
+ /**
+ * Format flag: print only physical ops (for optimizer tests).
+ */
+ public static final String FORMAT_ONLY_PHYSICAL_OPS = "format-only-physical-ops";
+
+ /**
+ * Format flag: wrap out-of-band data in HTML.
+ */
+ public static final String FORMAT_HTML = "format-html";
+
+ /**
+ * Format flag: print CSV header line.
+ */
+ public static final String FORMAT_CSV_HEADER = "format-csv-header";
+
+ // Standard execution flags.
+ private final boolean executeQuery;
+ private final boolean generateJobSpec;
+ private final boolean optimize;
+
+ // Output path for primary execution.
+ private final PrintWriter out;
+
+ // Output format.
+ private final OutputFormat fmt;
+
+ // Flags.
+ private final Map<String,Boolean> flags;
+
+ /**
+ * Create a SessionConfig object with all default values:
+ *
+ * - All format flags set to "false".
+ * - All out-of-band outputs set to "null".
+ * - "Optimize" set to "true".
+ * - "Execute Query" set to "true".
+ * - "Generate Job Spec" set to "true".
+ * @param out PrintWriter for execution output.
+ * @param fmt Output format for execution output.
+ */
+ public SessionConfig(PrintWriter out, OutputFormat fmt) {
+ this(out, fmt, true, true, true);
+ }
+
+ /**
+ * Create a SessionConfig object with all optional values set to defaults:
+ *
+ * - All format flags set to "false".
+ * - All out-of-band outputs set to "false".
+ * @param out PrintWriter for execution output.
+ * @param fmt Output format for execution output.
+ * @param optimize Whether to optimize the execution.
+ * @param executeQuery Whether to execute the query or not.
+ * @param generateJobSpec Whether to generate the Hyracks job specification (if
+ * false, job cannot be executed).
+ */
+ public SessionConfig(PrintWriter out, OutputFormat fmt, boolean optimize, boolean executeQuery, boolean generateJobSpec) {
+ this.out = out;
+ this.fmt = fmt;
this.optimize = optimize;
- this.printExprParam = printExprParam;
- this.printRewrittenExprParam = printRewrittenExprParam;
- this.printLogicalPlanParam = printLogicalPlanParam;
- this.printOptimizedLogicalPlanParam = printOptimizedLogicalPlanParam;
- this.printPhysicalOpsOnly = printPhysicalOpsOnly;
this.executeQuery = executeQuery;
this.generateJobSpec = generateJobSpec;
- this.printJob = printJob;
+ this.flags = new HashMap<String,Boolean>();
}
- public boolean isPrintExprParam() {
- return printExprParam;
+ /**
+ * Retrieve the PrintWriter to produce output to.
+ */
+ public PrintWriter out() {
+ return this.out;
}
- public boolean isPrintRewrittenExprParam() {
- return printRewrittenExprParam;
+ /**
+ * Retrieve the OutputFormat for this execution.
+ */
+ public OutputFormat fmt() {
+ return this.fmt;
}
- public boolean isPrintLogicalPlanParam() {
- return printLogicalPlanParam;
- }
-
- public boolean isPrintOptimizedLogicalPlanParam() {
- return printOptimizedLogicalPlanParam;
- }
-
- public boolean isPrintJob() {
- return printJob;
- }
-
- public boolean isPrintPhysicalOpsOnly() {
- return printPhysicalOpsOnly;
- }
-
+ /**
+ * Retrieve the value of the "execute query" flag.
+ */
public boolean isExecuteQuery() {
return executeQuery;
}
+ /**
+ * Retrieve the value of the "optimize" flag.
+ */
public boolean isOptimize() {
return optimize;
}
+ /**
+ * Retrieve the value of the "generate job spec" flag.
+ */
public boolean isGenerateJobSpec() {
return generateJobSpec;
}
-}
\ No newline at end of file
+
+ /**
+ * Specify all out-of-band settings at once. For convenience of older code.
+ */
+ public void setOOBData(boolean expr_tree, boolean rewritten_expr_tree,
+ boolean logical_plan, boolean optimized_logical_plan,
+ boolean hyracks_job) {
+ this.set(OOB_EXPR_TREE, expr_tree);
+ this.set(OOB_REWRITTEN_EXPR_TREE, rewritten_expr_tree);
+ this.set(OOB_LOGICAL_PLAN, logical_plan);
+ this.set(OOB_OPTIMIZED_LOGICAL_PLAN, optimized_logical_plan);
+ this.set(OOB_HYRACKS_JOB, hyracks_job);
+ }
+
+ /**
+ * Specify a flag.
+ * @param flag One of the OOB_ or FORMAT_ constants from this class.
+ * @param value Value for the flag (all flags default to "false").
+ */
+ public void set(String flag, boolean value) {
+ flags.put(flag, Boolean.valueOf(value));
+ }
+
+ /**
+ * Retrieve the setting of a format-specific flag.
+ * @param flag One of the FORMAT_ constants from this class.
+ * @returns true or false (all flags default to "false").
+ */
+ public boolean is(String flag) {
+ Boolean value = flags.get(flag);
+ return value == null ? false : value.booleanValue();
+ }
+}
diff --git a/asterix-app/src/main/java/edu/uci/ics/asterix/api/http/servlet/APIServlet.java b/asterix-app/src/main/java/edu/uci/ics/asterix/api/http/servlet/APIServlet.java
index be88a1e..4984741 100644
--- a/asterix-app/src/main/java/edu/uci/ics/asterix/api/http/servlet/APIServlet.java
+++ b/asterix-app/src/main/java/edu/uci/ics/asterix/api/http/servlet/APIServlet.java
@@ -30,8 +30,8 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import edu.uci.ics.asterix.api.common.APIFramework.OutputFormat;
import edu.uci.ics.asterix.api.common.SessionConfig;
+import edu.uci.ics.asterix.api.common.SessionConfig.OutputFormat;
import edu.uci.ics.asterix.aql.base.Statement;
import edu.uci.ics.asterix.aql.parser.AQLParser;
import edu.uci.ics.asterix.aql.parser.ParseException;
@@ -54,12 +54,23 @@
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
- OutputFormat format = OutputFormat.HTML;
- if (request.getContentType().equals("application/json")) {
- format = OutputFormat.JSON;
- } else if (request.getContentType().equals("text/plain")) {
+ OutputFormat format;
+ boolean csv_and_header = false;
+ String output = request.getParameter("output-format");
+ if (output.equals("ADM")) {
format = OutputFormat.ADM;
}
+ else if (output.equals("CSV")) {
+ format = OutputFormat.CSV;
+ }
+ else if (output.equals("CSV-Header")) {
+ format = OutputFormat.CSV;
+ csv_and_header = true;
+ }
+ else {
+ // Default output format
+ format = OutputFormat.JSON;
+ }
String query = request.getParameter("query");
String printExprParam = request.getParameter("print-expr-tree");
@@ -87,11 +98,14 @@
}
AQLParser parser = new AQLParser(query);
List<Statement> aqlStatements = parser.parse();
- SessionConfig sessionConfig = new SessionConfig(true, isSet(printExprParam),
- isSet(printRewrittenExprParam), isSet(printLogicalPlanParam),
- isSet(printOptimizedLogicalPlanParam), false, isSet(executeQuery), true, isSet(printJob));
+ SessionConfig sessionConfig = new SessionConfig(out, format, true, isSet(executeQuery), true);
+ sessionConfig.set(SessionConfig.FORMAT_HTML, true);
+ sessionConfig.set(SessionConfig.FORMAT_CSV_HEADER, csv_and_header);
+ sessionConfig.setOOBData(isSet(printExprParam), isSet(printRewrittenExprParam),
+ isSet(printLogicalPlanParam), isSet(printOptimizedLogicalPlanParam),
+ isSet(printJob));
MetadataManager.INSTANCE.init();
- AqlTranslator aqlTranslator = new AqlTranslator(aqlStatements, out, sessionConfig, format);
+ AqlTranslator aqlTranslator = new AqlTranslator(aqlStatements, sessionConfig);
double duration = 0;
long startTime = System.currentTimeMillis();
aqlTranslator.compileAndExecute(hcc, hds, AqlTranslator.ResultDelivery.SYNC);
diff --git a/asterix-app/src/main/java/edu/uci/ics/asterix/api/http/servlet/QueryResultAPIServlet.java b/asterix-app/src/main/java/edu/uci/ics/asterix/api/http/servlet/QueryResultAPIServlet.java
index 5ccbfc8..3f104fe 100644
--- a/asterix-app/src/main/java/edu/uci/ics/asterix/api/http/servlet/QueryResultAPIServlet.java
+++ b/asterix-app/src/main/java/edu/uci/ics/asterix/api/http/servlet/QueryResultAPIServlet.java
@@ -26,6 +26,8 @@
import org.json.JSONObject;
import edu.uci.ics.asterix.api.common.APIFramework;
+import edu.uci.ics.asterix.api.common.SessionConfig;
+import edu.uci.ics.asterix.api.common.SessionConfig.OutputFormat;
import edu.uci.ics.asterix.result.ResultReader;
import edu.uci.ics.asterix.result.ResultUtils;
import edu.uci.ics.hyracks.api.client.HyracksConnection;
@@ -78,30 +80,14 @@
ResultReader resultReader = new ResultReader(hcc, hds);
resultReader.open(jobId, rsId);
- APIFramework.OutputFormat format;
- // QQQ This code is duplicated from RESTAPIServlet, and is
- // erroneous anyway. The output format is determined by
- // the initial query and cannot be modified here, so we need
- // to find a way to send the same OutputFormat value here as
- // was originally determined there. Need to save this value on
+ // QQQ The output format is determined by the initial
+ // query and cannot be modified here, so calling back to
+ // initResponse() is really an error. We need to find a
+ // way to send the same OutputFormat value here as was
+ // originally determined there. Need to save this value on
// some object that we can obtain here.
- String accept = request.getHeader("Accept");
- if ((accept == null) || (accept.contains("application/x-adm"))) {
- format = APIFramework.OutputFormat.ADM;
- response.setContentType("application/x-adm");
- } else if (accept.contains("text/html")) {
- format = APIFramework.OutputFormat.HTML;
- response.setContentType("text/html");
- } else if (accept.contains("text/csv")) {
- format = APIFramework.OutputFormat.CSV;
- response.setContentType("text/csv; header=present");
- } else {
- // JSON output is the default; most generally useful for a
- // programmatic HTTP API
- format = APIFramework.OutputFormat.JSON;
- response.setContentType("application/json");
- }
- ResultUtils.displayResults(resultReader, out, format);
+ SessionConfig sessionConfig = RESTAPIServlet.initResponse(request, response);
+ ResultUtils.displayResults(resultReader, sessionConfig);
} catch (Exception e) {
out.println(e.getMessage());
diff --git a/asterix-app/src/main/java/edu/uci/ics/asterix/api/http/servlet/RESTAPIServlet.java b/asterix-app/src/main/java/edu/uci/ics/asterix/api/http/servlet/RESTAPIServlet.java
index e783741..4e8427d 100644
--- a/asterix-app/src/main/java/edu/uci/ics/asterix/api/http/servlet/RESTAPIServlet.java
+++ b/asterix-app/src/main/java/edu/uci/ics/asterix/api/http/servlet/RESTAPIServlet.java
@@ -31,8 +31,8 @@
import org.json.JSONObject;
import edu.uci.ics.asterix.api.common.APIFramework;
-import edu.uci.ics.asterix.api.common.APIFramework.OutputFormat;
import edu.uci.ics.asterix.api.common.SessionConfig;
+import edu.uci.ics.asterix.api.common.SessionConfig.OutputFormat;
import edu.uci.ics.asterix.aql.base.Statement;
import edu.uci.ics.asterix.aql.base.Statement.Kind;
import edu.uci.ics.asterix.aql.parser.AQLParser;
@@ -55,6 +55,67 @@
private static final String HYRACKS_DATASET_ATTR = "edu.uci.ics.asterix.HYRACKS_DATASET";
+ /**
+ * Initialize the Content-Type of the response, and construct a
+ * SessionConfig with the appropriate output writer and output-format
+ * based on the Accept: header and other servlet parameters.
+ */
+ static SessionConfig initResponse(HttpServletRequest request, HttpServletResponse response)
+ throws IOException {
+ response.setCharacterEncoding("utf-8");
+
+ // JSON output is the default; most generally useful for a
+ // programmatic HTTP API
+ OutputFormat format = OutputFormat.JSON;
+
+ // First check the "output" servlet parameter.
+ String output = request.getParameter("output");
+ String accept = request.getHeader("Accept");
+ if (output != null) {
+ if (output.equals("CSV")) {
+ format = OutputFormat.CSV;
+ }
+ else if (output.equals("ADM")) {
+ format = OutputFormat.ADM;
+ }
+ }
+ else {
+ // Second check the Accept: HTTP header.
+ if (accept != null) {
+ if (accept.contains("application/x-adm")) {
+ format = OutputFormat.ADM;
+ } else if (accept.contains("text/csv")) {
+ format = OutputFormat.CSV;
+ }
+ }
+ }
+
+ SessionConfig sessionConfig = new SessionConfig(response.getWriter(), format);
+
+ // Now that format is set, output the content-type
+ switch (format) {
+ case ADM:
+ response.setContentType("application/x-adm");
+ break;
+ case JSON:
+ response.setContentType("application/json");
+ break;
+ case CSV: {
+ // Check for header parameter or in Accept:.
+ if ("present".equals(request.getParameter("header")) ||
+ (accept != null && accept.contains("header=present"))) {
+ response.setContentType("text/csv; header=present");
+ sessionConfig.set(SessionConfig.FORMAT_CSV_HEADER, true);
+ }
+ else {
+ response.setContentType("text/csv; header=absent");
+ }
+ }
+ };
+
+ return sessionConfig;
+ }
+
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
@@ -72,28 +133,7 @@
public void handleRequest(HttpServletRequest request, HttpServletResponse response, String query)
throws IOException {
- response.setCharacterEncoding("utf-8");
-
- PrintWriter out = response.getWriter();
- APIFramework.OutputFormat format;
- // QQQ For now switch solely based on Accept header. Later will add
- // an "output" query parameter.
- String accept = request.getHeader("Accept");
- if ((accept == null) || (accept.contains("application/x-adm"))) {
- format = OutputFormat.ADM;
- response.setContentType("application/x-adm");
- } else if (accept.contains("text/html")) {
- format = OutputFormat.HTML;
- response.setContentType("text/html");
- } else if (accept.contains("text/csv")) {
- format = OutputFormat.CSV;
- response.setContentType("text/csv; header=present");
- } else {
- // JSON output is the default; most generally useful for a
- // programmatic HTTP API
- format = APIFramework.OutputFormat.JSON;
- response.setContentType("application/json");
- }
+ SessionConfig sessionConfig = initResponse(request, response);
AqlTranslator.ResultDelivery resultDelivery = whichResultDelivery(request);
ServletContext context = getServletContext();
@@ -113,21 +153,19 @@
AQLParser parser = new AQLParser(query);
List<Statement> aqlStatements = parser.parse();
if (!containsForbiddenStatements(aqlStatements)) {
- SessionConfig sessionConfig = new SessionConfig(true, false, false, false, false, false, true, true,
- false);
MetadataManager.INSTANCE.init();
- AqlTranslator aqlTranslator = new AqlTranslator(aqlStatements, out, sessionConfig, format);
+ AqlTranslator aqlTranslator = new AqlTranslator(aqlStatements, sessionConfig);
aqlTranslator.compileAndExecute(hcc, hds, resultDelivery);
}
} catch (ParseException | TokenMgrError | edu.uci.ics.asterix.aqlplus.parser.TokenMgrError pe) {
GlobalConfig.ASTERIX_LOGGER.log(Level.SEVERE, pe.getMessage(), pe);
String errorMessage = ResultUtils.buildParseExceptionMessage(pe, query);
JSONObject errorResp = ResultUtils.getErrorResponse(2, errorMessage, "", "");
- out.write(errorResp.toString());
+ sessionConfig.out().write(errorResp.toString());
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (Exception e) {
GlobalConfig.ASTERIX_LOGGER.log(Level.SEVERE, e.getMessage(), e);
- ResultUtils.apiErrorHandler(out, e);
+ ResultUtils.apiErrorHandler(sessionConfig.out(), e);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
diff --git a/asterix-app/src/main/java/edu/uci/ics/asterix/api/java/AsterixJavaClient.java b/asterix-app/src/main/java/edu/uci/ics/asterix/api/java/AsterixJavaClient.java
index e0ba829..54804e7 100644
--- a/asterix-app/src/main/java/edu/uci/ics/asterix/api/java/AsterixJavaClient.java
+++ b/asterix-app/src/main/java/edu/uci/ics/asterix/api/java/AsterixJavaClient.java
@@ -19,9 +19,9 @@
import java.util.List;
import edu.uci.ics.asterix.api.common.APIFramework;
-import edu.uci.ics.asterix.api.common.APIFramework.OutputFormat;
import edu.uci.ics.asterix.api.common.Job;
import edu.uci.ics.asterix.api.common.SessionConfig;
+import edu.uci.ics.asterix.api.common.SessionConfig.OutputFormat;
import edu.uci.ics.asterix.aql.base.Statement;
import edu.uci.ics.asterix.aql.parser.AQLParser;
import edu.uci.ics.asterix.aql.parser.ParseException;
@@ -76,21 +76,25 @@
}
MetadataManager.INSTANCE.init();
- SessionConfig pc = new SessionConfig(optimize, false, printRewrittenExpressions, printLogicalPlan,
- printOptimizedPlan, printPhysicalOpsOnly, true, generateBinaryRuntime, printJob);
+ SessionConfig conf = new SessionConfig(writer, OutputFormat.ADM, optimize, true, generateBinaryRuntime);
+ conf.setOOBData(false, printRewrittenExpressions, printLogicalPlan,
+ printOptimizedPlan, printJob);
+ if (printPhysicalOpsOnly) {
+ conf.set(SessionConfig.FORMAT_ONLY_PHYSICAL_OPS, true);
+ }
- AqlTranslator aqlTranslator = new AqlTranslator(aqlStatements, writer, pc, OutputFormat.ADM);
+ AqlTranslator aqlTranslator = new AqlTranslator(aqlStatements, conf);
aqlTranslator.compileAndExecute(hcc, null, AqlTranslator.ResultDelivery.SYNC);
writer.flush();
}
public void execute() throws Exception {
if (dmlJobs != null) {
- APIFramework.executeJobArray(hcc, dmlJobs, writer, OutputFormat.ADM);
+ APIFramework.executeJobArray(hcc, dmlJobs, writer);
}
if (queryJobSpec != null) {
- APIFramework.executeJobArray(hcc, new JobSpecification[] { queryJobSpec }, writer, OutputFormat.ADM);
+ APIFramework.executeJobArray(hcc, new JobSpecification[] { queryJobSpec }, writer);
}
}
-}
\ No newline at end of file
+}
diff --git a/asterix-app/src/main/java/edu/uci/ics/asterix/aql/translator/AqlTranslator.java b/asterix-app/src/main/java/edu/uci/ics/asterix/aql/translator/AqlTranslator.java
index 52fc25e..0184a65 100644
--- a/asterix-app/src/main/java/edu/uci/ics/asterix/aql/translator/AqlTranslator.java
+++ b/asterix-app/src/main/java/edu/uci/ics/asterix/aql/translator/AqlTranslator.java
@@ -37,9 +37,9 @@
import org.json.JSONObject;
import edu.uci.ics.asterix.api.common.APIFramework;
-import edu.uci.ics.asterix.api.common.APIFramework.OutputFormat;
import edu.uci.ics.asterix.api.common.Job;
import edu.uci.ics.asterix.api.common.SessionConfig;
+import edu.uci.ics.asterix.api.common.SessionConfig.OutputFormat;
import edu.uci.ics.asterix.aql.base.Statement;
import edu.uci.ics.asterix.aql.expression.CompactStatement;
import edu.uci.ics.asterix.aql.expression.ConnectFeedStatement;
@@ -188,18 +188,14 @@
public static final boolean IS_DEBUG_MODE = false;//true
private final List<Statement> aqlStatements;
- private final PrintWriter out;
private final SessionConfig sessionConfig;
- private final OutputFormat pdf;
private Dataverse activeDefaultDataverse;
private final List<FunctionDecl> declaredFunctions;
- public AqlTranslator(List<Statement> aqlStatements, PrintWriter out, SessionConfig pc, APIFramework.OutputFormat pdf)
+ public AqlTranslator(List<Statement> aqlStatements, SessionConfig conf)
throws MetadataException, AsterixException {
this.aqlStatements = aqlStatements;
- this.out = out;
- this.sessionConfig = pc;
- this.pdf = pdf;
+ this.sessionConfig = conf;
declaredFunctions = getDeclaredFunctions(aqlStatements);
}
@@ -1740,8 +1736,7 @@
CompiledLoadFromFileStatement cls = new CompiledLoadFromFileStatement(dataverseName, loadStmt
.getDatasetName().getValue(), loadStmt.getAdapter(), loadStmt.getProperties(),
loadStmt.dataIsAlreadySorted());
- JobSpecification spec = APIFramework.compileQuery(null, metadataProvider, null, 0, null, sessionConfig,
- out, pdf, cls);
+ JobSpecification spec = APIFramework.compileQuery(null, metadataProvider, null, 0, null, sessionConfig, cls);
MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
bActiveTxn = false;
if (spec != null) {
@@ -1837,12 +1832,12 @@
// Query Rewriting (happens under the same ongoing metadata transaction)
Pair<Query, Integer> reWrittenQuery = APIFramework.reWriteQuery(declaredFunctions, metadataProvider, query,
- sessionConfig, out, pdf);
+ sessionConfig);
// Query Compilation (happens under the same ongoing metadata
// transaction)
JobSpecification spec = APIFramework.compileQuery(declaredFunctions, metadataProvider, reWrittenQuery.first,
- reWrittenQuery.second, stmt == null ? null : stmt.getDatasetName(), sessionConfig, out, pdf, stmt);
+ reWrittenQuery.second, stmt == null ? null : stmt.getDatasetName(), sessionConfig, stmt);
return spec;
@@ -2187,8 +2182,8 @@
handle.put(jobId.getId());
handle.put(metadataProvider.getResultSetId().getId());
response.put("handle", handle);
- out.print(response);
- out.flush();
+ sessionConfig.out().print(response);
+ sessionConfig.out().flush();
hcc.waitForCompletion(jobId);
break;
case SYNC:
@@ -2198,10 +2193,11 @@
// In this case (the normal case), we don't use the
// "response" JSONObject - just stream the results
// to the "out" PrintWriter
- if (pdf == OutputFormat.CSV) {
- ResultUtils.displayCSVHeader(metadataProvider.findOutputRecordType(), out);
+ if (sessionConfig.fmt() == OutputFormat.CSV &&
+ sessionConfig.is(SessionConfig.FORMAT_CSV_HEADER)) {
+ ResultUtils.displayCSVHeader(metadataProvider.findOutputRecordType(), sessionConfig);
}
- ResultUtils.displayResults(resultReader, out, pdf);
+ ResultUtils.displayResults(resultReader, sessionConfig);
hcc.waitForCompletion(jobId);
break;
@@ -2211,8 +2207,8 @@
handle.put(metadataProvider.getResultSetId().getId());
response.put("handle", handle);
hcc.waitForCompletion(jobId);
- out.print(response);
- out.flush();
+ sessionConfig.out().print(response);
+ sessionConfig.out().flush();
break;
default:
break;
@@ -2715,7 +2711,7 @@
private JobId runJob(IHyracksClientConnection hcc, JobSpecification spec, boolean waitForCompletion)
throws Exception {
- JobId[] jobIds = executeJobArray(hcc, new Job[] { new Job(spec) }, out, waitForCompletion);
+ JobId[] jobIds = executeJobArray(hcc, new Job[] { new Job(spec) }, sessionConfig.out(), waitForCompletion);
return jobIds[0];
}
diff --git a/asterix-app/src/main/java/edu/uci/ics/asterix/hyracks/bootstrap/FeedLifecycleListener.java b/asterix-app/src/main/java/edu/uci/ics/asterix/hyracks/bootstrap/FeedLifecycleListener.java
index 9c7ff81..4cdc91c 100644
--- a/asterix-app/src/main/java/edu/uci/ics/asterix/hyracks/bootstrap/FeedLifecycleListener.java
+++ b/asterix-app/src/main/java/edu/uci/ics/asterix/hyracks/bootstrap/FeedLifecycleListener.java
@@ -1104,7 +1104,7 @@
public void reviveFeed(String dataverse, String feedName, String dataset, String feedPolicy) {
PrintWriter writer = new PrintWriter(System.out, true);
- SessionConfig pc = new SessionConfig(true, false, false, false, false, false, true, true, false);
+ SessionConfig conf = new SessionConfig(writer, SessionConfig.OutputFormat.ADM);
try {
DataverseDecl dataverseDecl = new DataverseDecl(new Identifier(dataverse));
ConnectFeedStatement stmt = new ConnectFeedStatement(new Identifier(dataverse),
@@ -1113,7 +1113,7 @@
List<Statement> statements = new ArrayList<Statement>();
statements.add(dataverseDecl);
statements.add(stmt);
- AqlTranslator translator = new AqlTranslator(statements, writer, pc, APIFramework.OutputFormat.ADM);
+ AqlTranslator translator = new AqlTranslator(statements, conf);
translator.compileAndExecute(AsterixAppContextInfo.getInstance().getHcc(), null,
AqlTranslator.ResultDelivery.SYNC);
if (LOGGER.isLoggable(Level.INFO)) {
@@ -1147,7 +1147,7 @@
private void endFeed(FeedInfo feedInfo) {
MetadataTransactionContext ctx = null;
PrintWriter writer = new PrintWriter(System.out, true);
- SessionConfig pc = new SessionConfig(true, false, false, false, false, false, true, true, false);
+ SessionConfig conf = new SessionConfig(writer, SessionConfig.OutputFormat.ADM);
try {
ctx = MetadataManager.INSTANCE.beginTransaction();
DisconnectFeedStatement stmt = new DisconnectFeedStatement(new Identifier(
@@ -1159,7 +1159,7 @@
new Identifier(feedInfo.feedConnectionId.getDataverse()));
statements.add(dataverseDecl);
statements.add(stmt);
- AqlTranslator translator = new AqlTranslator(statements, writer, pc, APIFramework.OutputFormat.ADM);
+ AqlTranslator translator = new AqlTranslator(statements, conf);
translator.compileAndExecute(AsterixAppContextInfo.getInstance().getHcc(), null,
AqlTranslator.ResultDelivery.SYNC);
if (LOGGER.isLoggable(Level.INFO)) {
diff --git a/asterix-app/src/main/java/edu/uci/ics/asterix/result/ResultUtils.java b/asterix-app/src/main/java/edu/uci/ics/asterix/result/ResultUtils.java
index f558eb6..326697f 100644
--- a/asterix-app/src/main/java/edu/uci/ics/asterix/result/ResultUtils.java
+++ b/asterix-app/src/main/java/edu/uci/ics/asterix/result/ResultUtils.java
@@ -32,7 +32,8 @@
import org.json.JSONException;
import org.json.JSONObject;
-import edu.uci.ics.asterix.api.common.APIFramework;
+import edu.uci.ics.asterix.api.common.SessionConfig;
+import edu.uci.ics.asterix.api.common.SessionConfig.OutputFormat;
import edu.uci.ics.asterix.api.http.servlet.APIServlet;
import edu.uci.ics.asterix.om.types.ARecordType;
import edu.uci.ics.hyracks.algebricks.common.exceptions.AlgebricksException;
@@ -61,22 +62,29 @@
return s;
}
- public static void displayCSVHeader(ARecordType recordType, PrintWriter out) {
+ public static void displayCSVHeader(ARecordType recordType, SessionConfig conf) {
+ // If HTML-ifying, we have to output this here before the header -
+ // pretty ugly
+ if (conf.is(SessionConfig.FORMAT_HTML)) {
+ conf.out().println("<h4>Results:</h4>");
+ conf.out().println("<pre>");
+ }
+
String[] fieldNames = recordType.getFieldNames();
boolean notfirst = false;
for (String name : fieldNames) {
if (notfirst) {
- out.print(',');
+ conf.out().print(',');
}
notfirst = true;
- out.print('"');
- out.print(name.replace("\"", "\"\""));
- out.print('"');
+ conf.out().print('"');
+ conf.out().print(name.replace("\"", "\"\""));
+ conf.out().print('"');
}
- out.print("\r\n");
+ conf.out().print("\r\n");
}
- public static void displayResults(ResultReader resultReader, PrintWriter out, APIFramework.OutputFormat pdf)
+ public static void displayResults(ResultReader resultReader, SessionConfig conf)
throws HyracksDataException {
IFrameTupleAccessor fta = resultReader.getFrameTupleAccessor();
@@ -90,11 +98,15 @@
// Whether this is the first instance being output
boolean notfirst = false;
- switch (pdf) {
- case HTML:
- out.println("<h4>Results:</h4>");
- out.println("<pre>");
- // Fall through
+ // If we're outputting CSV with a header, the HTML header was already
+ // output by displayCSVHeader(), so skip it here
+ if (conf.is(SessionConfig.FORMAT_HTML) &&
+ ! (conf.fmt() == OutputFormat.CSV && conf.is(SessionConfig.FORMAT_CSV_HEADER))) {
+ conf.out().println("<h4>Results:</h4>");
+ conf.out().println("<pre>");
+ }
+
+ switch (conf.fmt()) {
case CSV:
need_commas = false;
break;
@@ -103,7 +115,7 @@
// Conveniently, JSON and ADM have the same syntax for an
// "ordered list", and our representation of the result of a
// statement is an ordered list of instances.
- out.print("[ ");
+ conf.out().print("[ ");
break;
}
@@ -119,19 +131,19 @@
bbis.setByteBuffer(buffer, start);
byte[] recordBytes = new byte[length];
int numread = bbis.read(recordBytes, 0, length);
- if (pdf == APIFramework.OutputFormat.CSV) {
+ if (conf.fmt() == OutputFormat.CSV) {
if ( (numread > 0) && (recordBytes[numread-1] == '\n') ) {
numread--;
}
}
result = new String(recordBytes, 0, numread, UTF_8);
if (need_commas && notfirst) {
- out.print(", ");
+ conf.out().print(", ");
}
notfirst = true;
- out.print(result);
- if (pdf == APIFramework.OutputFormat.CSV) {
- out.print("\r\n");
+ conf.out().print(result);
+ if (conf.fmt() == OutputFormat.CSV) {
+ conf.out().print("\r\n");
}
}
buffer.clear();
@@ -145,21 +157,21 @@
} while (resultReader.read(buffer) > 0);
}
- out.flush();
+ conf.out().flush();
- switch (pdf) {
- case HTML:
- out.println("</pre>");
- break;
+ switch (conf.fmt()) {
case JSON:
case ADM:
- out.println(" ]");
+ conf.out().println(" ]");
break;
case CSV:
// Nothing to do
break;
}
+ if (conf.is(SessionConfig.FORMAT_HTML)) {
+ conf.out().println("</pre>");
+ }
}
public static JSONObject getErrorResponse(int errorCode, String errorMessage, String errorSummary,
diff --git a/asterix-app/src/main/resources/webui/querytemplate.html b/asterix-app/src/main/resources/webui/querytemplate.html
index 0adb518..bcd177c 100644
--- a/asterix-app/src/main/resources/webui/querytemplate.html
+++ b/asterix-app/src/main/resources/webui/querytemplate.html
@@ -186,15 +186,23 @@
<textarea rows="10" id="qry" name="query" class="query" value="%s" placeholder="Type your AQL query ..."></textarea>
</div>
- <div class="btn-group">
- <button id="checkboxes-on" class="btn">
- <i id="opts" class="icon-ok" style="display:none;"></i>Select Options</button>
- <button id="clear-query-button" class="btn">Clear Query</button>
- <!-- <button id="checkboxes-off" class="btn">Clear All Options</button> -->
- <button type="submit" id="run-btn" class="btn btn-custom-darken">Run</button>
- </div>
+ <div class="btn-group">
+ <button id="checkboxes-on" class="btn">
+ <i id="opts" class="icon-ok" style="display:none;"></i>Select Options</button>
+ <button id="clear-query-button" class="btn">Clear Query</button>
+ <!-- <button id="checkboxes-off" class="btn">Clear All Options</button> -->
+ <button type="submit" id="run-btn" class="btn btn-custom-darken">Run</button>
+ </div>
<div>
+ <label class="checkbox optlabel"> Output Format:<br/>
+ <select name="output-format" class="btn">
+ <option selected value="JSON">JSON</option>
+ <option value="ADM">ADM</option>
+ <option value="CSV">CSV (no header)</option>
+ <option value="CSV-Header">CSV (with header)</option>
+ </select>
+ </label>
<label class="checkbox optlabel"><input type="checkbox" name="print-expr-tree" value="true" /> Print parsed expressions</label>
<label class="checkbox optlabel"><input type="checkbox" name="print-rewritten-expr-tree" value="true" /> Print rewritten expressions</label>
<label class="checkbox optlabel"><input type="checkbox" name="print-logical-plan" value="true" /> Print logical plan</label>
diff --git a/asterix-app/src/test/resources/runtimets/results/csv/basic-types-header/basic-types.1.csv b/asterix-app/src/test/resources/runtimets/results/csv/basic-types-header/basic-types.1.csv
new file mode 100644
index 0000000..941639e
--- /dev/null
+++ b/asterix-app/src/test/resources/runtimets/results/csv/basic-types-header/basic-types.1.csv
@@ -0,0 +1,2 @@
+"id","name","money"
+12345,Chris,18.25
diff --git a/asterix-app/src/test/resources/runtimets/results/csv/basic-types/basic-types.1.csv b/asterix-app/src/test/resources/runtimets/results/csv/basic-types/basic-types.1.csv
index 941639e..c7fe1a0 100644
--- a/asterix-app/src/test/resources/runtimets/results/csv/basic-types/basic-types.1.csv
+++ b/asterix-app/src/test/resources/runtimets/results/csv/basic-types/basic-types.1.csv
@@ -1,2 +1 @@
-"id","name","money"
12345,Chris,18.25
diff --git a/asterix-app/src/test/resources/runtimets/testsuite.xml b/asterix-app/src/test/resources/runtimets/testsuite.xml
index 214056f..a22eb35 100644
--- a/asterix-app/src/test/resources/runtimets/testsuite.xml
+++ b/asterix-app/src/test/resources/runtimets/testsuite.xml
@@ -6569,6 +6569,11 @@
<output-dir compare="CSV">basic-types</output-dir>
</compilation-unit>
</test-case>
+ <test-case FilePath="csv">
+ <compilation-unit name="basic-types">
+ <output-dir compare="CSV_Header">basic-types-header</output-dir>
+ </compilation-unit>
+ </test-case>
</test-group>
<test-group name="binary">
<test-case FilePath="binary">
diff --git a/asterix-common/src/test/java/edu/uci/ics/asterix/test/aql/TestsUtils.java b/asterix-common/src/test/java/edu/uci/ics/asterix/test/aql/TestsUtils.java
index 99afe9a..70e2f6f 100644
--- a/asterix-common/src/test/java/edu/uci/ics/asterix/test/aql/TestsUtils.java
+++ b/asterix-common/src/test/java/edu/uci/ics/asterix/test/aql/TestsUtils.java
@@ -245,7 +245,7 @@
}
//Executes AQL in either async or async-defer mode.
- public static InputStream executeAnyAQLAsync(String str, boolean defer) throws Exception {
+ public static InputStream executeAnyAQLAsync(String str, boolean defer, OutputFormat fmt) throws Exception {
final String url = "http://localhost:19002/aql";
// Create a method instance.
@@ -256,6 +256,7 @@
method.setQueryString(new NameValuePair[] { new NameValuePair("mode", "asynchronous") });
}
method.setRequestEntity(new StringRequestEntity(str));
+ method.setRequestHeader("Accept", fmt.mimeType());
// Provide custom retry handler is necessary
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(3, false));
@@ -265,16 +266,17 @@
String theHandle = IOUtils.toString(resultStream, "UTF-8");
//take the handle and parse it so results can be retrieved
- InputStream handleResult = getHandleResult(theHandle);
+ InputStream handleResult = getHandleResult(theHandle, fmt);
return handleResult;
}
- private static InputStream getHandleResult(String handle) throws Exception {
+ private static InputStream getHandleResult(String handle, OutputFormat fmt) throws Exception {
final String url = "http://localhost:19002/query/result";
// Create a method instance.
GetMethod method = new GetMethod(url);
method.setQueryString(new NameValuePair[] { new NameValuePair("handle", handle) });
+ method.setRequestHeader("Accept", fmt.mimeType());
// Provide custom retry handler is necessary
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(3, false));
@@ -417,9 +419,9 @@
if (ctx.getType().equalsIgnoreCase("query"))
resultStream = executeQuery(statement, fmt);
else if (ctx.getType().equalsIgnoreCase("async"))
- resultStream = executeAnyAQLAsync(statement, false);
+ resultStream = executeAnyAQLAsync(statement, false, fmt);
else if (ctx.getType().equalsIgnoreCase("asyncdefer"))
- resultStream = executeAnyAQLAsync(statement, true);
+ resultStream = executeAnyAQLAsync(statement, true, fmt);
if (queryCount >= expectedResultFileCtxs.size()) {
throw new IllegalStateException("no result file for " + testFile.toString());
diff --git a/asterix-doc/src/site/markdown/csv.md b/asterix-doc/src/site/markdown/csv.md
index 338aa55..6e830f0 100644
--- a/asterix-doc/src/site/markdown/csv.md
+++ b/asterix-doc/src/site/markdown/csv.md
@@ -73,13 +73,9 @@
("format"="delimited-text"),
("header"="true"));
-This is useful when the CSV file was produced from an earlier
-AsterixDB operation, as AsterixDB's CSV output always has a header
-line.
-
CSV data may also be loaded from HDFS; see [Accessing External
Data](aql/externaldata.html) for details. However please note that
-CSV files on HDFS cannot have headers; attempting to specify
+CSV files on HDFS cannot have headers. Attempting to specify
"header"="true" when reading from HDFS could result in non-header
lines of data being skipped as well.
@@ -139,22 +135,35 @@
#### Request the CSV Output Format
When sending requests to the Asterix HTTP API, Asterix decides what
-format to use for rendering the results based on the `Accept` HTTP
-header. By default, Asterix will produce JSON output, and this can be
-requested explicitly by specifying the MIME type `application/json`.
-To select CSV output, set the `Accept` header on your request to the
-MIME type `text/csv`. The details of how to accomplish this will of
-course depend on what tools you are using to contact the HTTP API.
-Here is an example from a Unix shell prompt using the command-line
-utility "curl":
+format to use for rendering the results in one of two ways:
- curl -G -H "Accept: text/csv" "http://localhost:19002/query" --data-urlencode '
- query=use dataverse csv;
+* A HTTP query parameter named "output", which must be set to one of
+ the following values: `JSON`, `CSV`, or `ADM`.
+
+* Based on the [`Accept` HTTP header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1)
+
+By default, Asterix will produce JSON output. To select CSV output,
+pass the parameter `output=CSV`, or set the `Accept` header on your
+request to the MIME type `text/csv`. The details of how to accomplish
+this will of course depend on what tools you are using to contact the
+HTTP API. Here is an example from a Unix shell prompt using the
+command-line utility "curl" and specifying the "output query parameter:
+
+ curl -G "http://localhost:19002/query" \
+ --data-urlencode 'output=CSV' \
+ --data-urlencode 'query=use dataverse csv;
+ set output-record-type "csv_type";
+ for $n in dataset csv_set return $n;'
+
+Alternately, the same query using the `Accept` header:
+
+ curl -G -H "Accept: text/csv" "http://localhost:19002/query" \
+ --data-urlencode 'query=use dataverse csv;
set output-record-type "csv_type";
for $n in dataset csv_set return $n;'
Similarly, a trivial Java program to execute the above sample query
-would be:
+and selecting CSV output via the `Accept` header would be:
import java.net.HttpURLConnection;
import java.net.URL;
@@ -184,13 +193,22 @@
For either of the above examples, the output would be:
- "id","money","name"
1,18.5,"Peter Krabnitz"
2,74.5,"Jesse Stevens"
assuming you had already run the previous examples to create the
dataverse and populate the dataset.
+#### Outputting CSV with a Header
+
+By default, AsterixDB will produce CSV results with no header line.
+If you want a header, you may explicitly request it in one of two ways:
+
+* By passing the HTTP query parameter "header" with the value "present"
+
+* By specifying the MIME type {{text/csv; header=present}} in your
+HTTP Accept: header. This is consistent with RFC 4180.
+
#### Issues with open datatypes and optional fields
As mentioned earlier, CSV is a rigid format. It cannot express records
diff --git a/asterix-test-framework/src/main/java/edu/uci/ics/asterix/testframework/context/TestCaseContext.java b/asterix-test-framework/src/main/java/edu/uci/ics/asterix/testframework/context/TestCaseContext.java
index 7589909..6488c95 100644
--- a/asterix-test-framework/src/main/java/edu/uci/ics/asterix/testframework/context/TestCaseContext.java
+++ b/asterix-test-framework/src/main/java/edu/uci/ics/asterix/testframework/context/TestCaseContext.java
@@ -36,7 +36,8 @@
NONE ("", ""),
ADM ("adm", "application/x-adm"),
JSON ("json", "application/json"),
- CSV ("csv", "text/csv");
+ CSV ("csv", "text/csv"),
+ CSV_HEADER ("csv-header", "text/csv; header=present");
private final String extension;
private final String mimetype;
@@ -62,6 +63,8 @@
return OutputFormat.JSON;
case CSV:
return OutputFormat.CSV;
+ case CSV_HEADER:
+ return OutputFormat.CSV_HEADER;
case INSPECT:
case IGNORE:
return OutputFormat.NONE;
diff --git a/asterix-test-framework/src/main/resources/Catalog.xsd b/asterix-test-framework/src/main/resources/Catalog.xsd
index a33399c..e43acdc 100644
--- a/asterix-test-framework/src/main/resources/Catalog.xsd
+++ b/asterix-test-framework/src/main/resources/Catalog.xsd
@@ -188,6 +188,7 @@
<xs:enumeration value="Ignore"/>
<xs:enumeration value="JSON"/>
<xs:enumeration value="CSV"/>
+ <xs:enumeration value="CSV_Header"/>
</xs:restriction>
</xs:simpleType>