You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by ap...@apache.org on 2026/06/08 15:44:25 UTC
(phoenix) 04/08: [WIP] Convert full-text EXPLAIN ITs to fluent API; reshape SMJ + add clientSteps
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch PHOENIX-7876-feature
in repository https://gitbox.apache.org/repos/asf/phoenix.git
commit 07a8afcb3304de501818866f07e0926260b72a61
Author: Andrew Purtell <ap...@apache.org>
AuthorDate: Sat Jun 6 00:24:41 2026 -0700
[WIP] Convert full-text EXPLAIN ITs to fluent API; reshape SMJ + add clientSteps
Convert the four remaining full-text EXPLAIN ITs to pure fluent assertPlan
assertions.
In ExplainPlanAttributs sort-merge-join is reshaped into a synthetic root
carrying the join operator with separate lhs and rhs children. Every node
carries an ordered clientSteps list. Production builders populate the new
fields. The emitted text is unchanged.
---
.../phoenix/compile/ExplainPlanAttributes.java | 61 +++-
.../phoenix/execute/ClientAggregatePlan.java | 77 +++--
.../org/apache/phoenix/execute/ClientScanPlan.java | 30 +-
.../apache/phoenix/execute/SortMergeJoinPlan.java | 20 +-
.../phoenix/execute/TupleProjectionPlan.java | 4 +-
.../phoenix/iterate/CursorResultIterator.java | 4 +-
.../iterate/DistinctAggregatingResultIterator.java | 4 +-
.../iterate/FilterAggregatingResultIterator.java | 4 +-
.../phoenix/iterate/FilterResultIterator.java | 4 +-
.../phoenix/iterate/LimitingResultIterator.java | 4 +-
.../iterate/MergeSortRowKeyResultIterator.java | 1 +
.../iterate/MergeSortTopNResultIterator.java | 9 +-
.../phoenix/iterate/OffsetResultIterator.java | 4 +-
.../phoenix/iterate/OrderedResultIterator.java | 6 +-
.../phoenix/iterate/SegmentResultIterator.java | 1 +
.../phoenix/iterate/SequenceResultIterator.java | 6 +-
.../phoenix/end2end/CostBasedDecisionIT.java | 379 +++++++++------------
.../org/apache/phoenix/end2end/DerivedTableIT.java | 108 +++---
.../phoenix/end2end/SortMergeJoinMoreIT.java | 6 +-
.../phoenix/end2end/TenantSpecificViewIndexIT.java | 41 +--
.../org/apache/phoenix/end2end/UnionAllIT.java | 107 ++----
.../end2end/join/SortMergeJoinGlobalIndexIT.java | 35 +-
.../end2end/join/SortMergeJoinLocalIndexIT.java | 30 +-
.../end2end/join/SortMergeJoinNoIndexIT.java | 20 +-
.../end2end/join/SubqueryUsingSortMergeJoinIT.java | 6 +-
.../query/explain/ExplainJsonNormalizer.java | 5 +
.../phoenix/query/explain/ExplainPlanTest.java | 81 ++++-
.../phoenix/query/explain/ExplainPlanTestUtil.java | 34 ++
28 files changed, 578 insertions(+), 513 deletions(-)
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java
index 9f69b2bc73..5bc1fe53b7 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java
@@ -19,6 +19,8 @@ package org.apache.phoenix.compile;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.apache.hadoop.hbase.HRegionLocation;
@@ -27,11 +29,6 @@ import org.apache.phoenix.parse.HintNode;
import org.apache.phoenix.parse.HintNode.Hint;
import org.apache.phoenix.schema.PColumn;
-/**
- * ExplainPlan attributes that contain individual attributes of ExplainPlan that we can assert
- * against. This also makes attribute retrieval easier as an API rather than retrieving list of
- * Strings containing entire plan.
- */
@JsonPropertyOrder({ "abstractExplainPlan", "hint", "explainScanType", "consistency", "tableName",
"keyRanges", "scanTimeRangeMin", "scanTimeRangeMax", "splitsChunk", "useRoundRobinIterator",
"samplingRate", "hexStringRVCOffset", "iteratorTypeAndScanSize", "estimatedRows",
@@ -41,8 +38,9 @@ import org.apache.phoenix.schema.PColumn;
"serverGroupByLimit", "serverSortedBy", "serverOffset", "serverRowLimit", "clientFilterBy",
"clientAggregate", "clientDistinctFilter", "clientAfterAggregate", "clientSortAlgo",
"clientSortedBy", "clientOffset", "clientRowLimit", "clientSequenceCount", "clientCursorName",
- "rhsJoinQueryExplainPlan", "subPlans", "dynamicServerFilter", "afterJoinFilter",
- "joinScannerLimit", "sortMergeSkipMerge", "regionLocations", "numRegionLocationLookups" })
+ "clientSteps", "lhsJoinQueryExplainPlan", "rhsJoinQueryExplainPlan", "subPlans",
+ "dynamicServerFilter", "afterJoinFilter", "joinScannerLimit", "sortMergeSkipMerge",
+ "regionLocations", "numRegionLocationLookups" })
public class ExplainPlanAttributes {
// Plan identity and scan-level metadata
@@ -92,8 +90,11 @@ public class ExplainPlanAttributes {
private final Integer clientRowLimit;
private final Integer clientSequenceCount;
private final String clientCursorName;
+ // Ordered client-side pipeline (trimmed CLIENT* lines in emission order).
+ private final List<String> clientSteps;
// Join / sub-plan
+ private final ExplainPlanAttributes lhsJoinQueryExplainPlan;
private final ExplainPlanAttributes rhsJoinQueryExplainPlan;
private final List<ExplainPlanAttributes> subPlans;
private final String dynamicServerFilter;
@@ -148,6 +149,8 @@ public class ExplainPlanAttributes {
this.clientRowLimit = null;
this.clientSequenceCount = null;
this.clientCursorName = null;
+ this.clientSteps = null;
+ this.lhsJoinQueryExplainPlan = null;
this.rhsJoinQueryExplainPlan = null;
this.subPlans = null;
this.dynamicServerFilter = null;
@@ -170,6 +173,7 @@ public class ExplainPlanAttributes {
String clientFilterBy, String clientAggregate, String clientDistinctFilter,
String clientAfterAggregate, String clientSortAlgo, String clientSortedBy, Integer clientOffset,
Integer clientRowLimit, Integer clientSequenceCount, String clientCursorName,
+ List<String> clientSteps, ExplainPlanAttributes lhsJoinQueryExplainPlan,
ExplainPlanAttributes rhsJoinQueryExplainPlan, List<ExplainPlanAttributes> subPlans,
String dynamicServerFilter, String afterJoinFilter, Long joinScannerLimit,
boolean sortMergeSkipMerge, List<HRegionLocation> regionLocations,
@@ -214,6 +218,10 @@ public class ExplainPlanAttributes {
this.clientRowLimit = clientRowLimit;
this.clientSequenceCount = clientSequenceCount;
this.clientCursorName = clientCursorName;
+ this.clientSteps = (clientSteps == null || clientSteps.isEmpty())
+ ? null
+ : Collections.unmodifiableList(new ArrayList<>(clientSteps));
+ this.lhsJoinQueryExplainPlan = lhsJoinQueryExplainPlan;
this.rhsJoinQueryExplainPlan = rhsJoinQueryExplainPlan;
this.subPlans = subPlans;
this.dynamicServerFilter = dynamicServerFilter;
@@ -385,6 +393,14 @@ public class ExplainPlanAttributes {
return clientCursorName;
}
+ public List<String> getClientSteps() {
+ return clientSteps;
+ }
+
+ public ExplainPlanAttributes getLhsJoinQueryExplainPlan() {
+ return lhsJoinQueryExplainPlan;
+ }
+
public ExplainPlanAttributes getRhsJoinQueryExplainPlan() {
return rhsJoinQueryExplainPlan;
}
@@ -463,6 +479,8 @@ public class ExplainPlanAttributes {
private Integer clientRowLimit;
private Integer clientSequenceCount;
private String clientCursorName;
+ private List<String> clientSteps;
+ private ExplainPlanAttributes lhsJoinQueryExplainPlan;
private ExplainPlanAttributes rhsJoinQueryExplainPlan;
private List<ExplainPlanAttributes> subPlans;
private String dynamicServerFilter;
@@ -518,6 +536,9 @@ public class ExplainPlanAttributes {
this.clientRowLimit = explainPlanAttributes.getClientRowLimit();
this.clientSequenceCount = explainPlanAttributes.getClientSequenceCount();
this.clientCursorName = explainPlanAttributes.getClientCursorName();
+ List<String> srcClientSteps = explainPlanAttributes.getClientSteps();
+ this.clientSteps = srcClientSteps == null ? null : new ArrayList<>(srcClientSteps);
+ this.lhsJoinQueryExplainPlan = explainPlanAttributes.getLhsJoinQueryExplainPlan();
this.rhsJoinQueryExplainPlan = explainPlanAttributes.getRhsJoinQueryExplainPlan();
this.subPlans = explainPlanAttributes.getSubPlans();
this.dynamicServerFilter = explainPlanAttributes.getDynamicServerFilter();
@@ -731,6 +752,25 @@ public class ExplainPlanAttributes {
return this;
}
+ public ExplainPlanAttributesBuilder setClientSteps(List<String> clientSteps) {
+ this.clientSteps = clientSteps == null ? null : new ArrayList<>(clientSteps);
+ return this;
+ }
+
+ public ExplainPlanAttributesBuilder addClientStep(String step) {
+ if (this.clientSteps == null) {
+ this.clientSteps = new ArrayList<>();
+ }
+ this.clientSteps.add(step);
+ return this;
+ }
+
+ public ExplainPlanAttributesBuilder
+ setLhsJoinQueryExplainPlan(ExplainPlanAttributes lhsJoinQueryExplainPlan) {
+ this.lhsJoinQueryExplainPlan = lhsJoinQueryExplainPlan;
+ return this;
+ }
+
public ExplainPlanAttributesBuilder
setRhsJoinQueryExplainPlan(ExplainPlanAttributes rhsJoinQueryExplainPlan) {
this.rhsJoinQueryExplainPlan = rhsJoinQueryExplainPlan;
@@ -781,9 +821,10 @@ public class ExplainPlanAttributes {
serverDistinctFilter, serverMergeColumns, serverArrayElementProjection, serverAggregate,
serverGroupByLimit, serverSortedBy, serverOffset, serverRowLimit, clientFilterBy,
clientAggregate, clientDistinctFilter, clientAfterAggregate, clientSortAlgo, clientSortedBy,
- clientOffset, clientRowLimit, clientSequenceCount, clientCursorName,
- rhsJoinQueryExplainPlan, subPlans, dynamicServerFilter, afterJoinFilter, joinScannerLimit,
- sortMergeSkipMerge, regionLocations, numRegionLocationLookups);
+ clientOffset, clientRowLimit, clientSequenceCount, clientCursorName, clientSteps,
+ lhsJoinQueryExplainPlan, rhsJoinQueryExplainPlan, subPlans, dynamicServerFilter,
+ afterJoinFilter, joinScannerLimit, sortMergeSkipMerge, regionLocations,
+ numRegionLocationLookups);
}
}
}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/ClientAggregatePlan.java b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/ClientAggregatePlan.java
index e89bf515cc..4a3d1b5d28 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/ClientAggregatePlan.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/ClientAggregatePlan.java
@@ -226,63 +226,86 @@ public class ClientAggregatePlan extends ClientProcessingPlan {
ExplainPlanAttributesBuilder newBuilder =
new ExplainPlanAttributesBuilder(explainPlanAttributes);
if (where != null) {
- planSteps.add("CLIENT FILTER BY " + where.toString());
+ String step = "CLIENT FILTER BY " + where.toString();
+ planSteps.add(step);
newBuilder.setClientFilterBy(where.toString());
+ newBuilder.addClientStep(step);
}
if (groupBy.isEmpty()) {
- planSteps.add("CLIENT AGGREGATE INTO SINGLE ROW");
- newBuilder.setClientAggregate("CLIENT AGGREGATE INTO SINGLE ROW");
+ String step = "CLIENT AGGREGATE INTO SINGLE ROW";
+ planSteps.add(step);
+ newBuilder.setClientAggregate(step);
+ newBuilder.addClientStep(step);
} else if (groupBy.isOrderPreserving()) {
- planSteps.add(
- "CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY " + groupBy.getExpressions().toString());
- newBuilder.setClientAggregate(
- "CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY " + groupBy.getExpressions().toString());
+ String step =
+ "CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY " + groupBy.getExpressions().toString();
+ planSteps.add(step);
+ newBuilder.setClientAggregate(step);
+ newBuilder.addClientStep(step);
} else if (useHashAgg) {
- planSteps
- .add("CLIENT HASH AGGREGATE INTO DISTINCT ROWS BY " + groupBy.getExpressions().toString());
- newBuilder.setClientAggregate(
- "CLIENT HASH AGGREGATE INTO DISTINCT ROWS BY " + groupBy.getExpressions().toString());
+ String step =
+ "CLIENT HASH AGGREGATE INTO DISTINCT ROWS BY " + groupBy.getExpressions().toString();
+ planSteps.add(step);
+ newBuilder.setClientAggregate(step);
+ newBuilder.addClientStep(step);
if (orderBy == OrderBy.FWD_ROW_KEY_ORDER_BY || orderBy == OrderBy.REV_ROW_KEY_ORDER_BY) {
- planSteps.add("CLIENT SORTED BY " + groupBy.getKeyExpressions().toString());
+ String sortStep = "CLIENT SORTED BY " + groupBy.getKeyExpressions().toString();
+ planSteps.add(sortStep);
newBuilder.setClientSortedBy(groupBy.getKeyExpressions().toString());
+ newBuilder.addClientStep(sortStep);
}
} else {
- planSteps.add("CLIENT SORTED BY " + groupBy.getKeyExpressions().toString());
- planSteps
- .add("CLIENT AGGREGATE INTO DISTINCT ROWS BY " + groupBy.getExpressions().toString());
+ String sortStep = "CLIENT SORTED BY " + groupBy.getKeyExpressions().toString();
+ String aggStep =
+ "CLIENT AGGREGATE INTO DISTINCT ROWS BY " + groupBy.getExpressions().toString();
+ planSteps.add(sortStep);
+ planSteps.add(aggStep);
newBuilder.setClientSortedBy(groupBy.getKeyExpressions().toString());
- newBuilder.setClientAggregate(
- "CLIENT AGGREGATE INTO DISTINCT ROWS BY " + groupBy.getExpressions().toString());
+ newBuilder.setClientAggregate(aggStep);
+ newBuilder.addClientStep(sortStep);
+ newBuilder.addClientStep(aggStep);
}
if (having != null) {
- planSteps.add("CLIENT AFTER-AGGREGATION FILTER BY " + having.toString());
- newBuilder.setClientAfterAggregate("CLIENT AFTER-AGGREGATION FILTER BY " + having.toString());
+ String step = "CLIENT AFTER-AGGREGATION FILTER BY " + having.toString();
+ planSteps.add(step);
+ newBuilder.setClientAfterAggregate(step);
+ newBuilder.addClientStep(step);
}
if (statement.isDistinct() && statement.isAggregate()) {
- planSteps.add("CLIENT DISTINCT ON " + projector.toString());
+ String step = "CLIENT DISTINCT ON " + projector.toString();
+ planSteps.add(step);
newBuilder.setClientDistinctFilter(projector.toString());
+ newBuilder.addClientStep(step);
}
if (offset != null) {
- planSteps.add("CLIENT OFFSET " + offset);
+ String step = "CLIENT OFFSET " + offset;
+ planSteps.add(step);
newBuilder.setClientOffset(offset);
+ newBuilder.addClientStep(step);
}
if (orderBy.getOrderByExpressions().isEmpty()) {
if (limit != null) {
- planSteps.add("CLIENT " + limit + " ROW LIMIT");
+ String step = "CLIENT " + limit + " ROW LIMIT";
+ planSteps.add(step);
newBuilder.setClientRowLimit(limit);
+ newBuilder.addClientStep(step);
}
} else {
- planSteps
- .add("CLIENT" + (limit == null ? "" : " TOP " + limit + " ROW" + (limit == 1 ? "" : "S"))
- + " SORTED BY " + orderBy.getOrderByExpressions().toString());
+ String step =
+ "CLIENT" + (limit == null ? "" : " TOP " + limit + " ROW" + (limit == 1 ? "" : "S"))
+ + " SORTED BY " + orderBy.getOrderByExpressions().toString();
+ planSteps.add(step);
newBuilder.setClientRowLimit(limit);
newBuilder.setClientSortedBy(orderBy.getOrderByExpressions().toString());
+ newBuilder.addClientStep(step);
}
if (context.getSequenceManager().getSequenceCount() > 0) {
int nSequences = context.getSequenceManager().getSequenceCount();
- planSteps.add(
- "CLIENT RESERVE VALUES FROM " + nSequences + " SEQUENCE" + (nSequences == 1 ? "" : "S"));
+ String step =
+ "CLIENT RESERVE VALUES FROM " + nSequences + " SEQUENCE" + (nSequences == 1 ? "" : "S");
+ planSteps.add(step);
newBuilder.setClientSequenceCount(nSequences);
+ newBuilder.addClientStep(step);
}
return new ExplainPlan(planSteps, newBuilder.build());
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/ClientScanPlan.java b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/ClientScanPlan.java
index 8bc7198960..9807ba8ef9 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/ClientScanPlan.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/ClientScanPlan.java
@@ -123,34 +123,46 @@ public class ClientScanPlan extends ClientProcessingPlan {
ExplainPlanAttributesBuilder newBuilder =
new ExplainPlanAttributesBuilder(explainPlanAttributes);
if (where != null) {
- planSteps.add("CLIENT FILTER BY " + where.toString());
+ String step = "CLIENT FILTER BY " + where.toString();
+ planSteps.add(step);
newBuilder.setClientFilterBy(where.toString());
+ newBuilder.addClientStep(step);
}
if (!orderBy.getOrderByExpressions().isEmpty()) {
if (offset != null) {
- planSteps.add("CLIENT OFFSET " + offset);
+ String step = "CLIENT OFFSET " + offset;
+ planSteps.add(step);
newBuilder.setClientOffset(offset);
+ newBuilder.addClientStep(step);
}
- planSteps
- .add("CLIENT" + (limit == null ? "" : " TOP " + limit + " ROW" + (limit == 1 ? "" : "S"))
- + " SORTED BY " + orderBy.getOrderByExpressions().toString());
+ String step =
+ "CLIENT" + (limit == null ? "" : " TOP " + limit + " ROW" + (limit == 1 ? "" : "S"))
+ + " SORTED BY " + orderBy.getOrderByExpressions().toString();
+ planSteps.add(step);
newBuilder.setClientRowLimit(limit);
newBuilder.setClientSortedBy(orderBy.getOrderByExpressions().toString());
+ newBuilder.addClientStep(step);
} else {
if (offset != null) {
- planSteps.add("CLIENT OFFSET " + offset);
+ String step = "CLIENT OFFSET " + offset;
+ planSteps.add(step);
newBuilder.setClientOffset(offset);
+ newBuilder.addClientStep(step);
}
if (limit != null) {
- planSteps.add("CLIENT " + limit + " ROW LIMIT");
+ String step = "CLIENT " + limit + " ROW LIMIT";
+ planSteps.add(step);
newBuilder.setClientRowLimit(limit);
+ newBuilder.addClientStep(step);
}
}
if (context.getSequenceManager().getSequenceCount() > 0) {
int nSequences = context.getSequenceManager().getSequenceCount();
- planSteps.add(
- "CLIENT RESERVE VALUES FROM " + nSequences + " SEQUENCE" + (nSequences == 1 ? "" : "S"));
+ String step =
+ "CLIENT RESERVE VALUES FROM " + nSequences + " SEQUENCE" + (nSequences == 1 ? "" : "S");
+ planSteps.add(step);
newBuilder.setClientSequenceCount(nSequences);
+ newBuilder.addClientStep(step);
}
return new ExplainPlan(planSteps, newBuilder.build());
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java
index ea210c09a4..9e1cd2290b 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java
@@ -192,11 +192,6 @@ public class SortMergeJoinPlan implements QueryPlan {
ExplainPlan lhsExplainPlan = lhsPlan.getExplainPlan();
List<String> lhsPlanSteps = lhsExplainPlan.getPlanSteps();
ExplainPlanAttributes lhsPlanAttributes = lhsExplainPlan.getPlanStepsAsAttributes();
- ExplainPlanAttributesBuilder lhsPlanBuilder =
- new ExplainPlanAttributesBuilder(lhsPlanAttributes);
- lhsPlanBuilder
- .setAbstractExplainPlan("SORT-MERGE-JOIN (" + joinType.toString().toUpperCase() + ")");
- lhsPlanBuilder.setSortMergeSkipMerge(rhsSchema.getFieldCount() == 0);
for (String step : lhsPlanSteps) {
steps.add(" " + step);
@@ -206,15 +201,20 @@ public class SortMergeJoinPlan implements QueryPlan {
ExplainPlan rhsExplainPlan = rhsPlan.getExplainPlan();
List<String> rhsPlanSteps = rhsExplainPlan.getPlanSteps();
ExplainPlanAttributes rhsPlanAttributes = rhsExplainPlan.getPlanStepsAsAttributes();
- ExplainPlanAttributesBuilder rhsPlanBuilder =
- new ExplainPlanAttributesBuilder(rhsPlanAttributes);
-
- lhsPlanBuilder.setRhsJoinQueryExplainPlan(rhsPlanBuilder.build());
for (String step : rhsPlanSteps) {
steps.add(" " + step);
}
- return new ExplainPlan(steps, lhsPlanBuilder.build());
+
+ // Build a synthetic root that holds the join operator and its two operands as separate
+ // child plans so nested sort-merge-joins can be represented.
+ ExplainPlanAttributesBuilder rootBuilder = new ExplainPlanAttributesBuilder();
+ rootBuilder
+ .setAbstractExplainPlan("SORT-MERGE-JOIN (" + joinType.toString().toUpperCase() + ")");
+ rootBuilder.setSortMergeSkipMerge(rhsSchema.getFieldCount() == 0);
+ rootBuilder.setLhsJoinQueryExplainPlan(lhsPlanAttributes);
+ rootBuilder.setRhsJoinQueryExplainPlan(rhsPlanAttributes);
+ return new ExplainPlan(steps, rootBuilder.build());
}
@Override
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/TupleProjectionPlan.java b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/TupleProjectionPlan.java
index ac38657f18..413f046d22 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/TupleProjectionPlan.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/TupleProjectionPlan.java
@@ -147,10 +147,12 @@ public class TupleProjectionPlan extends DelegateQueryPlan {
List<String> planSteps = Lists.newArrayList(explainPlan.getPlanSteps());
ExplainPlanAttributes explainPlanAttributes = explainPlan.getPlanStepsAsAttributes();
if (postFilter != null) {
- planSteps.add("CLIENT FILTER BY " + postFilter.toString());
+ String step = "CLIENT FILTER BY " + postFilter.toString();
+ planSteps.add(step);
ExplainPlanAttributesBuilder newBuilder =
new ExplainPlanAttributesBuilder(explainPlanAttributes);
newBuilder.setClientFilterBy(postFilter.toString());
+ newBuilder.addClientStep(step);
explainPlanAttributes = newBuilder.build();
}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/CursorResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/CursorResultIterator.java
index 9c44579366..113c6fad45 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/CursorResultIterator.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/CursorResultIterator.java
@@ -60,7 +60,9 @@ public class CursorResultIterator implements ResultIterator {
ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
delegate.explain(planSteps, explainPlanAttributesBuilder);
explainPlanAttributesBuilder.setClientCursorName(cursorName);
- planSteps.add("CLIENT CURSOR " + cursorName);
+ String step = "CLIENT CURSOR " + cursorName;
+ planSteps.add(step);
+ explainPlanAttributesBuilder.addClientStep(step);
}
@Override
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/DistinctAggregatingResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/DistinctAggregatingResultIterator.java
index 10f081c8a4..6dfdfb8857 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/DistinctAggregatingResultIterator.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/DistinctAggregatingResultIterator.java
@@ -134,7 +134,9 @@ public class DistinctAggregatingResultIterator implements AggregatingResultItera
ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
targetAggregatingResultIterator.explain(planSteps, explainPlanAttributesBuilder);
explainPlanAttributesBuilder.setClientDistinctFilter(rowProjector.toString());
- planSteps.add("CLIENT DISTINCT ON " + rowProjector.toString());
+ String step = "CLIENT DISTINCT ON " + rowProjector.toString();
+ planSteps.add(step);
+ explainPlanAttributesBuilder.addClientStep(step);
}
@Override
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/FilterAggregatingResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/FilterAggregatingResultIterator.java
index fb3609066c..82d6ba6824 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/FilterAggregatingResultIterator.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/FilterAggregatingResultIterator.java
@@ -82,7 +82,9 @@ public class FilterAggregatingResultIterator implements AggregatingResultIterato
ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
delegate.explain(planSteps, explainPlanAttributesBuilder);
explainPlanAttributesBuilder.setClientFilterBy(expression.toString());
- planSteps.add("CLIENT FILTER BY " + expression.toString());
+ String step = "CLIENT FILTER BY " + expression.toString();
+ planSteps.add(step);
+ explainPlanAttributesBuilder.addClientStep(step);
}
@Override
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/FilterResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/FilterResultIterator.java
index f6631dee43..9435f20464 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/FilterResultIterator.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/FilterResultIterator.java
@@ -80,7 +80,9 @@ public class FilterResultIterator extends LookAheadResultIterator {
ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
delegate.explain(planSteps, explainPlanAttributesBuilder);
explainPlanAttributesBuilder.setClientFilterBy(expression.toString());
- planSteps.add("CLIENT FILTER BY " + expression.toString());
+ String step = "CLIENT FILTER BY " + expression.toString();
+ planSteps.add(step);
+ explainPlanAttributesBuilder.addClientStep(step);
}
@Override
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/LimitingResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/LimitingResultIterator.java
index 0d60c3b04d..1a90c87d3b 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/LimitingResultIterator.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/LimitingResultIterator.java
@@ -55,7 +55,9 @@ public class LimitingResultIterator extends DelegateResultIterator {
ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
super.explain(planSteps, explainPlanAttributesBuilder);
explainPlanAttributesBuilder.setClientRowLimit(limit);
- planSteps.add("CLIENT " + limit + " ROW LIMIT");
+ String step = "CLIENT " + limit + " ROW LIMIT";
+ planSteps.add(step);
+ explainPlanAttributesBuilder.addClientStep(step);
}
@Override
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/MergeSortRowKeyResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/MergeSortRowKeyResultIterator.java
index 2412c2a092..7691504d93 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/MergeSortRowKeyResultIterator.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/MergeSortRowKeyResultIterator.java
@@ -59,6 +59,7 @@ public class MergeSortRowKeyResultIterator extends MergeSortResultIterator {
resultIterators.explain(planSteps, explainPlanAttributesBuilder);
explainPlanAttributesBuilder.setClientSortAlgo("CLIENT MERGE SORT");
planSteps.add("CLIENT MERGE SORT");
+ explainPlanAttributesBuilder.addClientStep("CLIENT MERGE SORT");
}
@Override
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java
index 2c72682e19..767656a950 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java
@@ -116,13 +116,18 @@ public class MergeSortTopNResultIterator extends MergeSortResultIterator {
resultIterators.explain(planSteps, explainPlanAttributesBuilder);
explainPlanAttributesBuilder.setClientSortAlgo("CLIENT MERGE SORT");
planSteps.add("CLIENT MERGE SORT");
+ explainPlanAttributesBuilder.addClientStep("CLIENT MERGE SORT");
if (offset > 0) {
explainPlanAttributesBuilder.setClientOffset(offset);
- planSteps.add("CLIENT OFFSET " + offset);
+ String step = "CLIENT OFFSET " + offset;
+ planSteps.add(step);
+ explainPlanAttributesBuilder.addClientStep(step);
}
if (limit > 0) {
explainPlanAttributesBuilder.setClientRowLimit(limit);
- planSteps.add("CLIENT LIMIT " + limit);
+ String step = "CLIENT LIMIT " + limit;
+ planSteps.add(step);
+ explainPlanAttributesBuilder.addClientStep(step);
}
}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/OffsetResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/OffsetResultIterator.java
index bc092a0a2e..fc369eed25 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/OffsetResultIterator.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/OffsetResultIterator.java
@@ -92,7 +92,9 @@ public class OffsetResultIterator extends DelegateResultIterator {
ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
super.explain(planSteps, explainPlanAttributesBuilder);
explainPlanAttributesBuilder.setClientOffset(offset);
- planSteps.add("CLIENT OFFSET " + offset);
+ String step = "CLIENT OFFSET " + offset;
+ planSteps.add(step);
+ explainPlanAttributesBuilder.addClientStep(step);
}
@Override
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/OrderedResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/OrderedResultIterator.java
index 2cc3ccb3f6..1662b5b465 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/OrderedResultIterator.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/OrderedResultIterator.java
@@ -514,9 +514,11 @@ public class OrderedResultIterator implements PeekingResultIterator {
explainPlanAttributesBuilder.setClientOffset(offset);
explainPlanAttributesBuilder.setClientRowLimit(limit);
explainPlanAttributesBuilder.setClientSortedBy(orderByExpressions.toString());
- planSteps.add("CLIENT" + (offset == null || offset == 0 ? "" : " OFFSET " + offset)
+ String step = "CLIENT" + (offset == null || offset == 0 ? "" : " OFFSET " + offset)
+ (limit == null ? "" : " TOP " + limit + " ROW" + (limit == 1 ? "" : "S")) + " SORTED BY "
- + orderByExpressions.toString());
+ + orderByExpressions.toString();
+ planSteps.add(step);
+ explainPlanAttributesBuilder.addClientStep(step);
}
@Override
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/SegmentResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/SegmentResultIterator.java
index 1bed34afbd..752600a296 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/SegmentResultIterator.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/SegmentResultIterator.java
@@ -120,5 +120,6 @@ public class SegmentResultIterator extends BaseResultIterator {
public void explain(List<String> planSteps,
ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
planSteps.add("CLIENT SEGMENT SCAN");
+ explainPlanAttributesBuilder.addClientStep("CLIENT SEGMENT SCAN");
}
}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/SequenceResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/SequenceResultIterator.java
index b58ba58610..ae98654cfa 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/SequenceResultIterator.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/SequenceResultIterator.java
@@ -60,8 +60,10 @@ public class SequenceResultIterator extends DelegateResultIterator {
super.explain(planSteps, explainPlanAttributesBuilder);
int nSequences = sequenceManager.getSequenceCount();
explainPlanAttributesBuilder.setClientSequenceCount(nSequences);
- planSteps
- .add("CLIENT RESERVE VALUES FROM " + nSequences + " SEQUENCE" + (nSequences == 1 ? "" : "S"));
+ String step =
+ "CLIENT RESERVE VALUES FROM " + nSequences + " SEQUENCE" + (nSequences == 1 ? "" : "S");
+ planSteps.add(step);
+ explainPlanAttributesBuilder.addClientStep(step);
}
@Override
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CostBasedDecisionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CostBasedDecisionIT.java
index fa08004c32..02d85a4f2e 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CostBasedDecisionIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CostBasedDecisionIT.java
@@ -17,23 +17,18 @@
*/
package org.apache.phoenix.end2end;
+import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertMutationPlan;
+import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan;
import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
-import java.sql.ResultSet;
import java.util.Map;
import java.util.Properties;
-import org.apache.phoenix.compile.ExplainPlan;
-import org.apache.phoenix.compile.ExplainPlanAttributes;
-import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
import org.apache.phoenix.query.BaseTest;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.util.PropertiesUtil;
-import org.apache.phoenix.util.QueryUtil;
import org.apache.phoenix.util.ReadOnlyProps;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -79,7 +74,7 @@ public class CostBasedDecisionIT extends BaseTest {
String query =
"SELECT rowkey, c1, c2 FROM " + tableName + " where c1 LIKE 'X0%' ORDER BY rowkey";
// Use the data table plan that opts out order-by when stats are not available.
- verifyQueryPlan(query, "FULL SCAN");
+ assertPlan(conn, query).scanType("FULL SCAN");
PreparedStatement stmt =
conn.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2) VALUES (?, ?, ?)");
@@ -94,7 +89,7 @@ public class CostBasedDecisionIT extends BaseTest {
conn.createStatement().execute("UPDATE STATISTICS " + tableName);
// Use the index table plan that has a lower cost when stats become available.
- verifyQueryPlan(query, "RANGE SCAN");
+ assertPlan(conn, query).scanType("RANGE SCAN");
} finally {
conn.close();
}
@@ -116,16 +111,9 @@ public class CostBasedDecisionIT extends BaseTest {
String query =
"SELECT c1, max(rowkey), max(c2) FROM " + tableName + " where rowkey <= 'z' GROUP BY c1";
// Use the index table plan that opts out order-by when stats are not available.
- ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class)
- .optimizeQuery().getExplainPlan();
- ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes();
- assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize());
- assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType());
- assertEquals(tableName, explainPlanAttributes.getTableName());
- assertEquals(" [*] - ['z']", explainPlanAttributes.getKeyRanges());
- assertEquals("SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]",
- explainPlanAttributes.getServerAggregate());
- assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo());
+ assertPlan(conn, query).iteratorType("PARALLEL").scanType("RANGE SCAN").table(tableName)
+ .keyRanges(" [*] - ['z']").serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]")
+ .clientSortAlgo("CLIENT MERGE SORT");
PreparedStatement stmt =
conn.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2) VALUES (?, ?, ?)");
@@ -142,18 +130,11 @@ public class CostBasedDecisionIT extends BaseTest {
// Given that the range on C1 is meaningless and group-by becomes
// order-preserving if using the data table, the data table plan should
// come out as the best plan based on the costs.
- plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery()
- .getExplainPlan();
- explainPlanAttributes = plan.getPlanStepsAsAttributes();
- assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize());
- assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType());
- assertEquals(indexName + "(" + tableName + ")", explainPlanAttributes.getTableName());
- assertEquals(" [1]", explainPlanAttributes.getKeyRanges());
- assertTrue(explainPlanAttributes.isServerFirstKeyOnlyProjection());
- assertEquals("\"ROWKEY\" <= 'z'", explainPlanAttributes.getServerWhereFilter());
- assertEquals("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]",
- explainPlanAttributes.getServerAggregate());
- assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo());
+ assertPlan(conn, query).iteratorType("PARALLEL").scanType("RANGE SCAN")
+ .table(indexName + "(" + tableName + ")").keyRanges(" [1]")
+ .serverFirstKeyOnlyProjection(true).serverWhereFilter("\"ROWKEY\" <= 'z'")
+ .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]")
+ .clientSortAlgo("CLIENT MERGE SORT");
} finally {
conn.close();
}
@@ -178,16 +159,10 @@ public class CostBasedDecisionIT extends BaseTest {
String query =
"SELECT * FROM " + tableName + " where c1 BETWEEN 10 AND 20 AND c2 < 9000 AND C3 < 5000";
// Use the idx2 plan with a wider PK slot span when stats are not available.
- ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class)
- .optimizeQuery().getExplainPlan();
- ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes();
- assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize());
- assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType());
- assertEquals(indexName2 + "(" + tableName + ")", explainPlanAttributes.getTableName());
- assertEquals(" [2,*] - [2,9,000]", explainPlanAttributes.getKeyRanges());
- assertEquals("((\"C1\" >= 10 AND \"C1\" <= 20) AND TO_INTEGER(\"C3\") < 5000)",
- explainPlanAttributes.getServerWhereFilter());
- assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo());
+ assertPlan(conn, query).iteratorType("PARALLEL").scanType("RANGE SCAN")
+ .table(indexName2 + "(" + tableName + ")").keyRanges(" [2,*] - [2,9,000]")
+ .serverWhereFilter("((\"C1\" >= 10 AND \"C1\" <= 20) AND TO_INTEGER(\"C3\") < 5000)")
+ .clientSortAlgo("CLIENT MERGE SORT");
PreparedStatement stmt = conn
.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2, c3) VALUES (?, ?, ?, ?)");
@@ -202,16 +177,9 @@ public class CostBasedDecisionIT extends BaseTest {
conn.createStatement().execute("UPDATE STATISTICS " + tableName);
// Use the idx2 plan that scans less data when stats become available.
- plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery()
- .getExplainPlan();
- explainPlanAttributes = plan.getPlanStepsAsAttributes();
- assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize());
- assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType());
- assertEquals(indexName1 + "(" + tableName + ")", explainPlanAttributes.getTableName());
- assertEquals(" [1,10] - [1,20]", explainPlanAttributes.getKeyRanges());
- assertEquals("(\"C2\" < 9000 AND \"C3\" < 5000)",
- explainPlanAttributes.getServerWhereFilter());
- assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo());
+ assertPlan(conn, query).iteratorType("PARALLEL").scanType("RANGE SCAN")
+ .table(indexName1 + "(" + tableName + ")").keyRanges(" [1,10] - [1,20]")
+ .serverWhereFilter("(\"C2\" < 9000 AND \"C3\" < 5000)").clientSortAlgo("CLIENT MERGE SORT");
} finally {
conn.close();
}
@@ -236,11 +204,11 @@ public class CostBasedDecisionIT extends BaseTest {
String query = "UPSERT INTO " + tableName + " SELECT * FROM " + tableName
+ " where c1 BETWEEN 10 AND 20 AND c2 < 9000 AND C3 < 5000";
// Use the idx2 plan with a wider PK slot span when stats are not available.
- verifyQueryPlan(query,
- "UPSERT SELECT\n" + "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName2 + "(" + tableName
- + ")" + " [2,*] - [2,9,000]\n"
- + " SERVER FILTER BY ((\"C1\" >= 10 AND \"C1\" <= 20) AND TO_INTEGER(\"C3\") < 5000)\n"
- + "CLIENT MERGE SORT");
+ assertMutationPlan(conn, query).abstractExplainPlan("UPSERT SELECT").iteratorType("PARALLEL")
+ .scanType("RANGE SCAN").table(indexName2 + "(" + tableName + ")")
+ .keyRanges(" [2,*] - [2,9,000]")
+ .serverWhereFilter("((\"C1\" >= 10 AND \"C1\" <= 20) AND TO_INTEGER(\"C3\") < 5000)")
+ .clientSortAlgo("CLIENT MERGE SORT");
PreparedStatement stmt = conn
.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2, c3) VALUES (?, ?, ?, ?)");
@@ -255,10 +223,10 @@ public class CostBasedDecisionIT extends BaseTest {
conn.createStatement().execute("UPDATE STATISTICS " + tableName);
// Use the idx2 plan that scans less data when stats become available.
- verifyQueryPlan(query,
- "UPSERT SELECT\n" + "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName1 + "(" + tableName
- + ")" + " [1,10] - [1,20]\n" + " SERVER FILTER BY (\"C2\" < 9000 AND \"C3\" < 5000)\n"
- + "CLIENT MERGE SORT");
+ assertMutationPlan(conn, query).abstractExplainPlan("UPSERT SELECT").iteratorType("PARALLEL")
+ .scanType("RANGE SCAN").table(indexName1 + "(" + tableName + ")")
+ .keyRanges(" [1,10] - [1,20]").serverWhereFilter("(\"C2\" < 9000 AND \"C3\" < 5000)")
+ .clientSortAlgo("CLIENT MERGE SORT");
} finally {
conn.close();
}
@@ -283,11 +251,11 @@ public class CostBasedDecisionIT extends BaseTest {
String query =
"DELETE FROM " + tableName + " where c1 BETWEEN 10 AND 20 AND c2 < 9000 AND C3 < 5000";
// Use the idx2 plan with a wider PK slot span when stats are not available.
- verifyQueryPlan(query,
- "DELETE ROWS CLIENT SELECT\n" + "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName2 + "("
- + tableName + ")" + " [2,*] - [2,9,000]\n"
- + " SERVER FILTER BY ((\"C1\" >= 10 AND \"C1\" <= 20) AND TO_INTEGER(\"C3\") < 5000)\n"
- + "CLIENT MERGE SORT");
+ assertMutationPlan(conn, query).abstractExplainPlan("DELETE ROWS CLIENT SELECT")
+ .iteratorType("PARALLEL").scanType("RANGE SCAN").table(indexName2 + "(" + tableName + ")")
+ .keyRanges(" [2,*] - [2,9,000]")
+ .serverWhereFilter("((\"C1\" >= 10 AND \"C1\" <= 20) AND TO_INTEGER(\"C3\") < 5000)")
+ .clientSortAlgo("CLIENT MERGE SORT");
PreparedStatement stmt = conn
.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2, c3) VALUES (?, ?, ?, ?)");
@@ -302,10 +270,10 @@ public class CostBasedDecisionIT extends BaseTest {
conn.createStatement().execute("UPDATE STATISTICS " + tableName);
// Use the idx2 plan that scans less data when stats become available.
- verifyQueryPlan(query,
- "DELETE ROWS CLIENT SELECT\n" + "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName1 + "("
- + tableName + ")" + " [1,10] - [1,20]\n"
- + " SERVER FILTER BY (\"C2\" < 9000 AND \"C3\" < 5000)\n" + "CLIENT MERGE SORT");
+ assertMutationPlan(conn, query).abstractExplainPlan("DELETE ROWS CLIENT SELECT")
+ .iteratorType("PARALLEL").scanType("RANGE SCAN").table(indexName1 + "(" + tableName + ")")
+ .keyRanges(" [1,10] - [1,20]").serverWhereFilter("(\"C2\" < 9000 AND \"C3\" < 5000)")
+ .clientSortAlgo("CLIENT MERGE SORT");
} finally {
conn.close();
}
@@ -328,12 +296,13 @@ public class CostBasedDecisionIT extends BaseTest {
+ " where rowkey <= 'z' GROUP BY c1 " + "UNION ALL SELECT c1, max(rowkey), max(c2) FROM "
+ tableName + " where rowkey >= 'a' GROUP BY c1";
// Use the default plan when stats are not available.
- verifyQueryPlan(query,
- "UNION ALL OVER 2 QUERIES\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + tableName
- + " [*] - ['z']\n" + " SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]\n"
- + " CLIENT MERGE SORT\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + tableName
- + " ['a'] - [*]\n" + " SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]\n"
- + " CLIENT MERGE SORT");
+ assertPlan(conn, query).abstractExplainPlan("UNION ALL OVER 2 QUERIES")
+ .iteratorType("PARALLEL").scanType("RANGE SCAN").table(tableName).keyRanges(" [*] - ['z']")
+ .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]")
+ .clientSortAlgo("CLIENT MERGE SORT").rhs().iteratorType("PARALLEL").scanType("RANGE SCAN")
+ .table(tableName).keyRanges(" ['a'] - [*]")
+ .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]")
+ .clientSortAlgo("CLIENT MERGE SORT");
PreparedStatement stmt =
conn.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2) VALUES (?, ?, ?)");
@@ -348,18 +317,16 @@ public class CostBasedDecisionIT extends BaseTest {
conn.createStatement().execute("UPDATE STATISTICS " + tableName);
// Use the optimal plan based on cost when stats become available.
- verifyQueryPlan(query,
- "UNION ALL OVER 2 QUERIES\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName
- + "(" + tableName + ") [1]\n" + " SERVER MERGE [0.C2]\n"
- + " SERVER PROJECTION FILTER BY FIRST KEY ONLY\n"
- + " SERVER FILTER BY \"ROWKEY\" <= 'z'\n"
- + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]\n"
- + " CLIENT MERGE SORT\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName
- + "(" + tableName + ") [1]\n" + " SERVER MERGE [0.C2]\n"
- + " SERVER PROJECTION FILTER BY FIRST KEY ONLY\n"
- + " SERVER FILTER BY \"ROWKEY\" >= 'a'\n"
- + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]\n"
- + " CLIENT MERGE SORT");
+ assertPlan(conn, query).abstractExplainPlan("UNION ALL OVER 2 QUERIES")
+ .iteratorType("PARALLEL").scanType("RANGE SCAN").table(indexName + "(" + tableName + ")")
+ .keyRanges(" [1]").serverMergeColumns("[0.C2]").serverFirstKeyOnlyProjection(true)
+ .serverWhereFilter("\"ROWKEY\" <= 'z'")
+ .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]")
+ .clientSortAlgo("CLIENT MERGE SORT").rhs().iteratorType("PARALLEL").scanType("RANGE SCAN")
+ .table(indexName + "(" + tableName + ")").keyRanges(" [1]").serverMergeColumns("[0.C2]")
+ .serverFirstKeyOnlyProjection(true).serverWhereFilter("\"ROWKEY\" >= 'a'")
+ .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]")
+ .clientSortAlgo("CLIENT MERGE SORT");
} finally {
conn.close();
}
@@ -383,12 +350,13 @@ public class CostBasedDecisionIT extends BaseTest {
+ " where rowkey <= 'z' GROUP BY c1) t2 "
+ "ON t1.rowkey = t2.mrk WHERE t1.c1 LIKE 'X0%' ORDER BY t1.rowkey";
// Use the default plan when stats are not available.
- verifyQueryPlan(query,
- "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + tableName + "\n"
- + " SERVER FILTER BY C1 LIKE 'X0%'\n" + " PARALLEL INNER-JOIN TABLE 0\n"
- + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + tableName + " [*] - ['z']\n"
- + " SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]\n"
- + " CLIENT MERGE SORT\n" + " DYNAMIC SERVER FILTER BY T1.ROWKEY IN (T2.MRK)");
+ assertPlan(conn, query).iteratorType("PARALLEL").scanType("FULL SCAN").table(tableName)
+ .serverWhereFilter("C1 LIKE 'X0%'")
+ .dynamicServerFilter("DYNAMIC SERVER FILTER BY T1.ROWKEY IN (T2.MRK)").subPlanCount(1)
+ .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").iteratorType("PARALLEL")
+ .scanType("RANGE SCAN").table(tableName).keyRanges(" [*] - ['z']")
+ .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]")
+ .clientSortAlgo("CLIENT MERGE SORT");
PreparedStatement stmt =
conn.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2) VALUES (?, ?, ?)");
@@ -403,18 +371,17 @@ public class CostBasedDecisionIT extends BaseTest {
conn.createStatement().execute("UPDATE STATISTICS " + tableName);
// Use the optimal plan based on cost when stats become available.
- verifyQueryPlan(query,
- "CLIENT PARALLEL 626-WAY RANGE SCAN OVER " + indexName + "(" + tableName
- + ") [1,'X0'] - [1,'X1']\n" + " SERVER MERGE [0.C2]\n"
- + " SERVER PROJECTION FILTER BY FIRST KEY ONLY\n"
- + " SERVER SORTED BY [\"T1.:ROWKEY\"]\n" + "CLIENT MERGE SORT\n"
- + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER "
- + indexName + "(" + tableName + ") [1]\n" + " SERVER MERGE [0.C2]\n"
- + " SERVER PROJECTION FILTER BY FIRST KEY ONLY\n"
- + " SERVER FILTER BY \"ROWKEY\" <= 'z'\n"
- + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]\n"
- + " CLIENT MERGE SORT\n"
- + " DYNAMIC SERVER FILTER BY \"T1.:ROWKEY\" IN (T2.MRK)");
+ assertPlan(conn, query).iteratorType("PARALLEL").scanType("RANGE SCAN")
+ .table(indexName + "(" + tableName + ")").keyRanges(" [1,'X0'] - [1,'X1']")
+ .serverMergeColumns("[0.C2]").serverFirstKeyOnlyProjection(true)
+ .serverSortedBy("[\"T1.:ROWKEY\"]").clientSortAlgo("CLIENT MERGE SORT")
+ .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"T1.:ROWKEY\" IN (T2.MRK)").subPlanCount(1)
+ .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").iteratorType("PARALLEL")
+ .scanType("RANGE SCAN").table(indexName + "(" + tableName + ")").keyRanges(" [1]")
+ .serverMergeColumns("[0.C2]").serverFirstKeyOnlyProjection(true)
+ .serverWhereFilter("\"ROWKEY\" <= 'z'")
+ .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]")
+ .clientSortAlgo("CLIENT MERGE SORT");
} finally {
conn.close();
}
@@ -440,11 +407,7 @@ public class CostBasedDecisionIT extends BaseTest {
String indexPlan = "(\"ROWKEY\" >= 1 AND \"ROWKEY\" <= 10)";
// Use the index table plan that opts out order-by when stats are not available.
- ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class)
- .optimizeQuery().getExplainPlan();
- ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes();
- assertTrue(explainPlanAttributes.isServerFirstKeyOnlyProjection());
- assertEquals(indexPlan, explainPlanAttributes.getServerWhereFilter());
+ assertPlan(conn, query).serverFirstKeyOnlyProjection(true).serverWhereFilter(indexPlan);
PreparedStatement stmt =
conn.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2) VALUES (?, ?, ?)");
@@ -459,17 +422,10 @@ public class CostBasedDecisionIT extends BaseTest {
conn.createStatement().execute("UPDATE STATISTICS " + tableName);
// Use the data table plan that has a lower cost when stats are available.
- plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery()
- .getExplainPlan();
- explainPlanAttributes = plan.getPlanStepsAsAttributes();
- assertEquals(dataPlan, explainPlanAttributes.getServerSortedBy());
+ assertPlan(conn, query).serverSortedBy(dataPlan);
// Use the index table plan as has been hinted.
- plan = conn.prepareStatement(hintedQuery).unwrap(PhoenixPreparedStatement.class)
- .optimizeQuery().getExplainPlan();
- explainPlanAttributes = plan.getPlanStepsAsAttributes();
- assertTrue(explainPlanAttributes.isServerFirstKeyOnlyProjection());
- assertEquals(indexPlan, explainPlanAttributes.getServerWhereFilter());
+ assertPlan(conn, hintedQuery).serverFirstKeyOnlyProjection(true).serverWhereFilter(indexPlan);
} finally {
conn.close();
}
@@ -482,17 +438,9 @@ public class CostBasedDecisionIT extends BaseTest {
+ "ON t1.ID = t2.ID";
Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
- ExplainPlan plan = conn.prepareStatement(q).unwrap(PhoenixPreparedStatement.class)
- .optimizeQuery().getExplainPlan();
- ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes();
- assertEquals("SORT-MERGE-JOIN (INNER)", explainPlanAttributes.getAbstractExplainPlan());
- assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize());
- assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType());
- assertEquals(testTable500, explainPlanAttributes.getTableName());
- ExplainPlanAttributes rhsTable = explainPlanAttributes.getRhsJoinQueryExplainPlan();
- assertEquals("PARALLEL 1-WAY", rhsTable.getIteratorTypeAndScanSize());
- assertEquals("FULL SCAN ", rhsTable.getExplainScanType());
- assertEquals(testTable1000, rhsTable.getTableName());
+ assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").lhs()
+ .iteratorType("PARALLEL").scanType("FULL SCAN").table(testTable500).end().rhs()
+ .iteratorType("PARALLEL").scanType("FULL SCAN").table(testTable1000);
}
}
@@ -505,20 +453,11 @@ public class CostBasedDecisionIT extends BaseTest {
+ "ON t1.ID = t2.ID\n" + "WHERE t1.COL1 < 200";
Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
- ExplainPlan plan = conn.prepareStatement(q).unwrap(PhoenixPreparedStatement.class)
- .optimizeQuery().getExplainPlan();
- ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes();
- assertEquals("SORT-MERGE-JOIN (INNER)", explainPlanAttributes.getAbstractExplainPlan());
- assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize());
- assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType());
- assertEquals("COL1 < 200", explainPlanAttributes.getServerWhereFilter());
- assertEquals(testTable500, explainPlanAttributes.getTableName());
- assertEquals("CLIENT AGGREGATE INTO SINGLE ROW", explainPlanAttributes.getClientAggregate());
- ExplainPlanAttributes rhsTable = explainPlanAttributes.getRhsJoinQueryExplainPlan();
- assertEquals("PARALLEL 1-WAY", rhsTable.getIteratorTypeAndScanSize());
- assertEquals("FULL SCAN ", rhsTable.getExplainScanType());
- assertTrue(rhsTable.isServerFirstKeyOnlyProjection());
- assertEquals(testTable1000, rhsTable.getTableName());
+ assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (INNER)")
+ .clientAggregate("CLIENT AGGREGATE INTO SINGLE ROW").lhs().iteratorType("PARALLEL")
+ .scanType("FULL SCAN").table(testTable500).serverWhereFilter("COL1 < 200").end().rhs()
+ .iteratorType("PARALLEL").scanType("FULL SCAN").serverFirstKeyOnlyProjection(true)
+ .table(testTable1000);
}
}
@@ -527,10 +466,13 @@ public class CostBasedDecisionIT extends BaseTest {
public void testJoinStrategy3() throws Exception {
String q = "SELECT *\n" + "FROM " + testTable500 + " t1 JOIN " + testTable1000 + " t2\n"
+ "ON t1.COL1 = t2.ID\n" + "WHERE t1.ID > 200";
- String expected = "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + testTable1000 + "\n"
- + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER "
- + testTable500 + " [201] - [*]\n" + " DYNAMIC SERVER FILTER BY T2.ID IN (T1.COL1)";
- verifyQueryPlan(q, expected);
+ Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+ try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+ assertPlan(conn, q).iteratorType("PARALLEL").scanType("FULL SCAN").table(testTable1000)
+ .dynamicServerFilter("DYNAMIC SERVER FILTER BY T2.ID IN (T1.COL1)").subPlanCount(1)
+ .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").iteratorType("PARALLEL")
+ .scanType("RANGE SCAN").table(testTable500).keyRanges(" [201] - [*]");
+ }
}
/**
@@ -541,10 +483,13 @@ public class CostBasedDecisionIT extends BaseTest {
public void testJoinStrategy4() throws Exception {
String q = "SELECT *\n" + "FROM " + testTable990 + " t1 JOIN " + testTable1000 + " t2\n"
+ "ON t1.ID = t2.COL1";
- String expected = "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + testTable990 + "\n"
- + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER "
- + testTable1000 + "\n" + " DYNAMIC SERVER FILTER BY T1.ID IN (T2.COL1)";
- verifyQueryPlan(q, expected);
+ Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+ try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+ assertPlan(conn, q).iteratorType("PARALLEL").scanType("FULL SCAN").table(testTable990)
+ .dynamicServerFilter("DYNAMIC SERVER FILTER BY T1.ID IN (T2.COL1)").subPlanCount(1)
+ .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").iteratorType("PARALLEL")
+ .scanType("FULL SCAN").table(testTable1000);
+ }
}
/** Hash-join wins over sort-merge-join w/ smaller side ordered. */
@@ -552,10 +497,13 @@ public class CostBasedDecisionIT extends BaseTest {
public void testJoinStrategy5() throws Exception {
String q = "SELECT *\n" + "FROM " + testTable500 + " t1 JOIN " + testTable1000 + " t2\n"
+ "ON t1.ID = t2.COL1\n" + "WHERE t1.ID > 200";
- String expected = "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + testTable1000 + "\n"
- + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER "
- + testTable500 + " [201] - [*]";
- verifyQueryPlan(q, expected);
+ Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+ try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+ assertPlan(conn, q).iteratorType("PARALLEL").scanType("FULL SCAN").table(testTable1000)
+ .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0")
+ .iteratorType("PARALLEL").scanType("RANGE SCAN").table(testTable500)
+ .keyRanges(" [201] - [*]");
+ }
}
/** Hash-join wins over sort-merge-join w/o any side ordered. */
@@ -563,10 +511,13 @@ public class CostBasedDecisionIT extends BaseTest {
public void testJoinStrategy6() throws Exception {
String q = "SELECT *\n" + "FROM " + testTable500 + " t1 JOIN " + testTable1000 + " t2\n"
+ "ON t1.COL1 = t2.COL1\n" + "WHERE t1.ID > 200";
- String expected = "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + testTable1000 + "\n"
- + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER "
- + testTable500 + " [201] - [*]";
- verifyQueryPlan(q, expected);
+ Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+ try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+ assertPlan(conn, q).iteratorType("PARALLEL").scanType("FULL SCAN").table(testTable1000)
+ .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0")
+ .iteratorType("PARALLEL").scanType("RANGE SCAN").table(testTable500)
+ .keyRanges(" [201] - [*]");
+ }
}
/**
@@ -578,11 +529,14 @@ public class CostBasedDecisionIT extends BaseTest {
public void testJoinStrategy7() throws Exception {
String q = "SELECT *\n" + "FROM " + testTable500 + " t1 JOIN " + testTable1000 + " t2\n"
+ "ON t1.ID = t2.ID\n" + "ORDER BY t1.COL1";
- String expected = "CLIENT PARALLEL 1001-WAY FULL SCAN OVER " + testTable1000 + "\n"
- + " SERVER SORTED BY [T1.COL1]\n" + "CLIENT MERGE SORT\n"
- + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER "
- + testTable500 + "\n" + " DYNAMIC SERVER FILTER BY T2.ID IN (T1.ID)";
- verifyQueryPlan(q, expected);
+ Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+ try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+ assertPlan(conn, q).iteratorType("PARALLEL").scanType("FULL SCAN").table(testTable1000)
+ .serverSortedBy("[T1.COL1]").clientSortAlgo("CLIENT MERGE SORT")
+ .dynamicServerFilter("DYNAMIC SERVER FILTER BY T2.ID IN (T1.ID)").subPlanCount(1).subPlan(0)
+ .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").iteratorType("PARALLEL")
+ .scanType("FULL SCAN").table(testTable500);
+ }
}
/**
@@ -596,19 +550,9 @@ public class CostBasedDecisionIT extends BaseTest {
+ "ON t1.ID = t2.ID\n" + "ORDER BY t1.COL1 LIMIT 5";
Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
- ExplainPlan plan = conn.prepareStatement(q).unwrap(PhoenixPreparedStatement.class)
- .optimizeQuery().getExplainPlan();
- ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes();
- assertEquals("SORT-MERGE-JOIN (INNER)", explainPlanAttributes.getAbstractExplainPlan());
- assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize());
- assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType());
- assertEquals(testTable500, explainPlanAttributes.getTableName());
- assertEquals("[T1.COL1]", explainPlanAttributes.getClientSortedBy());
- assertEquals(new Integer(5), explainPlanAttributes.getClientRowLimit());
- ExplainPlanAttributes rhsTable = explainPlanAttributes.getRhsJoinQueryExplainPlan();
- assertEquals("PARALLEL 1-WAY", rhsTable.getIteratorTypeAndScanSize());
- assertEquals("FULL SCAN ", rhsTable.getExplainScanType());
- assertEquals(testTable1000, rhsTable.getTableName());
+ assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").clientSortedBy("[T1.COL1]")
+ .clientRowLimit(5).lhs().iteratorType("PARALLEL").scanType("FULL SCAN").table(testTable500)
+ .end().rhs().iteratorType("PARALLEL").scanType("FULL SCAN").table(testTable1000);
}
}
@@ -620,28 +564,36 @@ public class CostBasedDecisionIT extends BaseTest {
String q = "SELECT *\n" + "FROM " + testTable1000 + " t1 LEFT JOIN " + testTable500 + " t2\n"
+ "ON t1.ID = t2.ID AND t2.ID > 200\n" + "LEFT JOIN " + testTable990 + " t3\n"
+ "ON t1.ID = t3.ID AND t3.ID < 100";
- String expected = "SORT-MERGE-JOIN (LEFT) TABLES\n" + " SORT-MERGE-JOIN (LEFT) TABLES\n"
- + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + testTable1000 + "\n" + " AND\n"
- + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + testTable500 + " [201] - [*]\n" + "AND\n"
- + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + testTable990 + " [*] - [100]";
- verifyQueryPlan(q, expected);
+ Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+ try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+ assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)").lhs()
+ .abstractExplainPlan("SORT-MERGE-JOIN (LEFT)").lhs().iteratorType("PARALLEL")
+ .scanType("FULL SCAN").table(testTable1000).end().rhs().iteratorType("PARALLEL")
+ .scanType("RANGE SCAN").table(testTable500).keyRanges(" [201] - [*]").end().end().rhs()
+ .iteratorType("PARALLEL").scanType("RANGE SCAN").table(testTable990)
+ .keyRanges(" [*] - [100]");
+ }
}
/**
- * Multi-table join: a mix of join strategies chosen based on cost.
+ * Multi-table join: a mix of join strategies chosen based on cost. SMJ root, lhs is hash-join,
+ * rhs is range scan.
*/
@Test
public void testJoinStrategy10() throws Exception {
String q = "SELECT *\n" + "FROM " + testTable1000 + " t1 JOIN " + testTable500 + " t2\n"
+ "ON t1.ID = t2.COL1 AND t2.ID > 200\n" + "JOIN " + testTable990 + " t3\n"
+ "ON t1.ID = t3.ID AND t3.ID < 100";
- String expected =
- "SORT-MERGE-JOIN (INNER) TABLES\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER "
- + testTable1000 + "\n" + " PARALLEL INNER-JOIN TABLE 0\n"
- + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + testTable500 + " [201] - [*]\n"
- + " DYNAMIC SERVER FILTER BY T1.ID IN (T2.COL1)\n" + "AND\n"
- + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + testTable990 + " [*] - [100]";
- verifyQueryPlan(q, expected);
+ Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+ try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+ assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").lhs()
+ .iteratorType("PARALLEL").scanType("FULL SCAN").table(testTable1000)
+ .dynamicServerFilter("DYNAMIC SERVER FILTER BY T1.ID IN (T2.COL1)").subPlanCount(1)
+ .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").iteratorType("PARALLEL")
+ .scanType("RANGE SCAN").table(testTable500).keyRanges(" [201] - [*]").end().end().rhs()
+ .iteratorType("PARALLEL").scanType("RANGE SCAN").table(testTable990)
+ .keyRanges(" [*] - [100]");
+ }
}
/**
@@ -653,11 +605,15 @@ public class CostBasedDecisionIT extends BaseTest {
String q = "SELECT *\n" + "FROM " + testTable1000 + " t1 JOIN " + testTable500 + " t2\n"
+ "ON t1.COL2 = t2.COL1 AND t2.ID > 200\n" + "JOIN " + testTable990 + " t3\n"
+ "ON t1.COL1 = t3.COL2 AND t3.ID < 100";
- String expected = "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + testTable1000 + "\n"
- + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER "
- + testTable500 + " [201] - [*]\n" + " PARALLEL INNER-JOIN TABLE 1\n"
- + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + testTable990 + " [*] - [100]";
- verifyQueryPlan(q, expected);
+ Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+ try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+ assertPlan(conn, q).iteratorType("PARALLEL").scanType("FULL SCAN").table(testTable1000)
+ .subPlanCount(2).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0")
+ .iteratorType("PARALLEL").scanType("RANGE SCAN").table(testTable500)
+ .keyRanges(" [201] - [*]").end().subPlan(1)
+ .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1").iteratorType("PARALLEL")
+ .scanType("RANGE SCAN").table(testTable990).keyRanges(" [*] - [100]");
+ }
}
/**
@@ -668,29 +624,16 @@ public class CostBasedDecisionIT extends BaseTest {
public void testJoinStrategy12() throws Exception {
String q = "SELECT *\n" + "FROM " + testTable1000 + " t1 JOIN " + testTable990 + " t2\n"
+ "ON t1.COL2 = t2.COL1\n" + "JOIN " + testTable990 + " t3\n" + "ON t1.COL1 = t3.COL2";
- String expected =
- "SORT-MERGE-JOIN (INNER) TABLES\n" + " CLIENT PARALLEL 1001-WAY FULL SCAN OVER "
- + testTable1000 + "\n" + " SERVER SORTED BY [T1.COL1]\n" + " CLIENT MERGE SORT\n"
- + " PARALLEL INNER-JOIN TABLE 0\n"
- + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + testTable990 + "\n" + "AND\n"
- + " CLIENT PARALLEL 991-WAY FULL SCAN OVER " + testTable990 + "\n"
- + " SERVER SORTED BY [T3.COL2]\n" + " CLIENT MERGE SORT";
- verifyQueryPlan(q, expected);
- }
-
- private static void verifyQueryPlan(String query, String expected) throws Exception {
Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
- Connection conn = DriverManager.getConnection(getUrl(), props);
- ResultSet rs = conn.createStatement().executeQuery("explain " + query);
- String plan = QueryUtil.getExplainPlan(rs);
- // These assertions verify cost-based plan structure (scan type, join strategy, ordering). The
- // per-scan INDEX and REGIONS PLANNED annotations carry guidepost-derived region counts that are
- // covered directly by the attribute-based tests, so strip them here to keep these structural
- // substring checks stable.
- String normalizedPlan = plan.replaceAll("(?m)^ *INDEX [^\n]*\n", "")
- .replaceAll("(?m)^ *REGIONS PLANNED [^\n]*\n", "");
- assertTrue("Expected '" + expected + "' in the plan:\n" + plan + ".",
- normalizedPlan.contains(expected));
+ try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+ assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").lhs()
+ .iteratorType("PARALLEL").scanType("FULL SCAN").table(testTable1000)
+ .serverSortedBy("[T1.COL1]").clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0)
+ .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").iteratorType("PARALLEL")
+ .scanType("FULL SCAN").table(testTable990).end().end().rhs().iteratorType("PARALLEL")
+ .scanType("FULL SCAN").table(testTable990).serverSortedBy("[T3.COL2]")
+ .clientSortAlgo("CLIENT MERGE SORT");
+ }
}
private static String initTestTableValues(int rows) throws Exception {
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DerivedTableIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DerivedTableIT.java
index d35960fae9..2e809ae609 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DerivedTableIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DerivedTableIT.java
@@ -17,6 +17,7 @@
*/
package org.apache.phoenix.end2end;
+import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan;
import static org.apache.phoenix.util.TestUtil.A_VALUE;
import static org.apache.phoenix.util.TestUtil.B_VALUE;
import static org.apache.phoenix.util.TestUtil.C_VALUE;
@@ -43,8 +44,8 @@ import java.sql.ResultSet;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
+import org.apache.phoenix.query.explain.ExplainPlanTestUtil.ExplainPlanAssert;
import org.apache.phoenix.util.PropertiesUtil;
-import org.apache.phoenix.util.QueryUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -54,8 +55,6 @@ import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.apache.phoenix.thirdparty.com.google.common.collect.Lists;
@@ -68,12 +67,31 @@ public class DerivedTableIT extends ParallelStatsDisabledIT {
public TestName name = new TestName();
private String[] indexDDL;
- private String[] plans;
+ private PlanSpec[] plans;
private String tableName;
- private static final Logger LOGGER = LoggerFactory.getLogger(DerivedTableIT.class);
+ /**
+ * Structured expected plan for one query under {@link #testNestedAggregationsWithGroupBy}.
+ * Captures the table the server-side scan targets, the {@code SERVER AGGREGATE} string, and the
+ * ordered list of client-side pipeline steps.
+ */
+ private static final class PlanSpec {
+ final String tableSuffix;
+ final String serverAggregate;
+ final String[] clientSteps;
+
+ PlanSpec(String tableSuffix, String serverAggregate, String... clientSteps) {
+ this.tableSuffix = tableSuffix;
+ this.serverAggregate = serverAggregate;
+ this.clientSteps = clientSteps;
+ }
+
+ String resolvedTable(String concreteTableName) {
+ return tableSuffix.replace(dynamicTableName, concreteTableName);
+ }
+ }
- public DerivedTableIT(String[] indexDDL, String[] plans) {
+ public DerivedTableIT(String[] indexDDL, PlanSpec[] plans) {
this.indexDDL = indexDDL;
this.plans = plans;
}
@@ -92,13 +110,6 @@ public class DerivedTableIT extends ParallelStatsDisabledIT {
conn.createStatement().execute(ddl);
}
}
- String[] newplan = new String[plans.length];
- if (plans != null && plans.length > 0) {
- for (int i = 0; i < plans.length; i++) {
- newplan[i] = plans[i].replace(dynamicTableName, tableName);
- }
- plans = newplan;
- }
}
@After
@@ -109,41 +120,36 @@ public class DerivedTableIT extends ParallelStatsDisabledIT {
}
@Parameters(name = "DerivedTableIT_{index}") // name is used by failsafe as file name in reports
- public static synchronized Collection<Object> data() {
- List<Object> testCases = Lists.newArrayList();
- testCases.add(new String[][] {
- { "CREATE INDEX " + dynamicTableName + "_DERIVED_IDX ON " + dynamicTableName
+ public static synchronized Collection<Object[]> data() {
+ List<Object[]> testCases = Lists.newArrayList();
+ // Indexed case: server-side aggregate runs over the covered index (PARALLEL 1-WAY).
+ testCases.add(new Object[] {
+ new String[] { "CREATE INDEX " + dynamicTableName + "_DERIVED_IDX ON " + dynamicTableName
+ " (a_byte) INCLUDE (A_STRING, B_STRING)" },
- { "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + dynamicTableName + "_DERIVED_IDX\n"
- + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"A_STRING\", \"B_STRING\"]\n"
- + "CLIENT MERGE SORT\n" + "CLIENT SORTED BY [\"B_STRING\"]\n" + "CLIENT SORTED BY [A]\n"
- + "CLIENT AGGREGATE INTO DISTINCT ROWS BY [A]\n" + "CLIENT SORTED BY [A DESC]",
-
- "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + dynamicTableName + "_DERIVED_IDX\n"
- + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"A_STRING\", \"B_STRING\"]\n"
- + "CLIENT MERGE SORT\n" + "CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY [A]\n"
- + "CLIENT DISTINCT ON [COLLECTDISTINCT(B)]\n" + "CLIENT SORTED BY [A DESC]" } });
- testCases.add(new String[][] { {},
- { "CLIENT PARALLEL 4-WAY FULL SCAN OVER " + dynamicTableName + "\n"
- + " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]\n"
- + "CLIENT MERGE SORT\n" + "CLIENT SORTED BY [B_STRING]\n" + "CLIENT SORTED BY [A]\n"
- + "CLIENT AGGREGATE INTO DISTINCT ROWS BY [A]\n" + "CLIENT SORTED BY [A DESC]",
-
- "CLIENT PARALLEL 4-WAY FULL SCAN OVER " + dynamicTableName + "\n"
- + " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]\n"
- + "CLIENT MERGE SORT\n" + "CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY [A]\n"
- + "CLIENT DISTINCT ON [COLLECTDISTINCT(B)]\n" + "CLIENT SORTED BY [A DESC]" } });
+ new PlanSpec[] {
+ new PlanSpec(dynamicTableName + "_DERIVED_IDX",
+ "SERVER AGGREGATE INTO DISTINCT ROWS BY [\"A_STRING\", \"B_STRING\"]",
+ "CLIENT MERGE SORT", "CLIENT SORTED BY [\"B_STRING\"]", "CLIENT SORTED BY [A]",
+ "CLIENT AGGREGATE INTO DISTINCT ROWS BY [A]", "CLIENT SORTED BY [A DESC]"),
+ new PlanSpec(dynamicTableName + "_DERIVED_IDX",
+ "SERVER AGGREGATE INTO DISTINCT ROWS BY [\"A_STRING\", \"B_STRING\"]",
+ "CLIENT MERGE SORT", "CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY [A]",
+ "CLIENT DISTINCT ON [COLLECTDISTINCT(B)]", "CLIENT SORTED BY [A DESC]") } });
+ // Non-indexed case: server-side aggregate runs over the data table (PARALLEL 4-WAY).
+ testCases.add(new Object[] { new String[] {}, new PlanSpec[] {
+ new PlanSpec(dynamicTableName, "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]",
+ "CLIENT MERGE SORT", "CLIENT SORTED BY [B_STRING]", "CLIENT SORTED BY [A]",
+ "CLIENT AGGREGATE INTO DISTINCT ROWS BY [A]", "CLIENT SORTED BY [A DESC]"),
+ new PlanSpec(dynamicTableName, "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]",
+ "CLIENT MERGE SORT", "CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY [A]",
+ "CLIENT DISTINCT ON [COLLECTDISTINCT(B)]", "CLIENT SORTED BY [A DESC]") } });
return testCases;
}
- /**
- * Removes the per-scan {@code INDEX} and {@code REGIONS PLANNED} annotation lines so these
- * structural plan assertions stay stable; those attributes are covered by the attribute-based
- * tests and {@code REGIONS PLANNED} carries an environment-dependent region count.
- */
- private static String stripScanAnnotations(String plan) {
- return plan.replaceAll("(?m)^ *INDEX [^\n]*\n", "")
- .replaceAll("(?m)^ *REGIONS PLANNED [^\n]*\n", "");
+ private void verifyPlan(Connection conn, String query, PlanSpec spec) throws Exception {
+ ExplainPlanAssert assertion = assertPlan(conn, query);
+ assertion.iteratorType("PARALLEL").scanType("FULL SCAN").table(spec.resolvedTable(tableName))
+ .serverAggregate(spec.serverAggregate).clientSteps(spec.clientSteps);
}
@Test
@@ -394,12 +400,7 @@ public class DerivedTableIT extends ParallelStatsDisabledIT {
assertFalse(rs.next());
- rs = conn.createStatement().executeQuery("EXPLAIN WITH REGIONS " + query);
- String explainPlanOutput = QueryUtil.getExplainPlan(rs);
- LOGGER.info("Explain plan output: {}", explainPlanOutput);
- String[] splitExplainPlan = explainPlanOutput.split("\\n \\(region locations = \\[region=");
- String[] secondSplitExplainPlan = splitExplainPlan[1].split("]\\)");
- assertEquals(plans[0], stripScanAnnotations(splitExplainPlan[0] + secondSplitExplainPlan[1]));
+ verifyPlan(conn, query, plans[0]);
// distinct b (groupby a, b) groupby a orderby a
query = "SELECT DISTINCT COLLECTDISTINCT(t.b) FROM (SELECT b_string b, a_string a FROM "
@@ -421,12 +422,7 @@ public class DerivedTableIT extends ParallelStatsDisabledIT {
assertFalse(rs.next());
- rs = conn.createStatement().executeQuery("EXPLAIN WITH REGIONS " + query);
- explainPlanOutput = QueryUtil.getExplainPlan(rs);
- LOGGER.info("Explain plan output: {}", explainPlanOutput);
- splitExplainPlan = explainPlanOutput.split("\\n \\(region locations = \\[region=");
- secondSplitExplainPlan = splitExplainPlan[1].split("]\\)");
- assertEquals(plans[1], stripScanAnnotations(splitExplainPlan[0] + secondSplitExplainPlan[1]));
+ verifyPlan(conn, query, plans[1]);
// (orderby) groupby
query = "SELECT t.a_string, count(*) FROM (SELECT * FROM " + tableName
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortMergeJoinMoreIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortMergeJoinMoreIT.java
index 0116d31e1a..4a626c01a7 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortMergeJoinMoreIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortMergeJoinMoreIT.java
@@ -427,14 +427,14 @@ public class SortMergeJoinMoreIT extends ParallelStatsDisabledIT {
String rhsClientSortedBy = i == 0 ? "[BUCKET, \"TIMESTAMP\"]" : null;
assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(true)
- .iteratorType("PARALLEL").scanType("SKIP SCAN ON 2 RANGES").table(eventCountTableName)
- .keyRanges(lhsKeyRanges).serverFirstKeyOnlyProjection(true)
+ .lhs().iteratorType("PARALLEL").scanType("SKIP SCAN ON 2 RANGES")
+ .table(eventCountTableName).keyRanges(lhsKeyRanges).serverFirstKeyOnlyProjection(true)
.serverDistinctFilter("SERVER DISTINCT PREFIX FILTER OVER [BUCKET, TIMESTAMP, LOCATION]")
.serverAggregate(
"SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [BUCKET, TIMESTAMP, LOCATION]")
.clientSortAlgo("CLIENT MERGE SORT").clientSortedBy("[BUCKET, TIMESTAMP]")
.clientAggregate("CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY [E.BUCKET, E.TIMESTAMP]")
- .rhs().iteratorType("PARALLEL").scanType("SKIP SCAN ON 2 RANGES").table(t[i])
+ .end().rhs().iteratorType("PARALLEL").scanType("SKIP SCAN ON 2 RANGES").table(t[i])
.keyRanges(rhsKeyRanges).serverFirstKeyOnlyProjection(true)
.serverWhereFilter("SRC_LOCATION = DST_LOCATION")
.serverDistinctFilter(
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificViewIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificViewIndexIT.java
index 0a8ef6a425..dfc9c7a7c9 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificViewIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificViewIndexIT.java
@@ -17,6 +17,7 @@
*/
package org.apache.phoenix.end2end;
+import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan;
import static org.apache.phoenix.util.MetaDataUtil.getViewIndexSequenceName;
import static org.apache.phoenix.util.MetaDataUtil.getViewIndexSequenceSchemaName;
import static org.apache.phoenix.util.PhoenixRuntime.TENANT_ID_ATTRIB;
@@ -41,7 +42,6 @@ import org.apache.phoenix.schema.ColumnNotFoundException;
import org.apache.phoenix.schema.PNameFactory;
import org.apache.phoenix.util.MetaDataUtil;
import org.apache.phoenix.util.PhoenixRuntime;
-import org.apache.phoenix.util.QueryUtil;
import org.apache.phoenix.util.SchemaUtil;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@@ -358,40 +358,33 @@ public class TenantSpecificViewIndexIT extends BaseTenantSpecificViewIndexIT {
viewConn.createStatement()
.execute("CREATE VIEW IF NOT EXISTS " + viewName + " AS SELECT * FROM " + tableName);
- String query = "EXPLAIN SELECT PARENT_ID FROM " + viewName + " WHERE PARENT_TYPE='001' "
+ String query1 = "SELECT PARENT_ID FROM " + viewName + " WHERE PARENT_TYPE='001' "
+ "AND (CREATED_DATE > to_date('2011-01-01') AND CREATED_DATE < to_date('2016-10-31'))"
+ "ORDER BY PARENT_TYPE,CREATED_DATE LIMIT 501";
- ResultSet rs = viewConn.createStatement().executeQuery(query);
- String exptectedIndexName = SchemaUtil.getTableName(SCHEMA1, "IDX");
- String expectedPlanFormat = "CLIENT SERIAL 1-WAY RANGE SCAN OVER " + exptectedIndexName
- + " ['tenant1 ','001','%s 00:00:00.001'] - ['tenant1 ','001','%s 00:00:00.000']"
- + "\n" + " SERVER PROJECTION FILTER BY FIRST KEY ONLY" + "\n"
- + " SERVER 501 ROW LIMIT" + "\n" + "CLIENT 501 ROW LIMIT";
- assertEquals(String.format(expectedPlanFormat, "2011-01-01", "2016-10-31"),
- stripScanAnnotations(QueryUtil.getExplainPlan(rs)));
+ String expectedIndexName = SchemaUtil.getTableName(SCHEMA1, "IDX");
+ String expectedKeyRangesFormat =
+ " ['tenant1 ','001','%s 00:00:00.001'] - ['tenant1 ','001','%s 00:00:00.000']";
- query = "EXPLAIN SELECT PARENT_ID FROM " + viewName + " WHERE PARENT_TYPE='001' "
+ assertPlan(viewConn, query1).iteratorType("SERIAL").scanType("RANGE SCAN")
+ .indexName(expectedIndexName)
+ .keyRanges(String.format(expectedKeyRangesFormat, "2011-01-01", "2016-10-31"))
+ .serverFirstKeyOnlyProjection(true).serverRowLimit(501L).clientRowLimit(501)
+ .clientSteps("CLIENT 501 ROW LIMIT");
+
+ String query2 = "SELECT PARENT_ID FROM " + viewName + " WHERE PARENT_TYPE='001' "
+ " AND (CREATED_DATE >= to_date('2011-01-01') AND CREATED_DATE <= to_date('2016-01-01'))"
+ " AND (CREATED_DATE > to_date('2012-10-21') AND CREATED_DATE < to_date('2016-10-31')) "
+ "ORDER BY PARENT_TYPE,CREATED_DATE LIMIT 501";
- rs = viewConn.createStatement().executeQuery(query);
- assertEquals(String.format(expectedPlanFormat, "2012-10-21", "2016-01-01"),
- stripScanAnnotations(QueryUtil.getExplainPlan(rs)));
+ assertPlan(viewConn, query2).iteratorType("SERIAL").scanType("RANGE SCAN")
+ .indexName(expectedIndexName)
+ .keyRanges(String.format(expectedKeyRangesFormat, "2012-10-21", "2016-01-01"))
+ .serverFirstKeyOnlyProjection(true).serverRowLimit(501L).clientRowLimit(501)
+ .clientSteps("CLIENT 501 ROW LIMIT");
}
}
- /**
- * Removes the per-scan {@code INDEX} and {@code REGIONS PLANNED} annotation lines so this plan
- * assertion stays stable; those attributes are covered by the attribute-based tests and
- * {@code REGIONS PLANNED} carries an environment-dependent region count.
- */
- private static String stripScanAnnotations(String plan) {
- return plan.replaceAll("(?m)^ *INDEX [^\n]*\n", "")
- .replaceAll("(?m)^ *REGIONS PLANNED [^\n]*\n", "");
- }
-
@Test
public void testCurrentTimeWithViewIndexes() throws Exception {
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UnionAllIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UnionAllIT.java
index e468e6a7ba..7144653792 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UnionAllIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UnionAllIT.java
@@ -17,10 +17,10 @@
*/
package org.apache.phoenix.end2end;
+import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan;
import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -30,15 +30,10 @@ import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
-import java.sql.Statement;
import java.util.Properties;
-import org.apache.phoenix.compile.ExplainPlan;
-import org.apache.phoenix.compile.ExplainPlanAttributes;
import org.apache.phoenix.exception.SQLExceptionCode;
-import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.util.PropertiesUtil;
-import org.apache.phoenix.util.QueryUtil;
import org.apache.phoenix.util.TestUtil;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@@ -621,84 +616,34 @@ public class UnionAllIT extends ParallelStatsDisabledIT {
+ " CONSTRAINT pk PRIMARY KEY (a_string))\n";
createTestTable(getUrl(), ddl);
- ddl = "select a_string, col1 from " + tableName1 + " union all select a_string, col1 from "
- + tableName2 + " order by col1 limit 1";
- ExplainPlan plan = conn.prepareStatement(ddl).unwrap(PhoenixPreparedStatement.class)
- .optimizeQuery().getExplainPlan();
- ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes();
- assertEquals("UNION ALL OVER 2 QUERIES", explainPlanAttributes.getAbstractExplainPlan());
- assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize());
- assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType());
- assertEquals(tableName1, explainPlanAttributes.getTableName());
- assertEquals("[COL1]", explainPlanAttributes.getServerSortedBy());
- assertEquals(1L, explainPlanAttributes.getServerRowLimit().longValue());
- assertEquals(1, explainPlanAttributes.getClientRowLimit().intValue());
- assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo());
- ExplainPlanAttributes rhsPlanAttributes = explainPlanAttributes.getRhsJoinQueryExplainPlan();
- assertEquals("PARALLEL 1-WAY", rhsPlanAttributes.getIteratorTypeAndScanSize());
- assertEquals("FULL SCAN ", rhsPlanAttributes.getExplainScanType());
- assertEquals(tableName2, rhsPlanAttributes.getTableName());
- assertEquals("[COL1]", rhsPlanAttributes.getServerSortedBy());
- assertEquals(1L, rhsPlanAttributes.getServerRowLimit().longValue());
- assertEquals(1, rhsPlanAttributes.getClientRowLimit().intValue());
- assertEquals("CLIENT MERGE SORT", rhsPlanAttributes.getClientSortAlgo());
-
- String limitPlan = "UNION ALL OVER 2 QUERIES\n" + " CLIENT SERIAL 1-WAY FULL SCAN OVER "
- + tableName1 + "\n" + " SERVER 2 ROW LIMIT\n" + " CLIENT 2 ROW LIMIT\n"
- + " CLIENT SERIAL 1-WAY FULL SCAN OVER " + tableName2 + "\n"
- + " SERVER 2 ROW LIMIT\n" + " CLIENT 2 ROW LIMIT\n" + "CLIENT 2 ROW LIMIT";
-
- ddl = "select a_string, col1 from " + tableName1 + " union all select a_string, col1 from "
- + tableName2 + " limit 2";
- plan = conn.prepareStatement(ddl).unwrap(PhoenixPreparedStatement.class).optimizeQuery()
- .getExplainPlan();
- explainPlanAttributes = plan.getPlanStepsAsAttributes();
- assertEquals("UNION ALL OVER 2 QUERIES", explainPlanAttributes.getAbstractExplainPlan());
- assertEquals("SERIAL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize());
- assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType());
- assertEquals(tableName1, explainPlanAttributes.getTableName());
- assertNull(explainPlanAttributes.getServerSortedBy());
- assertEquals(2L, explainPlanAttributes.getServerRowLimit().longValue());
- assertEquals(2, explainPlanAttributes.getClientRowLimit().intValue());
- rhsPlanAttributes = explainPlanAttributes.getRhsJoinQueryExplainPlan();
- assertEquals("SERIAL 1-WAY", rhsPlanAttributes.getIteratorTypeAndScanSize());
- assertEquals("FULL SCAN ", rhsPlanAttributes.getExplainScanType());
- assertEquals(tableName2, rhsPlanAttributes.getTableName());
- assertNull(rhsPlanAttributes.getServerSortedBy());
- assertEquals(2L, rhsPlanAttributes.getServerRowLimit().longValue());
- assertEquals(2, rhsPlanAttributes.getClientRowLimit().intValue());
-
- Statement stmt = conn.createStatement();
- stmt.setMaxRows(2);
- ResultSet rs = stmt.executeQuery("explain " + ddl);
- assertEquals(limitPlan, stripScanAnnotations(QueryUtil.getExplainPlan(rs)));
-
- ddl = "select a_string, col1 from " + tableName1 + " union all select a_string, col1 from "
- + tableName2;
- plan = conn.prepareStatement(ddl).unwrap(PhoenixPreparedStatement.class).optimizeQuery()
- .getExplainPlan();
- explainPlanAttributes = plan.getPlanStepsAsAttributes();
- assertEquals("UNION ALL OVER 2 QUERIES", explainPlanAttributes.getAbstractExplainPlan());
- assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize());
- assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType());
- assertEquals(tableName1, explainPlanAttributes.getTableName());
- rhsPlanAttributes = explainPlanAttributes.getRhsJoinQueryExplainPlan();
- assertEquals("PARALLEL 1-WAY", rhsPlanAttributes.getIteratorTypeAndScanSize());
- assertEquals("FULL SCAN ", rhsPlanAttributes.getExplainScanType());
- assertEquals(tableName2, rhsPlanAttributes.getTableName());
+ String orderLimit = "select a_string, col1 from " + tableName1
+ + " union all select a_string, col1 from " + tableName2 + " order by col1 limit 1";
+ assertPlan(conn, orderLimit).abstractExplainPlan("UNION ALL OVER 2 QUERIES")
+ .iteratorType("PARALLEL").scanType("FULL SCAN").table(tableName1).serverSortedBy("[COL1]")
+ .serverRowLimit(1L).clientRowLimit(1).clientSortAlgo("CLIENT MERGE SORT").rhs()
+ .iteratorType("PARALLEL").scanType("FULL SCAN").table(tableName2).serverSortedBy("[COL1]")
+ .serverRowLimit(1L).clientRowLimit(1).clientSortAlgo("CLIENT MERGE SORT");
+
+ String limitOnly = "select a_string, col1 from " + tableName1
+ + " union all select a_string, col1 from " + tableName2 + " limit 2";
+ // The LHS branch's builder is shared with the union plan's root builder, so the LHS
+ // root carries both the inner branch's CLIENT 2 ROW LIMIT and the union's outer
+ // CLIENT 2 ROW LIMIT in its clientSteps. The RHS branch has its own separate builder.
+ assertPlan(conn, limitOnly).abstractExplainPlan("UNION ALL OVER 2 QUERIES")
+ .iteratorType("SERIAL").scanType("FULL SCAN").table(tableName1).serverSortedBy(null)
+ .serverRowLimit(2L).clientRowLimit(2)
+ .clientSteps("CLIENT 2 ROW LIMIT", "CLIENT 2 ROW LIMIT").rhs().iteratorType("SERIAL")
+ .scanType("FULL SCAN").table(tableName2).serverSortedBy(null).serverRowLimit(2L)
+ .clientRowLimit(2).clientSteps("CLIENT 2 ROW LIMIT");
+
+ String noLimit = "select a_string, col1 from " + tableName1
+ + " union all select a_string, col1 from " + tableName2;
+ assertPlan(conn, noLimit).abstractExplainPlan("UNION ALL OVER 2 QUERIES")
+ .iteratorType("PARALLEL").scanType("FULL SCAN").table(tableName1).rhs()
+ .iteratorType("PARALLEL").scanType("FULL SCAN").table(tableName2);
}
}
- /**
- * Removes the per-scan {@code INDEX} and {@code REGIONS PLANNED} annotation lines so this
- * structural plan assertion stays stable; those attributes are covered by the attribute-based
- * assertions above and {@code REGIONS PLANNED} carries an environment-dependent region count.
- */
- private static String stripScanAnnotations(String plan) {
- return plan.replaceAll("(?m)^ *INDEX [^\n]*\n", "")
- .replaceAll("(?m)^ *REGIONS PLANNED [^\n]*\n", "");
- }
-
@Test
public void testBug2295() throws Exception {
String tableName1 = generateUniqueName();
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinGlobalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinGlobalIndexIT.java
index 108be15272..170b295a7a 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinGlobalIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinGlobalIndexIT.java
@@ -60,25 +60,24 @@ public class SortMergeJoinGlobalIndexIT extends SortMergeJoinIT {
String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME);
String itemIndex = getSchemaName() + ".idx_item";
String supplierIndex = getSchemaName() + ".idx_supplier";
- assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)").scanType("FULL SCAN")
- .table(supplierIndex).serverFirstKeyOnlyProjection(true)
- .serverSortedBy("[\"S.:supplier_id\"]").clientSortAlgo("CLIENT MERGE SORT")
- .sortMergeSkipMerge(false).rhs().abstractExplainPlan("SORT-MERGE-JOIN (INNER)")
- .scanType("FULL SCAN").table(itemIndex).serverSortedBy("[\"I.:item_id\"]")
- .clientSortAlgo("CLIENT MERGE SORT").sortMergeSkipMerge(true)
- .clientSortedBy("[\"I.0:supplier_id\"]").rhs().scanType("FULL SCAN").table(order)
- .serverWhereFilter("QUANTITY < 5000").serverSortedBy("[\"O.item_id\"]")
- .clientSortAlgo("CLIENT MERGE SORT").end().end();
+ assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)").sortMergeSkipMerge(false)
+ .lhs().scanType("FULL SCAN").table(supplierIndex).serverFirstKeyOnlyProjection(true)
+ .serverSortedBy("[\"S.:supplier_id\"]").clientSortAlgo("CLIENT MERGE SORT").end().rhs()
+ .abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(true)
+ .clientSortedBy("[\"I.0:supplier_id\"]").lhs().scanType("FULL SCAN").table(itemIndex)
+ .serverSortedBy("[\"I.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end().rhs()
+ .scanType("FULL SCAN").table(order).serverWhereFilter("QUANTITY < 5000")
+ .serverSortedBy("[\"O.item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end().end();
}
@Override
protected void assertSelfJoinPlan(Connection conn, String query) throws Exception {
String itemIndex = getSchemaName() + ".idx_item";
- assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").scanType("FULL SCAN")
- .table(itemIndex).serverFirstKeyOnlyProjection(true).serverSortedBy("[\"I1.:item_id\"]")
- .clientSortAlgo("CLIENT MERGE SORT").sortMergeSkipMerge(false).rhs().scanType("FULL SCAN")
- .table(itemIndex).serverFirstKeyOnlyProjection(true).serverSortedBy("[\"I2.:item_id\"]")
- .clientSortAlgo("CLIENT MERGE SORT").end();
+ assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(false)
+ .lhs().scanType("FULL SCAN").table(itemIndex).serverFirstKeyOnlyProjection(true)
+ .serverSortedBy("[\"I1.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end().rhs()
+ .scanType("FULL SCAN").table(itemIndex).serverFirstKeyOnlyProjection(true)
+ .serverSortedBy("[\"I2.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end();
}
@Override
@@ -91,10 +90,10 @@ public class SortMergeJoinGlobalIndexIT extends SortMergeJoinIT {
statement.setMaxRows(4);
ExplainPlanAttributes attributes =
statement.optimizeQuery().getExplainPlan().getPlanStepsAsAttributes();
- assertPlan(attributes).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").scanType("FULL SCAN")
- .table(itemIndex).serverFirstKeyOnlyProjection(true).serverSortedBy("[\"I.:item_id\"]")
- .clientSortAlgo("CLIENT MERGE SORT").sortMergeSkipMerge(false).clientRowLimit(4).rhs()
- .scanType("FULL SCAN").table(order)
+ assertPlan(attributes).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(false)
+ .clientRowLimit(4).lhs().scanType("FULL SCAN").table(itemIndex)
+ .serverFirstKeyOnlyProjection(true).serverSortedBy("[\"I.:item_id\"]")
+ .clientSortAlgo("CLIENT MERGE SORT").end().rhs().scanType("FULL SCAN").table(order)
.serverSortedBy(queryIndex == 0 ? "[\"O.item_id\"]" : "[\"item_id\"]")
.clientSortAlgo("CLIENT MERGE SORT").end();
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinLocalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinLocalIndexIT.java
index 8b3cd696eb..53abdd2569 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinLocalIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinLocalIndexIT.java
@@ -62,14 +62,14 @@ public class SortMergeJoinLocalIndexIT extends SortMergeJoinIT {
String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME);
String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX);
String supplierIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_SUPPLIER_INDEX);
- assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)").scanType("RANGE SCAN")
- .table(supplierIndex + "(" + supplier + ")").keyRanges(" [1]")
+ assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)").sortMergeSkipMerge(false)
+ .lhs().scanType("RANGE SCAN").table(supplierIndex + "(" + supplier + ")").keyRanges(" [1]")
.serverFirstKeyOnlyProjection(true).serverSortedBy("[\"S.:supplier_id\"]")
- .clientSortAlgo("CLIENT MERGE SORT").sortMergeSkipMerge(false).rhs()
- .abstractExplainPlan("SORT-MERGE-JOIN (INNER)").scanType("RANGE SCAN")
+ .clientSortAlgo("CLIENT MERGE SORT").end().rhs()
+ .abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(true)
+ .clientSortedBy("[\"I.0:supplier_id\"]").lhs().scanType("RANGE SCAN")
.table(itemIndex + "(" + item + ")").keyRanges(" [1]").serverSortedBy("[\"I.:item_id\"]")
- .clientSortAlgo("CLIENT MERGE SORT").sortMergeSkipMerge(true)
- .clientSortedBy("[\"I.0:supplier_id\"]").rhs().scanType("FULL SCAN").table(order)
+ .clientSortAlgo("CLIENT MERGE SORT").end().rhs().scanType("FULL SCAN").table(order)
.serverWhereFilter("QUANTITY < 5000").serverSortedBy("[\"O.item_id\"]")
.clientSortAlgo("CLIENT MERGE SORT").end().end();
}
@@ -78,12 +78,12 @@ public class SortMergeJoinLocalIndexIT extends SortMergeJoinIT {
protected void assertSelfJoinPlan(Connection conn, String query) throws Exception {
String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME);
String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX);
- assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").scanType("RANGE SCAN")
+ assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(false)
+ .lhs().scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]")
+ .serverFirstKeyOnlyProjection(true).serverSortedBy("[\"I1.:item_id\"]")
+ .clientSortAlgo("CLIENT MERGE SORT").end().rhs().scanType("RANGE SCAN")
.table(itemIndex + "(" + item + ")").keyRanges(" [1]").serverFirstKeyOnlyProjection(true)
- .serverSortedBy("[\"I1.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT")
- .sortMergeSkipMerge(false).rhs().scanType("RANGE SCAN").table(itemIndex + "(" + item + ")")
- .keyRanges(" [1]").serverFirstKeyOnlyProjection(true).serverSortedBy("[\"I2.:item_id\"]")
- .clientSortAlgo("CLIENT MERGE SORT").end();
+ .serverSortedBy("[\"I2.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end();
}
@Override
@@ -97,10 +97,10 @@ public class SortMergeJoinLocalIndexIT extends SortMergeJoinIT {
statement.setMaxRows(4);
ExplainPlanAttributes attributes =
statement.optimizeQuery().getExplainPlan().getPlanStepsAsAttributes();
- assertPlan(attributes).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").scanType("RANGE SCAN")
- .table(itemIndex + "(" + item + ")").keyRanges(" [1]").serverFirstKeyOnlyProjection(true)
- .serverSortedBy("[\"I.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT")
- .sortMergeSkipMerge(false).clientRowLimit(4).rhs().scanType("FULL SCAN").table(order)
+ assertPlan(attributes).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(false)
+ .clientRowLimit(4).lhs().scanType("RANGE SCAN").table(itemIndex + "(" + item + ")")
+ .keyRanges(" [1]").serverFirstKeyOnlyProjection(true).serverSortedBy("[\"I.:item_id\"]")
+ .clientSortAlgo("CLIENT MERGE SORT").end().rhs().scanType("FULL SCAN").table(order)
.serverSortedBy(queryIndex == 0 ? "[\"O.item_id\"]" : "[\"item_id\"]")
.clientSortAlgo("CLIENT MERGE SORT").end();
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinNoIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinNoIndexIT.java
index 3eb29809b5..683030f07d 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinNoIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinNoIndexIT.java
@@ -60,19 +60,19 @@ public class SortMergeJoinNoIndexIT extends SortMergeJoinIT {
String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME);
String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME);
String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME);
- assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)").scanType("FULL SCAN")
- .table(supplier).sortMergeSkipMerge(false).rhs()
- .abstractExplainPlan("SORT-MERGE-JOIN (INNER)").scanType("FULL SCAN").table(item)
- .sortMergeSkipMerge(true).clientSortedBy("[\"I.supplier_id\"]").rhs().scanType("FULL SCAN")
- .table(order).serverWhereFilter("QUANTITY < 5000").serverSortedBy("[\"O.item_id\"]")
- .clientSortAlgo("CLIENT MERGE SORT").end().end();
+ assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)").sortMergeSkipMerge(false)
+ .lhs().scanType("FULL SCAN").table(supplier).end().rhs()
+ .abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(true)
+ .clientSortedBy("[\"I.supplier_id\"]").lhs().scanType("FULL SCAN").table(item).end().rhs()
+ .scanType("FULL SCAN").table(order).serverWhereFilter("QUANTITY < 5000")
+ .serverSortedBy("[\"O.item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end().end();
}
@Override
protected void assertSelfJoinPlan(Connection conn, String query) throws Exception {
String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME);
- assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").scanType("FULL SCAN")
- .table(item).sortMergeSkipMerge(false).rhs().scanType("FULL SCAN").table(item)
+ assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(false)
+ .lhs().scanType("FULL SCAN").table(item).end().rhs().scanType("FULL SCAN").table(item)
.serverFirstKeyOnlyProjection(true).end();
}
@@ -86,8 +86,8 @@ public class SortMergeJoinNoIndexIT extends SortMergeJoinIT {
statement.setMaxRows(4);
ExplainPlanAttributes attributes =
statement.optimizeQuery().getExplainPlan().getPlanStepsAsAttributes();
- assertPlan(attributes).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").scanType("FULL SCAN")
- .table(item).sortMergeSkipMerge(false).clientRowLimit(4).rhs().scanType("FULL SCAN")
+ assertPlan(attributes).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(false)
+ .clientRowLimit(4).lhs().scanType("FULL SCAN").table(item).end().rhs().scanType("FULL SCAN")
.table(order).serverSortedBy(queryIndex == 0 ? "[\"O.item_id\"]" : "[\"item_id\"]")
.clientSortAlgo("CLIENT MERGE SORT").end();
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SubqueryUsingSortMergeJoinIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SubqueryUsingSortMergeJoinIT.java
index 74b95b8029..5f8a68d1bc 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SubqueryUsingSortMergeJoinIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SubqueryUsingSortMergeJoinIT.java
@@ -184,8 +184,7 @@ public class SubqueryUsingSortMergeJoinIT extends BaseJoinIT {
assertFalse(rs.next());
assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)")
- .sortMergeSkipMerge(false).scanType("FULL SCAN").table(tableName5).iteratorType("PARALLEL")
- .rhs().subPlan(0).scanType("FULL SCAN").table(tableName4)
+ .sortMergeSkipMerge(false).rhs().subPlan(0).scanType("FULL SCAN").table(tableName4)
.serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]")
.clientSortAlgo("CLIENT MERGE SORT");
} finally {
@@ -241,8 +240,7 @@ public class SubqueryUsingSortMergeJoinIT extends BaseJoinIT {
assertFalse(rs.next());
assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)")
- .sortMergeSkipMerge(false).scanType("FULL SCAN").table(tableName5).iteratorType("PARALLEL")
- .rhs().subPlan(0).scanType("FULL SCAN").table(tableName4)
+ .sortMergeSkipMerge(false).rhs().subPlan(0).scanType("FULL SCAN").table(tableName4)
.serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]")
.clientSortAlgo("CLIENT MERGE SORT");
} finally {
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainJsonNormalizer.java b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainJsonNormalizer.java
index 22fa3e715f..39e18fde96 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainJsonNormalizer.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainJsonNormalizer.java
@@ -79,6 +79,11 @@ public final class ExplainJsonNormalizer {
.replaceAll(DYNAMIC_FILTER_ALIAS_PLACEHOLDER));
}
+ JsonNode lhs = obj.get("lhsJoinQueryExplainPlan");
+ if (lhs != null && lhs.isObject()) {
+ normalize(lhs);
+ }
+
JsonNode rhs = obj.get("rhsJoinQueryExplainPlan");
if (rhs != null && rhs.isObject()) {
normalize(rhs);
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTest.java b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTest.java
index 46facbc4d9..1326750984 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTest.java
@@ -28,6 +28,7 @@ import static org.junit.Assert.fail;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.sql.Connection;
import java.sql.DriverManager;
@@ -225,7 +226,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
"CLIENT MERGE SORT"),
scanAttrs("FULL SCAN ", "ATABLE", "")
.put("serverAggregate", "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]")
- .put("clientSortAlgo", "CLIENT MERGE SORT"));
+ .put("clientSortAlgo", "CLIENT MERGE SORT")
+ .set("clientSteps", clientSteps("CLIENT MERGE SORT")));
}
@Test
@@ -239,7 +241,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
"CLIENT MERGE SORT"),
scanAttrs("FULL SCAN ", "ATABLE", "").put("serverWhereFilter", "A_INTEGER = 1")
.put("serverAggregate", "SERVER AGGREGATE INTO DISTINCT ROWS BY [ENTITY_ID, ROUND(A_TIME)]")
- .put("clientSortAlgo", "CLIENT MERGE SORT"));
+ .put("clientSortAlgo", "CLIENT MERGE SORT")
+ .set("clientSteps", clientSteps("CLIENT MERGE SORT")));
}
@Test
@@ -250,7 +253,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
"CLIENT MERGE SORT", "CLIENT LIMIT 3"),
scanAttrs("FULL SCAN ", "ATABLE", "").put("serverSortedBy", "[A_STRING DESC]")
.put("serverRowLimit", 3).put("clientRowLimit", 3)
- .put("clientSortAlgo", "CLIENT MERGE SORT"));
+ .put("clientSortAlgo", "CLIENT MERGE SORT")
+ .set("clientSteps", clientSteps("CLIENT MERGE SORT", "CLIENT LIMIT 3")));
}
@Test
@@ -263,7 +267,9 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
"CLIENT FILTER BY MAX(A_STRING) = 'a'"),
scanAttrs("FULL SCAN ", "ATABLE", "")
.put("serverAggregate", "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]")
- .put("clientFilterBy", "MAX(A_STRING) = 'a'").put("clientSortAlgo", "CLIENT MERGE SORT"));
+ .put("clientFilterBy", "MAX(A_STRING) = 'a'").put("clientSortAlgo", "CLIENT MERGE SORT")
+ .set("clientSteps",
+ clientSteps("CLIENT MERGE SORT", "CLIENT FILTER BY MAX(A_STRING) = 'a'")));
}
@Test
@@ -279,7 +285,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']")
.put("serverWhereFilter",
"(ENTITY_ID != '00E00000000002' AND X_INTEGER = 2 AND A_INTEGER < 5)")
- .put("serverRowLimit", 10).put("clientRowLimit", 10));
+ .put("serverRowLimit", 10).put("clientRowLimit", 10)
+ .set("clientSteps", clientSteps("CLIENT 10 ROW LIMIT")));
}
@Test
@@ -405,7 +412,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
"CLIENT MERGE SORT", "CLIENT 5 ROW LIMIT"),
scanAttrs("FULL SCAN ", "ATABLE", "")
.put("serverAggregate", "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]")
- .put("clientRowLimit", 5).put("clientSortAlgo", "CLIENT MERGE SORT"));
+ .put("clientRowLimit", 5).put("clientSortAlgo", "CLIENT MERGE SORT")
+ .set("clientSteps", clientSteps("CLIENT MERGE SORT", "CLIENT 5 ROW LIMIT")));
}
@Test
@@ -418,7 +426,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
"CLIENT MERGE SORT", "CLIENT LIMIT 10"),
scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000001']").put("serverSortedBy", "[A_STRING]")
.put("serverRowLimit", 10).put("clientRowLimit", 10)
- .put("clientSortAlgo", "CLIENT MERGE SORT"));
+ .put("clientSortAlgo", "CLIENT MERGE SORT")
+ .set("clientSteps", clientSteps("CLIENT MERGE SORT", "CLIENT LIMIT 10")));
}
@Test
@@ -431,7 +440,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
"CLIENT MERGE SORT", "CLIENT LIMIT 10"),
scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000001']")
.put("serverSortedBy", "[A_STRING DESC NULLS LAST]").put("serverRowLimit", 10)
- .put("clientRowLimit", 10).put("clientSortAlgo", "CLIENT MERGE SORT"));
+ .put("clientRowLimit", 10).put("clientSortAlgo", "CLIENT MERGE SORT")
+ .set("clientSteps", clientSteps("CLIENT MERGE SORT", "CLIENT LIMIT 10")));
}
@Test
@@ -448,7 +458,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000001']")
.put("serverAggregate",
"SERVER AGGREGATE INTO DISTINCT ROWS BY [ORGANIZATION_ID, ENTITY_ID, ROUND(A_DATE)]")
- .put("clientRowLimit", 10).put("clientSortAlgo", "CLIENT MERGE SORT"));
+ .put("clientRowLimit", 10).put("clientSortAlgo", "CLIENT MERGE SORT")
+ .set("clientSteps", clientSteps("CLIENT MERGE SORT", "CLIENT 10 ROW LIMIT")));
}
@Test
@@ -463,12 +474,19 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
scanAttrs("FULL SCAN ", "ATABLE", "").put("serverWhereFilter", "A_INTEGER = 1")
.put("serverAggregate", "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]")
.put("clientFilterBy", "MAX(A_STRING) = 'a'").put("clientSortedBy", "[B_STRING]")
- .put("clientOffset", 0).put("clientSortAlgo", "CLIENT MERGE SORT"));
+ .put("clientOffset", 0).put("clientSortAlgo", "CLIENT MERGE SORT")
+ .set("clientSteps", clientSteps("CLIENT MERGE SORT", "CLIENT FILTER BY MAX(A_STRING) = 'a'",
+ "CLIENT SORTED BY [B_STRING]")));
}
@Test
public void testSortMergeJoin() throws Exception {
+ ObjectNode lhs = scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']");
ObjectNode rhs = scanAttrs("FULL SCAN ", "ATABLE", "");
+ ObjectNode expected = defaultAttrs();
+ expected.put("abstractExplainPlan", "SORT-MERGE-JOIN (INNER)");
+ expected.set("lhsJoinQueryExplainPlan", lhs);
+ expected.set("rhsJoinQueryExplainPlan", rhs);
verifyQuery("sortMergeJoin",
"SELECT /*+ USE_SORT_MERGE_JOIN */ a.a_string, b.a_string FROM atable a"
+ " JOIN atable b ON a.organization_id = b.organization_id"
@@ -478,8 +496,7 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
" INDEX ATABLE", " REGIONS PLANNED <N>", "AND",
" CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " INDEX ATABLE",
" REGIONS PLANNED <N>"),
- scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']")
- .put("abstractExplainPlan", "SORT-MERGE-JOIN (INNER)").set("rhsJoinQueryExplainPlan", rhs));
+ expected);
}
@Test
@@ -613,7 +630,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
" REGIONS PLANNED <N>", " SERVER PROJECTION FILTER BY FIRST KEY ONLY",
"CLIENT RESERVE VALUES FROM 1 SEQUENCE"),
scanAttrs("FULL SCAN ", "ATABLE", "").put("serverFirstKeyOnlyProjection", true)
- .put("clientSequenceCount", 1));
+ .put("clientSequenceCount", 1)
+ .set("clientSteps", clientSteps("CLIENT RESERVE VALUES FROM 1 SEQUENCE")));
}
@Test
@@ -623,7 +641,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
" SALT BUCKETS 4", " REGIONS PLANNED <N>", " SERVER FILTER BY V = 7",
"CLIENT MERGE SORT"),
scanAttrs("FULL SCAN ", "EO_SALTED", "").put("saltBuckets", 4)
- .put("serverWhereFilter", "V = 7").put("clientSortAlgo", "CLIENT MERGE SORT"));
+ .put("serverWhereFilter", "V = 7").put("clientSortAlgo", "CLIENT MERGE SORT")
+ .set("clientSteps", clientSteps("CLIENT MERGE SORT")));
}
@Test
@@ -637,7 +656,7 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
attrs().put("iteratorTypeAndScanSize", "SERIAL <N>-WAY").put("consistency", "STRONG")
.put("explainScanType", "RANGE SCAN ").put("tableName", "EO_MT_BASE")
.put("indexName", "EO_MT_VIEW").put("keyRanges", " ['tenant42']").put("serverRowLimit", 1)
- .put("clientRowLimit", 1));
+ .put("clientRowLimit", 1).set("clientSteps", clientSteps("CLIENT 1 ROW LIMIT")));
}
@Test
@@ -838,6 +857,27 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
assertTrue(nestedRhs.get("regionLocations").isNull());
}
+ @Test
+ public void testJsonNormalizerRecursesIntoLhsJoinQueryExplainPlan() {
+ ObjectNode root = mapper.createObjectNode();
+ root.put("iteratorTypeAndScanSize", "PARALLEL 5-WAY");
+ root.put("numRegionLocationLookups", 1);
+ ObjectNode lhs = mapper.createObjectNode();
+ lhs.put("iteratorTypeAndScanSize", "PARALLEL 7-WAY");
+ lhs.put("numRegionLocationLookups", 42);
+ lhs.set("regionLocations", mapper.createArrayNode().add(1));
+ root.set("lhsJoinQueryExplainPlan", lhs);
+
+ new ExplainJsonNormalizer().normalize(root);
+
+ assertEquals("PARALLEL <N>-WAY", root.get("iteratorTypeAndScanSize").asText());
+ assertEquals(0, root.get("numRegionLocationLookups").asInt());
+ JsonNode nestedLhs = root.get("lhsJoinQueryExplainPlan");
+ assertEquals("PARALLEL <N>-WAY", nestedLhs.get("iteratorTypeAndScanSize").asText());
+ assertEquals(0, nestedLhs.get("numRegionLocationLookups").asInt());
+ assertTrue(nestedLhs.get("regionLocations").isNull());
+ }
+
@Test
public void testJacksonFieldOrderMatchesPropertyOrderAnnotation() throws Exception {
// The serialized field order must exactly follow the @JsonPropertyOrder declaration. Deriving
@@ -1057,6 +1097,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
n.putNull("clientSequenceCount");
n.putNull("clientCursorName");
n.putNull("clientSortAlgo");
+ n.putNull("clientSteps");
+ n.putNull("lhsJoinQueryExplainPlan");
n.putNull("rhsJoinQueryExplainPlan");
n.putNull("serverMergeColumns");
n.putNull("regionLocations");
@@ -1096,6 +1138,15 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
return defaultAttrs();
}
+ /** Build a {@code clientSteps} JSON array for embedding into an expected attributes object. */
+ private static ArrayNode clientSteps(String... steps) {
+ ArrayNode arr = mapper.createArrayNode();
+ for (String s : steps) {
+ arr.add(s);
+ }
+ return arr;
+ }
+
private static ExplainPlan samplePlan(String way, String scanType) {
ExplainPlanAttributes a = new ExplainPlanAttributesBuilder().setIteratorTypeAndScanSize(way)
.setExplainScanType(scanType).setTableName("T")
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTestUtil.java b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTestUtil.java
index 0f79f98d34..8c01511018 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTestUtil.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTestUtil.java
@@ -357,6 +357,13 @@ public final class ExplainPlanTestUtil {
return this;
}
+ /** Navigate to the left hand side plan (e.g. sort-merge join). */
+ public ExplainPlanAssert lhs() {
+ ExplainPlanAttributes lhs = attributes.getLhsJoinQueryExplainPlan();
+ assertNotNull(at("lhsJoinQueryExplainPlan") + " must not be null", lhs);
+ return new ExplainPlanAssert(lhs, this, context + ".lhs");
+ }
+
/** Navigate to the right-hand side plan (sort-merge join / union all). */
public ExplainPlanAssert rhs() {
ExplainPlanAttributes rhs = attributes.getRhsJoinQueryExplainPlan();
@@ -364,6 +371,33 @@ public final class ExplainPlanTestUtil {
return new ExplainPlanAssert(rhs, this, context + ".rhs");
}
+ /** Assert the number of ordered client-side pipeline steps on this node. */
+ public ExplainPlanAssert clientStepCount(int expected) {
+ List<String> steps = attributes.getClientSteps();
+ int actual = steps == null ? 0 : steps.size();
+ assertEquals(at("clientSteps.size"), expected, actual);
+ return this;
+ }
+
+ /** Assert the i-th ordered client-side pipeline step on this node. */
+ public ExplainPlanAssert clientStep(int i, String expected) {
+ List<String> steps = attributes.getClientSteps();
+ assertNotNull(at("clientSteps") + " must not be null", steps);
+ assertTrue(at("clientSteps") + " has no index " + i + " (size=" + steps.size() + ")",
+ i >= 0 && i < steps.size());
+ assertEquals(at("clientSteps[" + i + "]"), expected, steps.get(i));
+ return this;
+ }
+
+ /** Assert the entire ordered client-side pipeline on this node matches {@code expected}. */
+ public ExplainPlanAssert clientSteps(String... expected) {
+ List<String> actual = attributes.getClientSteps();
+ List<String> actualOrEmpty =
+ actual == null ? java.util.Collections.<String> emptyList() : actual;
+ assertEquals(at("clientSteps"), java.util.Arrays.asList(expected), actualOrEmpty);
+ return this;
+ }
+
/** Assert the number of hash-join sub-plans (children). */
public ExplainPlanAssert subPlanCount(int expected) {
List<ExplainPlanAttributes> subPlans = attributes.getSubPlans();