This is an automated email from the ASF dual-hosted git repository.
apurtell pushed a commit to branch PHOENIX-7876-feature
in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/PHOENIX-7876-feature by this
push:
new 637399d67d PHOENIX-7899 Emit plan level estimates only once in EXPLAIN
(#2520)
637399d67d is described below
commit 637399d67da3b69e42eb83a1d1d154e8ae9d2a63
Author: Andrew Purtell <[email protected]>
AuthorDate: Thu Jun 11 15:49:05 2026 -0700
PHOENIX-7899 Emit plan level estimates only once in EXPLAIN (#2520)
Co-authored-by: Claude Opus 4.8[1m] <[email protected]>
---
.../org/apache/phoenix/compile/DeleteCompiler.java | 3 +
.../phoenix/compile/ExplainPlanAttributes.java | 116 ++++++++++++++-------
.../org/apache/phoenix/compile/UpsertCompiler.java | 3 +
.../org/apache/phoenix/execute/BaseQueryPlan.java | 4 +-
.../phoenix/execute/ClientAggregatePlan.java | 1 +
.../org/apache/phoenix/execute/ClientScanPlan.java | 1 +
.../org/apache/phoenix/execute/HashJoinPlan.java | 1 +
.../apache/phoenix/execute/SortMergeJoinPlan.java | 1 +
.../java/org/apache/phoenix/execute/UnionPlan.java | 1 +
.../phoenix/iterate/BaseResultIterators.java | 4 +-
.../org/apache/phoenix/iterate/ExplainTable.java | 18 ++++
.../org/apache/phoenix/jdbc/PhoenixStatement.java | 38 ++++---
.../end2end/ExplainPlanWithStatsEnabledIT.java | 61 +++++++++++
.../phoenix/schema/stats/BaseStatsCollectorIT.java | 32 +++---
.../query/explain/ExplainJsonNormalizer.java | 9 ++
.../phoenix/query/explain/ExplainPlanTest.java | 17 ++-
.../phoenix/query/explain/ExplainPlanTestUtil.java | 16 +++
17 files changed, 254 insertions(+), 72 deletions(-)
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
index dc78cae33b..5acc76a12e 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
@@ -803,6 +803,7 @@ public class DeleteCompiler {
new ExplainPlanAttributesBuilder().setAbstractExplainPlan("DELETE
SINGLE ROW");
if (getContext().isRoot()) {
ExplainTable.populateTopOfPlanAttributes(builder, getContext(),
getTargetRef());
+ ExplainTable.populateTopOfPlanEstimates(builder, this);
}
return new ExplainPlan(Collections.singletonList("DELETE SINGLE ROW"),
builder.build());
}
@@ -989,6 +990,7 @@ public class DeleteCompiler {
planSteps.addAll(queryPlanSteps);
if (getContext().isRoot()) {
ExplainTable.populateTopOfPlanAttributes(newBuilder, getContext(),
getTargetRef());
+ ExplainTable.populateTopOfPlanEstimates(newBuilder, this);
}
return new ExplainPlan(planSteps, newBuilder.build());
}
@@ -1127,6 +1129,7 @@ public class DeleteCompiler {
planSteps.addAll(queryPlanSteps);
if (getContext().isRoot()) {
ExplainTable.populateTopOfPlanAttributes(newBuilder, getContext(),
getTargetRef());
+ ExplainTable.populateTopOfPlanEstimates(newBuilder, this);
}
return new ExplainPlan(planSteps, newBuilder.build());
}
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 db729b014a..9b9dd8dd8c 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
@@ -36,11 +36,12 @@ import org.apache.phoenix.schema.PColumn;
* Strings containing entire plan.
*/
@JsonPropertyOrder({ "tenantId", "viewName", "viewBaseName", "cdcScopes",
"txnProvider", "rewrites",
- "abstractExplainPlan", "hint", "explainScanType", "consistency",
"tableName", "keyRanges",
- "indexName", "indexKind", "indexRule", "indexRejected", "saltBuckets",
"regionsPlanned",
- "scanTimeRangeMin", "scanTimeRangeMax", "splitsChunk",
"useRoundRobinIterator", "samplingRate",
- "hexStringRVCOffset", "iteratorTypeAndScanSize", "estimatedRows",
"estimatedSizeInBytes",
- "serverWhereFilter", "serverDistinctFilter", "serverMergeColumns",
"serverArrayElementProjection",
+ "estimatedRows", "estimatedSizeInBytes", "estimateInfoTs",
"abstractExplainPlan", "hint",
+ "explainScanType", "consistency", "tableName", "keyRanges", "indexName",
"indexKind", "indexRule",
+ "indexRejected", "saltBuckets", "regionsPlanned", "scanTimeRangeMin",
"scanTimeRangeMax",
+ "splitsChunk", "useRoundRobinIterator", "samplingRate", "hexStringRVCOffset",
+ "iteratorTypeAndScanSize", "scanEstimatedRows", "scanEstimatedSizeInBytes",
"serverWhereFilter",
+ "serverDistinctFilter", "serverMergeColumns", "serverArrayElementProjection",
"serverFirstKeyOnlyProjection", "serverEmptyColumnOnlyProjection",
"serverAggregate",
"serverGroupByLimit", "serverSortedBy", "serverOffset", "serverRowLimit",
"clientFilterBy",
"clientAggregate", "clientDistinctFilter", "clientAfterAggregate",
"clientSortAlgo",
@@ -57,6 +58,10 @@ public class ExplainPlanAttributes {
private final String cdcScopes;
private final String txnProvider;
private final List<String> rewrites;
+ // Plan-total estimates.
+ private final Long estimatedRows;
+ private final Long estimatedSizeInBytes;
+ private final Long estimateInfoTs;
// Plan identity and scan-level metadata
private final String abstractExplainPlan;
@@ -78,8 +83,9 @@ public class ExplainPlanAttributes {
private final Double samplingRate;
private final String hexStringRVCOffset;
private final String iteratorTypeAndScanSize;
- private final Long estimatedRows;
- private final Long estimatedSizeInBytes;
+ // Per-scan estimates (populated on each plan level from stats).
+ private final Long scanEstimatedRows;
+ private final Long scanEstimatedSizeInBytes;
// Server-side operations
private final String serverWhereFilter;
@@ -132,6 +138,9 @@ public class ExplainPlanAttributes {
this.cdcScopes = null;
this.txnProvider = null;
this.rewrites = null;
+ this.estimatedRows = null;
+ this.estimatedSizeInBytes = null;
+ this.estimateInfoTs = null;
this.abstractExplainPlan = null;
this.hint = null;
this.explainScanType = null;
@@ -151,8 +160,8 @@ public class ExplainPlanAttributes {
this.samplingRate = null;
this.hexStringRVCOffset = null;
this.iteratorTypeAndScanSize = null;
- this.estimatedRows = null;
- this.estimatedSizeInBytes = null;
+ this.scanEstimatedRows = null;
+ this.scanEstimatedSizeInBytes = null;
this.serverWhereFilter = null;
this.serverDistinctFilter = null;
this.serverMergeColumns = null;
@@ -188,13 +197,14 @@ public class ExplainPlanAttributes {
}
public ExplainPlanAttributes(String tenantId, String viewName, String
viewBaseName,
- String cdcScopes, String txnProvider, List<String> rewrites, String
abstractExplainPlan,
- Hint hint, String explainScanType, Consistency consistency, String
tableName, String keyRanges,
+ String cdcScopes, String txnProvider, List<String> rewrites, Long
estimatedRows,
+ Long estimatedSizeInBytes, Long estimateInfoTs, String
abstractExplainPlan, Hint hint,
+ String explainScanType, Consistency consistency, String tableName, String
keyRanges,
String indexName, String indexKind, String indexRule,
List<RejectedIndexEntry> indexRejected,
Integer saltBuckets, Integer regionsPlanned, Long scanTimeRangeMin, Long
scanTimeRangeMax,
Integer splitsChunk, boolean useRoundRobinIterator, Double samplingRate,
- String hexStringRVCOffset, String iteratorTypeAndScanSize, Long
estimatedRows,
- Long estimatedSizeInBytes, String serverWhereFilter, String
serverDistinctFilter,
+ String hexStringRVCOffset, String iteratorTypeAndScanSize, Long
scanEstimatedRows,
+ Long scanEstimatedSizeInBytes, String serverWhereFilter, String
serverDistinctFilter,
Set<PColumn> serverMergeColumns, boolean serverArrayElementProjection,
boolean serverFirstKeyOnlyProjection, boolean
serverEmptyColumnOnlyProjection,
String serverAggregate, Integer serverGroupByLimit, String serverSortedBy,
Integer serverOffset,
@@ -214,6 +224,9 @@ public class ExplainPlanAttributes {
this.rewrites = (rewrites == null || rewrites.isEmpty())
? null
: Collections.unmodifiableList(new ArrayList<>(rewrites));
+ this.estimatedRows = estimatedRows;
+ this.estimatedSizeInBytes = estimatedSizeInBytes;
+ this.estimateInfoTs = estimateInfoTs;
this.abstractExplainPlan = abstractExplainPlan;
this.hint = hint;
this.explainScanType = explainScanType;
@@ -235,8 +248,8 @@ public class ExplainPlanAttributes {
this.samplingRate = samplingRate;
this.hexStringRVCOffset = hexStringRVCOffset;
this.iteratorTypeAndScanSize = iteratorTypeAndScanSize;
- this.estimatedRows = estimatedRows;
- this.estimatedSizeInBytes = estimatedSizeInBytes;
+ this.scanEstimatedRows = scanEstimatedRows;
+ this.scanEstimatedSizeInBytes = scanEstimatedSizeInBytes;
this.serverWhereFilter = serverWhereFilter;
this.serverDistinctFilter = serverDistinctFilter;
this.serverMergeColumns = serverMergeColumns;
@@ -373,6 +386,14 @@ public class ExplainPlanAttributes {
return iteratorTypeAndScanSize;
}
+ public Long getScanEstimatedRows() {
+ return scanEstimatedRows;
+ }
+
+ public Long getScanEstimatedSizeInBytes() {
+ return scanEstimatedSizeInBytes;
+ }
+
public Long getEstimatedRows() {
return estimatedRows;
}
@@ -381,6 +402,10 @@ public class ExplainPlanAttributes {
return estimatedSizeInBytes;
}
+ public Long getEstimateInfoTs() {
+ return estimateInfoTs;
+ }
+
public String getServerWhereFilter() {
return serverWhereFilter;
}
@@ -522,6 +547,9 @@ public class ExplainPlanAttributes {
private String cdcScopes;
private String txnProvider;
private List<String> rewrites;
+ private Long estimatedRows;
+ private Long estimatedSizeInBytes;
+ private Long estimateInfoTs;
private String abstractExplainPlan;
private HintNode.Hint hint;
private String explainScanType;
@@ -541,8 +569,8 @@ public class ExplainPlanAttributes {
private Double samplingRate;
private String hexStringRVCOffset;
private String iteratorTypeAndScanSize;
- private Long estimatedRows;
- private Long estimatedSizeInBytes;
+ private Long scanEstimatedRows;
+ private Long scanEstimatedSizeInBytes;
private String serverWhereFilter;
private String serverDistinctFilter;
private Set<PColumn> serverMergeColumns;
@@ -588,6 +616,9 @@ public class ExplainPlanAttributes {
this.txnProvider = explainPlanAttributes.getTxnProvider();
List<String> srcRewrites = explainPlanAttributes.getRewrites();
this.rewrites = srcRewrites == null ? null : new
ArrayList<>(srcRewrites);
+ this.estimatedRows = explainPlanAttributes.getEstimatedRows();
+ this.estimatedSizeInBytes =
explainPlanAttributes.getEstimatedSizeInBytes();
+ this.estimateInfoTs = explainPlanAttributes.getEstimateInfoTs();
this.abstractExplainPlan =
explainPlanAttributes.getAbstractExplainPlan();
this.hint = explainPlanAttributes.getHint();
this.explainScanType = explainPlanAttributes.getExplainScanType();
@@ -608,8 +639,8 @@ public class ExplainPlanAttributes {
this.samplingRate = explainPlanAttributes.getSamplingRate();
this.hexStringRVCOffset = explainPlanAttributes.getHexStringRVCOffset();
this.iteratorTypeAndScanSize =
explainPlanAttributes.getIteratorTypeAndScanSize();
- this.estimatedRows = explainPlanAttributes.getEstimatedRows();
- this.estimatedSizeInBytes =
explainPlanAttributes.getEstimatedSizeInBytes();
+ this.scanEstimatedRows = explainPlanAttributes.getScanEstimatedRows();
+ this.scanEstimatedSizeInBytes =
explainPlanAttributes.getScanEstimatedSizeInBytes();
this.serverWhereFilter = explainPlanAttributes.getServerWhereFilter();
this.serverDistinctFilter =
explainPlanAttributes.getServerDistinctFilter();
this.serverMergeColumns = explainPlanAttributes.getServerMergeColumns();
@@ -684,6 +715,21 @@ public class ExplainPlanAttributes {
return this;
}
+ public ExplainPlanAttributesBuilder setEstimatedRows(Long estimatedRows) {
+ this.estimatedRows = estimatedRows;
+ return this;
+ }
+
+ public ExplainPlanAttributesBuilder setEstimatedSizeInBytes(Long
estimatedSizeInBytes) {
+ this.estimatedSizeInBytes = estimatedSizeInBytes;
+ return this;
+ }
+
+ public ExplainPlanAttributesBuilder setEstimateInfoTs(Long estimateInfoTs)
{
+ this.estimateInfoTs = estimateInfoTs;
+ return this;
+ }
+
public ExplainPlanAttributesBuilder setAbstractExplainPlan(String
abstractExplainPlan) {
this.abstractExplainPlan = abstractExplainPlan;
return this;
@@ -779,13 +825,13 @@ public class ExplainPlanAttributes {
return this;
}
- public ExplainPlanAttributesBuilder setEstimatedRows(Long estimatedRows) {
- this.estimatedRows = estimatedRows;
+ public ExplainPlanAttributesBuilder setScanEstimatedRows(Long
scanEstimatedRows) {
+ this.scanEstimatedRows = scanEstimatedRows;
return this;
}
- public ExplainPlanAttributesBuilder setEstimatedSizeInBytes(Long
estimatedSizeInBytes) {
- this.estimatedSizeInBytes = estimatedSizeInBytes;
+ public ExplainPlanAttributesBuilder setScanEstimatedSizeInBytes(Long
scanEstimatedSizeInBytes) {
+ this.scanEstimatedSizeInBytes = scanEstimatedSizeInBytes;
return this;
}
@@ -965,18 +1011,18 @@ public class ExplainPlanAttributes {
public ExplainPlanAttributes build() {
return new ExplainPlanAttributes(tenantId, viewName, viewBaseName,
cdcScopes, txnProvider,
- rewrites, abstractExplainPlan, hint, explainScanType, consistency,
tableName, keyRanges,
- indexName, indexKind, indexRule, indexRejected, saltBuckets,
regionsPlanned,
- scanTimeRangeMin, scanTimeRangeMax, splitsChunk,
useRoundRobinIterator, samplingRate,
- hexStringRVCOffset, iteratorTypeAndScanSize, estimatedRows,
estimatedSizeInBytes,
- serverWhereFilter, serverDistinctFilter, serverMergeColumns,
serverArrayElementProjection,
- serverFirstKeyOnlyProjection, serverEmptyColumnOnlyProjection,
serverAggregate,
- serverGroupByLimit, serverSortedBy, serverOffset, serverRowLimit,
clientFilterBy,
- clientAggregate, clientDistinctFilter, clientAfterAggregate,
clientSortAlgo, clientSortedBy,
- clientOffset, clientRowLimit, clientSequenceCount, clientCursorName,
clientSteps,
- lhsJoinQueryExplainPlan, rhsJoinQueryExplainPlan, subPlans,
dynamicServerFilter,
- afterJoinFilter, joinScannerLimit, sortMergeSkipMerge, regionLocations,
- regionLocationsTotalSize, numRegionLocationLookups);
+ rewrites, estimatedRows, estimatedSizeInBytes, estimateInfoTs,
abstractExplainPlan, hint,
+ explainScanType, consistency, tableName, keyRanges, indexName,
indexKind, indexRule,
+ indexRejected, saltBuckets, regionsPlanned, scanTimeRangeMin,
scanTimeRangeMax, splitsChunk,
+ useRoundRobinIterator, samplingRate, hexStringRVCOffset,
iteratorTypeAndScanSize,
+ scanEstimatedRows, scanEstimatedSizeInBytes, serverWhereFilter,
serverDistinctFilter,
+ serverMergeColumns, serverArrayElementProjection,
serverFirstKeyOnlyProjection,
+ serverEmptyColumnOnlyProjection, serverAggregate, serverGroupByLimit,
serverSortedBy,
+ serverOffset, serverRowLimit, clientFilterBy, clientAggregate,
clientDistinctFilter,
+ clientAfterAggregate, clientSortAlgo, clientSortedBy, clientOffset,
clientRowLimit,
+ clientSequenceCount, clientCursorName, clientSteps,
lhsJoinQueryExplainPlan,
+ rhsJoinQueryExplainPlan, subPlans, dynamicServerFilter,
afterJoinFilter, joinScannerLimit,
+ sortMergeSkipMerge, regionLocations, regionLocationsTotalSize,
numRegionLocationLookups);
}
}
}
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
index 8a26cb8c8c..f5838a936e 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
@@ -1256,6 +1256,7 @@ public class UpsertCompiler {
planSteps.addAll(queryPlanSteps);
if (getContext().isRoot()) {
ExplainTable.populateTopOfPlanAttributes(newBuilder, getContext(),
getTargetRef());
+ ExplainTable.populateTopOfPlanEstimates(newBuilder, this);
}
return new ExplainPlan(planSteps, newBuilder.build());
}
@@ -1445,6 +1446,7 @@ public class UpsertCompiler {
ExplainPlanAttributesBuilder builder =
new
ExplainPlanAttributesBuilder(ExplainPlanAttributes.getDefaultExplainPlan());
ExplainTable.populateTopOfPlanAttributes(builder, getContext(),
getTargetRef());
+ ExplainTable.populateTopOfPlanEstimates(builder, this);
return new ExplainPlan(planSteps, builder.build());
}
return new ExplainPlan(planSteps);
@@ -1573,6 +1575,7 @@ public class UpsertCompiler {
planSteps.addAll(queryPlanSteps);
if (getContext().isRoot()) {
ExplainTable.populateTopOfPlanAttributes(newBuilder, getContext(),
getTargetRef());
+ ExplainTable.populateTopOfPlanEstimates(newBuilder, this);
}
return new ExplainPlan(planSteps, newBuilder.build());
}
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/BaseQueryPlan.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/BaseQueryPlan.java
index d6839d6fa9..b955437b83 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/BaseQueryPlan.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/BaseQueryPlan.java
@@ -542,7 +542,8 @@ public abstract class BaseQueryPlan implements QueryPlan {
return planSteps;
}
- private Pair<List<String>, ExplainPlanAttributes>
getPlanStepsV2(ResultIterator iterator) {
+ private Pair<List<String>, ExplainPlanAttributes>
getPlanStepsV2(ResultIterator iterator)
+ throws SQLException {
List<String> planSteps = Lists.newArrayListWithExpectedSize(5);
ExplainPlanAttributesBuilder builder = new ExplainPlanAttributesBuilder();
iterator.explain(planSteps, builder);
@@ -553,6 +554,7 @@ public abstract class BaseQueryPlan implements QueryPlan {
}
if (context.isRoot()) {
ExplainTable.populateTopOfPlanAttributes(builder, context,
getTableRef());
+ ExplainTable.populateTopOfPlanEstimates(builder, this);
}
return Pair.of(planSteps, builder.build());
}
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 c0dc5ffe68..4ecdb69117 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
@@ -311,6 +311,7 @@ public class ClientAggregatePlan extends
ClientProcessingPlan {
if (context.isRoot()) {
ExplainTable.populateTopOfPlanAttributes(newBuilder, context,
getTableRef());
+ ExplainTable.populateTopOfPlanEstimates(newBuilder, this);
}
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 ae155b3a12..ec9f15d801 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
@@ -168,6 +168,7 @@ public class ClientScanPlan extends ClientProcessingPlan {
if (context.isRoot()) {
ExplainTable.populateTopOfPlanAttributes(newBuilder, context,
getTableRef());
+ ExplainTable.populateTopOfPlanEstimates(newBuilder, this);
}
return new ExplainPlan(planSteps, newBuilder.build());
}
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java
index 5a92f4adca..9995c2f06c 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java
@@ -363,6 +363,7 @@ public class HashJoinPlan extends DelegateQueryPlan {
}
if (getContext().isRoot()) {
ExplainTable.populateTopOfPlanAttributes(builder, getContext(),
getTableRef());
+ ExplainTable.populateTopOfPlanEstimates(builder, this);
}
return new ExplainPlan(planSteps, builder.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 e3ebae1779..3c769cf7bb 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
@@ -220,6 +220,7 @@ public class SortMergeJoinPlan implements QueryPlan {
rootBuilder.setRhsJoinQueryExplainPlan(rhsPlanAttributes);
if (getContext().isRoot()) {
ExplainTable.populateTopOfPlanAttributes(rootBuilder, getContext(),
getTableRef());
+ ExplainTable.populateTopOfPlanEstimates(rootBuilder, this);
}
return new ExplainPlan(steps, rootBuilder.build());
}
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/UnionPlan.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/UnionPlan.java
index a704750319..0ff5496839 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/UnionPlan.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/UnionPlan.java
@@ -245,6 +245,7 @@ public class UnionPlan implements QueryPlan {
addUnionTailLines(steps, builder);
if (getContext().isRoot()) {
ExplainTable.populateTopOfPlanAttributes(builder, getContext(),
getTableRef());
+ ExplainTable.populateTopOfPlanEstimates(builder, this);
}
return new ExplainPlan(steps, builder.build());
}
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java
index 97f1dbf0b7..87956b1141 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java
@@ -1850,8 +1850,8 @@ public abstract class BaseResultIterators extends
ExplainTable implements Result
buf.append(estimatedRows).append(" ROWS ");
buf.append(estimatedSize).append(" BYTES ");
if (explainPlanAttributesBuilder != null) {
- explainPlanAttributesBuilder.setEstimatedRows(estimatedRows);
- explainPlanAttributesBuilder.setEstimatedSizeInBytes(estimatedSize);
+ explainPlanAttributesBuilder.setScanEstimatedRows(estimatedRows);
+
explainPlanAttributesBuilder.setScanEstimatedSizeInBytes(estimatedSize);
}
}
}
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java
index ca08b984fd..28318ea2c6 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java
@@ -17,6 +17,7 @@
*/
package org.apache.phoenix.iterate;
+import java.sql.SQLException;
import java.text.Format;
import java.util.ArrayList;
import java.util.Arrays;
@@ -41,6 +42,7 @@ import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
import org.apache.phoenix.compile.ScanRanges;
import org.apache.phoenix.compile.StatementContext;
+import org.apache.phoenix.compile.StatementPlan;
import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
import org.apache.phoenix.filter.BooleanExpressionFilter;
import org.apache.phoenix.filter.DistinctPrefixFilter;
@@ -227,6 +229,22 @@ public abstract class ExplainTable {
}
}
+ /**
+ * Populate the plan-total estimate attributes on the supplied builder from
the given plan. Should
+ * be invoked only for a root plan.
+ * @param builder the attributes builder to populate (no-op when null)
+ * @param plan the plan to read estimates from (no-op when null)
+ */
+ public static void populateTopOfPlanEstimates(ExplainPlanAttributesBuilder
builder,
+ StatementPlan plan) throws SQLException {
+ if (builder == null || plan == null) {
+ return;
+ }
+ builder.setEstimatedRows(plan.getEstimatedRowsToScan());
+ builder.setEstimatedSizeInBytes(plan.getEstimatedBytesToScan());
+ builder.setEstimateInfoTs(plan.getEstimateInfoTimestamp());
+ }
+
private static int collectAppliedRewrites(StatementContext context,
Set<String> out) {
int flattenCount = context.getDerivedTableFlattenCount();
out.addAll(context.getAppliedRewrites());
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
index 173d8ef29b..0b982288a4 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
@@ -1018,29 +1018,37 @@ public class PhoenixStatement implements
PhoenixMonitoredStatement, SQLCloseable
Long estimatedBytesToScan = plan.getEstimatedBytesToScan();
Long estimatedRowsToScan = plan.getEstimatedRowsToScan();
Long estimateInfoTimestamp = plan.getEstimateInfoTimestamp();
+ // The three estimate columns are plan totals, not per-step values. Emit
them only on the
+ // top-of-plan. Subsequent rows carry just the plan step column.
+ boolean firstRow = true;
for (String planStep : planSteps) {
byte[] row = PVarchar.INSTANCE.toBytes(planStep);
- List<Cell> cells = Lists.newArrayListWithCapacity(3);
+ // The top-of-plan row carries the plan step plus up to three estimate
cells. Every other
+ // row carries only the plan step.
+ List<Cell> cells = Lists.newArrayListWithCapacity(firstRow ? 4 : 1);
cells.add(PhoenixKeyValueUtil.newKeyValue(row, EXPLAIN_PLAN_FAMILY,
EXPLAIN_PLAN_COLUMN,
MetaDataProtocol.MIN_TABLE_TIMESTAMP, ByteUtil.EMPTY_BYTE_ARRAY));
- if (estimatedBytesToScan != null) {
- cells.add(
- PhoenixKeyValueUtil.newKeyValue(row, EXPLAIN_PLAN_FAMILY,
EXPLAIN_PLAN_BYTES_ESTIMATE,
- MetaDataProtocol.MIN_TABLE_TIMESTAMP,
PLong.INSTANCE.toBytes(estimatedBytesToScan)));
- }
- if (estimatedRowsToScan != null) {
- cells.add(
- PhoenixKeyValueUtil.newKeyValue(row, EXPLAIN_PLAN_FAMILY,
EXPLAIN_PLAN_ROWS_ESTIMATE,
- MetaDataProtocol.MIN_TABLE_TIMESTAMP,
PLong.INSTANCE.toBytes(estimatedRowsToScan)));
- }
- if (estimateInfoTimestamp != null) {
- cells.add(
- PhoenixKeyValueUtil.newKeyValue(row, EXPLAIN_PLAN_FAMILY,
EXPLAIN_PLAN_ESTIMATE_INFO_TS,
- MetaDataProtocol.MIN_TABLE_TIMESTAMP,
PLong.INSTANCE.toBytes(estimateInfoTimestamp)));
+ if (firstRow) {
+ if (estimatedBytesToScan != null) {
+ cells.add(PhoenixKeyValueUtil.newKeyValue(row, EXPLAIN_PLAN_FAMILY,
+ EXPLAIN_PLAN_BYTES_ESTIMATE,
MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+ PLong.INSTANCE.toBytes(estimatedBytesToScan)));
+ }
+ if (estimatedRowsToScan != null) {
+ cells.add(
+ PhoenixKeyValueUtil.newKeyValue(row, EXPLAIN_PLAN_FAMILY,
EXPLAIN_PLAN_ROWS_ESTIMATE,
+ MetaDataProtocol.MIN_TABLE_TIMESTAMP,
PLong.INSTANCE.toBytes(estimatedRowsToScan)));
+ }
+ if (estimateInfoTimestamp != null) {
+ cells.add(PhoenixKeyValueUtil.newKeyValue(row, EXPLAIN_PLAN_FAMILY,
+ EXPLAIN_PLAN_ESTIMATE_INFO_TS,
MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+ PLong.INSTANCE.toBytes(estimateInfoTimestamp)));
+ }
}
Collections.sort(cells, CellComparator.getInstance());
Tuple tuple = new MultiKeyValueTuple(cells);
tuples.add(tuple);
+ firstRow = false;
}
final Long estimatedBytes = estimatedBytesToScan;
final Long estimatedRows = estimatedRowsToScan;
diff --git
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ExplainPlanWithStatsEnabledIT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ExplainPlanWithStatsEnabledIT.java
index 4767ac34ba..1c9a5a4bad 100644
---
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ExplainPlanWithStatsEnabledIT.java
+++
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ExplainPlanWithStatsEnabledIT.java
@@ -21,6 +21,7 @@ import static
org.apache.phoenix.query.QueryServicesOptions.DEFAULT_USE_STATS_FO
import static org.apache.phoenix.util.PhoenixRuntime.TENANT_ID_ATTRIB;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -34,7 +35,10 @@ import java.util.List;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.compile.ExplainPlanAttributes;
+import org.apache.phoenix.compile.QueryPlan;
import org.apache.phoenix.jdbc.PhoenixConnection;
+import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
import org.apache.phoenix.jdbc.PhoenixResultSet;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.PTableKey;
@@ -412,6 +416,63 @@ public class ExplainPlanWithStatsEnabledIT extends
ParallelStatsEnabledIT {
return new Estimate(estimatedRows, estimatedBytes, estimateInfoTs);
}
+ @Test
+ public void testTopOfPlanEstimatesMatchStats() throws Exception {
+ String sql = "SELECT * FROM " + tableB + " WHERE k >= ?";
+ List<Object> binds = Lists.newArrayList();
+ binds.add(99);
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ PhoenixPreparedStatement stmt =
+ conn.prepareStatement(sql).unwrap(PhoenixPreparedStatement.class);
+ stmt.setInt(1, 99);
+ QueryPlan plan = stmt.optimizeQuery();
+ Long planRows = plan.getEstimatedRowsToScan();
+ Long planBytes = plan.getEstimatedBytesToScan();
+ Long planTs = plan.getEstimateInfoTimestamp();
+ assertEquals((Long) 10L, planRows);
+ assertEquals((Long) 634L, planBytes);
+ assertTrue(planTs > 0);
+
+ // First-row EXPLAIN cells must equal the stats-driven values.
+ Estimate info = getByteRowEstimates(conn, sql, binds);
+ assertEquals(planRows, info.estimatedRows);
+ assertEquals(planBytes, info.estimatedBytes);
+ assertEquals(planTs, info.estimateInfoTs);
+
+ // Top-of-plan attributes must equal the stats-driven values.
+ ExplainPlanAttributes attrs =
plan.getExplainPlan().getPlanStepsAsAttributes();
+ assertEquals(planRows, attrs.getEstimatedRows());
+ assertEquals(planBytes, attrs.getEstimatedSizeInBytes());
+ assertEquals(planTs, attrs.getEstimateInfoTs());
+ }
+ }
+
+ @Test
+ public void testEstimateCellsOnlyOnFirstRow() throws Exception {
+ // A query whose plan emits a SERVER FILTER BY step in addition to the
scan line produces a
+ // multi row EXPLAIN result set with stats populated estimates at the
top-of-plan.
+ String sql = "SELECT * FROM " + tableB + " WHERE k >= 99 AND c1.a > c2.b";
+ try (Connection conn = DriverManager.getConnection(getUrl());
+ PreparedStatement stmt = conn.prepareStatement("EXPLAIN " + sql);
+ ResultSet rs = stmt.executeQuery()) {
+ assertTrue("expected at least one EXPLAIN row", rs.next());
+
assertNotNull(rs.getObject(PhoenixRuntime.EXPLAIN_PLAN_ESTIMATED_ROWS_READ_COLUMN));
+
assertNotNull(rs.getObject(PhoenixRuntime.EXPLAIN_PLAN_ESTIMATED_BYTES_READ_COLUMN));
+
assertNotNull(rs.getObject(PhoenixRuntime.EXPLAIN_PLAN_ESTIMATE_INFO_TS_COLUMN));
+ int extraRows = 0;
+ while (rs.next()) {
+ extraRows++;
+ rs.getObject(PhoenixRuntime.EXPLAIN_PLAN_ESTIMATED_ROWS_READ_COLUMN);
+ assertTrue("EST_ROWS_READ must be NULL on row " + (extraRows + 1),
rs.wasNull());
+ rs.getObject(PhoenixRuntime.EXPLAIN_PLAN_ESTIMATED_BYTES_READ_COLUMN);
+ assertTrue("EST_BYTES_READ must be NULL on row " + (extraRows + 1),
rs.wasNull());
+ rs.getObject(PhoenixRuntime.EXPLAIN_PLAN_ESTIMATE_INFO_TS_COLUMN);
+ assertTrue("EST_INFO_TS must be NULL on row " + (extraRows + 1),
rs.wasNull());
+ }
+ assertTrue("expected multi-step plan", extraRows >= 1);
+ }
+ }
+
@Test
public void testSettingUseStatsForParallelizationProperty() throws Exception
{
try (Connection conn = DriverManager.getConnection(getUrl())) {
diff --git
a/phoenix-core/src/it/java/org/apache/phoenix/schema/stats/BaseStatsCollectorIT.java
b/phoenix-core/src/it/java/org/apache/phoenix/schema/stats/BaseStatsCollectorIT.java
index 0b731fff6e..f6771376b4 100644
---
a/phoenix-core/src/it/java/org/apache/phoenix/schema/stats/BaseStatsCollectorIT.java
+++
b/phoenix-core/src/it/java/org/apache/phoenix/schema/stats/BaseStatsCollectorIT.java
@@ -254,8 +254,8 @@ public abstract class BaseStatsCollectorIT extends BaseTest
{
conn.createStatement()
.execute("CREATE TABLE " + fullTableName + " ( k CHAR(1) PRIMARY KEY )"
+ tableDDLOptions);
collectStatistics(conn, fullTableName);
- assertPlan(conn, "SELECT * FROM " +
fullTableName).splitsChunk(1).estimatedRows(0L)
- .estimatedBytes(20L).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN")
+ assertPlan(conn, "SELECT * FROM " +
fullTableName).splitsChunk(1).scanEstimatedRows(0L)
+ .scanEstimatedBytes(20L).iteratorType("PARALLEL 1-WAY").scanType("FULL
SCAN")
.table(physicalTableName).serverProjectionFilter(columnEncoded);
conn.close();
}
@@ -276,22 +276,22 @@ public abstract class BaseStatsCollectorIT extends
BaseTest {
assertPlan(conn, "SELECT v2 FROM " + fullTableName + " WHERE v2='foo'")
.splitsChunk(columnEncoded && !mutable ? 4 : 3)
- .estimatedRows(columnEncoded && !mutable ? 1L : 0L)
- .estimatedBytes(columnEncoded && !mutable ? 38L :
20L).iteratorType("PARALLEL 3-WAY")
+ .scanEstimatedRows(columnEncoded && !mutable ? 1L : 0L)
+ .scanEstimatedBytes(columnEncoded && !mutable ? 38L :
20L).iteratorType("PARALLEL 3-WAY")
.scanType("FULL SCAN").table(physicalTableName)
.serverWhereFilter("SERVER FILTER BY B.V2 =
'foo'").clientSortAlgo("CLIENT MERGE SORT");
long estimatedSizeInBytes = columnEncoded ? 28
: TransactionFactory.Provider.OMID.name().equals(transactionProvider) ?
38
: 34;
- assertPlan(conn, "SELECT * FROM " +
fullTableName).splitsChunk(4).estimatedRows(1L)
- .estimatedBytes(estimatedSizeInBytes).iteratorType("PARALLEL
3-WAY").scanType("FULL SCAN")
+ assertPlan(conn, "SELECT * FROM " +
fullTableName).splitsChunk(4).scanEstimatedRows(1L)
+ .scanEstimatedBytes(estimatedSizeInBytes).iteratorType("PARALLEL
3-WAY").scanType("FULL SCAN")
.table(physicalTableName).serverWhereFilter(null).clientSortAlgo("CLIENT
MERGE SORT");
assertPlan(conn, "SELECT * FROM " + fullTableName + " WHERE k =
'a'").splitsChunk(1)
- .estimatedRows(1L).estimatedBytes(columnEncoded ? 204L :
202L).iteratorType("PARALLEL 1-WAY")
- .scanType("POINT LOOKUP ON 1
KEY").table(physicalTableName).serverWhereFilter(null)
- .clientSortAlgo("CLIENT MERGE SORT");
+ .scanEstimatedRows(1L).scanEstimatedBytes(columnEncoded ? 204L : 202L)
+ .iteratorType("PARALLEL 1-WAY").scanType("POINT LOOKUP ON 1
KEY").table(physicalTableName)
+ .serverWhereFilter(null).clientSortAlgo("CLIENT MERGE SORT");
conn.close();
}
@@ -309,7 +309,8 @@ public abstract class BaseStatsCollectorIT extends BaseTest
{
Array array;
conn = upsertValues(props, fullTableName);
collectStatistics(conn, fullTableName);
- Long rows1 = assertPlan(conn, "SELECT k FROM " +
fullTableName).attributes().getEstimatedRows();
+ Long rows1 =
+ assertPlan(conn, "SELECT k FROM " +
fullTableName).attributes().getScanEstimatedRows();
stmt = upsertStmt(conn, fullTableName);
stmt.setString(1, "z");
s = new String[] { "xyz", "def", "ghi", "jkll", null, null, "xxx" };
@@ -321,7 +322,8 @@ public abstract class BaseStatsCollectorIT extends BaseTest
{
stmt.execute();
conn.commit();
collectStatistics(conn, fullTableName);
- Long rows2 = assertPlan(conn, "SELECT k FROM " +
fullTableName).attributes().getEstimatedRows();
+ Long rows2 =
+ assertPlan(conn, "SELECT k FROM " +
fullTableName).attributes().getScanEstimatedRows();
assertNotEquals(rows1, rows2);
conn.close();
}
@@ -573,8 +575,8 @@ public abstract class BaseStatsCollectorIT extends BaseTest
{
: (TransactionFactory.Provider.OMID.name().equals(transactionProvider))
? 25044
: 12420;
- assertPlan(conn, "SELECT * FROM " +
fullTableName).splitsChunk(26).estimatedRows(25L)
- .estimatedBytes(sizeInBytes).iteratorType("PARALLEL
1-WAY").scanType("FULL SCAN")
+ assertPlan(conn, "SELECT * FROM " +
fullTableName).splitsChunk(26).scanEstimatedRows(25L)
+ .scanEstimatedBytes(sizeInBytes).iteratorType("PARALLEL
1-WAY").scanType("FULL SCAN")
.table(physicalTableName);
ConnectionQueryServices services =
conn.unwrap(PhoenixConnection.class).getQueryServices();
@@ -636,8 +638,8 @@ public abstract class BaseStatsCollectorIT extends BaseTest
{
assertEquals(0, rs.getLong(1));
assertFalse(rs.next());
- assertPlan(conn, "SELECT * FROM " +
fullTableName).splitsChunk(1).estimatedRows(null)
- .estimatedBytes(null).iteratorType("PARALLEL 1-WAY").scanType("FULL
SCAN")
+ assertPlan(conn, "SELECT * FROM " +
fullTableName).splitsChunk(1).scanEstimatedRows(null)
+ .scanEstimatedBytes(null).iteratorType("PARALLEL 1-WAY").scanType("FULL
SCAN")
.table(physicalTableName);
}
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 eb96912f95..e07eb68261 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
@@ -66,12 +66,21 @@ public final class ExplainJsonNormalizer {
if (obj.has("regionsPlanned")) {
obj.set("regionsPlanned", NullNode.getInstance());
}
+ if (obj.has("scanEstimatedRows")) {
+ obj.set("scanEstimatedRows", NullNode.getInstance());
+ }
+ if (obj.has("scanEstimatedSizeInBytes")) {
+ obj.set("scanEstimatedSizeInBytes", NullNode.getInstance());
+ }
if (obj.has("estimatedRows")) {
obj.set("estimatedRows", NullNode.getInstance());
}
if (obj.has("estimatedSizeInBytes")) {
obj.set("estimatedSizeInBytes", NullNode.getInstance());
}
+ if (obj.has("estimateInfoTs")) {
+ obj.set("estimateInfoTs", NullNode.getInstance());
+ }
JsonNode iter = obj.get("iteratorTypeAndScanSize");
if (iter != null && iter.isTextual()) {
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 09ece953c6..cd4503bf3f 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
@@ -1167,15 +1167,21 @@ public class ExplainPlanTest extends
BaseConnectionlessQueryTest {
ObjectNode root = mapper.createObjectNode();
root.put("iteratorTypeAndScanSize", "PARALLEL 16-WAY");
root.put("splitsChunk", 4);
- root.put("estimatedRows", 1234L);
- root.put("estimatedSizeInBytes", 9876L);
+ root.put("scanEstimatedRows", 1234L);
+ root.put("scanEstimatedSizeInBytes", 9876L);
+ root.put("estimatedRows", 4321L);
+ root.put("estimatedSizeInBytes", 6789L);
+ root.put("estimateInfoTs", 1748880000000L);
root.put("numRegionLocationLookups", 7);
root.set("regionLocations", mapper.createArrayNode().add("anything"));
new ExplainJsonNormalizer().normalize(root);
assertEquals("PARALLEL <N>-WAY",
root.get("iteratorTypeAndScanSize").asText());
assertTrue(root.get("splitsChunk").isNull());
+ assertTrue(root.get("scanEstimatedRows").isNull());
+ assertTrue(root.get("scanEstimatedSizeInBytes").isNull());
assertTrue(root.get("estimatedRows").isNull());
assertTrue(root.get("estimatedSizeInBytes").isNull());
+ assertTrue(root.get("estimateInfoTs").isNull());
assertTrue(root.get("regionLocations").isNull());
assertEquals(0, root.get("numRegionLocationLookups").asInt());
}
@@ -1371,10 +1377,13 @@ public class ExplainPlanTest extends
BaseConnectionlessQueryTest {
n.putNull("cdcScopes");
n.putNull("txnProvider");
n.putNull("rewrites");
- n.putNull("abstractExplainPlan");
- n.putNull("splitsChunk");
n.putNull("estimatedRows");
n.putNull("estimatedSizeInBytes");
+ n.putNull("estimateInfoTs");
+ n.putNull("abstractExplainPlan");
+ n.putNull("splitsChunk");
+ n.putNull("scanEstimatedRows");
+ n.putNull("scanEstimatedSizeInBytes");
n.putNull("iteratorTypeAndScanSize");
n.putNull("samplingRate");
n.put("useRoundRobinIterator", false);
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 cac67da165..62e32025f0 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
@@ -451,6 +451,17 @@ public final class ExplainPlanTestUtil {
return this;
}
+ public ExplainPlanAssert scanEstimatedRows(Long expected) {
+ assertEquals(at("scanEstimatedRows"), expected,
attributes.getScanEstimatedRows());
+ return this;
+ }
+
+ public ExplainPlanAssert scanEstimatedBytes(Long expected) {
+ assertEquals(at("scanEstimatedSizeInBytes"), expected,
+ attributes.getScanEstimatedSizeInBytes());
+ return this;
+ }
+
public ExplainPlanAssert estimatedRows(Long expected) {
assertEquals(at("estimatedRows"), expected,
attributes.getEstimatedRows());
return this;
@@ -461,6 +472,11 @@ public final class ExplainPlanTestUtil {
return this;
}
+ public ExplainPlanAssert estimateInfoTs(Long expected) {
+ assertEquals(at("estimateInfoTs"), expected,
attributes.getEstimateInfoTs());
+ return this;
+ }
+
public ExplainPlanAssert splitsChunk(Integer expected) {
assertEquals(at("splitsChunk"), expected, attributes.getSplitsChunk());
return this;