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/07/24 00:23:40 UTC
(phoenix) 07/27: PHOENIX-7891 Explain the query optimizer's index selection rationale (#2513)
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 9125dcb14644dbd91b5d3cba79af97c8e9eed5fc
Author: Andrew Purtell <ap...@apache.org>
AuthorDate: Wed Jun 10 17:21:22 2026 -0700
PHOENIX-7891 Explain the query optimizer's index selection rationale (#2513)
Co-authored-by: Claude Opus 4.8[1m] <no...@anthropic.com>
---
.../phoenix/compile/ExplainPlanAttributes.java | 100 ++++++----
.../java/org/apache/phoenix/compile/QueryPlan.java | 14 ++
.../org/apache/phoenix/execute/BaseQueryPlan.java | 17 ++
.../apache/phoenix/execute/DelegateQueryPlan.java | 11 ++
.../phoenix/iterate/BaseResultIterators.java | 6 +
.../org/apache/phoenix/iterate/ExplainTable.java | 40 +++-
.../apache/phoenix/optimize/OptimizerDecision.java | 81 ++++++++
.../apache/phoenix/optimize/OptimizerReasons.java | 61 ++++++
.../apache/phoenix/optimize/QueryOptimizer.java | 212 +++++++++++++++++++--
.../phoenix/optimize/RejectedIndexEntry.java | 65 +++++++
.../end2end/BaseTenantSpecificViewIndexIT.java | 7 +-
.../phoenix/end2end/CostBasedDecisionIT.java | 60 ++++--
.../java/org/apache/phoenix/end2end/InListIT.java | 78 +++++---
.../org/apache/phoenix/end2end/ReverseScanIT.java | 7 +-
.../phoenix/end2end/TenantSpecificViewIndexIT.java | 7 +-
.../index/ChildViewsUseParentViewIndexIT.java | 10 +-
.../end2end/index/GlobalIndexCheckerIT.java | 18 +-
.../end2end/index/GlobalIndexOptimizationIT.java | 50 +++--
.../apache/phoenix/end2end/index/IndexUsageIT.java | 60 ++++--
.../apache/phoenix/end2end/index/LocalIndexIT.java | 81 +++++---
.../phoenix/end2end/index/MutableIndexIT.java | 40 ++--
.../phoenix/end2end/index/PartialIndexIT.java | 7 +-
.../phoenix/end2end/index/SaltedIndexIT.java | 27 ++-
.../phoenix/end2end/index/SingleCellIndexIT.java | 24 ++-
.../apache/phoenix/end2end/index/ViewIndexIT.java | 7 +-
.../end2end/join/HashJoinGlobalIndexIT.java | 18 +-
.../phoenix/end2end/join/HashJoinLocalIndexIT.java | 18 +-
.../phoenix/end2end/json/JsonFunctionsIT.java | 16 +-
.../apache/phoenix/compile/QueryCompilerTest.java | 16 +-
.../apache/phoenix/compile/QueryOptimizerTest.java | 5 +-
.../compile/StatementHintsCompilationTest.java | 4 +-
.../TenantSpecificViewIndexCompileTest.java | 22 ++-
.../org/apache/phoenix/query/QueryPlanTest.java | 22 ++-
.../phoenix/query/explain/ExplainPlanTest.java | 175 ++++++++++++++---
.../phoenix/query/explain/ExplainPlanTestUtil.java | 67 +++++++
35 files changed, 1179 insertions(+), 274 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 6c80e407d5..84012ddfa1 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
@@ -25,6 +25,7 @@ import java.util.List;
import java.util.Set;
import org.apache.hadoop.hbase.HRegionLocation;
import org.apache.hadoop.hbase.client.Consistency;
+import org.apache.phoenix.optimize.RejectedIndexEntry;
import org.apache.phoenix.parse.HintNode;
import org.apache.phoenix.parse.HintNode.Hint;
import org.apache.phoenix.schema.PColumn;
@@ -35,16 +36,16 @@ import org.apache.phoenix.schema.PColumn;
* Strings containing entire plan.
*/
@JsonPropertyOrder({ "abstractExplainPlan", "hint", "explainScanType", "consistency", "tableName",
- "keyRanges", "indexName", "indexKind", "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",
+ "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" })
public class ExplainPlanAttributes {
@@ -57,6 +58,8 @@ public class ExplainPlanAttributes {
private final String keyRanges;
private final String indexName;
private final String indexKind;
+ private final String indexRule;
+ private final List<RejectedIndexEntry> indexRejected;
private final Integer saltBuckets;
private final Integer regionsPlanned;
private final Long scanTimeRangeMin;
@@ -122,6 +125,8 @@ public class ExplainPlanAttributes {
this.keyRanges = null;
this.indexName = null;
this.indexKind = null;
+ this.indexRule = null;
+ this.indexRejected = null;
this.saltBuckets = null;
this.regionsPlanned = null;
this.scanTimeRangeMin = null;
@@ -169,20 +174,20 @@ public class ExplainPlanAttributes {
public ExplainPlanAttributes(String abstractExplainPlan, Hint hint, String explainScanType,
Consistency consistency, String tableName, String keyRanges, String indexName, String indexKind,
- 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,
- Set<PColumn> serverMergeColumns, boolean serverArrayElementProjection,
- boolean serverFirstKeyOnlyProjection, boolean serverEmptyColumnOnlyProjection,
- String serverAggregate, Integer serverGroupByLimit, String serverSortedBy, Integer serverOffset,
- Long serverRowLimit, 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,
+ 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, Set<PColumn> serverMergeColumns,
+ boolean serverArrayElementProjection, boolean serverFirstKeyOnlyProjection,
+ boolean serverEmptyColumnOnlyProjection, String serverAggregate, Integer serverGroupByLimit,
+ String serverSortedBy, Integer serverOffset, Long serverRowLimit, 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,
Integer regionLocationsTotalSize, int numRegionLocationLookups) {
this.abstractExplainPlan = abstractExplainPlan;
this.hint = hint;
@@ -192,6 +197,10 @@ public class ExplainPlanAttributes {
this.keyRanges = keyRanges;
this.indexName = indexName;
this.indexKind = indexKind;
+ this.indexRule = indexRule;
+ this.indexRejected = (indexRejected == null || indexRejected.isEmpty())
+ ? null
+ : Collections.unmodifiableList(new ArrayList<>(indexRejected));
this.saltBuckets = saltBuckets;
this.regionsPlanned = regionsPlanned;
this.scanTimeRangeMin = scanTimeRangeMin;
@@ -271,6 +280,14 @@ public class ExplainPlanAttributes {
return indexKind;
}
+ public String getIndexRule() {
+ return indexRule;
+ }
+
+ public List<RejectedIndexEntry> getIndexRejected() {
+ return indexRejected;
+ }
+
public Integer getSaltBuckets() {
return saltBuckets;
}
@@ -458,6 +475,8 @@ public class ExplainPlanAttributes {
private String keyRanges;
private String indexName;
private String indexKind;
+ private String indexRule;
+ private List<RejectedIndexEntry> indexRejected;
private Integer saltBuckets;
private Integer regionsPlanned;
private Long scanTimeRangeMin;
@@ -515,6 +534,9 @@ public class ExplainPlanAttributes {
this.keyRanges = explainPlanAttributes.getKeyRanges();
this.indexName = explainPlanAttributes.getIndexName();
this.indexKind = explainPlanAttributes.getIndexKind();
+ this.indexRule = explainPlanAttributes.getIndexRule();
+ List<RejectedIndexEntry> srcIndexRejected = explainPlanAttributes.getIndexRejected();
+ this.indexRejected = srcIndexRejected == null ? null : new ArrayList<>(srcIndexRejected);
this.saltBuckets = explainPlanAttributes.getSaltBuckets();
this.regionsPlanned = explainPlanAttributes.getRegionsPlanned();
this.scanTimeRangeMin = explainPlanAttributes.getScanTimeRangeMin();
@@ -602,6 +624,16 @@ public class ExplainPlanAttributes {
return this;
}
+ public ExplainPlanAttributesBuilder setIndexRule(String indexRule) {
+ this.indexRule = indexRule;
+ return this;
+ }
+
+ public ExplainPlanAttributesBuilder setIndexRejected(List<RejectedIndexEntry> indexRejected) {
+ this.indexRejected = indexRejected == null ? null : new ArrayList<>(indexRejected);
+ return this;
+ }
+
public ExplainPlanAttributesBuilder setSaltBuckets(Integer saltBuckets) {
this.saltBuckets = saltBuckets;
return this;
@@ -833,16 +865,16 @@ public class ExplainPlanAttributes {
public ExplainPlanAttributes build() {
return new ExplainPlanAttributes(abstractExplainPlan, hint, explainScanType, consistency,
- tableName, keyRanges, indexName, indexKind, 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,
+ 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);
}
}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/QueryPlan.java b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/QueryPlan.java
index a56a262b80..1b2d315c9b 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/QueryPlan.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/QueryPlan.java
@@ -28,6 +28,7 @@ import org.apache.phoenix.execute.visitor.QueryPlanVisitor;
import org.apache.phoenix.iterate.ParallelScanGrouper;
import org.apache.phoenix.iterate.ResultIterator;
import org.apache.phoenix.optimize.Cost;
+import org.apache.phoenix.optimize.OptimizerDecision;
import org.apache.phoenix.parse.FilterableStatement;
import org.apache.phoenix.parse.SelectStatement;
import org.apache.phoenix.query.KeyRange;
@@ -103,4 +104,17 @@ public interface QueryPlan extends StatementPlan {
* </pre>
*/
public List<OrderBy> getOutputOrderBys();
+
+ /**
+ * The optimizer's index selection rationale for this plan, or {@code null} if this plan did not
+ * participate in optimizer index selection.
+ */
+ default OptimizerDecision getOptimizerDecision() {
+ return null;
+ }
+
+ /** Records the optimizer's index selection rationale on this plan. */
+ default void setOptimizerDecision(OptimizerDecision decision) {
+ // no-op
+ }
}
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 e0dbc5afad..e7ca83c84d 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
@@ -57,6 +57,7 @@ import org.apache.phoenix.iterate.ParallelScanGrouper;
import org.apache.phoenix.iterate.ResultIterator;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixStatement.Operation;
+import org.apache.phoenix.optimize.OptimizerDecision;
import org.apache.phoenix.parse.FilterableStatement;
import org.apache.phoenix.parse.HintNode.Hint;
import org.apache.phoenix.parse.ParseNodeFactory;
@@ -110,6 +111,7 @@ public abstract class BaseQueryPlan implements QueryPlan {
protected Long estimateInfoTimestamp;
private boolean getEstimatesCalled;
protected boolean isApplicable = true;
+ private OptimizerDecision optimizerDecision;
protected BaseQueryPlan(StatementContext context, FilterableStatement statement, TableRef table,
RowProjector projection, ParameterMetaData paramMetaData, Integer limit, Integer offset,
@@ -543,6 +545,11 @@ public abstract class BaseQueryPlan implements QueryPlan {
List<String> planSteps = Lists.newArrayListWithExpectedSize(5);
ExplainPlanAttributesBuilder builder = new ExplainPlanAttributesBuilder();
iterator.explain(planSteps, builder);
+ OptimizerDecision decision = getOptimizerDecision();
+ if (decision != null) {
+ builder.setIndexRule(decision.getRule());
+ builder.setIndexRejected(decision.getRejectedIndexes());
+ }
return Pair.of(planSteps, builder.build());
}
@@ -585,6 +592,16 @@ public abstract class BaseQueryPlan implements QueryPlan {
this.isApplicable = isApplicable;
}
+ @Override
+ public OptimizerDecision getOptimizerDecision() {
+ return optimizerDecision;
+ }
+
+ @Override
+ public void setOptimizerDecision(OptimizerDecision decision) {
+ this.optimizerDecision = decision;
+ }
+
private void getEstimates() throws SQLException {
getEstimatesCalled = true;
// Initialize a dummy iterator to get the estimates based on stats.
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/DelegateQueryPlan.java b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/DelegateQueryPlan.java
index b78a4ab614..3ca9b8e059 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/DelegateQueryPlan.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/DelegateQueryPlan.java
@@ -32,6 +32,7 @@ import org.apache.phoenix.iterate.ParallelScanGrouper;
import org.apache.phoenix.iterate.ResultIterator;
import org.apache.phoenix.jdbc.PhoenixStatement.Operation;
import org.apache.phoenix.optimize.Cost;
+import org.apache.phoenix.optimize.OptimizerDecision;
import org.apache.phoenix.parse.FilterableStatement;
import org.apache.phoenix.query.KeyRange;
import org.apache.phoenix.schema.TableRef;
@@ -171,4 +172,14 @@ public abstract class DelegateQueryPlan implements QueryPlan {
public boolean isApplicable() {
return delegate.isApplicable();
}
+
+ @Override
+ public OptimizerDecision getOptimizerDecision() {
+ return delegate.getOptimizerDecision();
+ }
+
+ @Override
+ public void setOptimizerDecision(OptimizerDecision decision) {
+ delegate.setOptimizerDecision(decision);
+ }
}
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 7e81d3a534..97f1dbf0b7 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
@@ -86,6 +86,7 @@ import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
import org.apache.phoenix.hbase.index.util.VersionUtil;
import org.apache.phoenix.join.HashCacheClient;
import org.apache.phoenix.monitoring.OverAllQueryMetrics;
+import org.apache.phoenix.optimize.OptimizerDecision;
import org.apache.phoenix.parse.FilterableStatement;
import org.apache.phoenix.parse.HintNode;
import org.apache.phoenix.parse.HintNode.Hint;
@@ -645,6 +646,11 @@ public abstract class BaseResultIterators extends ExplainTable implements Result
return splits == null ? 0 : splits.size();
}
+ @Override
+ protected OptimizerDecision getOptimizerDecision() {
+ return plan.getOptimizerDecision();
+ }
+
@Override
public List<List<Scan>> getScans() {
if (scans == null) return Collections.emptyList();
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 badc779744..ce20eb4571 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
@@ -43,6 +43,9 @@ import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
import org.apache.phoenix.filter.BooleanExpressionFilter;
import org.apache.phoenix.filter.DistinctPrefixFilter;
import org.apache.phoenix.filter.EmptyColumnOnlyFilter;
+import org.apache.phoenix.optimize.OptimizerDecision;
+import org.apache.phoenix.optimize.OptimizerReasons;
+import org.apache.phoenix.optimize.RejectedIndexEntry;
import org.apache.phoenix.parse.HintNode;
import org.apache.phoenix.parse.HintNode.Hint;
import org.apache.phoenix.query.KeyRange;
@@ -131,6 +134,27 @@ public abstract class ExplainTable {
return 0;
}
+ /**
+ * The optimizer's index selection rationale for the plan this scan belongs to, used to render the
+ * per-scan {@code INDEX} rule comment and the {@code !INDEX} rejection comments. Returns
+ * {@code null} when the plan did not participate in optimizer index selection (DML, DDL, and
+ * non-optimizer plans).
+ * @return the decision, or {@code null} when unavailable
+ */
+ protected OptimizerDecision getOptimizerDecision() {
+ return null;
+ }
+
+ /**
+ * Whether {@code rule} is a default rule whose {@code INDEX} comment is suppressed. The default
+ * rules are {@link OptimizerReasons#RULE_DATA_TABLE} (no candidate indexes considered) and
+ * {@link OptimizerReasons#RULE_ONLY_CANDIDATE} (a single viable candidate).
+ */
+ private static boolean isDefaultRule(String rule) {
+ return OptimizerReasons.RULE_DATA_TABLE.equals(rule)
+ || OptimizerReasons.RULE_ONLY_CANDIDATE.equals(rule);
+ }
+
/**
* Logical name used to render a table or index in EXPLAIN output. Shared by both the scan
* {@code OVER} line's local index decoration and the per scan {@code INDEX} line.
@@ -216,7 +240,21 @@ public abstract class ExplainTable {
indexKind = null;
}
}
- planSteps.add(" INDEX " + explainIndexName + (indexKind == null ? "" : " " + indexKind));
+ OptimizerDecision decision = getOptimizerDecision();
+ StringBuilder indexLine = new StringBuilder(" INDEX ").append(explainIndexName);
+ if (indexKind != null) {
+ indexLine.append(" ").append(indexKind);
+ }
+ if (decision != null && !isDefaultRule(decision.getRule())) {
+ indexLine.append(" /* ").append(decision.getRule()).append(" */");
+ }
+ planSteps.add(indexLine.toString());
+ if (decision != null) {
+ for (RejectedIndexEntry rejected : decision.getRejectedIndexes()) {
+ planSteps
+ .add(" /* !INDEX " + rejected.getName() + " -- " + rejected.getReason() + " */");
+ }
+ }
Integer bucketNum = tableRef.getTable().getBucketNum();
if (bucketNum != null) {
planSteps.add(" SALT BUCKETS " + bucketNum);
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/optimize/OptimizerDecision.java b/phoenix-core-client/src/main/java/org/apache/phoenix/optimize/OptimizerDecision.java
new file mode 100644
index 0000000000..be137a0bca
--- /dev/null
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/optimize/OptimizerDecision.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.phoenix.optimize;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Records the optimizer's index selection rationale: the chosen index (or data table), the rule
+ * that selected it, and the indexes that were considered but rejected. The {@code rule} is one of
+ * the closed-set {@code RULE_*} labels in {@link OptimizerReasons}. The entry for each rejected
+ * index carries a {@code REASON_*} label.
+ */
+public final class OptimizerDecision {
+ private final String chosenIndex;
+ private final String rule;
+ private final List<RejectedIndexEntry> rejectedIndexes;
+
+ public OptimizerDecision(String chosenIndex, String rule,
+ List<RejectedIndexEntry> rejectedIndexes) {
+ this.chosenIndex = chosenIndex;
+ this.rule = rule;
+ this.rejectedIndexes = rejectedIndexes == null
+ ? Collections.emptyList()
+ : Collections.unmodifiableList(new ArrayList<>(rejectedIndexes));
+ }
+
+ public String getChosenIndex() {
+ return chosenIndex;
+ }
+
+ public String getRule() {
+ return rule;
+ }
+
+ /** Never null; unmodifiable; possibly empty. */
+ public List<RejectedIndexEntry> getRejectedIndexes() {
+ return rejectedIndexes;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof OptimizerDecision)) {
+ return false;
+ }
+ OptimizerDecision that = (OptimizerDecision) o;
+ return Objects.equals(chosenIndex, that.chosenIndex) && Objects.equals(rule, that.rule)
+ && rejectedIndexes.equals(that.rejectedIndexes);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(chosenIndex, rule, rejectedIndexes);
+ }
+
+ @Override
+ public String toString() {
+ return "OptimizerDecision{chosenIndex=" + chosenIndex + ", rule=" + rule + ", rejectedIndexes="
+ + rejectedIndexes + "}";
+ }
+}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/optimize/OptimizerReasons.java b/phoenix-core-client/src/main/java/org/apache/phoenix/optimize/OptimizerReasons.java
new file mode 100644
index 0000000000..6084183956
--- /dev/null
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/optimize/OptimizerReasons.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.phoenix.optimize;
+
+/**
+ * Closed-set label vocabulary for {@link OptimizerDecision}. The {@code RULE_*} constants name the
+ * rule the optimizer used to choose a plan. The {@code REASON_*} constants explain why a candidate
+ * index was rejected.
+ */
+public final class OptimizerReasons {
+
+ // RULE_* — chosen-plan rule labels.
+ public static final String RULE_POINT_LOOKUP = "point lookup";
+ public static final String RULE_ONLY_CANDIDATE = "only candidate";
+ public static final String RULE_CDC_INDEX = "CDC index";
+ public static final String RULE_COST_BASED = "cost-based";
+ public static final String RULE_HINT = "hint";
+ public static final String RULE_DATA_TABLE = "data table";
+ public static final String RULE_MORE_BOUND_PK_COLUMNS = "more bound PK columns";
+ public static final String RULE_ORDER_PRESERVING = "order-preserving";
+ public static final String RULE_NON_LOCAL_PREFERRED = "non-local preferred";
+ public static final String RULE_PARTIAL_INDEX_APPLICABLE = "partial index applicable";
+
+ // REASON_* — rejected-index reason labels.
+ public static final String REASON_NO_PK_PREFIX_BOUND = "no PK prefix bound";
+ public static final String REASON_DOES_NOT_COVER_PROJECTION = "does not cover projection";
+ public static final String REASON_PARTIAL_INDEX_PREDICATE_NOT_SATISFIED =
+ "partial index predicate not satisfied";
+ public static final String REASON_EXCLUDED_BY_NO_INDEX_HINT = "excluded by NO_INDEX hint";
+ public static final String REASON_LOCAL_INDEX_LOSES_TO_GLOBAL_BY_RULE =
+ "local index loses to global by rule";
+ public static final String REASON_COST_BASED_LOSS = "cost-based loss";
+ public static final String REASON_DEGENERATE_RANGE = "degenerate range";
+ public static final String REASON_FULL_SCAN_WOULD_BE_REQUIRED = "full scan would be required";
+ public static final String REASON_NOT_APPLICABLE_TO_JOIN = "not applicable to join";
+ public static final String REASON_PATH_EXPRESSION_DOES_NOT_MATCH =
+ "path expression does not match";
+
+ /** Builds the functional index rule label of the form {@code "matches <expr>"}. */
+ public static String matches(String expression) {
+ return "matches " + expression;
+ }
+
+ private OptimizerReasons() {
+ }
+}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java b/phoenix-core-client/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
index aec0c0edfc..28372949d1 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
@@ -20,6 +20,7 @@ package org.apache.phoenix.optimize;
import static org.apache.phoenix.query.QueryConstants.CDC_JSON_COL_NAME;
import java.sql.SQLException;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -156,7 +157,7 @@ public class QueryOptimizer {
&& (select.getWhere() == null || !select.getWhere().hasSubquery())
) {
return getApplicablePlansForSingleFlatQuery(dataPlan, statement, targetColumns,
- parallelIteratorFactory, stopAtBestPlan);
+ parallelIteratorFactory, stopAtBestPlan, new DecisionState());
}
Map<TableRef, QueryPlan> dataPlans = null;
@@ -224,7 +225,8 @@ public class QueryOptimizer {
private List<QueryPlan> getApplicablePlansForSingleFlatQuery(QueryPlan dataPlan,
PhoenixStatement statement, List<? extends PDatum> targetColumns,
- ParallelIteratorFactory parallelIteratorFactory, boolean stopAtBestPlan) throws SQLException {
+ ParallelIteratorFactory parallelIteratorFactory, boolean stopAtBestPlan, DecisionState state)
+ throws SQLException {
SelectStatement select = (SelectStatement) dataPlan.getStatement();
String indexHint = select.getHint().getHint(Hint.INDEX);
// Exit early if we have a point lookup w/o index hint as we can't get better than that
@@ -232,7 +234,8 @@ public class QueryOptimizer {
indexHint == null && dataPlan.getContext().getScanRanges().isPointLookup() && stopAtBestPlan
&& dataPlan.isApplicable()
) {
- return Collections.<QueryPlan> singletonList(dataPlan);
+ return Collections.<QueryPlan> singletonList(
+ recordDecision(dataPlan, OptimizerReasons.RULE_POINT_LOOKUP, state));
}
ColumnResolver indexResolver = null;
@@ -271,7 +274,11 @@ public class QueryOptimizer {
dataPlan.isApplicable() && (indexes.isEmpty() || dataPlan.isDegenerate()
|| dataPlan.getTableRef().hasDynamicCols() || select.getHint().hasHint(Hint.NO_INDEX))
) {
- return Collections.<QueryPlan> singletonList(dataPlan);
+ if (select.getHint().hasHint(Hint.NO_INDEX)) {
+ state.rejectAll(indexes, OptimizerReasons.REASON_EXCLUDED_BY_NO_INDEX_HINT);
+ }
+ return Collections.<
+ QueryPlan> singletonList(recordDecision(dataPlan, OptimizerReasons.RULE_DATA_TABLE, state));
}
// The targetColumns is set for UPSERT SELECT to ensure that the proper type conversion takes
// place.
@@ -297,7 +304,7 @@ public class QueryOptimizer {
if (!forCDC) {
plans.add(dataPlan);
hintedPlan = getHintedQueryPlan(statement, translatedIndexSelect, indexes, targetColumns,
- parallelIteratorFactory, plans);
+ parallelIteratorFactory, plans, state);
if (hintedPlan != null) {
PTable index = hintedPlan.getTableRef().getTable();
// Ignore any index hint with a CDC index as it is a truncated index, it only
@@ -307,7 +314,8 @@ public class QueryOptimizer {
stopAtBestPlan && hintedPlan.isApplicable()
&& (index.getIndexWhere() == null || isPartialIndexUsable(select, dataPlan, index))
) {
- return Collections.singletonList(hintedPlan);
+ return Collections
+ .singletonList(recordDecision(hintedPlan, OptimizerReasons.RULE_HINT, state));
}
plans.add(0, hintedPlan);
}
@@ -321,8 +329,9 @@ public class QueryOptimizer {
// the data table mutations outside the max lookback window
continue;
}
- QueryPlan plan = addPlan(statement, translatedIndexSelect, index, targetColumns,
+ AddPlanResult result = addPlan(statement, translatedIndexSelect, index, targetColumns,
parallelIteratorFactory, dataPlan, false, indexResolver);
+ QueryPlan plan = result.plan;
if (
plan != null
&& (index.getIndexWhere() == null || isPartialIndexUsable(select, dataPlan, index))
@@ -332,6 +341,12 @@ public class QueryOptimizer {
return Collections.singletonList(plan);
}
plans.add(plan);
+ } else if (plan == null) {
+ // Index candidate could not be built into a usable plan; record why it was rejected.
+ state.reject(result.rejection);
+ } else {
+ // Plan built, but the partial-index predicate is not satisfied by this query's WHERE.
+ state.reject(index, OptimizerReasons.REASON_PARTIAL_INDEX_PREDICATE_NOT_SATISFIED);
}
}
@@ -350,13 +365,14 @@ public class QueryOptimizer {
// OrderPlans
return hintedPlan == null
- ? orderPlansBestToWorst(select, applicablePlans, stopAtBestPlan)
+ ? orderPlansBestToWorst(select, applicablePlans, stopAtBestPlan, state, forCDC)
: applicablePlans;
}
private QueryPlan getHintedQueryPlan(PhoenixStatement statement, SelectStatement select,
List<PTable> indexes, List<? extends PDatum> targetColumns,
- ParallelIteratorFactory parallelIteratorFactory, List<QueryPlan> plans) throws SQLException {
+ ParallelIteratorFactory parallelIteratorFactory, List<QueryPlan> plans, DecisionState state)
+ throws SQLException {
QueryPlan dataPlan = plans.get(0);
String indexHint = select.getHint().getHint(Hint.INDEX);
if (indexHint == null) {
@@ -395,11 +411,12 @@ public class QueryOptimizer {
// Hinted index is applicable, so return it's index
PTable index = indexes.get(indexPos);
indexes.remove(indexPos);
- QueryPlan plan = addPlan(statement, select, index, targetColumns, parallelIteratorFactory,
- dataPlan, true, null);
- if (plan != null) {
- return plan;
+ AddPlanResult result = addPlan(statement, select, index, targetColumns,
+ parallelIteratorFactory, dataPlan, true, null);
+ if (result.plan != null) {
+ return result.plan;
}
+ state.reject(result.rejection);
}
startIndex = endIndex + 1;
}
@@ -416,7 +433,7 @@ public class QueryOptimizer {
return -1;
}
- private QueryPlan addPlan(PhoenixStatement statement, SelectStatement select, PTable index,
+ private AddPlanResult addPlan(PhoenixStatement statement, SelectStatement select, PTable index,
List<? extends PDatum> targetColumns, ParallelIteratorFactory parallelIteratorFactory,
QueryPlan dataPlan, boolean isHinted, ColumnResolver indexResolver) throws SQLException {
String tableAlias = dataPlan.getTableRef().getTableAlias();
@@ -436,7 +453,7 @@ public class QueryOptimizer {
isHinted, indexSelect, resolver);
}
- private QueryPlan addPlan(PhoenixStatement statement, SelectStatement select, PTable index,
+ private AddPlanResult addPlan(PhoenixStatement statement, SelectStatement select, PTable index,
List<? extends PDatum> targetColumns, ParallelIteratorFactory parallelIteratorFactory,
QueryPlan dataPlan, boolean isHinted, SelectStatement indexSelect, ColumnResolver resolver)
throws SQLException {
@@ -547,7 +564,7 @@ public class QueryOptimizer {
indexTableRef.getCurrentTime(), indexTable.getIndexDisableTimestamp()))
) {
if (plan.getProjector().getColumnCount() == nColumns) {
- return plan;
+ return AddPlanResult.success(plan);
} else {
String schemaNameStr =
index.getSchemaName() == null ? null : index.getSchemaName().getString();
@@ -619,15 +636,15 @@ public class QueryOptimizer {
new QueryCompiler(statement, rewriteResult.getRewrittenSelectStatement(),
rewriteResult.getColumnResolver(), targetColumns, parallelIteratorFactory,
dataPlan.getContext().getSequenceManager(), isProjected, true, dataPlans).compile();
- return plan;
+ return AddPlanResult.success(plan);
}
}
} catch (RowValueConstructorOffsetNotCoercibleException e) {
// Could not coerce the user provided RVC Offset so we do not have a plan to add.
- return null;
+ return AddPlanResult.rejected(index, OptimizerReasons.REASON_DEGENERATE_RANGE);
}
}
- return null;
+ return AddPlanResult.rejected(index, OptimizerReasons.REASON_DOES_NOT_COVER_PROJECTION);
}
// returns true if we can still use the index
@@ -650,9 +667,11 @@ public class QueryOptimizer {
* @return list of plans ordered from best to worst.
*/
private List<QueryPlan> orderPlansBestToWorst(SelectStatement select, List<QueryPlan> plans,
- boolean stopAtBestPlan) {
+ boolean stopAtBestPlan, DecisionState state, boolean forCDC) {
final QueryPlan dataPlan = plans.get(0);
if (plans.size() == 1) {
+ recordDecision(plans.get(0),
+ forCDC ? OptimizerReasons.RULE_CDC_INDEX : OptimizerReasons.RULE_ONLY_CANDIDATE, state);
return plans;
}
@@ -666,6 +685,14 @@ public class QueryOptimizer {
// Return ordered list based on cost if stats are available; otherwise fall
// back to static ordering.
if (!plans.get(0).getCost().isUnknown()) {
+ QueryPlan costWinner = plans.get(0);
+ for (int i = 1; i < plans.size(); i++) {
+ PTable losingTable = plans.get(i).getTableRef().getTable();
+ if (losingTable.getType() == PTableType.INDEX) {
+ state.reject(losingTable, OptimizerReasons.REASON_COST_BASED_LOSS);
+ }
+ }
+ recordDecision(costWinner, OptimizerReasons.RULE_COST_BASED, state);
return stopAtBestPlan ? plans.subList(0, 1) : plans;
}
}
@@ -789,9 +816,154 @@ public class QueryOptimizer {
});
+ // Capture the optimizer's rationale for the chosen plan without altering the ordering above.
+ QueryPlan winner = bestCandidates.get(0);
+ QueryPlan runnerUp =
+ bestCandidates.size() > 1 ? bestCandidates.get(1) : firstOther(plans, winner);
+ tagComparatorRejections(winner, plans, boundRanges, state);
+ recordDecision(winner, labelComparatorRule(winner, runnerUp, boundRanges), state);
+
return stopAtBestPlan ? bestCandidates.subList(0, 1) : bestCandidates;
}
+ /**
+ * Outcome of attempting to build an index candidate plan in {@link #addPlan}. Carries either the
+ * successfully built {@code plan} or a {@code rejection} explaining why the candidate index was
+ * not usable.
+ */
+ private static final class AddPlanResult {
+ final QueryPlan plan;
+ final RejectedIndexEntry rejection;
+
+ private AddPlanResult(QueryPlan plan, RejectedIndexEntry rejection) {
+ this.plan = plan;
+ this.rejection = rejection;
+ }
+
+ static AddPlanResult success(QueryPlan plan) {
+ return new AddPlanResult(plan, null);
+ }
+
+ static AddPlanResult rejected(PTable index, String reason) {
+ return new AddPlanResult(null,
+ new RejectedIndexEntry(index.getTableName().getString(), reason));
+ }
+ }
+
+ /**
+ * Accumulates the rejected index entries gathered while selecting an index for a single flat
+ * query. Rejections are deduplicated by index name.
+ */
+ private static final class DecisionState {
+ private final List<RejectedIndexEntry> rejections = new ArrayList<>();
+
+ void reject(String name, String reason) {
+ for (RejectedIndexEntry existing : rejections) {
+ if (existing.getName().equals(name)) {
+ return;
+ }
+ }
+ rejections.add(new RejectedIndexEntry(name, reason));
+ }
+
+ void reject(PTable index, String reason) {
+ reject(index.getTableName().getString(), reason);
+ }
+
+ void reject(RejectedIndexEntry entry) {
+ if (entry != null) {
+ reject(entry.getName(), entry.getReason());
+ }
+ }
+
+ void rejectAll(List<PTable> indexes, String reason) {
+ for (PTable index : indexes) {
+ reject(index, reason);
+ }
+ }
+
+ List<RejectedIndexEntry> getRejections() {
+ return rejections;
+ }
+ }
+
+ /**
+ * Records the optimizer's index selection rationale on the chosen plan and returns it so callers
+ * can use this inline at a {@code return} site.
+ */
+ private static QueryPlan recordDecision(QueryPlan winner, String rule, DecisionState state) {
+ winner.setOptimizerDecision(
+ new OptimizerDecision(winner.getTableRef().getTable().getTableName().getString(), rule,
+ state == null ? null : state.getRejections()));
+ return winner;
+ }
+
+ /** First plan in {@code plans} that is not {@code exclude}, or {@code null} if there is none. */
+ private static QueryPlan firstOther(List<QueryPlan> plans, QueryPlan exclude) {
+ for (QueryPlan plan : plans) {
+ if (plan != exclude) {
+ return plan;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * The bound-PK-column count for {@code plan}.
+ */
+ private static int adjustedBoundCount(QueryPlan plan, int boundRanges) {
+ PTable table = plan.getTableRef().getTable();
+ int boundCount = plan.getContext().getScanRanges().getBoundPkColumnCount();
+ boundCount += table.getViewIndexId() == null ? 0 : (boundRanges - 1);
+ boundCount -= plan.getContext().getScanRanges().isSalted() ? 1 : 0;
+ return boundCount;
+ }
+
+ /**
+ * Returns the {@code RULE_*} label for the chosen plan by replaying the comparator ladder in
+ * {@link #orderPlansBestToWorst} against the runner up and returning the first decisive branch.
+ */
+ private static String labelComparatorRule(QueryPlan winner, QueryPlan runnerUp, int boundRanges) {
+ if (runnerUp == null) {
+ return OptimizerReasons.RULE_ONLY_CANDIDATE;
+ }
+ PTable winnerTable = winner.getTableRef().getTable();
+ PTable runnerUpTable = runnerUp.getTableRef().getTable();
+ if (adjustedBoundCount(winner, boundRanges) != adjustedBoundCount(runnerUp, boundRanges)) {
+ return OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS;
+ }
+ if (winner.getGroupBy() != null && runnerUp.getGroupBy() != null) {
+ if (winner.getGroupBy().isOrderPreserving() != runnerUp.getGroupBy().isOrderPreserving()) {
+ return OptimizerReasons.RULE_ORDER_PRESERVING;
+ }
+ }
+ if (winnerTable.getIndexWhere() != null && runnerUpTable.getIndexWhere() == null) {
+ return OptimizerReasons.RULE_PARTIAL_INDEX_APPLICABLE;
+ }
+ return OptimizerReasons.RULE_NON_LOCAL_PREFERRED;
+ }
+
+ /** Tags every index candidate that lost to {@code winner} with a rejection reason. */
+ private static void tagComparatorRejections(QueryPlan winner, List<QueryPlan> plans,
+ int boundRanges, DecisionState state) {
+ int winnerBound = adjustedBoundCount(winner, boundRanges);
+ boolean winnerNonLocal = winner.getTableRef().getTable().getIndexType() != IndexType.LOCAL;
+ for (QueryPlan plan : plans) {
+ if (plan == winner) {
+ continue;
+ }
+ PTable table = plan.getTableRef().getTable();
+ if (table.getType() != PTableType.INDEX) {
+ continue;
+ }
+ if (adjustedBoundCount(plan, boundRanges) < winnerBound) {
+ state.reject(table, OptimizerReasons.REASON_NO_PK_PREFIX_BOUND);
+ } else if (table.getIndexType() == IndexType.LOCAL && winnerNonLocal) {
+ state.reject(table, OptimizerReasons.REASON_LOCAL_INDEX_LOSES_TO_GLOBAL_BY_RULE);
+ }
+ }
+ }
+
private static class WhereConditionRewriter extends AndRewriterBooleanParseNodeVisitor {
private final ColumnResolver dataResolver;
private final ExpressionCompiler expressionCompiler;
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/optimize/RejectedIndexEntry.java b/phoenix-core-client/src/main/java/org/apache/phoenix/optimize/RejectedIndexEntry.java
new file mode 100644
index 0000000000..81568ac7c9
--- /dev/null
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/optimize/RejectedIndexEntry.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.phoenix.optimize;
+
+import java.util.Objects;
+
+/**
+ * An index the optimizer considered as a candidate but did not choose, paired with the reason it
+ * was rejected. The reason is one of the closed-set {@code REASON_*} labels in
+ * {@link OptimizerReasons}.
+ */
+public final class RejectedIndexEntry {
+ private final String name;
+ private final String reason;
+
+ public RejectedIndexEntry(String name, String reason) {
+ this.name = Objects.requireNonNull(name, "name");
+ this.reason = Objects.requireNonNull(reason, "reason");
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getReason() {
+ return reason;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof RejectedIndexEntry)) {
+ return false;
+ }
+ RejectedIndexEntry that = (RejectedIndexEntry) o;
+ return name.equals(that.name) && reason.equals(that.reason);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, reason);
+ }
+
+ @Override
+ public String toString() {
+ return "RejectedIndexEntry{name=" + name + ", reason=" + reason + "}";
+ }
+}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificViewIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificViewIndexIT.java
index 70708610c2..3c9b80282c 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificViewIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificViewIndexIT.java
@@ -29,6 +29,7 @@ import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.apache.hadoop.hbase.util.Pair;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.schema.types.PVarbinary;
import org.apache.phoenix.util.PhoenixRuntime;
import org.apache.phoenix.util.SchemaUtil;
@@ -197,7 +198,8 @@ public abstract class BaseTenantSpecificViewIndexIT extends SplitSystemCatalogIT
assertPlan(conn, "SELECT k1, k2, v2 FROM " + viewName + " WHERE v2='" + valuePrefix + "v2-1'")
.iteratorType(iteratorTypeAndScanSize).serverFirstKeyOnlyProjection(true)
.scanType("RANGE SCAN").clientSortAlgo(clientSortAlgo).table(expectedTableName)
- .keyRanges(keyRanges);
+ .keyRanges(keyRanges).indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedNone();
}
private void createAndVerifyIndexNonStringTenantId(Connection conn, String viewName,
@@ -213,7 +215,8 @@ public abstract class BaseTenantSpecificViewIndexIT extends SplitSystemCatalogIT
.table(SchemaUtil.getTableName(SchemaUtil.getSchemaNameFromFullName(viewName), indexName)
+ "(" + tableName + ")")
.keyRanges(" [1," + tenantId + ",'" + valuePrefix + "v2-1']")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedNone();
}
private Connection createTenantConnection(String tenantId) throws SQLException {
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 15fe361aaa..0556e706a5 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
@@ -31,6 +31,7 @@ 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.optimize.OptimizerReasons;
import org.apache.phoenix.query.BaseTest;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.util.PropertiesUtil;
@@ -79,7 +80,9 @@ 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.
- assertPlan(conn, query).scanType("FULL SCAN");
+ assertPlan(conn, query).scanType("FULL SCAN")
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedCount(1).indexRejected(
+ 0, tableName + "_IDX", OptimizerReasons.REASON_LOCAL_INDEX_LOSES_TO_GLOBAL_BY_RULE);
PreparedStatement stmt =
conn.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2) VALUES (?, ?, ?)");
@@ -94,7 +97,8 @@ 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.
- assertPlan(conn, query).scanType("RANGE SCAN");
+ assertPlan(conn, query).scanType("RANGE SCAN").indexRule(OptimizerReasons.RULE_COST_BASED)
+ .indexRejectedNone();
} finally {
conn.close();
}
@@ -243,7 +247,8 @@ public class CostBasedDecisionIT extends BaseTest {
.keyRanges(" [2,*] - [2,9,000]")
.serverWhereFilter(
"SERVER FILTER BY ((\"C1\" >= 10 AND \"C1\" <= 20) AND TO_INTEGER(\"C3\") < 5000)")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED)
+ .indexRejectedNone();
PreparedStatement stmt = conn
.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2, c3) VALUES (?, ?, ?, ?)");
@@ -262,7 +267,9 @@ public class CostBasedDecisionIT extends BaseTest {
.scanType("RANGE SCAN").table(indexName1 + "(" + tableName + ")")
.keyRanges(" [1,10] - [1,20]")
.serverWhereFilter("SERVER FILTER BY (\"C2\" < 9000 AND \"C3\" < 5000)")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_COST_BASED)
+ .indexRejectedCount(1)
+ .indexRejected(0, indexName2, OptimizerReasons.REASON_COST_BASED_LOSS);
} finally {
conn.close();
}
@@ -292,7 +299,8 @@ public class CostBasedDecisionIT extends BaseTest {
.keyRanges(" [2,*] - [2,9,000]")
.serverWhereFilter(
"SERVER FILTER BY ((\"C1\" >= 10 AND \"C1\" <= 20) AND TO_INTEGER(\"C3\") < 5000)")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED)
+ .indexRejectedNone();
PreparedStatement stmt = conn
.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2, c3) VALUES (?, ?, ?, ?)");
@@ -311,7 +319,9 @@ public class CostBasedDecisionIT extends BaseTest {
.iteratorType("PARALLEL").scanType("RANGE SCAN").table(indexName1 + "(" + tableName + ")")
.keyRanges(" [1,10] - [1,20]")
.serverWhereFilter("SERVER FILTER BY (\"C2\" < 9000 AND \"C3\" < 5000)")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_COST_BASED)
+ .indexRejectedCount(1)
+ .indexRejected(0, indexName2, OptimizerReasons.REASON_COST_BASED_LOSS);
} finally {
conn.close();
}
@@ -337,10 +347,14 @@ public class CostBasedDecisionIT extends BaseTest {
assertPlan(conn, query).abstractExplainPlan("UNION ALL OVER 2 QUERIES").subPlanCount(2)
.subPlan(0).iteratorType("PARALLEL").scanType("RANGE SCAN").table(tableName)
.keyRanges(" [*] - ['z']").serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]")
- .clientSortAlgo("CLIENT MERGE SORT").end().subPlan(1).iteratorType("PARALLEL")
- .scanType("RANGE SCAN").table(tableName).keyRanges(" ['a'] - [*]")
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedCount(1)
+ .indexRejected(0, indexName, OptimizerReasons.REASON_NO_PK_PREFIX_BOUND).end().subPlan(1)
+ .iteratorType("PARALLEL").scanType("RANGE SCAN").table(tableName).keyRanges(" ['a'] - [*]")
.serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]")
- .clientSortAlgo("CLIENT MERGE SORT").end();
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedCount(1)
+ .indexRejected(0, indexName, OptimizerReasons.REASON_NO_PK_PREFIX_BOUND).end();
PreparedStatement stmt =
conn.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2) VALUES (?, ?, ?)");
@@ -360,12 +374,13 @@ public class CostBasedDecisionIT extends BaseTest {
.table(indexName + "(" + tableName + ")").keyRanges(" [1]").serverMergeColumns("[0.C2]")
.serverFirstKeyOnlyProjection(true).serverWhereFilter("SERVER FILTER BY \"ROWKEY\" <= 'z'")
.serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]")
- .clientSortAlgo("CLIENT MERGE SORT").end().subPlan(1).iteratorType("PARALLEL")
- .scanType("RANGE SCAN").table(indexName + "(" + tableName + ")").keyRanges(" [1]")
- .serverMergeColumns("[0.C2]").serverFirstKeyOnlyProjection(true)
- .serverWhereFilter("SERVER FILTER BY \"ROWKEY\" >= 'a'")
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_COST_BASED)
+ .indexRejectedNone().end().subPlan(1).iteratorType("PARALLEL").scanType("RANGE SCAN")
+ .table(indexName + "(" + tableName + ")").keyRanges(" [1]").serverMergeColumns("[0.C2]")
+ .serverFirstKeyOnlyProjection(true).serverWhereFilter("SERVER FILTER BY \"ROWKEY\" >= 'a'")
.serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]")
- .clientSortAlgo("CLIENT MERGE SORT").end();
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_COST_BASED)
+ .indexRejectedNone().end();
} finally {
conn.close();
}
@@ -391,11 +406,14 @@ public class CostBasedDecisionIT extends BaseTest {
// Use the default plan when stats are not available.
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(tableName)
.serverWhereFilter("SERVER FILTER BY C1 LIKE 'X0%'")
- .dynamicServerFilter("DYNAMIC SERVER FILTER BY T1.ROWKEY IN (T2.MRK)").subPlanCount(1)
- .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */")
+ .dynamicServerFilter("DYNAMIC SERVER FILTER BY T1.ROWKEY IN (T2.MRK)").indexRule(null)
+ .indexRejectedNone().subPlanCount(1).subPlan(0)
+ .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */")
.iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(tableName)
.keyRanges(" [*] - ['z']").serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedCount(1)
+ .indexRejected(0, indexName, OptimizerReasons.REASON_NO_PK_PREFIX_BOUND);
PreparedStatement stmt =
conn.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2) VALUES (?, ?, ?)");
@@ -414,13 +432,15 @@ public class CostBasedDecisionIT extends BaseTest {
.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 /* HASH BUILD RIGHT */")
+ .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"T1.:ROWKEY\" IN (T2.MRK)").indexRule(null)
+ .indexRejectedNone().subPlanCount(1).subPlan(0)
+ .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */")
.iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
.table(indexName + "(" + tableName + ")").keyRanges(" [1]").serverMergeColumns("[0.C2]")
.serverFirstKeyOnlyProjection(true).serverWhereFilter("SERVER FILTER BY \"ROWKEY\" <= 'z'")
.serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_COST_BASED)
+ .indexRejectedNone();
} finally {
conn.close();
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/InListIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/InListIT.java
index ccbd52caf0..8b3541e73c 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/InListIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/InListIT.java
@@ -53,6 +53,7 @@ import org.apache.phoenix.compile.StatementContext;
import org.apache.phoenix.expression.Expression;
import org.apache.phoenix.iterate.ExplainTable;
import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.parse.ColumnParseNode;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.query.QueryServices;
@@ -1024,13 +1025,15 @@ public class InListIT extends ParallelStatsDisabledIT {
try (PreparedStatement preparedStmt = viewConn.prepareStatement("SELECT * FROM "
+ tenantView + " WHERE (ID1, ID2) " + "IN (('005xx000001Sv6o', '000000000000500'))")) {
assertPlan(PhoenixRuntime.getOptimizedQueryPlan(preparedStmt))
- .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING);
+ .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING)
+ .indexRule(OptimizerReasons.RULE_POINT_LOOKUP).indexRejectedNone();
}
try (PreparedStatement preparedStmt = viewConn.prepareStatement("SELECT * FROM "
+ tenantView + " WHERE (ID2, ID1) " + "IN (('000000000000500', '005xx000001Sv6o'))")) {
assertPlan(PhoenixRuntime.getOptimizedQueryPlan(preparedStmt))
- .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING);
+ .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING)
+ .indexRule(OptimizerReasons.RULE_POINT_LOOKUP).indexRejectedNone();
}
stmt.execute(
@@ -1065,14 +1068,16 @@ public class InListIT extends ParallelStatsDisabledIT {
+ tenantView + " WHERE (ID1, ID2) " + "IN (('005xx000001Sv6o', '000000000000500'),"
+ "('bar', '000000000000400')," + "('foo', '000000000000300'))")) {
assertPlan(PhoenixRuntime.getOptimizedQueryPlan(preparedStmt))
- .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING);
+ .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING)
+ .indexRule(OptimizerReasons.RULE_POINT_LOOKUP).indexRejectedNone();
}
try (PreparedStatement preparedStmt =
viewConn.prepareStatement("SELECT * FROM " + tenantView + " WHERE (ID2, ID1) IN "
+ "(('bar', '005xx000001Sv6o')," + "('foo', '005xx000001Sv6o'))")) {
assertPlan(PhoenixRuntime.getOptimizedQueryPlan(preparedStmt))
- .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING);
+ .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING)
+ .indexRule(OptimizerReasons.RULE_POINT_LOOKUP).indexRejectedNone();
}
stmt.execute("DELETE FROM " + tenantView + " WHERE (ID2, ID1) IN "
@@ -1111,14 +1116,16 @@ public class InListIT extends ParallelStatsDisabledIT {
+ "(('005xx000001Sv6o', '000000000000500')," + "('005xx000001Sv6o', '000000000000400'),"
+ "('005xx000001Sv6o', '000000000000300'))")) {
assertPlan(PhoenixRuntime.getOptimizedQueryPlan(preparedStmt))
- .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING);
+ .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING)
+ .indexRule(OptimizerReasons.RULE_POINT_LOOKUP).indexRejectedNone();
}
try (PreparedStatement preparedStmt = viewConn.prepareStatement("SELECT * FROM "
+ tenantView + " WHERE (ID2, ID1) IN " + "(('000000000000400', '005xx000001Sv6o'),"
+ "('000000000000300', '005xx000001Sv6o'))")) {
assertPlan(PhoenixRuntime.getOptimizedQueryPlan(preparedStmt))
- .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING);
+ .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING)
+ .indexRule(OptimizerReasons.RULE_POINT_LOOKUP).indexRejectedNone();
}
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM " + tenantView
+ " WHERE (ID2, ID1) IN " + "(('000000000000400', '005xx000001Sv6o'),"
@@ -1159,13 +1166,15 @@ public class InListIT extends ParallelStatsDisabledIT {
+ "(('005xx000001Sv6o', '000000000000500')," + "('005xx000001Sv6o', '000000000000400'))");
QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
assertEquals(numberOfRowsToScan, queryPlan.getEstimatedRowsToScan());
- assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING);
+ assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING)
+ .indexRule(OptimizerReasons.RULE_POINT_LOOKUP).indexRejectedNone();
viewConn.prepareStatement("DELETE FROM " + tenantView + " WHERE (ID1, ID2) IN "
+ "(('005xx000001Sv6o', '000000000000500')," + "('005xx000001Sv6o', '000000000000400'))");
queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
assertEquals(numberOfRowsToScan, queryPlan.getEstimatedRowsToScan());
- assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING);
+ assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING)
+ .indexRule(OptimizerReasons.RULE_POINT_LOOKUP).indexRejectedNone();
}
}
@@ -1174,22 +1183,26 @@ public class InListIT extends ParallelStatsDisabledIT {
PreparedStatement preparedStmt = viewConn.prepareStatement("SELECT * FROM " + tenantView
+ " WHERE (ID1) IN " + "(('005xx000001Sv6o')," + "('005xx000001Sv6o'))");
QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
- assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN");
+ assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
viewConn.prepareStatement("DELETE FROM " + tenantView + " WHERE (ID1) IN "
+ "(('005xx000001Sv6o')," + "('005xx000001Sv6o'))");
queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
- assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN");
+ assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
preparedStmt = viewConn.prepareStatement("SELECT * FROM " + tenantView + " WHERE (ID2) IN "
+ "(('000000000000500')," + "('000000000000400'))");
queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
- assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN");
+ assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
viewConn.prepareStatement("DELETE FROM " + tenantView + " WHERE (ID2) IN "
+ "(('000000000000500')," + "('000000000000400'))");
queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
- assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN");
+ assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
}
}
@@ -1198,22 +1211,26 @@ public class InListIT extends ParallelStatsDisabledIT {
PreparedStatement preparedStmt = viewConn.prepareStatement("SELECT * FROM " + tenantView
+ " WHERE (ID1, ID3) IN " + "(('005xx000001Sv6o', 1)," + "('005xx000001Sv6o', 2))");
QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
- assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN");
+ assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
viewConn.prepareStatement("DELETE FROM " + tenantView + " WHERE (ID1, ID3) IN "
+ "(('005xx000001Sv6o', 1)," + "('005xx000001Sv6o', 2))");
queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
- assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN");
+ assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
preparedStmt = viewConn.prepareStatement("SELECT * FROM " + tenantView
+ " WHERE (ID2, ID3) IN " + "(('000000000000500', 1)," + "('000000000000400', 2))");
queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
- assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN");
+ assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
viewConn.prepareStatement("DELETE FROM " + tenantView + " WHERE (ID2, ID3) IN "
+ "(('000000000000500', 1)," + "('000000000000400', 2))");
queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
- assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN");
+ assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
}
}
@@ -1223,12 +1240,14 @@ public class InListIT extends ParallelStatsDisabledIT {
PreparedStatement preparedStmt = viewConn.prepareStatement(
"SELECT * FROM " + tenantView + " WHERE (ID3, ID4) IN " + "((1, 1)," + "(2, 2))");
QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
- assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN");
+ assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
viewConn.prepareStatement(
"DELETE FROM " + tenantView + " WHERE (ID3, ID4) IN " + "((1, 1)," + "(2, 2))");
queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
- assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN");
+ assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
}
}
@@ -1730,19 +1749,22 @@ public class InListIT extends ParallelStatsDisabledIT {
"SELECT * FROM " + view + " WHERE (ID1, ID2) IN " + "((1, 1)," + "(2, 2))");
QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
assertEquals(new Long(2), queryPlan.getEstimatedRowsToScan());
- assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING);
+ assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING)
+ .indexRule(OptimizerReasons.RULE_POINT_LOOKUP).indexRejectedNone();
preparedStmt =
conn.prepareStatement("SELECT * FROM " + view + " WHERE (ID2, ID1) IN ((1, 1),(2, 2))");
queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
assertEquals(new Long(2), queryPlan.getEstimatedRowsToScan());
- assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING);
+ assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING)
+ .indexRule(OptimizerReasons.RULE_POINT_LOOKUP).indexRejectedNone();
preparedStmt =
conn.prepareStatement("SELECT * FROM " + view + " WHERE (ID2, ID1) IN ((1, 1),(2, 2))");
queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
assertEquals(new Long(2), queryPlan.getEstimatedRowsToScan());
- assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING);
+ assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING)
+ .indexRule(OptimizerReasons.RULE_POINT_LOOKUP).indexRejectedNone();
ResultSet rs = stmt.executeQuery("SELECT ID1, ID2, ID5, ID4 FROM " + view
+ " WHERE (POWER(ID2, 2), ID1) IN " + "((4.0, 9)," + "(10, 12))");
@@ -1788,19 +1810,22 @@ public class InListIT extends ParallelStatsDisabledIT {
PreparedStatement preparedStmt = conn.prepareStatement(
"SELECT * FROM " + view + " WHERE (ID4, ID2) IN " + "((1, 1)," + "(2, 2))");
QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
- assertPlan(queryPlan).scanType("RANGE SCAN");
+ assertPlan(queryPlan).scanType("RANGE SCAN").indexRule(OptimizerReasons.RULE_ONLY_CANDIDATE)
+ .indexRejectedCount(1);
preparedStmt = conn.prepareStatement(
"SELECT ID1,ID5 FROM " + view + " WHERE (ID1, ID2) IN " + "((1, 1),(2, 2))");
queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
assertEquals(new Long(2), queryPlan.getEstimatedRowsToScan());
- assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING);
+ assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING)
+ .indexRule(OptimizerReasons.RULE_POINT_LOOKUP).indexRejectedNone();
preparedStmt =
conn.prepareStatement("SELECT * FROM " + view + " WHERE (ID2, ID1) IN ((1, 1),(2, 2))");
queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
assertEquals(new Long(2), queryPlan.getEstimatedRowsToScan());
- assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING);
+ assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING)
+ .indexRule(OptimizerReasons.RULE_POINT_LOOKUP).indexRejectedNone();
ResultSet rs = stmt.executeQuery("SELECT ID1, ID2, ID5, ID4 FROM " + view
+ " WHERE (POWER(ID2, 2), ID1) IN " + "((4.0, 9)," + "(10, 12))");
@@ -1839,7 +1864,8 @@ public class InListIT extends ParallelStatsDisabledIT {
"SELECT VAL2 FROM " + fullTableName + " WHERE (ID2, ID1) IN ((1, 1),(2, 2))");
QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt);
queryPlan.getTableRef().getTable().getType();
- assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING);
+ assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING)
+ .indexRule(OptimizerReasons.RULE_POINT_LOOKUP).indexRejectedNone();
}
}
@@ -2181,6 +2207,8 @@ public class InListIT extends ParallelStatsDisabledIT {
int lastBoundCol = 0;
setBindVariables(stmt, lastBoundCol, numInLists, testPKTypes);
QueryPlan plan = stmt.compileQuery(query.toString());
+ // NOTE: compileQuery() does not run the QueryOptimizer, so the plan carries no
+ // OptimizerDecision (indexRule is null); intentionally no indexRule assertion here.
if (expectSkipScan) {
assertPlan(plan).iteratorType("PARALLEL 1-WAY")
.scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING);
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ReverseScanIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ReverseScanIT.java
index c1bab87569..5d87cddd23 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ReverseScanIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ReverseScanIT.java
@@ -38,6 +38,7 @@ import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.util.PropertiesUtil;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@@ -81,7 +82,8 @@ public class ReverseScanIT extends ParallelStatsDisabledIT {
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").clientSortedBy("REVERSE")
.scanType("FULL SCAN").table(tableName).serverFirstKeyOnlyProjection(true)
- .serverWhereFilter("SERVER FILTER BY ENTITY_ID >= '00A323122312312'");
+ .serverWhereFilter("SERVER FILTER BY ENTITY_ID >= '00A323122312312'")
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
PreparedStatement statement = conn.prepareStatement("SELECT entity_id FROM " + tableName
+ " WHERE organization_id = ? AND entity_id >= ? ORDER BY organization_id DESC, entity_id DESC");
@@ -179,7 +181,8 @@ public class ReverseScanIT extends ParallelStatsDisabledIT {
assertPlan(conn, query).iteratorType("SERIAL 1-WAY").clientSortedBy("REVERSE")
.scanType("RANGE SCAN").table(indexName).keyRanges(" [not null]")
- .serverFirstKeyOnlyProjection(true).serverRowLimit(1L).clientRowLimit(1);
+ .serverFirstKeyOnlyProjection(true).serverRowLimit(1L).clientRowLimit(1)
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
}
}
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 000dad55ab..84f6131ec6 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
@@ -37,6 +37,7 @@ import org.apache.hadoop.hbase.util.Bytes;
import org.apache.phoenix.compile.ExplainPlan;
import org.apache.phoenix.compile.ExplainPlanAttributes;
import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.schema.ColumnNotFoundException;
import org.apache.phoenix.schema.PNameFactory;
@@ -367,7 +368,8 @@ public class TenantSpecificViewIndexIT extends BaseTenantSpecificViewIndexIT {
.keyRanges(" ['tenant1 ','001','2011-01-01 00:00:00.001']"
+ " - ['tenant1 ','001','2016-10-31 00:00:00.000']")
.serverFirstKeyOnlyProjection(true).serverRowLimit(501L).clientRowLimit(501)
- .clientSteps("CLIENT 501 ROW LIMIT");
+ .clientSteps("CLIENT 501 ROW LIMIT").indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedNone();
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'))"
@@ -378,7 +380,8 @@ public class TenantSpecificViewIndexIT extends BaseTenantSpecificViewIndexIT {
.keyRanges(" ['tenant1 ','001','2012-10-21 00:00:00.001']"
+ " - ['tenant1 ','001','2016-01-01 00:00:00.000']")
.serverFirstKeyOnlyProjection(true).serverRowLimit(501L).clientRowLimit(501)
- .clientSteps("CLIENT 501 ROW LIMIT");
+ .clientSteps("CLIENT 501 ROW LIMIT").indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedNone();
}
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ChildViewsUseParentViewIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ChildViewsUseParentViewIndexIT.java
index f0bcbab7ee..08471a9e5b 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ChildViewsUseParentViewIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ChildViewsUseParentViewIndexIT.java
@@ -32,6 +32,7 @@ import java.util.Properties;
import org.apache.phoenix.end2end.ParallelStatsDisabledIT;
import org.apache.phoenix.end2end.ParallelStatsDisabledTest;
import org.apache.phoenix.jdbc.PhoenixConnection;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.schema.PTable;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@@ -167,7 +168,8 @@ public class ChildViewsUseParentViewIndexIT extends ParallelStatsDisabledIT {
assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY").serverFirstKeyOnlyProjection(true)
.scanType("SKIP SCAN ON 3 KEYS").table("_IDX_" + baseTableName)
.keyRanges(" [" + Short.MIN_VALUE + ",'1'" + childViewScanKey + "] - [" + Short.MIN_VALUE
- + ",'3'" + childViewScanKey + "]");
+ + ",'3'" + childViewScanKey + "]")
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
ResultSet rs = conn.createStatement().executeQuery(sql);
assertTrue(rs.next());
@@ -189,7 +191,8 @@ public class ChildViewsUseParentViewIndexIT extends ParallelStatsDisabledIT {
assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY")
.serverWhereFilter(
"SERVER FILTER BY (A4 IN ('1','2','3') AND ((A1 = 'X' AND A2 = 'Y') AND A3 = 'Z'))")
- .scanType("FULL SCAN").table(baseTableName);
+ .scanType("FULL SCAN").table(baseTableName).indexRule(OptimizerReasons.RULE_DATA_TABLE)
+ .indexRejectedNone();
ResultSet rs = conn.createStatement().executeQuery(sql);
assertTrue(rs.next());
@@ -264,7 +267,8 @@ public class ChildViewsUseParentViewIndexIT extends ParallelStatsDisabledIT {
assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY").serverFirstKeyOnlyProjection(true)
.scanType("SKIP SCAN ON 5 RANGES").table("_IDX_" + baseTableName)
.keyRanges(" [" + Short.MIN_VALUE + ",'00Dxxxxxxxxxxx1','003xxxxxxxxxxx1',*] - ["
- + Short.MIN_VALUE + ",'00Dxxxxxxxxxxx1','003xxxxxxxxxxx5',~'2016-01-01 06:00:00.000']");
+ + Short.MIN_VALUE + ",'00Dxxxxxxxxxxx1','003xxxxxxxxxxx5',~'2016-01-01 06:00:00.000']")
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
ResultSet rs = conn.createStatement().executeQuery(sql);
for (int i = 0; i < expectedRows; ++i) {
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java
index 31e55b55d3..f0ffe1fd9f 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java
@@ -60,6 +60,7 @@ import org.apache.phoenix.end2end.IndexToolIT;
import org.apache.phoenix.end2end.NeedsOwnMiniClusterTest;
import org.apache.phoenix.hbase.index.IndexRegionObserver;
import org.apache.phoenix.mapreduce.index.IndexTool;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.query.BaseTest;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.schema.PTableImpl;
@@ -143,9 +144,15 @@ public class GlobalIndexCheckerIT extends BaseTest {
public static void assertExplainPlan(Connection conn, String selectSql, String dataTableFullName,
String indexTableFullName) throws SQLException {
+ assertExplainPlan(conn, selectSql, dataTableFullName, indexTableFullName,
+ OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS);
+ }
+
+ public static void assertExplainPlan(Connection conn, String selectSql, String dataTableFullName,
+ String indexTableFullName, String expectedRule) throws SQLException {
// Verify the query is served by a RANGE SCAN over the index table not the data table.
ExplainPlanAttributes attributes = getExplainAttributes(conn, selectSql);
- assertPlan(attributes).scanType("RANGE SCAN");
+ assertPlan(attributes).scanType("RANGE SCAN").indexRule(expectedRule);
String actualTable =
attributes.getTableName() == null ? null : attributes.getTableName().replaceAll(":", ".");
String expectedTable = SchemaUtil.normalizeIdentifier(indexTableFullName);
@@ -287,7 +294,9 @@ public class GlobalIndexCheckerIT extends BaseTest {
+ dataTableName + " WHERE val1 = 'bc' AND " + "PHOENIX_ROW_TIMESTAMP() > TO_DATE('"
+ after.toString() + "','yyyy-MM-dd HH:mm:ss.SSS', '" + timeZoneID + "')";
// Verify that we will read from the data table
- assertPlan(conn, noIndexQuery).scanType("FULL SCAN").table(dataTableName);
+ assertPlan(conn, noIndexQuery).scanType("FULL SCAN").table(dataTableName)
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedCount(1)
+ .indexRejected(0, indexTableName, OptimizerReasons.REASON_EXCLUDED_BY_NO_INDEX_HINT);
rs = conn.createStatement().executeQuery(noIndexQuery);
assertTrue(rs.next());
assertEquals("bc", rs.getString(1));
@@ -374,7 +383,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
+ "PHOENIX_ROW_TIMESTAMP() > TO_DATE('" + initial.toString()
+ "','yyyy-MM-dd HH:mm:ss.SSS', '" + timeZoneID + "')";
- assertExplainPlan(conn, query, dataTableName, indexTableName);
+ assertExplainPlan(conn, query, dataTableName, indexTableName, OptimizerReasons.RULE_HINT);
waitForEventualConsistency();
rs = conn.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -746,7 +755,8 @@ public class GlobalIndexCheckerIT extends BaseTest {
// now read the same row from data table
selectSql = "SELECT * from " + dataTableName + " WHERE id = 'a'";
- assertPlan(conn, selectSql).table(dataTableName);
+ assertPlan(conn, selectSql).table(dataTableName).indexRule(OptimizerReasons.RULE_POINT_LOOKUP)
+ .indexRejectedNone();
try (ResultSet rs = conn.createStatement().executeQuery(selectSql)) {
assertTrue(rs.next());
assertEquals("a", rs.getString(1));
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexOptimizationIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexOptimizationIT.java
index 39800cf2c4..6fd1de6f90 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexOptimizationIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexOptimizationIT.java
@@ -30,6 +30,7 @@ import java.sql.SQLException;
import java.util.regex.Pattern;
import org.apache.phoenix.end2end.NeedsOwnMiniClusterTest;
import org.apache.phoenix.end2end.ParallelStatsDisabledIT;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.query.explain.ExplainPlanTestUtil.ExplainPlanAssert;
import org.apache.phoenix.util.PhoenixRuntime;
import org.apache.phoenix.util.SchemaUtil;
@@ -83,7 +84,9 @@ public class GlobalIndexOptimizationIT extends ParallelStatsDisabledIT {
assertMutationPlan(conn1, query).abstractExplainPlan("DELETE ROWS CLIENT SELECT")
.scanType("RANGE SCAN").table(indexTableName + "L(" + dataTableName + ")")
.keyRanges(" [1,*] - [1,100]").serverFirstKeyOnlyProjection(true)
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedCount(1)
+ .indexRejected(0, indexTableName + "G", OptimizerReasons.REASON_NO_PK_PREFIX_BOUND);
ResultSet rs = conn1.createStatement().executeQuery("SELECT COUNT(*) FROM " + dataTableName);
rs.next();
int count = rs.getInt(1);
@@ -154,7 +157,8 @@ public class GlobalIndexOptimizationIT extends ParallelStatsDisabledIT {
String query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ * FROM "
+ dataTableName + " where v1='a'";
assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName).keyRanges(" ['a']")
- .serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true);
+ .serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true)
+ .indexRule(OptimizerReasons.RULE_HINT).indexRejectedNone();
ResultSet rs = conn1.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -172,7 +176,8 @@ public class GlobalIndexOptimizationIT extends ParallelStatsDisabledIT {
query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ * FROM "
+ dataTableName + " where v1='a'";
assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName).keyRanges(" ['a']")
- .serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true);
+ .serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true)
+ .indexRule(OptimizerReasons.RULE_HINT).indexRejectedNone();
rs = conn1.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -193,7 +198,8 @@ public class GlobalIndexOptimizationIT extends ParallelStatsDisabledIT {
+ dataTableName + " where v1='a' limit 1";
assertPlan(conn1, query).iteratorType("SERIAL").scanType("RANGE SCAN").table(indexTableName)
.keyRanges(" ['a']").serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true)
- .serverRowLimit(1L).clientRowLimit(1);
+ .serverRowLimit(1L).clientRowLimit(1).indexRule(OptimizerReasons.RULE_HINT)
+ .indexRejectedNone();
rs = conn1.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -209,7 +215,8 @@ public class GlobalIndexOptimizationIT extends ParallelStatsDisabledIT {
+ " where v1<='z' and k3 > 1 order by V1,t_id";
assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName)
.keyRanges(" [*] - ['z']").serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true)
- .serverWhereFilter("SERVER FILTER BY \"K3\" > 1");
+ .serverWhereFilter("SERVER FILTER BY \"K3\" > 1").indexRule(OptimizerReasons.RULE_HINT)
+ .indexRejectedNone();
rs = conn1.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -239,11 +246,13 @@ public class GlobalIndexOptimizationIT extends ParallelStatsDisabledIT {
ExplainPlanAssert skipScanJoinPlan = assertPlan(conn1, query).scanType("FULL SCAN")
.table(dataTableName).serverWhereFilter("SERVER FILTER BY K3 > 1")
.serverSortedBy("[" + dataTableName + ".V1, " + dataTableName + ".T_ID]")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_HINT)
+ .indexRejectedNone();
skipScanJoinPlan.subPlanCount(1).subPlan(0)
.abstractExplainPlan("SKIP-SCAN-JOIN TABLE 0 /* HASH BUILD RIGHT */")
.scanType("RANGE SCAN").table(indexTableName).keyRanges(" [*] - ['z']")
- .serverFirstKeyOnlyProjection(true).end();
+ .serverFirstKeyOnlyProjection(true).indexRule(OptimizerReasons.RULE_DATA_TABLE)
+ .indexRejectedNone().end();
// The dynamic server filter references compiler-generated positional aliases ($N.$N) whose
// numbers are not stable, so match the structural shape of the attribute with a regex.
String dynamicServerFilter = skipScanJoinPlan.attributes().getDynamicServerFilter();
@@ -282,7 +291,8 @@ public class GlobalIndexOptimizationIT extends ParallelStatsDisabledIT {
assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName)
.keyRanges(" [*] - ['z']").serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true)
.serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"V1\", \"T_ID\", \"K3\"]")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_HINT)
+ .indexRejectedNone();
rs = conn1.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -309,7 +319,8 @@ public class GlobalIndexOptimizationIT extends ParallelStatsDisabledIT {
assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName)
.keyRanges(" [*] - ['z']").serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true)
- .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"V1\"]");
+ .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"V1\"]")
+ .indexRule(OptimizerReasons.RULE_HINT).indexRejectedNone();
rs = conn1.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -342,7 +353,8 @@ public class GlobalIndexOptimizationIT extends ParallelStatsDisabledIT {
String query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName
+ ")*/ k1,k2,k3,v1 FROM " + dataTableName + " where v1='a'";
assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName)
- .keyRanges(" ['tid1','a']").serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true);
+ .keyRanges(" ['tid1','a']").serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true)
+ .indexRule(OptimizerReasons.RULE_HINT).indexRejectedNone();
ResultSet rs = conn1.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -395,7 +407,8 @@ public class GlobalIndexOptimizationIT extends ParallelStatsDisabledIT {
assertPlan(conn1, query).scanType("SKIP SCAN ON 2 KEYS").table("_IDX_" + dataTableName)
.keyRanges(" [" + Short.MIN_VALUE + ",1] - [" + Short.MIN_VALUE + ",2]")
- .serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true);
+ .serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true)
+ .indexRule(OptimizerReasons.RULE_HINT).indexRejectedNone();
rs = conn1.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -433,7 +446,8 @@ public class GlobalIndexOptimizationIT extends ParallelStatsDisabledIT {
String query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName
+ ")*/ t_id, k1, k2, V1 FROM " + dataTableName + " where v1='a'";
assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName).keyRanges(" ['a']")
- .serverFirstKeyOnlyProjection(true);
+ .serverFirstKeyOnlyProjection(true).indexRule(OptimizerReasons.RULE_HINT)
+ .indexRejectedNone();
ResultSet rs = conn1.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -449,7 +463,9 @@ public class GlobalIndexOptimizationIT extends ParallelStatsDisabledIT {
// No INDEX hint specified
query = "SELECT t_id, k1, k2, k3, V1 FROM " + dataTableName + " where v1='a'";
assertPlan(conn1, query).scanType("FULL SCAN").table(dataTableName)
- .serverWhereFilter("SERVER FILTER BY V1 = 'a'");
+ .serverWhereFilter("SERVER FILTER BY V1 = 'a'")
+ .indexRule(OptimizerReasons.RULE_ONLY_CANDIDATE).indexRejectedCount(1)
+ .indexRejected(0, indexTableName, OptimizerReasons.REASON_DOES_NOT_COVER_PROJECTION);
rs = conn1.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -467,7 +483,9 @@ public class GlobalIndexOptimizationIT extends ParallelStatsDisabledIT {
// No where clause
query = "SELECT t_id, k1, k2, k3, V1 from " + dataTableFullName + " order by V1,t_id";
assertPlan(conn1, query).scanType("FULL SCAN").table(dataTableName)
- .serverSortedBy("[V1, T_ID]").clientSortAlgo("CLIENT MERGE SORT");
+ .serverSortedBy("[V1, T_ID]").clientSortAlgo("CLIENT MERGE SORT")
+ .indexRule(OptimizerReasons.RULE_ONLY_CANDIDATE).indexRejectedCount(1)
+ .indexRejected(0, indexTableName, OptimizerReasons.REASON_DOES_NOT_COVER_PROJECTION);
rs = conn1.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -501,7 +519,9 @@ public class GlobalIndexOptimizationIT extends ParallelStatsDisabledIT {
"SELECT t_id, k1, k2, k3, V1 from " + dataTableFullName + " where k3 > 1 order by V1,t_id";
assertPlan(conn1, query).scanType("FULL SCAN").table(dataTableName)
.serverWhereFilter("SERVER FILTER BY K3 > 1").serverSortedBy("[V1, T_ID]")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_ONLY_CANDIDATE)
+ .indexRejectedCount(1)
+ .indexRejected(0, indexTableName, OptimizerReasons.REASON_DOES_NOT_COVER_PROJECTION);
rs = conn1.createStatement().executeQuery(query);
assertTrue(rs.next());
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexUsageIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexUsageIT.java
index f1fbc4175c..74826e9f65 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexUsageIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexUsageIT.java
@@ -38,6 +38,7 @@ import org.apache.phoenix.end2end.ParallelStatsDisabledIT;
import org.apache.phoenix.end2end.ParallelStatsDisabledTest;
import org.apache.phoenix.exception.SQLExceptionCode;
import org.apache.phoenix.execute.CommitException;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.query.explain.ExplainPlanTestUtil;
import org.apache.phoenix.util.DateUtil;
@@ -133,7 +134,8 @@ public class IndexUsageIT extends ParallelStatsDisabledIT {
ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, groupBySql)
.iteratorType("PARALLEL 1-WAY").serverFirstKeyOnlyProjection(true)
.serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY "
- + "[TO_BIGINT(\"(A.INT_COL1 + B.INT_COL2)\")]");
+ + "[TO_BIGINT(\"(A.INT_COL1 + B.INT_COL2)\")]")
+ .indexRule(OptimizerReasons.RULE_ORDER_PRESERVING).indexRejectedNone();
if (localIndex) {
basePlan.scanType("RANGE SCAN")
.table("INDEX_TEST." + indexName + "(" + fullDataTableName + ")")
@@ -189,7 +191,8 @@ public class IndexUsageIT extends ParallelStatsDisabledIT {
.serverDistinctFilter(
"SERVER DISTINCT PREFIX FILTER OVER " + "[TO_BIGINT(\"(A.INT_COL1 + 1)\")]")
.serverAggregate(
- "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY " + "[TO_BIGINT(\"(A.INT_COL1 + 1)\")]");
+ "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY " + "[TO_BIGINT(\"(A.INT_COL1 + 1)\")]")
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
if (localIndex) {
basePlan.table("INDEX_TEST." + indexName + "(" + fullDataTableName + ")")
.keyRanges(" [1,0] - [1,*]").clientSortAlgo("CLIENT MERGE SORT");
@@ -243,7 +246,8 @@ public class IndexUsageIT extends ParallelStatsDisabledIT {
String sql = "SELECT int_col1+1 FROM " + fullDataTableName + " where int_col1+1 IN (2)";
ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, sql)
- .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").serverFirstKeyOnlyProjection(true);
+ .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").serverFirstKeyOnlyProjection(true)
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
if (localIndex) {
basePlan.table("INDEX_TEST." + indexName + "(" + fullDataTableName + ")")
.keyRanges(" [1,2]").clientSortAlgo("CLIENT MERGE SORT");
@@ -296,7 +300,8 @@ public class IndexUsageIT extends ParallelStatsDisabledIT {
String sql = "SELECT int_col1+1 AS foo FROM " + fullDataTableName + " ORDER BY foo";
ExplainPlanTestUtil.ExplainPlanAssert basePlan =
- assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY").serverFirstKeyOnlyProjection(true);
+ assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY").serverFirstKeyOnlyProjection(true)
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
if (localIndex) {
basePlan.scanType("RANGE SCAN")
.table("INDEX_TEST." + indexName + "(" + fullDataTableName + ")").keyRanges(" [1]")
@@ -370,7 +375,8 @@ public class IndexUsageIT extends ParallelStatsDisabledIT {
query = "SELECT (\"V1\" || '_' || \"v2\"), k, \"V1\", \"v2\" FROM " + dataTableName
+ " WHERE (\"V1\" || '_' || \"v2\") = 'x_1'";
ExplainPlanTestUtil.ExplainPlanAssert basePlan =
- assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN");
+ assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
if (localIndex) {
basePlan.table(indexName + "(" + dataTableName + ")").keyRanges(" [1,'x_1']")
.clientSortAlgo("CLIENT MERGE SORT");
@@ -394,7 +400,8 @@ public class IndexUsageIT extends ParallelStatsDisabledIT {
query =
"SELECT \"V1\", \"V1\" as foo1, (\"V1\" || '_' || \"v2\") as foo, (\"V1\" || '_' || \"v2\") as \"Foo1\", (\"V1\" || '_' || \"v2\") FROM "
+ dataTableName + " ORDER BY foo";
- basePlan = assertPlan(conn, query).iteratorType("PARALLEL 1-WAY");
+ basePlan = assertPlan(conn, query).iteratorType("PARALLEL 1-WAY")
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
if (localIndex) {
basePlan.scanType("RANGE SCAN").table(indexName + "(" + dataTableName + ")")
.keyRanges(" [1]").clientSortAlgo("CLIENT MERGE SORT");
@@ -474,10 +481,13 @@ public class IndexUsageIT extends ParallelStatsDisabledIT {
if (localIndex) {
basePlan.scanType("RANGE SCAN")
.table("INDEX_TEST." + indexName + "(" + fullDataTableName + ")").keyRanges(" [1,2]")
- .clientSortAlgo("CLIENT MERGE SORT").serverFirstKeyOnlyProjection(true);
+ .clientSortAlgo("CLIENT MERGE SORT").serverFirstKeyOnlyProjection(true)
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
} else {
basePlan.scanType("FULL SCAN").table(fullDataTableName).clientSortAlgo(null)
- .serverWhereFilter("SERVER FILTER BY (A.INT_COL1 + 1) = 2");
+ .serverWhereFilter("SERVER FILTER BY (A.INT_COL1 + 1) = 2")
+ .indexRule(OptimizerReasons.RULE_ONLY_CANDIDATE).indexRejectedCount(1)
+ .indexRejected(0, indexName, OptimizerReasons.REASON_DOES_NOT_COVER_PROJECTION);
}
ResultSet rs = conn.createStatement().executeQuery(sql);
@@ -526,7 +536,8 @@ public class IndexUsageIT extends ParallelStatsDisabledIT {
String query = "SELECT k1, k2, k3, s1, s2 FROM " + viewName + " WHERE k1+k2+k3 = 173.0";
ExplainPlanTestUtil.ExplainPlanAssert basePlan =
- assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN");
+ assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
if (local) {
basePlan.table(indexName1 + "(" + dataTableName + ")").keyRanges(" [1,173]")
.clientSortAlgo("CLIENT MERGE SORT");
@@ -549,7 +560,8 @@ public class IndexUsageIT extends ParallelStatsDisabledIT {
query = "SELECT k1, k2, s1||'_'||s2 FROM " + viewName + " WHERE (s1||'_'||s2)='foo2_bar2'";
basePlan = assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
- .serverFirstKeyOnlyProjection(true);
+ .serverFirstKeyOnlyProjection(true).indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedCount(1);
if (local) {
basePlan.table(indexName2 + "(" + dataTableName + ")")
.keyRanges(" [" + (2) + ",'foo2_bar2']").clientSortAlgo("CLIENT MERGE SORT");
@@ -612,7 +624,8 @@ public class IndexUsageIT extends ParallelStatsDisabledIT {
"SELECT s2||'_'||s3 FROM " + viewName + " WHERE k2=1 AND (s2||'_'||s3)='abc_cab'";
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
- .table(indexName2).keyRanges(" [1,'abc_cab','foo']").serverFirstKeyOnlyProjection(true);
+ .table(indexName2).keyRanges(" [1,'abc_cab','foo']").serverFirstKeyOnlyProjection(true)
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedCount(1);
rs = conn.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -622,8 +635,10 @@ public class IndexUsageIT extends ParallelStatsDisabledIT {
conn.createStatement().execute("ALTER VIEW " + viewName + " DROP COLUMN s4");
// i2 cannot be used since s4 has been dropped from the view, so i1 will be used
assertPlan(conn, query).scanType("RANGE SCAN").tableContains(indexName1).keyRanges(" [1]")
- .serverFirstKeyOnlyProjection(true).serverWhereFilter(
- "SERVER FILTER BY ((\"S2\" || '_' || \"S3\") = 'abc_cab' AND \"S1\" = 'foo')");
+ .serverFirstKeyOnlyProjection(true)
+ .serverWhereFilter(
+ "SERVER FILTER BY ((\"S2\" || '_' || \"S3\") = 'abc_cab' AND \"S1\" = 'foo')")
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
rs = conn.createStatement().executeQuery(query);
assertTrue(rs.next());
assertEquals("abc_cab", rs.getString(1));
@@ -708,7 +723,8 @@ public class IndexUsageIT extends ParallelStatsDisabledIT {
query = "SELECT k FROM " + dataTableName + " WHERE REGEXP_SUBSTR(v,'id:\\\\w+') = 'id:id1'";
ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, query)
- .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").serverFirstKeyOnlyProjection(true);
+ .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").serverFirstKeyOnlyProjection(true)
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
if (localIndex) {
basePlan.table(indexName + "(" + dataTableName + ")").keyRanges(" [1,'id:id1']")
.clientSortAlgo("CLIENT MERGE SORT");
@@ -779,17 +795,19 @@ public class IndexUsageIT extends ParallelStatsDisabledIT {
if (localIndex) {
assertPlan(conn, query).scanType("RANGE SCAN")
.tableContains(indexName + "(" + tableName + ")").keyRanges(" [1,'1David']")
- .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1)
- .subPlan(0).scanType("RANGE SCAN").tableContains(indexName + "(" + tableName + ")")
- .keyRanges(" [1]").serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT")
+ .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").indexRule(null)
+ .indexRejectedNone().subPlanCount(1).subPlan(0).scanType("RANGE SCAN")
+ .tableContains(indexName + "(" + tableName + ")").keyRanges(" [1]")
+ .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT")
.abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT, SKIP MERGE */")
- .end();
+ .indexRule(null).indexRejectedNone().end();
} else {
assertPlan(conn, query).scanType("RANGE SCAN").tableContains(indexName)
- .keyRanges(" ['1David']").serverFirstKeyOnlyProjection(true).subPlanCount(1).subPlan(0)
- .scanType("FULL SCAN").tableContains(indexName).serverFirstKeyOnlyProjection(true)
+ .keyRanges(" ['1David']").serverFirstKeyOnlyProjection(true).indexRule(null)
+ .indexRejectedNone().subPlanCount(1).subPlan(0).scanType("FULL SCAN")
+ .tableContains(indexName).serverFirstKeyOnlyProjection(true)
.abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT, SKIP MERGE */")
- .end();
+ .indexRule(null).indexRejectedNone().end();
}
rs = conn.createStatement().executeQuery(query);
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java
index 02c38fe89e..1c099561a9 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java
@@ -64,6 +64,7 @@ import org.apache.phoenix.hbase.index.IndexRegionSplitPolicy;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixResultSet;
import org.apache.phoenix.jdbc.PhoenixStatement;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.schema.PNameFactory;
import org.apache.phoenix.schema.PTable;
@@ -437,18 +438,23 @@ public class LocalIndexIT extends BaseLocalIndexIT {
// 1. same prefix length, no other restrictions, but v3 is in the SELECT. Use the main table.
assertPlan(conn, "SELECT * FROM " + tableName + " WHERE pk1 = 3 AND pk2 = 4")
.iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(physicalTableName.toString())
- .keyRanges(" [3,4]");
+ .keyRanges(" [3,4]").indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED)
+ .indexRejectedCount(1)
+ .indexRejected(0, indexName, OptimizerReasons.REASON_LOCAL_INDEX_LOSES_TO_GLOBAL_BY_RULE);
// 2. same prefix length, no other restrictions. Only index columns used. Use the index.
assertPlan(conn, "SELECT v2 FROM " + tableName + " WHERE pk1 = 3 AND pk2 = 4")
.iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
.table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,3,4]")
- .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT");
+ .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT")
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
// 3. same prefix length, but there's a column not on the index
assertPlan(conn, "SELECT v2 FROM " + tableName + " WHERE pk1 = 3 AND pk2 = 4 AND v3 = 1")
.iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(physicalTableName.toString())
- .keyRanges(" [3,4]").serverWhereFilter("SERVER FILTER BY V3 = 1");
+ .keyRanges(" [3,4]").serverWhereFilter("SERVER FILTER BY V3 = 1")
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedCount(1)
+ .indexRejected(0, indexName, OptimizerReasons.REASON_LOCAL_INDEX_LOSES_TO_GLOBAL_BY_RULE);
// 4. Longer prefix on the index, use it.
assertPlan(conn,
@@ -456,7 +462,8 @@ public class LocalIndexIT extends BaseLocalIndexIT {
.iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
.table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,3,4,3]")
.serverMergeColumns("[0.V3]").serverFirstKeyOnlyProjection(true)
- .serverWhereFilter("SERVER FILTER BY \"V3\" = 1").clientSortAlgo("CLIENT MERGE SORT");
+ .serverWhereFilter("SERVER FILTER BY \"V3\" = 1").clientSortAlgo("CLIENT MERGE SORT")
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
}
@Test
@@ -486,7 +493,8 @@ public class LocalIndexIT extends BaseLocalIndexIT {
.iteratorType("PARALLEL").scanType("RANGE SCAN")
.table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,2,3]")
.serverMergeColumns("[0.V1]").serverFirstKeyOnlyProjection(true)
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedNone();
}
@Test
@@ -513,45 +521,57 @@ public class LocalIndexIT extends BaseLocalIndexIT {
assertPlan(conn, "SELECT COUNT(*) FROM " + tableName).iteratorType("PARALLEL 1-WAY")
.scanType("RANGE SCAN").table(fullIndexName + "(" + indexPhysicalTableName + ")")
.keyRanges(" [1]").serverFirstKeyOnlyProjection(true)
- .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW");
+ .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW")
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
// 2. All column projected, no filtering by indexed column, not using the index
assertPlan(conn, "SELECT * FROM " + tableName).iteratorType("PARALLEL 1-WAY")
- .scanType("FULL SCAN").table(physicalTableName.toString());
+ .scanType("FULL SCAN").table(physicalTableName.toString())
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedCount(1)
+ .indexRejected(0, indexName, OptimizerReasons.REASON_LOCAL_INDEX_LOSES_TO_GLOBAL_BY_RULE);
// 3. if the index can avoid a sort operation, use it
assertPlan(conn, "SELECT * FROM " + tableName + " ORDER BY v2").iteratorType("PARALLEL 1-WAY")
.scanType("RANGE SCAN").table(fullIndexName + "(" + indexPhysicalTableName + ")")
.keyRanges(" [1]").serverMergeColumns("[0.V1]").serverFirstKeyOnlyProjection(true)
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED)
+ .indexRejectedNone();
// 4. but can't use the index if not ORDERing by a prefix of the index key.
assertPlan(conn, "SELECT * FROM " + tableName + " ORDER BY v3").iteratorType("PARALLEL 1-WAY")
.scanType("FULL SCAN").table(physicalTableName.toString()).serverSortedBy("[V3]")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED)
+ .indexRejectedCount(1)
+ .indexRejected(0, indexName, OptimizerReasons.REASON_LOCAL_INDEX_LOSES_TO_GLOBAL_BY_RULE);
// 5. If we pin the prefix of the index key we use the index avoiding sorting on the postfix
assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v2 = 2 ORDER BY v3")
.iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
.table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,2]")
.serverMergeColumns("[0.V1]").serverFirstKeyOnlyProjection(true)
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedNone();
// 6. Filtering by a non-indexed column will not use the index
assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v1 = 3").iteratorType("PARALLEL 1-WAY")
.scanType("FULL SCAN").table(physicalTableName.toString())
- .serverWhereFilter("SERVER FILTER BY V1 = 3.0");
+ .serverWhereFilter("SERVER FILTER BY V1 = 3.0")
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedCount(1)
+ .indexRejected(0, indexName, OptimizerReasons.REASON_LOCAL_INDEX_LOSES_TO_GLOBAL_BY_RULE);
// 7. Also don't use an index if not filtering on a prefix of the key
assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v3 = 1").iteratorType("PARALLEL 1-WAY")
.scanType("FULL SCAN").table(physicalTableName.toString())
- .serverWhereFilter("SERVER FILTER BY V3 = 1");
+ .serverWhereFilter("SERVER FILTER BY V3 = 1")
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedCount(1)
+ .indexRejected(0, indexName, OptimizerReasons.REASON_LOCAL_INDEX_LOSES_TO_GLOBAL_BY_RULE);
// 8. Filtering along a prefix of the index key can use the index
assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v2 = 2").iteratorType("PARALLEL 1-WAY")
.scanType("RANGE SCAN").table(fullIndexName + "(" + indexPhysicalTableName + ")")
.keyRanges(" [1,2]").serverMergeColumns("[0.V1]").serverFirstKeyOnlyProjection(true)
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedNone();
// 9. Make sure a gap in the index columns still uses the index as long as a prefix is specified
assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v2 = 2 AND v4 = 4")
@@ -559,19 +579,23 @@ public class LocalIndexIT extends BaseLocalIndexIT {
.table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,2]")
.serverMergeColumns("[0.V1]").serverFirstKeyOnlyProjection(true)
.serverWhereFilter("SERVER FILTER BY TO_INTEGER(\"V4\") = 4")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedNone();
// 10. Use index even when also filtering on non-indexed column
assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v2 = 2 AND v1 = 3")
.iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
.table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,2]")
.serverMergeColumns("[0.V1]").serverFirstKeyOnlyProjection(true)
- .serverWhereFilter("SERVER FILTER BY \"V1\" = 3.0").clientSortAlgo("CLIENT MERGE SORT");
+ .serverWhereFilter("SERVER FILTER BY \"V1\" = 3.0").clientSortAlgo("CLIENT MERGE SORT")
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
// 11. Another case of not using a prefix of the index key
assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v1 = 3 AND v3 = 1 AND v4 = 1")
.iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(physicalTableName.toString())
- .serverWhereFilter("SERVER FILTER BY (V1 = 3.0 AND V3 = 1 AND V4 = 1)");
+ .serverWhereFilter("SERVER FILTER BY (V1 = 3.0 AND V3 = 1 AND V4 = 1)")
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedCount(1)
+ .indexRejected(0, indexName, OptimizerReasons.REASON_LOCAL_INDEX_LOSES_TO_GLOBAL_BY_RULE);
}
@Test
@@ -779,7 +803,8 @@ public class LocalIndexIT extends BaseLocalIndexIT {
assertPlan(conn1, query).scanType("RANGE SCAN")
.table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1]")
.serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true)
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED)
+ .indexRejectedNone();
ResultSet rs = conn1.createStatement().executeQuery(query);
String v = "";
@@ -798,7 +823,8 @@ public class LocalIndexIT extends BaseLocalIndexIT {
assertPlan(conn1, query).scanType("RANGE SCAN").clientSortedBy("REVERSE")
.table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1]")
.serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true)
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED)
+ .indexRejectedNone();
rs = conn1.createStatement().executeQuery(query);
v = "zz";
@@ -838,7 +864,8 @@ public class LocalIndexIT extends BaseLocalIndexIT {
String query = "SELECT V1 FROM " + tableName + " ORDER BY V1 DESC NULLS LAST";
assertPlan(conn1, query).scanType("RANGE SCAN").clientSortedBy("REVERSE")
.table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1]")
- .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT");
+ .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT")
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
ResultSet rs = conn1.createStatement().executeQuery(query);
String v = "zz";
@@ -884,7 +911,8 @@ public class LocalIndexIT extends BaseLocalIndexIT {
assertPlan(conn1, query).scanType("RANGE SCAN")
.table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,'a']")
.serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true)
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedNone();
rs = conn1.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -903,7 +931,8 @@ public class LocalIndexIT extends BaseLocalIndexIT {
assertPlan(conn1, query).scanType("RANGE SCAN")
.table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,*] - [1,'z']")
.serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true)
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedNone();
rs = conn1.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -936,7 +965,8 @@ public class LocalIndexIT extends BaseLocalIndexIT {
.table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,*] - [1,'z']")
.serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true)
.serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"V1\", \"T_ID\", \"K3\"]")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedNone();
rs = conn1.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -962,7 +992,8 @@ public class LocalIndexIT extends BaseLocalIndexIT {
.table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,*] - [1,'z']")
.serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true)
.serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"V1\"]")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedNone();
PhoenixStatement stmt = conn1.createStatement().unwrap(PhoenixStatement.class);
rs = stmt.executeQuery(query);
@@ -1007,7 +1038,9 @@ public class LocalIndexIT extends BaseLocalIndexIT {
assertPlan(conn1, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
.table(
SchemaUtil.getPhysicalTableName(Bytes.toBytes(indexTableName), isNamespaceMapped) + "2")
- .keyRanges(" ['a']").serverFirstKeyOnlyProjection(true);
+ .keyRanges(" ['a']").serverFirstKeyOnlyProjection(true)
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedCount(1)
+ .indexRejected(0, indexName, OptimizerReasons.REASON_LOCAL_INDEX_LOSES_TO_GLOBAL_BY_RULE);
conn1.close();
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/MutableIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/MutableIndexIT.java
index 39225c97e4..402073d4c4 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/MutableIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/MutableIndexIT.java
@@ -39,6 +39,7 @@ import org.apache.phoenix.end2end.ParallelStatsDisabledIT;
import org.apache.phoenix.end2end.ParallelStatsDisabledTest;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.PTableKey;
@@ -118,10 +119,12 @@ public class MutableIndexIT extends ParallelStatsDisabledIT {
if (localIndex) {
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
.table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1]")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED)
+ .indexRejectedNone();
} else {
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN")
- .table(fullIndexName);
+ .table(fullIndexName).indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED)
+ .indexRejectedNone();
}
ResultSet rs = conn.createStatement().executeQuery(query);
@@ -189,7 +192,8 @@ public class MutableIndexIT extends ParallelStatsDisabledIT {
.table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1,'chara',4]")
.serverMergeColumns(
"[B.VARCHAR_COL2, B.CHAR_COL2, B.INT_COL2, B.DECIMAL_COL2, B.DATE_COL]")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT")
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
rs = conn.createStatement().executeQuery(query);
assertTrue(rs.next());
assertEquals("varchar_b", rs.getString(1));
@@ -289,10 +293,12 @@ public class MutableIndexIT extends ParallelStatsDisabledIT {
if (localIndex) {
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
.table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1]")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED)
+ .indexRejectedNone();
} else {
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN")
- .table(fullIndexName);
+ .table(fullIndexName).indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED)
+ .indexRejectedNone();
}
rs = conn.createStatement().executeQuery(query);
@@ -312,10 +318,12 @@ public class MutableIndexIT extends ParallelStatsDisabledIT {
if (localIndex) {
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
.table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1]")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED)
+ .indexRejectedNone();
} else {
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN")
- .table(fullIndexName);
+ .table(fullIndexName).indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED)
+ .indexRejectedNone();
}
rs = conn.createStatement().executeQuery(query);
@@ -335,10 +343,12 @@ public class MutableIndexIT extends ParallelStatsDisabledIT {
if (localIndex) {
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
.table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1]")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED)
+ .indexRejectedNone();
} else {
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN")
- .table(fullIndexName);
+ .table(fullIndexName).indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED)
+ .indexRejectedNone();
}
rs = conn.createStatement().executeQuery(query);
@@ -408,10 +418,12 @@ public class MutableIndexIT extends ParallelStatsDisabledIT {
if (localIndex) {
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
.table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1]")
- .serverProjectionFilter(columnEncoded).clientSortAlgo("CLIENT MERGE SORT");
+ .serverProjectionFilter(columnEncoded).clientSortAlgo("CLIENT MERGE SORT")
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
} else {
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN")
- .table(fullIndexName).serverProjectionFilter(columnEncoded);
+ .table(fullIndexName).serverProjectionFilter(columnEncoded)
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
}
// make sure the data table looks like what we expect
rs = conn.createStatement().executeQuery(query);
@@ -534,10 +546,12 @@ public class MutableIndexIT extends ParallelStatsDisabledIT {
if (localIndex) {
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
.table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1]")
- .serverProjectionFilter(columnEncoded).clientSortAlgo("CLIENT MERGE SORT");
+ .serverProjectionFilter(columnEncoded).clientSortAlgo("CLIENT MERGE SORT")
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
} else {
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN")
- .table(fullIndexName).serverProjectionFilter(columnEncoded);
+ .table(fullIndexName).serverProjectionFilter(columnEncoded)
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
}
// check that the data table matches as expected
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/PartialIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/PartialIndexIT.java
index efa8528f0d..eeeb02bdf0 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/PartialIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/PartialIndexIT.java
@@ -55,6 +55,7 @@ import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
import org.apache.phoenix.jdbc.PhoenixResultSet;
import org.apache.phoenix.jdbc.PhoenixStatement;
import org.apache.phoenix.mapreduce.index.IndexTool;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.query.BaseTest;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.query.explain.ExplainPlanTestUtil;
@@ -1013,7 +1014,7 @@ public class PartialIndexIT extends BaseTest {
String selectSql = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ "
+ "A from " + dataTableName + " WHERE id2 = 100 AND id1 = 'id12'";
ExplainPlanTestUtil.assertPlan(conn, selectSql).scanType("POINT LOOKUP ON 1 KEY")
- .table(indexTableName);
+ .table(indexTableName).indexRule(OptimizerReasons.RULE_HINT).indexRejectedNone();
ResultSet rs = stmt.executeQuery(selectSql);
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
@@ -1022,13 +1023,13 @@ public class PartialIndexIT extends BaseTest {
selectSql = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ " + "A from "
+ dataTableName + " WHERE id2 = 10 AND id1 = 'id11'";
ExplainPlanTestUtil.assertPlan(conn, selectSql).scanType("POINT LOOKUP ON 1 KEY")
- .table(indexTableName);
+ .table(indexTableName).indexRule(null).indexRejectedNone();
rs = stmt.executeQuery(selectSql);
assertFalse(rs.next());
// No index hint so, use data table only as its point lookup
selectSql = "SELECT A from " + dataTableName + " WHERE id2 = 10 AND id1 = 'id11'";
ExplainPlanTestUtil.assertPlan(conn, selectSql).scanType("POINT LOOKUP ON 1 KEY")
- .table(dataTableName);
+ .table(dataTableName).indexRule(OptimizerReasons.RULE_POINT_LOOKUP).indexRejectedNone();
rs = stmt.executeQuery(selectSql);
assertTrue(rs.next());
assertEquals(1, rs.getInt(1));
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SaltedIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SaltedIndexIT.java
index 3dbd13f5bc..9803685178 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SaltedIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SaltedIndexIT.java
@@ -32,6 +32,7 @@ import java.util.Properties;
import org.apache.phoenix.end2end.ParallelStatsDisabledIT;
import org.apache.phoenix.end2end.ParallelStatsDisabledTest;
import org.apache.phoenix.jdbc.PhoenixConnection;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.schema.PTableKey;
import org.apache.phoenix.schema.types.PVarbinary;
@@ -160,14 +161,16 @@ public class SaltedIndexIT extends ParallelStatsDisabledIT {
if (indexSaltBuckets == null) {
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
- .table(indexTableFullName).keyRanges(" [~'y']").serverFirstKeyOnlyProjection(true);
+ .table(indexTableFullName).keyRanges(" [~'y']").serverFirstKeyOnlyProjection(true)
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
} else {
assertPlan(conn, query).iteratorType("PARALLEL 4-WAY").scanType("RANGE SCAN")
.table(indexTableFullName)
.keyRanges(" [X'00',~'y'] - ["
+ PVarbinary.INSTANCE.toStringLiteral(new byte[] { (byte) (indexSaltBuckets - 1) })
+ ",~'y']")
- .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT");
+ .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT")
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
}
// Will use index, so rows returned in DESC order.
@@ -184,14 +187,16 @@ public class SaltedIndexIT extends ParallelStatsDisabledIT {
assertFalse(rs.next());
if (indexSaltBuckets == null) {
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN")
- .table(indexTableFullName).keyRanges(" [*] - [~'x']").serverFirstKeyOnlyProjection(true);
+ .table(indexTableFullName).keyRanges(" [*] - [~'x']").serverFirstKeyOnlyProjection(true)
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
} else {
assertPlan(conn, query).iteratorType("PARALLEL 4-WAY").scanType("RANGE SCAN")
.table(indexTableFullName)
.keyRanges(" [X'00',*] - ["
+ PVarbinary.INSTANCE.toStringLiteral(new byte[] { (byte) (indexSaltBuckets - 1) })
+ ",~'x']")
- .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT");
+ .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT")
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
}
// Use data table, since point lookup trumps order by
@@ -202,7 +207,8 @@ public class SaltedIndexIT extends ParallelStatsDisabledIT {
assertEquals("x", rs.getString(2));
assertFalse(rs.next());
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("POINT LOOKUP ON 1 KEY")
- .table(dataTableFullName).serverSortedBy("[V]").clientSortAlgo("CLIENT MERGE SORT");
+ .table(dataTableFullName).serverSortedBy("[V]").clientSortAlgo("CLIENT MERGE SORT")
+ .indexRule(OptimizerReasons.RULE_POINT_LOOKUP).indexRejectedNone();
// Will use data table now, since there's a LIMIT clause and
// we're able to optimize out the ORDER BY, unless the data
@@ -219,11 +225,13 @@ public class SaltedIndexIT extends ParallelStatsDisabledIT {
if (tableSaltBuckets == null) {
assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN")
.table(dataTableFullName).serverWhereFilter("SERVER FILTER BY V >= 'x'").serverRowLimit(2L)
- .clientRowLimit(2);
+ .clientRowLimit(2).indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedNone();
} else {
assertPlan(conn, query).iteratorType("PARALLEL 3-WAY").scanType("FULL SCAN")
.table(dataTableFullName).serverWhereFilter("SERVER FILTER BY V >= 'x'").serverRowLimit(2L)
- .clientSortAlgo("CLIENT MERGE SORT").clientRowLimit(2);
+ .clientSortAlgo("CLIENT MERGE SORT").clientRowLimit(2)
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
}
// PHOENIX-6604
@@ -231,11 +239,12 @@ public class SaltedIndexIT extends ParallelStatsDisabledIT {
if (indexSaltBuckets == null) {
assertPlan(conn, query).iteratorType("SERIAL 1-WAY").scanType("FULL SCAN")
.table(indexTableFullName).serverFirstKeyOnlyProjection(true).serverRowLimit(1L)
- .clientRowLimit(1);
+ .clientRowLimit(1).indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
} else {
assertPlan(conn, query).iteratorType("PARALLEL 4-WAY").scanType("FULL SCAN")
.table(indexTableFullName).serverFirstKeyOnlyProjection(true).serverRowLimit(1L)
- .clientSortAlgo("CLIENT MERGE SORT").clientRowLimit(1);
+ .clientSortAlgo("CLIENT MERGE SORT").clientRowLimit(1)
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
}
}
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SingleCellIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SingleCellIndexIT.java
index c62d7fec75..070707aac0 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SingleCellIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SingleCellIndexIT.java
@@ -51,6 +51,7 @@ import org.apache.phoenix.end2end.ParallelStatsDisabledIT;
import org.apache.phoenix.end2end.ParallelStatsDisabledTest;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixStatement;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.util.PhoenixRuntime;
import org.apache.phoenix.util.PropertiesUtil;
@@ -108,7 +109,9 @@ public class SingleCellIndexIT extends ParallelStatsDisabledIT {
String selectFromData = "SELECT /*+ NO_INDEX */ PK1, INT_PK, V1, V2, V4 FROM " + tableName
+ " where V2 >= 3 and V4 LIKE 'V4%'";
- assertPlan(conn, selectFromData).table(tableName);
+ assertPlan(conn, selectFromData).table(tableName).indexRule(OptimizerReasons.RULE_DATA_TABLE)
+ .indexRejectedCount(1)
+ .indexRejected(0, idxName, OptimizerReasons.REASON_EXCLUDED_BY_NO_INDEX_HINT);
ResultSet rs = conn.createStatement().executeQuery(selectFromData);
assertTrue(rs.next());
@@ -121,7 +124,8 @@ public class SingleCellIndexIT extends ParallelStatsDisabledIT {
String selectFromIndex =
"SELECT PK1, INT_PK, V1, V2, V4 FROM " + tableName + " where V2 >= 3 and V4 LIKE 'V4%'";
- assertPlan(conn, selectFromIndex).table(idxName);
+ assertPlan(conn, selectFromIndex).table(idxName)
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
rs = conn.createStatement().executeQuery(selectFromIndex);
assertTrue(rs.next());
@@ -162,7 +166,8 @@ public class SingleCellIndexIT extends ParallelStatsDisabledIT {
String selectFromIndex =
"SELECT PK1, INT_PK, V1, V2, V4, V_NEW FROM " + tableName + " where V1='V199' AND V2=100";
- assertPlan(conn, selectFromIndex).table(idxName);
+ assertPlan(conn, selectFromIndex).table(idxName)
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
ResultSet rs = conn.createStatement().executeQuery(selectFromIndex);
assertTrue(rs.next());
@@ -201,7 +206,8 @@ public class SingleCellIndexIT extends ParallelStatsDisabledIT {
assertFalse(rs.next());
String selectFromIndex = "SELECT PK1, INT_PK, V1, V4 FROM " + tableName + " where V1='V11'";
- assertPlan(conn, selectFromIndex).table(idxName);
+ assertPlan(conn, selectFromIndex).table(idxName)
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
rs = conn.createStatement().executeQuery(selectFromIndex);
assertTrue(rs.next());
@@ -400,7 +406,8 @@ public class SingleCellIndexIT extends ParallelStatsDisabledIT {
String selectFromIndex = "SELECT PK1, INT_PK, A.V2, B.V3, A.V4, B.V5 FROM " + tableName
+ " where A.V4='V42' and B.V3 >= 3";
- assertPlan(conn, selectFromIndex).table(idxName);
+ assertPlan(conn, selectFromIndex).table(idxName)
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
rs = conn.createStatement().executeQuery(selectFromIndex);
assertTrue(rs.next());
assertEquals("PK2", rs.getString(1));
@@ -432,7 +439,9 @@ public class SingleCellIndexIT extends ParallelStatsDisabledIT {
conn.commit();
String selectFromData = "SELECT /*+ NO_INDEX */ PK1, INT_PK, V1, V2, V4 FROM " + tableName
+ " where INT_PK = 1 and V4 LIKE 'UpdatedV4'";
- assertPlan(conn, selectFromData).table(tableName);
+ assertPlan(conn, selectFromData).table(tableName).indexRule(OptimizerReasons.RULE_DATA_TABLE)
+ .indexRejectedCount(1)
+ .indexRejected(0, idxName, OptimizerReasons.REASON_EXCLUDED_BY_NO_INDEX_HINT);
ResultSet rs = conn.createStatement().executeQuery(selectFromData);
assertTrue(rs.next());
@@ -444,7 +453,8 @@ public class SingleCellIndexIT extends ParallelStatsDisabledIT {
String selectFromIndex =
"SELECT PK1, INT_PK, V1, V2, V4 FROM " + tableName + " where V2 >= 2 and V4 = 'UpdatedV4'";
- assertPlan(conn, selectFromIndex).table(idxName);
+ assertPlan(conn, selectFromIndex).table(idxName)
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
rs = conn.createStatement().executeQuery(selectFromIndex);
assertTrue(rs.next());
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java
index 7566387a62..3645db6de3 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java
@@ -64,6 +64,7 @@ import org.apache.phoenix.index.GlobalIndexChecker;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
import org.apache.phoenix.jdbc.PhoenixStatement;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.query.KeyRange;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.schema.PNameFactory;
@@ -260,7 +261,8 @@ public class ViewIndexIT extends SplitSystemCatalogIT {
.table(fullIndexName + "("
+ SchemaUtil.getPhysicalTableName(Bytes.toBytes(fullTableName), isNamespaceMapped) + ")")
.keyRanges(" [1,'10',100]").serverMergeColumns("[0.V1]").serverFirstKeyOnlyProjection(true)
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedNone();
ResultSet rs = conn1.prepareStatement(sql).executeQuery();
assertTrue(rs.next());
assertFalse(rs.next());
@@ -585,7 +587,8 @@ public class ViewIndexIT extends SplitSystemCatalogIT {
// Uses index and finds correct number of rows
assertPlan(conn, "SELECT COUNT(*) FROM " + fullTableName).iteratorType("PARALLEL 1-WAY")
.scanType("RANGE SCAN")
- .table(Bytes.toString(MetaDataUtil.getViewIndexPhysicalName(Bytes.toBytes(fullBaseName))));
+ .table(Bytes.toString(MetaDataUtil.getViewIndexPhysicalName(Bytes.toBytes(fullBaseName))))
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone();
}
// Force it not to use index and still finds correct number of rows
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinGlobalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinGlobalIndexIT.java
index a6319944e0..588cac984d 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinGlobalIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinGlobalIndexIT.java
@@ -26,6 +26,7 @@ import java.util.Map;
import org.apache.phoenix.compile.ExplainPlanAttributes;
import org.apache.phoenix.end2end.ParallelStatsDisabledTest;
import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.junit.experimental.categories.Category;
import org.junit.runners.Parameterized.Parameters;
@@ -226,7 +227,8 @@ public class HashJoinGlobalIndexIT extends HashJoinIT {
.serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [I.NAME]")
.clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0)
.abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */")
- .scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true).end();
+ .scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true)
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone().end();
}
@Override
@@ -238,7 +240,8 @@ public class HashJoinGlobalIndexIT extends HashJoinIT {
.clientSortAlgo("CLIENT MERGE SORT").clientSortedBy("[SUM(O.QUANTITY) DESC]").subPlanCount(1)
.subPlan(0)
.abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT, SKIP MERGE */")
- .scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true).end();
+ .scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true)
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone().end();
}
@Override
@@ -251,7 +254,8 @@ public class HashJoinGlobalIndexIT extends HashJoinIT {
.abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */")
.scanType("FULL SCAN").table(order)
.serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]")
- .clientSortAlgo("CLIENT MERGE SORT").end();
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_DATA_TABLE)
+ .indexRejectedNone().end();
}
@Override
@@ -263,7 +267,8 @@ public class HashJoinGlobalIndexIT extends HashJoinIT {
.subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD LEFT */")
.scanType("FULL SCAN").table(order)
.serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]")
- .clientSortAlgo("CLIENT MERGE SORT").end();
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_DATA_TABLE)
+ .indexRejectedNone().end();
}
@Override
@@ -282,7 +287,10 @@ public class HashJoinGlobalIndexIT extends HashJoinIT {
.scanType("FULL SCAN").table(idxItem).serverWhereFilter("SERVER FILTER BY \"NAME\" != 'T3'")
.subPlanCount(1).subPlan(0)
.abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD LEFT */")
- .scanType("FULL SCAN").table(supplier).end().end().end();
+ .scanType("FULL SCAN").table(supplier).indexRule(OptimizerReasons.RULE_ONLY_CANDIDATE)
+ .indexRejectedCount(1)
+ .indexRejected(0, "idx_supplier", OptimizerReasons.REASON_DOES_NOT_COVER_PROJECTION).end()
+ .end().end();
}
@Override
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinLocalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinLocalIndexIT.java
index 2ae8c4d42f..898fc33885 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinLocalIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinLocalIndexIT.java
@@ -34,6 +34,7 @@ import java.util.Properties;
import org.apache.phoenix.compile.ExplainPlanAttributes;
import org.apache.phoenix.end2end.ParallelStatsDisabledTest;
import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.util.PropertiesUtil;
import org.apache.phoenix.util.SchemaUtil;
import org.junit.Test;
@@ -275,7 +276,8 @@ public class HashJoinLocalIndexIT extends HashJoinIT {
.clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0)
.abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */")
.scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]")
- .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end();
+ .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT")
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone().end();
}
@Override
@@ -289,7 +291,8 @@ public class HashJoinLocalIndexIT extends HashJoinIT {
.subPlan(0)
.abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT, SKIP MERGE */")
.scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]")
- .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end();
+ .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT")
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED).indexRejectedNone().end();
}
@Override
@@ -304,7 +307,8 @@ public class HashJoinLocalIndexIT extends HashJoinIT {
.abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */")
.scanType("FULL SCAN").table(order)
.serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]")
- .clientSortAlgo("CLIENT MERGE SORT").end();
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_DATA_TABLE)
+ .indexRejectedNone().end();
}
@Override
@@ -318,7 +322,8 @@ public class HashJoinLocalIndexIT extends HashJoinIT {
.abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD LEFT */")
.scanType("FULL SCAN").table(order)
.serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]")
- .clientSortAlgo("CLIENT MERGE SORT").end();
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_DATA_TABLE)
+ .indexRejectedNone().end();
}
@Override
@@ -339,7 +344,10 @@ public class HashJoinLocalIndexIT extends HashJoinIT {
.serverWhereFilter("SERVER FILTER BY \"NAME\" != 'T3'").clientSortAlgo("CLIENT MERGE SORT")
.subPlanCount(1).subPlan(0)
.abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD LEFT */")
- .scanType("FULL SCAN").table(supplier).end().end().end();
+ .scanType("FULL SCAN").table(supplier).indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED)
+ .indexRejectedCount(1).indexRejected(0, JOIN_SUPPLIER_INDEX,
+ OptimizerReasons.REASON_LOCAL_INDEX_LOSES_TO_GLOBAL_BY_RULE)
+ .end().end().end();
}
@Override
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonFunctionsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonFunctionsIT.java
index 612c521867..f452aacace 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonFunctionsIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonFunctionsIT.java
@@ -45,6 +45,7 @@ import org.apache.phoenix.end2end.ParallelStatsDisabledIT;
import org.apache.phoenix.end2end.ParallelStatsDisabledTest;
import org.apache.phoenix.exception.SQLExceptionCode;
import org.apache.phoenix.jdbc.PhoenixConnection;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.PTableKey;
import org.apache.phoenix.schema.TableNotFoundException;
@@ -367,7 +368,8 @@ public class JsonFunctionsIT extends ParallelStatsDisabledIT {
String selectSql =
"SELECT JSON_VALUE(JSONCOL,'$.type'), " + "JSON_VALUE(JSONCOL,'$.info.address.town') FROM "
+ tableName + " WHERE JSON_VALUE(JSONCOL,'$.type') = 'Basic'";
- assertPlan(conn, selectSql).scanType("RANGE SCAN").table(indexName);
+ assertPlan(conn, selectSql).scanType("RANGE SCAN").table(indexName)
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
// Validate the total count of rows
String countSql = "SELECT COUNT(1) FROM " + tableName;
ResultSet rs = conn.createStatement().executeQuery(countSql);
@@ -567,7 +569,8 @@ public class JsonFunctionsIT extends ParallelStatsDisabledIT {
ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + query);
String explainPlan = QueryUtil.getExplainPlan(rs);
assertFalse(explainPlan.contains(" SERVER JSON FUNCTION PROJECTION"));
- assertPlan(conn, query).serverArrayElementProjection(false);
+ assertPlan(conn, query).serverArrayElementProjection(false)
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
rs = conn.createStatement().executeQuery(query);
assertTrue(rs.next());
assertEquals(conn.createArrayOf("INTEGER", new Integer[] { 1, 2 }), rs.getArray(1));
@@ -581,7 +584,8 @@ public class JsonFunctionsIT extends ParallelStatsDisabledIT {
rs = conn.createStatement().executeQuery("EXPLAIN " + query);
explainPlan = QueryUtil.getExplainPlan(rs);
assertTrue(explainPlan.contains(" SERVER JSON FUNCTION PROJECTION"));
- assertPlan(conn, query).serverArrayElementProjection(true);
+ assertPlan(conn, query).serverArrayElementProjection(true)
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
// only Array optimization and not Json
query = "SELECT arr[1], jsoncol, JSON_VALUE(jsoncol, '$.type')" + " FROM " + tableName
@@ -589,7 +593,8 @@ public class JsonFunctionsIT extends ParallelStatsDisabledIT {
rs = conn.createStatement().executeQuery("EXPLAIN " + query);
explainPlan = QueryUtil.getExplainPlan(rs);
assertFalse(explainPlan.contains(" SERVER JSON FUNCTION PROJECTION"));
- assertPlan(conn, query).serverArrayElementProjection(true);
+ assertPlan(conn, query).serverArrayElementProjection(true)
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
// only Json optimization and not Array Index
query = "SELECT arr, arr[1], JSON_VALUE(jsoncol, '$.type')" + " FROM " + tableName
@@ -597,7 +602,8 @@ public class JsonFunctionsIT extends ParallelStatsDisabledIT {
rs = conn.createStatement().executeQuery("EXPLAIN " + query);
explainPlan = QueryUtil.getExplainPlan(rs);
assertTrue(explainPlan.contains(" SERVER JSON FUNCTION PROJECTION"));
- assertPlan(conn, query).serverArrayElementProjection(false);
+ assertPlan(conn, query).serverArrayElementProjection(false)
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
}
}
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
index 495363b3b5..d806e615f1 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
@@ -84,6 +84,7 @@ import org.apache.phoenix.iterate.MergeSortTopNResultIterator;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
import org.apache.phoenix.jdbc.PhoenixStatement;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.parse.ParseNodeFactory;
import org.apache.phoenix.query.BaseConnectionlessQueryTest;
import org.apache.phoenix.query.QueryConstants;
@@ -7150,7 +7151,8 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest {
+ " where ts >= TIMESTAMP '2023-02-23 13:30:00' and ts < TIMESTAMP '2023-02-23 13:40:00'";
assertPlan(conn, query).scanType("RANGE SCAN").table(indexName)
.keyRanges(" [~1,677,159,600,000] - [~1,677,159,000,000]")
- .serverFirstKeyOnlyProjection(true);
+ .serverFirstKeyOnlyProjection(true).indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS)
+ .indexRejectedNone();
}
}
@@ -7168,7 +7170,8 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest {
assertEquals("\\x9E\\x9E\\x9F\\x00", Bytes.toStringBinary(openScan.getStartRow()));
assertEquals("\\x9E\\xFF", Bytes.toStringBinary(openScan.getStopRow()));
assertPlan(conn, openQry).scanType("RANGE SCAN").table(tableName)
- .keyRanges(" [~'aaa'] - [~'a']").serverFirstKeyOnlyProjection(true);
+ .keyRanges(" [~'aaa'] - [~'a']").serverFirstKeyOnlyProjection(true)
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
String closedQry = "select * from " + tableName + " where k >= 'a' and k <= 'aaa'";
Scan closedScan =
@@ -7176,7 +7179,8 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest {
assertEquals("\\x9E\\x9E\\x9E\\xFF", Bytes.toStringBinary(closedScan.getStartRow()));
assertEquals("\\x9F\\x00", Bytes.toStringBinary(closedScan.getStopRow()));
assertPlan(conn, closedQry).scanType("RANGE SCAN").table(tableName)
- .keyRanges(" [~'aaa'] - [~'a']").serverFirstKeyOnlyProjection(true);
+ .keyRanges(" [~'aaa'] - [~'a']").serverFirstKeyOnlyProjection(true)
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
}
}
@@ -7195,7 +7199,8 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest {
assertPlan(conn, query).scanType("RANGE SCAN").table("II").keyRanges(" [1]")
.serverMergeColumns("[0.V1, 0.V2, 0.V3, 0.V4]").serverFirstKeyOnlyProjection(true)
.serverWhereFilter("SERVER FILTER BY \"K2\" = 1").serverSortedBy("[\"K1\", \"V1\"]")
- .serverRowLimit(1L).clientRowLimit(1).clientSortAlgo("CLIENT MERGE SORT");
+ .serverRowLimit(1L).clientRowLimit(1).clientSortAlgo("CLIENT MERGE SORT")
+ .indexRule(OptimizerReasons.RULE_HINT).indexRejectedNone();
}
}
@@ -7263,7 +7268,8 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest {
stmt.execute("create index i on d(v2) include (v3)");
String query = "select /*+ index(d i) */ * from d where v2=1 and v3=1";
assertPlan(conn, query).scanType("RANGE SCAN").table("I").keyRanges(" [1]")
- .serverMergeColumns("[0.V1, 0.V4]").serverWhereFilter("SERVER FILTER BY \"V3\" = 1");
+ .serverMergeColumns("[0.V1, 0.V4]").serverWhereFilter("SERVER FILTER BY \"V3\" = 1")
+ .indexRule(OptimizerReasons.RULE_HINT).indexRejectedNone();
}
}
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java
index bc793f0a55..9ee22f54f7 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java
@@ -45,6 +45,7 @@ import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
import org.apache.phoenix.jdbc.PhoenixResultSet;
import org.apache.phoenix.jdbc.PhoenixStatement;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.parse.DeleteStatement;
import org.apache.phoenix.parse.HintNode.Hint;
import org.apache.phoenix.parse.SQLParser;
@@ -505,7 +506,9 @@ public class QueryOptimizerTest extends BaseConnectionlessQueryTest {
assertPlan(conn2,
"select * from INDEX_TEST_TABLE where A in ('1','2','3','4','5') and F in ('1111','2222','3333')")
.scanType("SKIP SCAN ON 15 KEYS").table("INDEX_TEST_TABLE_INDEX_F")
- .keyRanges(" ['1','1111'] - ['5','3333']");
+ .keyRanges(" ['1','1111'] - ['5','3333']")
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedCount(1)
+ .indexRejected(0, "INDEX_TEST_TABLE_INDEX_D", OptimizerReasons.REASON_NO_PK_PREFIX_BOUND);
}
@Test
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/StatementHintsCompilationTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/StatementHintsCompilationTest.java
index abbb829c9a..694475ddc3 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/compile/StatementHintsCompilationTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/StatementHintsCompilationTest.java
@@ -34,6 +34,7 @@ import org.apache.hadoop.hbase.filter.FilterList;
import org.apache.phoenix.filter.SkipScanFilter;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.query.BaseConnectionlessQueryTest;
import org.apache.phoenix.util.PropertiesUtil;
import org.apache.phoenix.util.TestUtil;
@@ -107,7 +108,8 @@ public class StatementHintsCompilationTest extends BaseConnectionlessQueryTest {
.serverWhereFilter("SERVER FILTER BY (CREATED_DATE >= DATE"
+ " '2012-11-01 00:00:00.000' AND CREATED_DATE < DATE '2012-11-30 00:00:00.000')")
.serverSortedBy("[ORGANIZATION_ID, PARENT_ID, CREATED_DATE DESC, ENTITY_HISTORY_ID]")
- .serverRowLimit(100L).clientSortAlgo("CLIENT MERGE SORT").clientRowLimit(100);
+ .serverRowLimit(100L).clientSortAlgo("CLIENT MERGE SORT").clientRowLimit(100)
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
}
@Test
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java
index 1a3e82c9c2..6665d18d88 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java
@@ -29,6 +29,7 @@ import java.util.Calendar;
import java.util.List;
import java.util.Properties;
import java.util.TimeZone;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.query.BaseConnectionlessQueryTest;
import org.apache.phoenix.query.explain.ExplainPlanTestUtil;
import org.apache.phoenix.util.DateUtil;
@@ -53,7 +54,8 @@ public class TenantSpecificViewIndexCompileTest extends BaseConnectionlessQueryT
assertPlan(conn, "SELECT v1,v2 FROM v WHERE v2 > 'a' ORDER BY v2")
.iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table("_IDX_T")
- .keyRanges(" [-9223372036854775808,'me','a'] - [-9223372036854775808,'me',*]");
+ .keyRanges(" [-9223372036854775808,'me','a'] - [-9223372036854775808,'me',*]")
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS);
}
@Test
@@ -172,7 +174,8 @@ public class TenantSpecificViewIndexCompileTest extends BaseConnectionlessQueryT
assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table("T")
.clientSortedBy("REVERSE")
.keyRanges(" ['tenant123456789','xyz','abcde',~'2015-01-01 08:00:00.000'] - "
- + "['tenant123456789','xyz','abcde',*]");
+ + "['tenant123456789','xyz','abcde',*]")
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
assertOrderByHasBeenOptimizedOut(conn, sql);
}
@@ -194,14 +197,17 @@ public class TenantSpecificViewIndexCompileTest extends BaseConnectionlessQueryT
assertPlan(conn, "SELECT v2 FROM v WHERE v2 > 'a' and k2 = 'a' ORDER BY v2,k2")
.iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table("_IDX_T")
.keyRanges(" [-9223372036854775808,'me','a'] - [-9223372036854775808,'me',*]")
- .serverFirstKeyOnlyProjection(true);
+ .serverFirstKeyOnlyProjection(true).indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS);
// Won't use index b/c v1 is not in index, but should optimize out k2 still from the order by
// K2 will still be referenced in the filter, as these are automatically tacked on to the where
// clause.
+ // The index i1 is rejected because it does not cover v1, leaving the data table as the only
+ // surviving candidate.
assertPlan(conn, "SELECT v1 FROM v WHERE v2 > 'a' ORDER BY k2").iteratorType("PARALLEL 1-WAY")
.scanType("RANGE SCAN").table("T").keyRanges(" ['me']")
- .serverWhereFilter("SERVER FILTER BY (V2 > 'a' AND K2 = 'a')");
+ .serverWhereFilter("SERVER FILTER BY (V2 > 'a' AND K2 = 'a')")
+ .indexRule(OptimizerReasons.RULE_ONLY_CANDIDATE).indexRejectedCount(1);
// If we match K2 against a constant not equal to it's view constant, we should get a degenerate
// plan. The DEGENERATE SCAN ExplainPlan does not populate structured attributes, so this
@@ -232,7 +238,8 @@ public class TenantSpecificViewIndexCompileTest extends BaseConnectionlessQueryT
// the updatable view
assertPlan(conn, "SELECT v2 FROM v2 WHERE v3 > 'a' and k2 = 'a' ORDER BY v3,k2")
.iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table("_IDX_T")
- .keyRanges(" [-9223372036854775808,'me','a'] - [-9223372036854775808,'me',*]");
+ .keyRanges(" [-9223372036854775808,'me','a'] - [-9223372036854775808,'me',*]")
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS);
}
// -----------------------------------------------------------------
@@ -249,13 +256,14 @@ public class TenantSpecificViewIndexCompileTest extends BaseConnectionlessQueryT
private void assertRangeScan(Connection conn, String sql, String keyRanges) throws SQLException {
assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table("T")
- .keyRanges(keyRanges);
+ .keyRanges(keyRanges).indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
}
private void assertRangeScanWithFilter(Connection conn, String sql, String keyRanges,
String serverWhereFilter) throws SQLException {
assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table("T")
- .keyRanges(keyRanges).serverWhereFilter(serverWhereFilter);
+ .keyRanges(keyRanges).serverWhereFilter(serverWhereFilter)
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
}
private void assertOrderByHasBeenOptimizedOut(Connection conn, String sql) throws SQLException {
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java b/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java
index 09849e3db1..19aca69bc2 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java
@@ -22,6 +22,7 @@ import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.util.PhoenixRuntime;
import org.apache.phoenix.util.PropertiesUtil;
import org.junit.Test;
@@ -49,21 +50,25 @@ public class QueryPlanTest extends BaseConnectionlessQueryTest {
// LIMIT 1 uses a SERIAL iterator and pushes the limit to both server and client.
assertPlan(conn, "SELECT * FROM TENANT_VIEW LIMIT 1").iteratorType("SERIAL")
.scanType("RANGE SCAN").table("BASE_MULTI_TENANT_TABLE").keyRanges(" ['tenantId']")
- .serverRowLimit(1L).clientRowLimit(1);
+ .serverRowLimit(1L).clientRowLimit(1).indexRule(OptimizerReasons.RULE_DATA_TABLE)
+ .indexRejectedNone();
// A very large limit falls back to a PARALLEL iterator.
assertPlan(conn, "SELECT * FROM TENANT_VIEW LIMIT " + Integer.MAX_VALUE)
.iteratorType("PARALLEL").scanType("RANGE SCAN").table("BASE_MULTI_TENANT_TABLE")
.keyRanges(" ['tenantId']").serverRowLimit((long) Integer.MAX_VALUE)
- .clientRowLimit(Integer.MAX_VALUE);
+ .clientRowLimit(Integer.MAX_VALUE).indexRule(OptimizerReasons.RULE_DATA_TABLE)
+ .indexRejectedNone();
assertPlan(conn, "SELECT * FROM TENANT_VIEW WHERE username = 'Joe' LIMIT 1")
.scanType("RANGE SCAN").table("BASE_MULTI_TENANT_TABLE").keyRanges(" ['tenantId']")
- .serverWhereFilter("SERVER FILTER BY USERNAME = 'Joe'").serverRowLimit(1L).clientRowLimit(1);
+ .serverWhereFilter("SERVER FILTER BY USERNAME = 'Joe'").serverRowLimit(1L).clientRowLimit(1)
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
assertPlan(conn, "SELECT * FROM TENANT_VIEW WHERE col = 'Joe' LIMIT 1").scanType("RANGE SCAN")
.table("BASE_MULTI_TENANT_TABLE").keyRanges(" ['tenantId']")
- .serverWhereFilter("SERVER FILTER BY COL = 'Joe'").serverRowLimit(1L).clientRowLimit(1);
+ .serverWhereFilter("SERVER FILTER BY COL = 'Joe'").serverRowLimit(1L).clientRowLimit(1)
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
}
@Test
@@ -82,7 +87,8 @@ public class QueryPlanTest extends BaseConnectionlessQueryTest {
assertPlan(conn, query).scanType("RANGE SCAN").table("FOO")
.keyRanges(
" [X'00','a',~'2016-01-28 23:59:59.999'] -" + " [X'13','a',~'2016-01-28 00:00:00.000']")
- .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT");
+ .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT")
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
} finally {
conn.close();
}
@@ -107,7 +113,8 @@ public class QueryPlanTest extends BaseConnectionlessQueryTest {
assertPlan(conn, query).useRoundRobinIterator(true).scanType("RANGE SCAN").table(tableName)
.keyRanges(
" [X'00','a',~'2016-01-28 23:59:59.999'] -" + " [X'13','a',~'2016-01-28 00:00:00.000']")
- .serverFirstKeyOnlyProjection(true);
+ .serverFirstKeyOnlyProjection(true).indexRule(OptimizerReasons.RULE_DATA_TABLE)
+ .indexRejectedNone();
} finally {
conn.close();
}
@@ -127,7 +134,8 @@ public class QueryPlanTest extends BaseConnectionlessQueryTest {
// server sort + client merge sort are planned.
assertPlan(conn, query).iteratorType("PARALLEL").scanType("RANGE SCAN").table("FOO")
.keyRanges(" ['a']").serverFirstKeyOnlyProjection(true).serverSortedBy("[B, C]")
- .clientSortAlgo("CLIENT MERGE SORT");
+ .clientSortAlgo("CLIENT MERGE SORT").indexRule(OptimizerReasons.RULE_DATA_TABLE)
+ .indexRejectedNone();
} finally {
conn.close();
}
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 8434f2040e..bb7f8133c4 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
@@ -22,6 +22,7 @@ import static org.apache.phoenix.query.QueryServices.DATE_FORMAT_ATTRIB;
import static org.apache.phoenix.util.PhoenixRuntime.TENANT_ID_ATTRIB;
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.assertTrue;
import static org.junit.Assert.fail;
@@ -46,6 +47,7 @@ import org.apache.hadoop.hbase.client.RegionInfoBuilder;
import org.apache.phoenix.compile.ExplainPlan;
import org.apache.phoenix.compile.ExplainPlanAttributes;
import org.apache.phoenix.compile.ExplainPlanAttributes.ExplainPlanAttributesBuilder;
+import org.apache.phoenix.optimize.OptimizerReasons;
import org.apache.phoenix.query.BaseConnectionlessQueryTest;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.schema.PColumn;
@@ -109,8 +111,9 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
"SELECT a_string, b_string FROM atable"
+ " WHERE organization_id = '00D000000000001' AND entity_id = '00E00000000001'"
+ " AND x_integer = 2 AND a_integer < 5",
- text("CLIENT PARALLEL <N>-WAY POINT LOOKUP ON 1 KEY OVER ATABLE", " INDEX ATABLE",
- " REGIONS PLANNED <N>", " SERVER FILTER BY (X_INTEGER = 2 AND A_INTEGER < 5)"),
+ text("CLIENT PARALLEL <N>-WAY POINT LOOKUP ON 1 KEY OVER ATABLE",
+ " INDEX ATABLE /* point lookup */", " REGIONS PLANNED <N>",
+ " SERVER FILTER BY (X_INTEGER = 2 AND A_INTEGER < 5)"),
scanAttrs("POINT LOOKUP ON 1 KEY ", "ATABLE", null).put("serverWhereFilter",
"SERVER FILTER BY (X_INTEGER = 2 AND A_INTEGER < 5)"));
}
@@ -121,8 +124,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
"SELECT a_string, b_string FROM atable"
+ " WHERE organization_id IN ('00D000000000001', '00D000000000005')"
+ " AND entity_id IN ('00E00000000000X','00E00000000000Z')",
- text("CLIENT PARALLEL <N>-WAY POINT LOOKUP ON 4 KEYS OVER ATABLE", " INDEX ATABLE",
- " REGIONS PLANNED <N>"),
+ text("CLIENT PARALLEL <N>-WAY POINT LOOKUP ON 4 KEYS OVER ATABLE",
+ " INDEX ATABLE /* point lookup */", " REGIONS PLANNED <N>"),
scanAttrs("POINT LOOKUP ON 4 KEYS ", "ATABLE", null));
}
@@ -473,8 +476,11 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
public void testSortMergeJoin() throws Exception {
// After the SMJ reshape the root is a synthetic node carrying just the join header and the
// two operand plans as separate lhs / rhs children.
- ObjectNode lhs = scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']");
- ObjectNode rhs = scanAttrs("FULL SCAN ", "ATABLE", "");
+ // Join operand leaves are compiled through the join path, not optimizer index selection, so
+ // they carry no decision rule.
+ ObjectNode lhs =
+ scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']").putNull("indexRule");
+ ObjectNode rhs = scanAttrs("FULL SCAN ", "ATABLE", "").putNull("indexRule");
ObjectNode expected = defaultAttrs();
expected.put("abstractExplainPlan", "SORT-MERGE-JOIN (INNER)");
expected.set("lhsJoinQueryExplainPlan", lhs);
@@ -495,9 +501,13 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
public void testHashJoinInner() throws Exception {
// HashJoinPlan root attributes come from the delegate scan. Each hash/skip-scan child
// is recorded under subPlans with its join header on abstractExplainPlan.
- ObjectNode child = scanAttrs("FULL SCAN ", "ATABLE", "").put("abstractExplainPlan",
- "PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */");
- ObjectNode expected = scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']");
+ // Hash join nodes (the delegate root and the build-side child) are compiled through the join
+ // path, not optimizer index selection, so they carry no decision rule.
+ ObjectNode child = scanAttrs("FULL SCAN ", "ATABLE", "")
+ .put("abstractExplainPlan", "PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */")
+ .putNull("indexRule");
+ ObjectNode expected =
+ scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']").putNull("indexRule");
expected.set("subPlans", mapper.createArrayNode().add(child));
expected.put("dynamicServerFilter",
"DYNAMIC SERVER FILTER BY A.ORGANIZATION_ID IN (B.ORGANIZATION_ID)");
@@ -521,7 +531,10 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
.put("abstractExplainPlan", "SKIP-SCAN-JOIN TABLE 0 /* HASH BUILD RIGHT */")
.put("serverWhereFilter", "SERVER FILTER BY A_INTEGER = 1")
.put("serverAggregate", "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [ORGANIZATION_ID]");
- ObjectNode expected = scanAttrs("FULL SCAN ", "ATABLE", "");
+ // The outer hash-join scan is compiled through the subquery/join path and carries no decision
+ // rule; the aggregate subquery build side is optimized separately and keeps its data-table
+ // rule.
+ ObjectNode expected = scanAttrs("FULL SCAN ", "ATABLE", "").putNull("indexRule");
expected.set("subPlans", mapper.createArrayNode().add(child));
expected.put("dynamicServerFilter",
"DYNAMIC SERVER FILTER BY ATABLE.ORGANIZATION_ID IN ($1.$2)");
@@ -562,15 +575,21 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
// A UNION ALL whose branches are each hash joins. Exercises recursive explain composition end
// to end and
// confirms each branch carries its own subPlans and dynamicServerFilter.
- ObjectNode joinChild1 = scanAttrs("FULL SCAN ", "ATABLE", "").put("abstractExplainPlan",
- "PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */");
- ObjectNode branch1 = scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']");
+ // Every node in a union-of-hash-joins is compiled through the join path, so none carry a
+ // decision rule.
+ ObjectNode joinChild1 = scanAttrs("FULL SCAN ", "ATABLE", "")
+ .put("abstractExplainPlan", "PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */")
+ .putNull("indexRule");
+ ObjectNode branch1 =
+ scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']").putNull("indexRule");
branch1.set("subPlans", mapper.createArrayNode().add(joinChild1));
branch1.put("dynamicServerFilter",
"DYNAMIC SERVER FILTER BY A.ORGANIZATION_ID IN (B.ORGANIZATION_ID)");
- ObjectNode joinChild2 = scanAttrs("FULL SCAN ", "ATABLE", "").put("abstractExplainPlan",
- "PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */");
- ObjectNode branch2 = scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000002']");
+ ObjectNode joinChild2 = scanAttrs("FULL SCAN ", "ATABLE", "")
+ .put("abstractExplainPlan", "PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */")
+ .putNull("indexRule");
+ ObjectNode branch2 =
+ scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000002']").putNull("indexRule");
branch2.set("subPlans", mapper.createArrayNode().add(joinChild2));
branch2.put("dynamicServerFilter",
"DYNAMIC SERVER FILTER BY A.ORGANIZATION_ID IN (B.ORGANIZATION_ID)");
@@ -630,8 +649,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
true,
text("UPSERT ROWS", "CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['00D000000000001']",
" INDEX ATABLE", " REGIONS PLANNED <N>"),
- scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']").put("abstractExplainPlan",
- "UPSERT ROWS"));
+ scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']")
+ .put("abstractExplainPlan", "UPSERT ROWS").putNull("indexRule"));
}
@Test
@@ -651,7 +670,7 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
" SERVER PROJECTION FILTER BY FIRST KEY ONLY", " SERVER FILTER BY ENTITY_ID = 'abc'"),
scanAttrs("FULL SCAN ", "ATABLE", "").put("abstractExplainPlan", "DELETE ROWS SERVER SELECT")
.put("serverFirstKeyOnlyProjection", true)
- .put("serverWhereFilter", "SERVER FILTER BY ENTITY_ID = 'abc'"));
+ .put("serverWhereFilter", "SERVER FILTER BY ENTITY_ID = 'abc'").putNull("indexRule"));
}
@Test
@@ -698,8 +717,9 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
" REGIONS PLANNED <N>", " SERVER 1 ROW LIMIT", "CLIENT 1 ROW LIMIT"),
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).set("clientSteps", clientSteps("CLIENT 1 ROW LIMIT")));
+ .put("indexName", "EO_MT_VIEW").put("indexRule", "data table")
+ .put("keyRanges", " ['tenant42']").put("serverRowLimit", 1).put("clientRowLimit", 1)
+ .set("clientSteps", clientSteps("CLIENT 1 ROW LIMIT")));
}
@Test
@@ -710,8 +730,11 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
String idx = generateUniqueName();
stmt.execute("CREATE TABLE " + base + " (k VARCHAR PRIMARY KEY, v1 VARCHAR, v2 VARCHAR)");
stmt.execute("CREATE INDEX " + idx + " ON " + base + " (v1) INCLUDE (v2)");
- ExplainPlanTestUtil.assertPlan(conn, "SELECT v1, v2 FROM " + base + " WHERE v1 = 'x'")
- .table(idx).indexName(idx).indexKind("GLOBAL");
+ String query = "SELECT v1, v2 FROM " + base + " WHERE v1 = 'x'";
+ ExplainPlanTestUtil.assertPlan(conn, query).table(idx).indexName(idx).indexKind("GLOBAL")
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedNone();
+ assertPlanContainsLine(conn, query,
+ " INDEX " + idx + " GLOBAL /* more bound PK columns */");
}
}
@@ -724,10 +747,11 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
stmt.execute("CREATE TABLE " + base + " (k VARCHAR PRIMARY KEY, v1 VARCHAR, v2 VARCHAR)");
stmt.execute("CREATE LOCAL INDEX " + idx + " ON " + base + " (v1)");
// The OVER line decorates a local index as <idx>(<phys>); the INDEX line prints just <idx>.
- ExplainPlanTestUtil
- .assertPlan(conn,
- "SELECT /*+ INDEX(" + base + " " + idx + ") */ k, v1 FROM " + base + " WHERE v1 = 'x'")
- .indexName(idx).indexKind("LOCAL");
+ String query =
+ "SELECT /*+ INDEX(" + base + " " + idx + ") */ k, v1 FROM " + base + " WHERE v1 = 'x'";
+ ExplainPlanTestUtil.assertPlan(conn, query).indexName(idx).indexKind("LOCAL")
+ .indexRule(OptimizerReasons.RULE_HINT).indexRejectedNone();
+ assertPlanContainsLine(conn, query, " INDEX " + idx + " LOCAL /* hint */");
}
}
@@ -739,10 +763,74 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
String idx = generateUniqueName();
stmt.execute("CREATE TABLE " + base + " (k VARCHAR PRIMARY KEY, v1 VARCHAR, v2 VARCHAR)");
stmt.execute("CREATE UNCOVERED INDEX " + idx + " ON " + base + " (v1)");
- ExplainPlanTestUtil
- .assertPlan(conn,
- "SELECT /*+ INDEX(" + base + " " + idx + ") */ k, v2 FROM " + base + " WHERE v1 = 'x'")
- .indexName(idx).indexKind("UNCOVERED GLOBAL");
+ String query =
+ "SELECT /*+ INDEX(" + base + " " + idx + ") */ k, v2 FROM " + base + " WHERE v1 = 'x'";
+ ExplainPlanTestUtil.assertPlan(conn, query).indexName(idx).indexKind("UNCOVERED GLOBAL")
+ .indexRule(OptimizerReasons.RULE_HINT).indexRejectedNone();
+ assertPlanContainsLine(conn, query, " INDEX " + idx + " UNCOVERED GLOBAL /* hint */");
+ }
+ }
+
+ /**
+ * A full scan over a table with no indexes records the {@code "data table"} default rule, which
+ * is suppressed. The INDEX line must be bare and carry no {@code !INDEX} rejection notes.
+ */
+ @Test
+ public void testNoCandidateDataTableBareIndexLine() throws Exception {
+ try (Connection conn = DriverManager.getConnection(getUrl(), defaultProps());
+ java.sql.Statement stmt = conn.createStatement()) {
+ String base = generateUniqueName();
+ stmt.execute("CREATE TABLE " + base + " (k VARCHAR PRIMARY KEY, v1 VARCHAR)");
+ String query = "SELECT v1 FROM " + base;
+ ExplainPlanTestUtil.assertPlan(conn, query).indexName(base).indexKind(null)
+ .indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
+ assertPlanContainsLine(conn, query, " INDEX " + base);
+ assertNoPlanLineContains(conn, query, "/*");
+ assertNoPlanLineContains(conn, query, "!INDEX");
+ }
+ }
+
+ /**
+ * A query whose {@code ORDER BY} can be served by a covered global index picks that index by the
+ * {@code "non-local preferred"} rule, which is non default and renders a rule comment on the
+ * INDEX line.
+ */
+ @Test
+ public void testIndexRuleNonLocalPreferred() throws Exception {
+ try (Connection conn = DriverManager.getConnection(getUrl(), defaultProps());
+ java.sql.Statement stmt = conn.createStatement()) {
+ String base = generateUniqueName();
+ String idx = generateUniqueName();
+ stmt.execute("CREATE TABLE " + base + " (k VARCHAR PRIMARY KEY, v1 VARCHAR, v2 VARCHAR)");
+ stmt.execute("CREATE INDEX " + idx + " ON " + base + " (v1) INCLUDE (v2)");
+ String query = "SELECT v1, v2 FROM " + base + " ORDER BY v1";
+ ExplainPlanTestUtil.assertPlan(conn, query).indexName(idx).indexKind("GLOBAL")
+ .indexRule(OptimizerReasons.RULE_NON_LOCAL_PREFERRED);
+ assertPlanContainsLine(conn, query,
+ " INDEX " + idx + " GLOBAL /* non-local preferred */");
+ }
+ }
+
+ /**
+ * A {@code GROUP BY} on a column with a LOCAL index chooses the data table by the
+ * {@code "more bound PK columns"} rule, which is non default and renders a rule comment.
+ */
+ @Test
+ public void testIndexRejectedRendered() throws Exception {
+ try (Connection conn = DriverManager.getConnection(getUrl(), defaultProps());
+ java.sql.Statement stmt = conn.createStatement()) {
+ String base = generateUniqueName();
+ String idx = base + "_IDX";
+ stmt
+ .execute("CREATE TABLE " + base + " (rowkey VARCHAR PRIMARY KEY, c1 VARCHAR, c2 VARCHAR)");
+ stmt.execute("CREATE LOCAL INDEX " + idx + " ON " + base + " (c1)");
+ String query =
+ "SELECT c1, max(rowkey), max(c2) FROM " + base + " WHERE rowkey <= 'z' GROUP BY c1";
+ ExplainPlanTestUtil.assertPlan(conn, query).indexName(base)
+ .indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedCount(1)
+ .indexRejected(0, idx, OptimizerReasons.REASON_NO_PK_PREFIX_BOUND);
+ assertPlanContainsLine(conn, query, " INDEX " + base + " /* more bound PK columns */");
+ assertPlanContainsLine(conn, query, " /* !INDEX " + idx + " -- no PK prefix bound */");
}
}
@@ -1012,6 +1100,25 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
return ExplainPlanTestUtil.getMutationExplainPlan(conn, query);
}
+ /** Assert that the optimized plan-steps text for {@code query} contains {@code expectedLine}. */
+ private static void assertPlanContainsLine(Connection conn, String query, String expectedLine)
+ throws SQLException {
+ List<String> steps = ExplainPlanTestUtil.getPlanSteps(conn, query);
+ assertTrue("expected plan to contain line '" + expectedLine + "' but was " + steps,
+ steps.contains(expectedLine));
+ }
+
+ /** Assert that no plan-steps text line for {@code query} contains {@code needle}. */
+ private static void assertNoPlanLineContains(Connection conn, String query, String needle)
+ throws SQLException {
+ List<String> steps = ExplainPlanTestUtil.getPlanSteps(conn, query);
+ for (String s : steps) {
+ assertFalse(
+ "expected no plan line containing '" + needle + "' but found '" + s + "' in " + steps,
+ s.contains(needle));
+ }
+ }
+
private static Properties defaultProps() {
Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
props.setProperty(DATE_FORMAT_ATTRIB, "yyyy-MM-dd");
@@ -1044,6 +1151,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
n.putNull("keyRanges");
n.putNull("indexName");
n.putNull("indexKind");
+ n.putNull("indexRule");
+ n.putNull("indexRejected");
n.putNull("saltBuckets");
n.putNull("regionsPlanned");
n.putNull("scanTimeRangeMin");
@@ -1100,6 +1209,10 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest {
// For a data table scan the per scan INDEX line names the same entity as tableName. View and
// index scans that diverge override indexName on the returned node.
n.put("indexName", table);
+ // A data-table scan that participated in optimizer index selection records its decision rule.
+ // A point lookup short-circuits to the point-lookup rule; any other data-table target lands on
+ // the data-table default. Index targets set their own rule on the returned node.
+ n.put("indexRule", scanType.trim().startsWith("POINT LOOKUP") ? "point lookup" : "data table");
if (keys != null) {
n.put("keyRanges", keys);
}
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 25a3347255..e01fa9d693 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
@@ -30,6 +30,7 @@ import org.apache.phoenix.compile.ExplainPlan;
import org.apache.phoenix.compile.ExplainPlanAttributes;
import org.apache.phoenix.compile.QueryPlan;
import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
+import org.apache.phoenix.optimize.RejectedIndexEntry;
/**
* Test helpers for retrieving the {@link ExplainPlan} and its structured
@@ -190,6 +191,72 @@ public final class ExplainPlanTestUtil {
return this;
}
+ /**
+ * Assert the optimizer's index-selection rule label, e.g. one of the
+ * {@code OptimizerReasons.RULE_*} constants (null when the plan did not participate in
+ * optimizer index selection).
+ */
+ public ExplainPlanAssert indexRule(String expected) {
+ assertEquals(at("indexRule"), expected, attributes.getIndexRule());
+ return this;
+ }
+
+ /**
+ * Assert the index-selection rule label starts with {@code prefix}. Useful for the functional
+ * index {@code "matches <expr>"} rule whose suffix is expression-dependent.
+ */
+ public ExplainPlanAssert indexRuleStartsWith(String prefix) {
+ String actual = attributes.getIndexRule();
+ assertNotNull(at("indexRule") + " must not be null", actual);
+ assertTrue(
+ at("indexRule") + " expected to start with '" + prefix + "' but was '" + actual + "'",
+ actual.startsWith(prefix));
+ return this;
+ }
+
+ /** Assert the number of rejected index candidates recorded for this plan. */
+ public ExplainPlanAssert indexRejectedCount(int expected) {
+ List<RejectedIndexEntry> rejected = attributes.getIndexRejected();
+ int actual = rejected == null ? 0 : rejected.size();
+ assertEquals(at("indexRejected.size"), expected, actual);
+ return this;
+ }
+
+ /** Assert the i-th rejected index candidate's name and reason. */
+ public ExplainPlanAssert indexRejected(int i, String name, String reason) {
+ List<RejectedIndexEntry> rejected = attributes.getIndexRejected();
+ assertNotNull(at("indexRejected") + " must not be null", rejected);
+ assertTrue(at("indexRejected") + " has no index " + i + " (size=" + rejected.size() + ")",
+ i >= 0 && i < rejected.size());
+ RejectedIndexEntry entry = rejected.get(i);
+ assertEquals(at("indexRejected[" + i + "].name"), name, entry.getName());
+ assertEquals(at("indexRejected[" + i + "].reason"), reason, entry.getReason());
+ return this;
+ }
+
+ /** Assert that no index candidates were rejected (null or empty list). */
+ public ExplainPlanAssert indexRejectedNone() {
+ List<RejectedIndexEntry> rejected = attributes.getIndexRejected();
+ assertTrue(at("indexRejected") + " expected none but was " + rejected,
+ rejected == null || rejected.isEmpty());
+ return this;
+ }
+
+ /**
+ * Assert that the rejected index list contains an entry with {@code name} and {@code reason}.
+ */
+ public ExplainPlanAssert indexRejectedContains(String name, String reason) {
+ List<RejectedIndexEntry> rejected = attributes.getIndexRejected();
+ assertNotNull(at("indexRejected") + " must not be null", rejected);
+ for (RejectedIndexEntry entry : rejected) {
+ if (entry.getName().equals(name) && entry.getReason().equals(reason)) {
+ return this;
+ }
+ }
+ throw new AssertionError(at("indexRejected") + " expected to contain {" + name + ", " + reason
+ + "} but was " + rejected);
+ }
+
/** Assert the salt bucket count of the scanned table (null when not salted). */
public ExplainPlanAssert saltBuckets(Integer expected) {
assertEquals(at("saltBuckets"), expected, attributes.getSaltBuckets());