-
Notifications
You must be signed in to change notification settings - Fork 111
Enhancement #3028: Add verbosity-level option to SQL EXPLAIN #3485
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 13 commits
6fbe5b8
d7da0ab
ecb37aa
d9eedc9
36178ea
d02442c
a9d07da
a4a2ace
e6c84b0
e5624c8
96fdbd0
b62e176
c8b54b2
69fd3cc
7a3d4cb
82fdb9e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -645,7 +645,8 @@ helpStatement | |
// details | ||
|
||
describeObjectClause | ||
: ( | ||
: level=(VERBOSE | MINIMAL)? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we want There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
( | ||
query | deleteStatement | insertStatement | ||
| updateStatement | executeContinuationStatement | ||
) #describeStatements | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -21,7 +21,6 @@ | |||||
package com.apple.foundationdb.relational.recordlayer.query; | ||||||
|
||||||
import com.apple.foundationdb.annotation.API; | ||||||
|
||||||
import com.apple.foundationdb.record.ExecuteProperties; | ||||||
import com.apple.foundationdb.record.PlanHashable; | ||||||
import com.apple.foundationdb.record.query.expressions.Comparisons; | ||||||
|
@@ -35,13 +34,13 @@ | |||||
import com.apple.foundationdb.record.query.plan.cascades.values.LiteralValue; | ||||||
import com.apple.foundationdb.record.query.plan.cascades.values.OfTypeValue; | ||||||
import com.apple.foundationdb.record.query.plan.cascades.values.Value; | ||||||
import com.apple.foundationdb.record.query.plan.explain.ExplainLevel; | ||||||
import com.apple.foundationdb.relational.api.RelationalArray; | ||||||
import com.apple.foundationdb.relational.api.RelationalStruct; | ||||||
import com.apple.foundationdb.relational.api.exceptions.RelationalException; | ||||||
import com.apple.foundationdb.relational.recordlayer.metadata.DataTypeUtils; | ||||||
import com.apple.foundationdb.relational.util.Assert; | ||||||
import com.apple.foundationdb.relational.util.SpotBugsSuppressWarnings; | ||||||
|
||||||
import com.google.common.collect.ImmutableList; | ||||||
import com.google.protobuf.ZeroCopyByteString; | ||||||
|
||||||
|
@@ -89,6 +88,8 @@ public class MutablePlanGenerationContext implements QueryExecutionContext { | |||||
|
||||||
private boolean forExplain; | ||||||
|
||||||
private int explainLevel; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would be nicer to use an enum instead of |
||||||
|
||||||
@Nullable | ||||||
private byte[] continuation; | ||||||
|
||||||
|
@@ -261,6 +262,7 @@ public MutablePlanGenerationContext(@Nonnull PreparedParams preparedParams, | |||||
constantObjectValues = new LinkedList<>(); | ||||||
shouldProcessLiteral = true; | ||||||
forExplain = false; | ||||||
this.explainLevel = ExplainLevel.SOME_DETAILS; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||||||
setContinuation(null); | ||||||
equalityConstraints = ImmutableList.builder(); | ||||||
} | ||||||
|
@@ -336,6 +338,10 @@ public boolean isForExplain() { | |||||
return forExplain; | ||||||
} | ||||||
|
||||||
@Override | ||||||
public int getExplainLevel() { | ||||||
return explainLevel; | ||||||
} | ||||||
|
||||||
@Nonnull | ||||||
public QueryPlanConstraint getPlanConstraintsForLiteralReferences() { | ||||||
|
@@ -372,6 +378,10 @@ public void setForExplain(boolean forExplain) { | |||||
this.forExplain = forExplain; | ||||||
} | ||||||
|
||||||
public void setExplainLevel(final int explainLevel) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||||||
this.explainLevel = explainLevel; | ||||||
} | ||||||
|
||||||
@Nonnull | ||||||
public Value processQueryLiteral(@Nonnull Type type, @Nullable Object literal, int tokenIndex) { | ||||||
return processQueryLiteralOrParameter(type, literal, null, null, tokenIndex); | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -24,6 +24,7 @@ | |||||
|
||||||
import com.apple.foundationdb.record.ExecuteProperties; | ||||||
import com.apple.foundationdb.record.PlanHashable; | ||||||
import com.apple.foundationdb.record.query.plan.explain.ExplainLevel; | ||||||
import com.apple.foundationdb.relational.util.SpotBugsSuppressWarnings; | ||||||
|
||||||
import javax.annotation.Nonnull; | ||||||
|
@@ -44,6 +45,8 @@ public final class NormalizedQueryExecutionContext implements QueryExecutionCont | |||||
|
||||||
private final boolean isForExplain; | ||||||
|
||||||
private int explainLevel; | ||||||
|
||||||
private final int parameterHash; | ||||||
|
||||||
@Nonnull | ||||||
|
@@ -53,10 +56,12 @@ private NormalizedQueryExecutionContext(@Nonnull Literals literals, | |||||
@Nullable byte[] continuation, | ||||||
int parameterHash, | ||||||
boolean isForExplain, | ||||||
int explainLevel, | ||||||
@Nonnull final PlanHashable.PlanHashMode planHashMode) { | ||||||
this.literals = literals; | ||||||
this.continuation = continuation; | ||||||
this.isForExplain = isForExplain; | ||||||
this.explainLevel = explainLevel; | ||||||
this.parameterHash = parameterHash; | ||||||
this.planHashMode = planHashMode; | ||||||
} | ||||||
|
@@ -90,6 +95,11 @@ public boolean isForExplain() { | |||||
return isForExplain; | ||||||
} | ||||||
|
||||||
@Override | ||||||
public int getExplainLevel() { | ||||||
return explainLevel; | ||||||
} | ||||||
|
||||||
@Nonnull | ||||||
@Override | ||||||
public PlanHashable.PlanHashMode getPlanHashMode() { | ||||||
|
@@ -107,6 +117,8 @@ public static final class Builder { | |||||
|
||||||
private boolean isForExplain; | ||||||
|
||||||
private int explainLevel; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||||||
|
||||||
@Nullable | ||||||
private byte[] continuation; | ||||||
|
||||||
|
@@ -120,6 +132,7 @@ private Builder() { | |||||
this.isForExplain = false; | ||||||
this.continuation = null; | ||||||
this.planHashMode = null; | ||||||
this.explainLevel = ExplainLevel.SOME_DETAILS; | ||||||
} | ||||||
|
||||||
@Nonnull | ||||||
|
@@ -146,6 +159,12 @@ public Builder setForExplain(boolean isForExplain) { | |||||
return this; | ||||||
} | ||||||
|
||||||
@Nonnull | ||||||
public Builder setExplainLevel(int level) { | ||||||
this.explainLevel = level; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||||||
return this; | ||||||
} | ||||||
|
||||||
@Nonnull | ||||||
public Builder setPlanHashMode(@Nonnull PlanHashable.PlanHashMode planHashMode) { | ||||||
this.planHashMode = planHashMode; | ||||||
|
@@ -155,7 +174,7 @@ public Builder setPlanHashMode(@Nonnull PlanHashable.PlanHashMode planHashMode) | |||||
@Nonnull | ||||||
public NormalizedQueryExecutionContext build() { | ||||||
return new NormalizedQueryExecutionContext(literalsBuilder.build(), continuation, | ||||||
parameterHash, isForExplain, | ||||||
parameterHash, isForExplain, explainLevel, | ||||||
Objects.requireNonNull(planHashMode)); | ||||||
} | ||||||
} | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,7 @@ | |
import com.apple.foundationdb.record.query.plan.cascades.values.FieldValue; | ||
import com.apple.foundationdb.record.query.plan.cascades.values.LiteralValue; | ||
import com.apple.foundationdb.record.query.plan.cascades.values.Value; | ||
import com.apple.foundationdb.record.query.plan.explain.ExplainLevel; | ||
import com.apple.foundationdb.record.util.pair.NonnullPair; | ||
import com.apple.foundationdb.relational.api.exceptions.ErrorCode; | ||
import com.apple.foundationdb.relational.generated.RelationalLexer; | ||
|
@@ -536,6 +537,13 @@ public Object visitExecuteContinuationStatement(@Nonnull RelationalParser.Execut | |
@Override | ||
public QueryPlan.LogicalQueryPlan visitFullDescribeStatement(@Nonnull RelationalParser.FullDescribeStatementContext ctx) { | ||
getDelegate().getPlanGenerationContext().setForExplain(ctx.EXPLAIN() != null); | ||
if (!ctx.describeObjectClause().getTokens(RelationalLexer.VERBOSE).isEmpty()) { | ||
getDelegate().getPlanGenerationContext().setExplainLevel(ExplainLevel.ALL_DETAILS); | ||
} else if (!ctx.describeObjectClause().getTokens(RelationalLexer.MINIMAL).isEmpty()) { | ||
getDelegate().getPlanGenerationContext().setExplainLevel(ExplainLevel.STRUCTURE); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we care about this level of distinction here, only an optional There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
} else { | ||
getDelegate().getPlanGenerationContext().setExplainLevel(ExplainLevel.SOME_DETAILS); | ||
} | ||
final var logicalOperator = Assert.castUnchecked(ctx.describeObjectClause().accept(this), LogicalOperator.class); | ||
return QueryPlan.LogicalQueryPlan.of(logicalOperator.getQuantifier().getRangesOver().get(), getDelegate().getPlanGenerationContext(), "TODO"); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -73,7 +73,7 @@ void explainResultSetMetadataTest() throws Exception { | |
final var expectedContTypes = List.of(Types.BINARY, Types.INTEGER, Types.VARCHAR); | ||
try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { | ||
executeInsert(ddl); | ||
try (RelationalPreparedStatement ps = ddl.setSchemaAndGetConnection().prepareStatement("EXPLAIN SELECT * FROM RestaurantComplexRecord")) { | ||
try (RelationalPreparedStatement ps = ddl.setSchemaAndGetConnection().prepareStatement("EXPLAIN VERBOSE SELECT * FROM RestaurantComplexRecord")) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please revert, and add targeted tests for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
try (final RelationalResultSet resultSet = ps.executeQuery()) { | ||
final var actualMetadata = resultSet.getMetaData(); | ||
org.junit.jupiter.api.Assertions.assertEquals(expectedLabels.size(), actualMetadata.getColumnCount()); | ||
|
@@ -205,6 +205,60 @@ void explainExecuteStatementTest() throws Exception { | |
} | ||
} | ||
|
||
@Test | ||
void explainVerboseTest() throws Exception { | ||
try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { | ||
executeInsert(ddl); | ||
try (RelationalPreparedStatement ps = ddl.setSchemaAndGetConnection().prepareStatement("EXPLAIN VERBOSE SELECT * FROM RestaurantComplexRecord where COUNT(reviews) > 3 and COUNT(reviews) < 100")) { | ||
ps.setMaxRows(2); | ||
try (final RelationalResultSet resultSet = ps.executeQuery()) { | ||
final var assertResult = ResultSetAssert.assertThat(resultSet); | ||
assertResult.hasNextRow() | ||
.hasColumn("PLAN", "ISCAN(RECORD_NAME_IDX <,>) | FILTER count_star(*) GREATER_THAN promote(@c12 AS LONG) AND count_star(*) LESS_THAN promote(@c19 AS LONG)") | ||
.hasColumn("PLAN_HASH", -1697137247L) | ||
.hasColumn("PLAN_CONTINUATION", null); | ||
assertResult.hasNoNextRow(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
void explainMinimalTest() throws Exception { | ||
try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { | ||
executeInsert(ddl); | ||
try (RelationalPreparedStatement ps = ddl.setSchemaAndGetConnection().prepareStatement("EXPLAIN MINIMAL SELECT * FROM RestaurantComplexRecord where COUNT(reviews) > 3 and COUNT(reviews) < 100")) { | ||
ps.setMaxRows(2); | ||
try (final RelationalResultSet resultSet = ps.executeQuery()) { | ||
final var assertResult = ResultSetAssert.assertThat(resultSet); | ||
assertResult.hasNextRow() | ||
.hasColumn("PLAN", "ISCAN(RECORD_NAME_IDX) | FILTER count_star(*) GREATER_THAN promote(@c12 AS LONG) AND count_star(*) LESS_THAN promote(@c19 AS LONG)") | ||
.hasColumn("PLAN_HASH", -1697137247L) | ||
.hasColumn("PLAN_CONTINUATION", null); | ||
assertResult.hasNoNextRow(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
void explainDefaultTest() throws Exception { | ||
try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { | ||
executeInsert(ddl); | ||
try (RelationalPreparedStatement ps = ddl.setSchemaAndGetConnection().prepareStatement("EXPLAIN SELECT * FROM RestaurantComplexRecord where COUNT(reviews) > 3 and COUNT(reviews) < 100")) { | ||
ps.setMaxRows(2); | ||
try (final RelationalResultSet resultSet = ps.executeQuery()) { | ||
final var assertResult = ResultSetAssert.assertThat(resultSet); | ||
assertResult.hasNextRow() | ||
.hasColumn("PLAN", "ISCAN(RECORD_NAME_IDX <,>) | FILTER count_star(*) GREATER_THAN promote(@c11 AS LONG) AND count_star(*) LESS_THAN promote(@c18 AS LONG)") | ||
.hasColumn("PLAN_HASH", -1697137247L) | ||
.hasColumn("PLAN_CONTINUATION", null); | ||
assertResult.hasNoNextRow(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private Continuation consumeResultAndGetContinuation(RelationalPreparedStatement ps, int numRows) throws SQLException { | ||
Continuation continuation; | ||
try (final RelationalResultSet resultSet = ps.executeQuery()) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@normen662 Done!