Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6fbe5b8
Commit for Issue 3028
satishsuryanarayan Jul 15, 2025
d7da0ab
Merge branch 'main' into ss/3028
satishsuryanarayan Jul 15, 2025
ecb37aa
Merge pull request #2 from satishsuryanarayan/ss/3028
satishsuryanarayan Jul 15, 2025
d9eedc9
Remove unused import
satishsuryanarayan Jul 17, 2025
36178ea
Merge branch 'FoundationDB:main' into main
satishsuryanarayan Jul 17, 2025
d02442c
Add explain level to NormalizedQueryExecutionContext
satishsuryanarayan Jul 17, 2025
a9d07da
Merge branch 'FoundationDB:main' into main
satishsuryanarayan Jul 17, 2025
a4a2ace
Revert to version in main
satishsuryanarayan Jul 17, 2025
e6c84b0
Merge branch 'main' of https://github.com/satishsuryanarayan/fdb-reco…
satishsuryanarayan Jul 17, 2025
e5624c8
Don't overwrite explainLevel
satishsuryanarayan Jul 17, 2025
96fdbd0
Override visitDescribeStatements instead of adding explainLevel to vi…
satishsuryanarayan Jul 17, 2025
b62e176
Added tests for explains
satishsuryanarayan Jul 18, 2025
c8b54b2
Changes suggested by Norman
satishsuryanarayan Jul 23, 2025
69fd3cc
Merge branch 'FoundationDB:main' into main
satishsuryanarayan Aug 7, 2025
7a3d4cb
Explain supports two options - VERBOSE or default
satishsuryanarayan Aug 7, 2025
82fdb9e
Merge branch 'main' of https://github.com/satishsuryanarayan/fdb-reco…
satishsuryanarayan Aug 7, 2025
e1f4ba6
Merge branch 'main' into main
satishsuryanarayan Aug 22, 2025
c88d3f4
Merge branch 'FoundationDB:main' into main
satishsuryanarayan Aug 27, 2025
6b9ee1b
Fix tests related to explain queries - the default explain now return…
satishsuryanarayan Aug 27, 2025
5073518
Merge branch 'FoundationDB:main' into main
satishsuryanarayan Aug 29, 2025
e5c06e0
Updated tests to support EXPLAIN and EXPLAIN VERBOSE - currently the …
satishsuryanarayan Aug 31, 2025
ee45b0e
Merge remote-tracking branch 'origin/main'
satishsuryanarayan Aug 31, 2025
891615b
Updated tests to support EXPLAIN and EXPLAIN VERBOSE - currently the …
satishsuryanarayan Aug 31, 2025
22c95e2
Merge branch 'main' into ss/explain_verbose
satishsuryanarayan Aug 31, 2025
453db90
Fix explain tests
satishsuryanarayan Aug 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,5 @@ fdb-relational-core/src/main/antlr/*.tokens

# output during release build process
/mixed-mode-results.md

.DS_Store
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,15 @@ public static String toStringForExternalExplain(@Nonnull final RecordQueryPlan p
return toStringForExternalExplain(plan, ExplainLevel.ALL_DETAILS, Integer.MAX_VALUE);
}

@Nonnull
public static String toStringForExternalExplain(@Nonnull final RecordQueryPlan plan, final boolean isVerboseExplainLevel) {
final var visitor = new ExplainPlanVisitor(Integer.MAX_VALUE);
final var explainTokens = visitor.visit(plan);
final var explainLevel = isVerboseExplainLevel ? ExplainLevel.ALL_DETAILS : ExplainLevel.STRUCTURE;
return explainTokens.render(explainLevel, new DefaultExplainFormatter(ExplainSelfContainedSymbolMap::new), explainTokens.getMaxLength(explainLevel))
.toString();
}

@Nonnull
public static String toStringForExternalExplain(@Nonnull final RecordQueryPlan plan, final int maxExplainLevel,
final int maxSize) {
Expand Down
3 changes: 2 additions & 1 deletion fdb-relational-core/src/main/antlr/RelationalLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ USAGE: 'USAGE';
USE: 'USE';
USING: 'USING';
VALUES: 'VALUES';
VERBOSE: 'VERBOSE';
WHEN: 'WHEN';
WHERE: 'WHERE';
WHILE: 'WHILE';
Expand Down Expand Up @@ -1352,4 +1353,4 @@ fragment DECIMAL_TYPE_MODIFIER: (INT_TYPE_MODIFIER | LONG_TYPE_MODIFIER);

ERROR_RECOGNITION
: . { this.notifyListeners(new LexerNoViableAltException(this, _input, _tokenStartCharIndex, null)); }
;
;
3 changes: 2 additions & 1 deletion fdb-relational-core/src/main/antlr/RelationalParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,8 @@ helpStatement
// details

describeObjectClause
: (
: (VERBOSE)?
(
query | deleteStatement | insertStatement
| updateStatement | executeContinuationStatement
) #describeStatements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,12 @@ public Void visitFullDescribeStatement(@Nonnull RelationalParser.FullDescribeSta
return visitChildren(ctx);
}

@Override
public Void visitDescribeStatements(@Nonnull RelationalParser.DescribeStatementsContext ctx) {
queryHasherContextBuilder.setIsVerboseExplainLevel(ctx.VERBOSE() != null);
return visitChildren(ctx);
}

@Override
public Void visitLimitClause(@Nonnull RelationalParser.LimitClauseContext ctx) {
if (ctx.offset != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -43,7 +42,6 @@
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;

Expand Down Expand Up @@ -91,6 +89,8 @@ public class MutablePlanGenerationContext implements QueryExecutionContext {

private boolean forExplain;

private boolean isVerboseExplainLevel;

@Nullable
private byte[] continuation;

Expand Down Expand Up @@ -279,6 +279,7 @@ public MutablePlanGenerationContext(@Nonnull PreparedParams preparedParams,
constantObjectValues = new LinkedList<>();
shouldProcessLiteral = true;
forExplain = false;
isVerboseExplainLevel = false;
setContinuation(null);
equalityConstraints = ImmutableList.builder();
}
Expand Down Expand Up @@ -354,6 +355,10 @@ public boolean isForExplain() {
return forExplain;
}

@Override
public boolean isVerboseExplainLevel() {
return isVerboseExplainLevel;
}

@Nonnull
public QueryPlanConstraint getPlanConstraintsForLiteralReferences() {
Expand Down Expand Up @@ -390,6 +395,10 @@ public void setForExplain(boolean forExplain) {
this.forExplain = forExplain;
}

public void setIsVerboseExplainLevel(boolean isVerboseExplainLevel) {
this.isVerboseExplainLevel = isVerboseExplainLevel;
}

@Nonnull
public Value processQueryLiteral(@Nonnull Type type, @Nullable Object literal, int tokenIndex) {
return processQueryLiteralOrParameter(type, literal, null, null, tokenIndex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public final class NormalizedQueryExecutionContext implements QueryExecutionCont

private final boolean isForExplain;

private final boolean isVerboseExplainLevel;

private final int parameterHash;

@Nonnull
Expand All @@ -53,10 +55,12 @@ private NormalizedQueryExecutionContext(@Nonnull Literals literals,
@Nullable byte[] continuation,
int parameterHash,
boolean isForExplain,
boolean isVerboseExplainLevel,
@Nonnull final PlanHashable.PlanHashMode planHashMode) {
this.literals = literals;
this.continuation = continuation;
this.isForExplain = isForExplain;
this.isVerboseExplainLevel = isVerboseExplainLevel;
this.parameterHash = parameterHash;
this.planHashMode = planHashMode;
}
Expand Down Expand Up @@ -90,6 +94,11 @@ public boolean isForExplain() {
return isForExplain;
}

@Override
public boolean isVerboseExplainLevel() {
return isVerboseExplainLevel;
}

@Nonnull
@Override
public PlanHashable.PlanHashMode getPlanHashMode() {
Expand All @@ -107,6 +116,8 @@ public static final class Builder {

private boolean isForExplain;

private boolean isVerboseExplainLevel;

@Nullable
private byte[] continuation;

Expand All @@ -120,6 +131,7 @@ private Builder() {
this.isForExplain = false;
this.continuation = null;
this.planHashMode = null;
this.isVerboseExplainLevel = false;
}

@Nonnull
Expand All @@ -146,6 +158,12 @@ public Builder setForExplain(boolean isForExplain) {
return this;
}

@Nonnull
public Builder setIsVerboseExplainLevel(boolean isVerboseExplainLevel) {
this.isVerboseExplainLevel = isVerboseExplainLevel;
return this;
}

@Nonnull
public Builder setPlanHashMode(@Nonnull PlanHashable.PlanHashMode planHashMode) {
this.planHashMode = planHashMode;
Expand All @@ -155,7 +173,7 @@ public Builder setPlanHashMode(@Nonnull PlanHashable.PlanHashMode planHashMode)
@Nonnull
public NormalizedQueryExecutionContext build() {
return new NormalizedQueryExecutionContext(literalsBuilder.build(), continuation,
parameterHash, isForExplain,
parameterHash, isForExplain, isVerboseExplainLevel,
Objects.requireNonNull(planHashMode));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ default EvaluationContext getEvaluationContext() {

boolean isForExplain(); // todo (yhatem) remove.

boolean isVerboseExplainLevel();

@Nonnull
PlanHashable.PlanHashMode getPlanHashMode();
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ public PhysicalQueryPlan withExecutionContext(@Nonnull final QueryExecutionConte
public String explain() {
final var executeProperties = queryExecutionContext.getExecutionPropertiesBuilder();
List<String> explainComponents = new ArrayList<>();
explainComponents.add(ExplainPlanVisitor.toStringForExternalExplain(recordQueryPlan));
explainComponents.add(ExplainPlanVisitor.toStringForExternalExplain(recordQueryPlan, queryExecutionContext.isVerboseExplainLevel()));
if (executeProperties.getReturnedRowLimit() != ReadTransaction.ROW_LIMIT_UNLIMITED) {
explainComponents.add("(limit=" + executeProperties.getReturnedRowLimit() + ")");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,11 @@ 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().setIsVerboseExplainLevel(true);
} else {
getDelegate().getPlanGenerationContext().setIsVerboseExplainLevel(false);
}
final var logicalOperator = Assert.castUnchecked(ctx.describeObjectClause().accept(this), LogicalOperator.class);
return QueryPlan.LogicalQueryPlan.of(logicalOperator.getQuantifier().getRangesOver().get(), getDelegate().getPlanGenerationContext(), "TODO");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ void testRelationalConnectionSetLogIsOverriddenByExecuteContinuationQueryOption(
rs.next();
}
Assertions.assertThat(logAppender.getLogEvents()).isNotEmpty();
Assertions.assertThat(logAppender.getLastLogEventMessage()).contains("plan=\"COVERING(RECORD_TYPE_COVERING <,> -> [NAME: VALUE[0], REST_NO: KEY[0]]) | MAP (_.NAME AS NAME)\"");
Assertions.assertThat(logAppender.getLastLogEventMessage()).contains("plan=\"COVERING(RECORD_TYPE_COVERING) | MAP (_.NAME AS NAME)\"");
}
}
}
Expand Down Expand Up @@ -276,7 +276,7 @@ void testRelationalConnectionSetLogWithExecuteContinuation(boolean setLogging) t
Assertions.assertThat(logAppender.getLogEvents()).isEmpty();
} else {
Assertions.assertThat(logAppender.getLogEvents()).isNotEmpty();
Assertions.assertThat(logAppender.getLastLogEventMessage()).contains("plan=\"COVERING(RECORD_TYPE_COVERING <,> -> [NAME: VALUE[0], REST_NO: KEY[0]]) | MAP (_.NAME AS NAME)\"");
Assertions.assertThat(logAppender.getLastLogEventMessage()).contains("plan=\"COVERING(RECORD_TYPE_COVERING) | MAP (_.NAME AS NAME)\"");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ void explainWithNoContinuationTest() throws Exception {
try (final RelationalResultSet resultSet = ps.executeQuery()) {
final var assertResult = ResultSetAssert.assertThat(resultSet);
assertResult.hasNextRow()
.hasColumn("PLAN", "ISCAN(RECORD_NAME_IDX <,>)")
.hasColumn("PLAN", "ISCAN(RECORD_NAME_IDX)")
.hasColumn("PLAN_HASH", -1635569052)
.hasColumn("PLAN_CONTINUATION", null);
assertResult.hasNoNextRow();
Expand All @@ -135,7 +135,7 @@ void explainWithContinuationNoSerializedPlanTest() throws Exception {
final var assertResult = ResultSetAssert.assertThat(resultSet);

assertResult.hasNextRow()
.hasColumn("PLAN", "ISCAN(RECORD_NAME_IDX <,>)")
.hasColumn("PLAN", "ISCAN(RECORD_NAME_IDX)")
.hasColumn("PLAN_HASH", -1635569052);
final var continuationInfo = resultSet.getStruct(5);
org.junit.jupiter.api.Assertions.assertNotNull(continuationInfo);
Expand Down Expand Up @@ -168,7 +168,7 @@ void explainWithContinuationSerializedPlanTest() throws Exception {
try (final RelationalResultSet resultSet = ps.executeQuery()) {
final var assertResult = ResultSetAssert.assertThat(resultSet);
assertResult.hasNextRow()
.hasColumn("PLAN", "ISCAN(RECORD_NAME_IDX <,>)")
.hasColumn("PLAN", "ISCAN(RECORD_NAME_IDX)")
.hasColumn("PLAN_HASH", -1635569052);
final var continuationInfo = resultSet.getStruct(5);
org.junit.jupiter.api.Assertions.assertNotNull(continuationInfo);
Expand Down Expand Up @@ -196,7 +196,7 @@ void explainWithContinuationSerializedPlanWithDifferentQueryTest() throws Except
continuation = consumeResultAndGetContinuation(ps, 2);
}

try (RelationalPreparedStatement ps = connection.prepareStatement("EXPLAIN SELECT rest_no FROM RestaurantComplexRecord WITH CONTINUATION ?cont")) {
try (RelationalPreparedStatement ps = connection.prepareStatement("EXPLAIN VERBOSE SELECT rest_no FROM RestaurantComplexRecord WITH CONTINUATION ?cont")) {
ps.setObject("cont", continuation.serialize());
try (final RelationalResultSet resultSet = ps.executeQuery()) {
final var assertResult = ResultSetAssert.assertThat(resultSet);
Expand Down Expand Up @@ -234,7 +234,7 @@ void explainExecuteStatementTest() throws Exception {
try (final RelationalResultSet resultSet = ps.executeQuery()) {
final var assertResult = ResultSetAssert.assertThat(resultSet);
assertResult.hasNextRow()
.hasColumn("PLAN", "ISCAN(RECORD_NAME_IDX <,>)")
.hasColumn("PLAN", "ISCAN(RECORD_NAME_IDX)")
.hasColumn("PLAN_HASH", -1635569052);
final var continuationInfo = resultSet.getStruct(5);
org.junit.jupiter.api.Assertions.assertNotNull(continuationInfo);
Expand All @@ -251,6 +251,42 @@ 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", -1697137247)
.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", -1697137247)
.hasColumn("PLAN_CONTINUATION", null);
assertResult.hasNoNextRow();
}
}
}
}

private Continuation consumeResultAndGetContinuation(RelationalPreparedStatement ps, int numRows) throws SQLException {
Continuation continuation;
try (final RelationalResultSet resultSet = ps.executeQuery()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,12 @@ void isNullPredicateUsesGroupIndex() throws Exception {
Assertions.assertTrue(statement.execute("EXPLAIN SELECT sum(c) FROM T1 WHERE a is null"), "Did not return a result set from a select statement!");
try (final RelationalResultSet resultSet = statement.getResultSet()) {
ResultSetAssert.assertThat(resultSet).hasNextRow()
.hasColumn("PLAN", "AISCAN(SUM_IDX [[null],[null]] BY_GROUP -> [_0: KEY:[0], _1: VALUE:[0]]) | MAP ((_._1 AS _0) AS _0) | ON EMPTY NULL | MAP (_._0._0 AS _0)");
.hasColumn("PLAN", "AISCAN(SUM_IDX -> [_0: KEY:[0], _1: VALUE:[0]]) | MAP ((_._1 AS _0) AS _0) | ON EMPTY NULL | MAP (_._0._0 AS _0)");
}
Assertions.assertTrue(statement.execute("EXPLAIN SELECT sum(c) FROM T1 WHERE a = 1"), "Did not return a result set from a select statement!");
try (final RelationalResultSet resultSet = statement.getResultSet()) {
ResultSetAssert.assertThat(resultSet).hasNextRow()
.hasColumn("PLAN", "AISCAN(SUM_IDX [EQUALS promote(@c11 AS LONG)] BY_GROUP -> [_0: KEY:[0], _1: VALUE:[0]]) | MAP ((_._1 AS _0) AS _0) | ON EMPTY NULL | MAP (_._0._0 AS _0)");
.hasColumn("PLAN", "AISCAN(SUM_IDX -> [_0: KEY:[0], _1: VALUE:[0]]) | MAP ((_._1 AS _0) AS _0) | ON EMPTY NULL | MAP (_._0._0 AS _0)");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ void explainTableScan() throws Exception {
try (final RelationalResultSet resultSet = statement.executeQuery("EXPLAIN SELECT * FROM RestaurantComplexRecord WHERE rest_no > 10")) {
resultSet.next();
String plan = resultSet.getString(1);
assertThat(plan).matches("(.*SCAN.*RESTAURANTCOMPLEXRECORD|.*COVERING.* <,>).*REST_NO GREATER_THAN promote\\(@c8 AS LONG\\).*");
assertThat(plan).matches("(.*RESTAURANTCOMPLEXRECORD|.*COVERING.*).*REST_NO GREATER_THAN promote\\(@c8 AS LONG\\).*");
}
}
}
Expand All @@ -259,7 +259,7 @@ void explainUnhintedIndexScan() throws Exception {
try (final RelationalResultSet resultSet = statement.executeQuery("EXPLAIN SELECT * FROM RestaurantComplexRecord AS R WHERE EXISTS (SELECT * FROM R.reviews AS RE WHERE RE.rating >= 9)")) {
resultSet.next();
String plan = resultSet.getString(1);
assertThat(plan).matches(".*ISCAN.*MV1.*\\[\\[GREATER_THAN_OR_EQUALS promote\\(@c24 AS LONG\\)\\]\\].*");
assertThat(plan).matches(".*ISCAN.*MV1.*");
}
}
}
Expand Down
Loading
Loading