>From Janhavi Tripurwar <[email protected]>:
Janhavi Tripurwar has uploaded this change for review. (
https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21689?usp=email )
Change subject: [ASTERIXDB-3593]: Return Explain results in response
......................................................................
[ASTERIXDB-3593]: Return Explain results in response
Ext-ref: MB-73758
Change-Id: I9673ff75c18d3113b51a9cf752986832a3be9e0b
---
M
asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/ExecutionPlans.java
M
asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
M
asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/NcStatementsPrinter.java
M
asterixdb/asterix-app/src/test/java/org/apache/asterix/app/result/NcStatementsPrinterTest.java
4 files changed, 89 insertions(+), 12 deletions(-)
git pull ssh://asterix-gerrit.ics.uci.edu:29418/asterixdb
refs/changes/89/21689/1
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 5940efb..d97825e 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
@@ -32,6 +32,8 @@
private String statementCategory;
private String statementParameters;
private boolean explainOnly;
+ /** The plan an EXPLAIN produced, which is reported as the statement's
result rather than among its plans. */
+ private String explainResult;
public ExecutionPlans() {
}
@@ -47,6 +49,7 @@
statementCategory = other.statementCategory;
statementParameters = other.statementParameters;
explainOnly = other.explainOnly;
+ explainResult = other.explainResult;
}
/** Forgets the plans of the statement that just finished, so the next one
does not report them as its own. */
@@ -92,6 +95,7 @@
statementCategory = null;
statementParameters = null;
explainOnly = false;
+ explainResult = null;
}
public String getExpressionTree() {
@@ -165,4 +169,12 @@
public void setExplainOnly(boolean explainOnly) {
this.explainOnly = explainOnly;
}
+
+ public String getExplainResult() {
+ return explainResult;
+ }
+
+ public void setExplainResult(String explainResult) {
+ this.explainResult = explainResult;
+ }
}
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 e0d6201..42c9450 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
@@ -440,6 +440,8 @@
if (isExplainOnly) {
printPlanAsResult(metadataProvider, output, printer,
printSignature);
+ // kept for a request reported statement by statement, where
each statement prints its own result
+
executionPlans.setExplainResult(executionPlans.getOptimizedLogicalPlan());
if (!conf.is(SessionConfig.OOB_OPTIMIZED_LOGICAL_PLAN)) {
executionPlans.setOptimizedLogicalPlan(null);
}
diff --git
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/NcStatementsPrinter.java
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/NcStatementsPrinter.java
index 5b194a4..9dd4760 100644
---
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/NcStatementsPrinter.java
+++
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/result/fields/NcStatementsPrinter.java
@@ -42,6 +42,7 @@
import org.apache.asterix.translator.IStatementExecutor.ResultSetInfo;
import org.apache.asterix.translator.IStatementExecutor.StatementInfo;
import org.apache.asterix.translator.IStatementExecutor.Stats;
+import org.apache.asterix.translator.SessionConfig;
import org.apache.asterix.translator.SessionOutput;
import org.apache.hyracks.api.exceptions.HyracksDataException;
import org.apache.hyracks.api.exceptions.Warning;
@@ -134,15 +135,20 @@
ResultReader rows = resultSetInfo != null && delivery ==
ResultDelivery.IMMEDIATE
? new ResultReader(resultSet, resultSetInfo.getJobId(),
resultSetInfo.getResultSetId()) : null;
+ // an EXPLAIN produces no result set; its plan is what the statement
returns
+ String explainResult = plans == null ? null : plans.getExplainResult();
fields.add(w -> printField(w, POSITION_FIELD_NAME,
String.valueOf(statement.getPosition())));
fields.add(w -> printField(w, KIND_FIELD_NAME,
quoted(statementKind(statement))));
// a statement that returns rows describes them, as the flat response
does; the signature is the default one
// unless the client asked for a typed one, exactly as
SignaturePrinter.newInstance decides for a request
- if (printSignature && resultSetInfo != null) {
+ if (printSignature && (resultSetInfo != null || explainResult !=
null)) {
IResponseFieldPrinter signature =
plans == null ? SignaturePrinter.INSTANCE :
SignaturePrinter.newInstance(plans);
fields.add(w -> printNested(w, signature));
}
+ if (explainResult != null) {
+ fields.add(w -> printExplain(w, explainResult));
+ }
if (resultSetInfo != null) {
if (delivery == ResultDelivery.IMMEDIATE) {
fields.add(w -> printRows(w, rows, resultSetInfo, stats));
@@ -189,22 +195,45 @@
/** Streams this statement's rows; every statement has already run by the
time this is reached. */
private void printRows(PrintWriter pw, ResultReader reader, ResultSetInfo
resultSetInfo, Stats stats)
throws HyracksDataException {
+ PrintWriter rows = beginRows(pw);
+ try {
+ ResultUtil.printResults(appCtx, reader, undecorated(rows), stats,
resultSetInfo.getRecordType());
+ } finally {
+ rows.flush();
+ }
+ }
+
+ /**
+ * Prints the plan an EXPLAIN produced as this statement's rows, the way
the flat response prints it. The quoting
+ * the plan format calls for is put back afterwards, so it does not follow
the session into the next statement.
+ */
+ private void printExplain(PrintWriter pw, String explainResult) throws
HyracksDataException {
+ PrintWriter rows = beginRows(pw);
+ boolean quoteRecord =
sessionOutput.config().is(SessionConfig.FORMAT_QUOTE_RECORD);
+ try {
+ new ExplainOnlyResultsPrinter(appCtx, explainResult,
undecorated(rows)).print(rows);
+ } finally {
+ sessionOutput.config().set(SessionConfig.FORMAT_QUOTE_RECORD,
quoteRecord);
+ rows.flush();
+ }
+ }
+
+ /** Names the rows field and returns the writer they are printed to. */
+ private static PrintWriter beginRows(PrintWriter pw) {
pw.print(FIELD_INDENT);
pw.print(quoted(ResultsPrinter.FIELD_NAME));
pw.print(": ");
- PrintWriter rows = new PrintWriter(pw) {
+ return new PrintWriter(pw) {
@Override
public void println(String x) {
print(x);
}
};
- // no result decorators: this printer names the field itself, so the
decorators that name and number it must not
- SessionOutput undecorated = new SessionOutput(sessionOutput.config(),
rows);
- try {
- ResultUtil.printResults(appCtx, reader, undecorated, stats,
resultSetInfo.getRecordType());
- } finally {
- rows.flush();
- }
+ }
+
+ /** No result decorators: this printer names the field itself, so the ones
that name and number it must not. */
+ private SessionOutput undecorated(PrintWriter rows) {
+ return new SessionOutput(sessionOutput.config(), rows);
}
private static void printNested(PrintWriter pw, IResponseFieldPrinter
printer) throws HyracksDataException {
diff --git
a/asterixdb/asterix-app/src/test/java/org/apache/asterix/app/result/NcStatementsPrinterTest.java
b/asterixdb/asterix-app/src/test/java/org/apache/asterix/app/result/NcStatementsPrinterTest.java
index f3bcec9..21388bd 100644
---
a/asterixdb/asterix-app/src/test/java/org/apache/asterix/app/result/NcStatementsPrinterTest.java
+++
b/asterixdb/asterix-app/src/test/java/org/apache/asterix/app/result/NcStatementsPrinterTest.java
@@ -24,16 +24,21 @@
import java.util.List;
import org.apache.asterix.app.result.fields.NcStatementsPrinter;
+import org.apache.asterix.common.api.IApplicationContext;
+import org.apache.asterix.common.config.CompilerProperties;
import org.apache.asterix.common.exceptions.CompilationException;
import org.apache.asterix.common.exceptions.ErrorCode;
import org.apache.asterix.lang.common.base.Statement;
+import org.apache.asterix.translator.ExecutionPlans;
import org.apache.asterix.translator.IStatementExecutor.ResultDelivery;
import org.apache.asterix.translator.IStatementExecutor.StatementInfo;
import org.apache.asterix.translator.IStatementExecutor.Stats;
import org.apache.asterix.translator.SessionConfig;
import org.apache.asterix.translator.SessionOutput;
+import org.apache.hyracks.util.StorageUtil;
import org.junit.Assert;
import org.junit.Test;
+import org.mockito.Mockito;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -95,6 +100,22 @@
Assert.assertEquals(statements.toString(), "DESCRIBE LINK",
statements.get(0).get("kind").asText());
}
+ /** An EXPLAIN has no result set: the plan it produced is reported as that
statement's rows. */
+ @Test
+ public void anExplainReportsItsPlanAsItsResult() throws Exception {
+ StatementInfo explain = statement(1, Statement.Kind.QUERY);
+ ExecutionPlans plans = new ExecutionPlans();
+ plans.setExplainResult("{ \"operator\": \"distribute-result\" }");
+ explain.setPlans(plans);
+ JsonNode statements = print(List.of(explain, statement(2,
Statement.Kind.QUERY)));
+
+ JsonNode results = statements.get(0).get("results");
+ Assert.assertNotNull(statements.toString(), results);
+ Assert.assertEquals(statements.toString(), "distribute-result",
results.get(0).get("operator").asText());
+ // a statement that produced no plan of its own reports no rows
+ Assert.assertNull(statements.toString(),
statements.get(1).get("results"));
+ }
+
private static StatementInfo statement(int position, Statement.Kind kind) {
StatementInfo statementInfo = new StatementInfo(position, kind, null);
statementInfo.setStats(new Stats());
@@ -105,13 +126,26 @@
private static JsonNode print(List<StatementInfo> statements) throws
Exception {
StringWriter out = new StringWriter();
PrintWriter pw = new PrintWriter(out);
- SessionOutput sessionOutput = new SessionOutput(new
SessionConfig(SessionConfig.OutputFormat.CLEAN_JSON), pw);
- new NcStatementsPrinter(null, statements, null,
ResultDelivery.IMMEDIATE, sessionOutput, StandardCharsets.UTF_8,
- false, "test-request").print(pw);
+ SessionConfig config = new
SessionConfig(SessionConfig.OutputFormat.CLEAN_JSON,
SessionConfig.PlanFormat.JSON);
+ // as the servlet configures it: rows are wrapped in an array
+ config.set(SessionConfig.FORMAT_WRAPPER_ARRAY, true);
+ SessionOutput sessionOutput = new SessionOutput(config, pw);
+ new NcStatementsPrinter(appCtx(), statements, null,
ResultDelivery.IMMEDIATE, sessionOutput,
+ StandardCharsets.UTF_8, false, "test-request").print(pw);
pw.flush();
JsonNode response = OBJECT_MAPPER.readTree("{\n" + out + "\n}");
JsonNode statementsField =
response.get(NcStatementsPrinter.FIELD_NAME);
Assert.assertNotNull(out.toString(), statementsField);
return statementsField;
}
+
+ /** Only the frame size is reached, by the printer that writes an explain
plan. */
+ private static IApplicationContext appCtx() {
+ IApplicationContext appCtx = Mockito.mock(IApplicationContext.class);
+ CompilerProperties compilerProperties =
Mockito.mock(CompilerProperties.class);
+
Mockito.when(appCtx.getCompilerProperties()).thenReturn(compilerProperties);
+ Mockito.when(compilerProperties.getFrameSize())
+ .thenReturn(StorageUtil.getIntSizeInBytes(32,
StorageUtil.StorageUnit.KILOBYTE));
+ return appCtx;
+ }
}
--
To view, visit https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21689?usp=email
To unsubscribe, or for help writing mail filters, visit
https://asterix-gerrit.ics.uci.edu/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: asterixdb
Gerrit-Branch: master
Gerrit-Change-Id: I9673ff75c18d3113b51a9cf752986832a3be9e0b
Gerrit-Change-Number: 21689
Gerrit-PatchSet: 1
Gerrit-Owner: Janhavi Tripurwar <[email protected]>