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 a5a43e89fe2244dda30b8c20281465d08742b51d Author: Andrew Purtell <[email protected]> AuthorDate: Tue Jun 9 12:02:12 2026 -0700 PHOENIX-7882 Per scan EXPLAIN output improvements (#2505) Co-authored-by: Claude Opus 4.8[1m] <[email protected]> --- .../phoenix/compile/ExplainPlanAttributes.java | 100 +++++++--- .../phoenix/iterate/BaseResultIterators.java | 5 + .../org/apache/phoenix/iterate/ExplainTable.java | 76 +++++++- .../phoenix/end2end/SortMergeJoinMoreIT.java | 4 +- .../end2end/join/SortMergeJoinGlobalIndexIT.java | 9 +- .../end2end/join/SortMergeJoinLocalIndexIT.java | 7 +- .../query/explain/ExplainJsonNormalizer.java | 3 + .../phoenix/query/explain/ExplainPlanTest.java | 203 +++++++++++++++------ .../phoenix/query/explain/ExplainPlanTestUtil.java | 27 +++ .../query/explain/ExplainTextNormalizer.java | 6 +- 10 files changed, 341 insertions(+), 99 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 74cd983332..fcf858479a 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 @@ -35,16 +35,16 @@ import org.apache.phoenix.schema.PColumn; * Strings containing entire plan. */ @JsonPropertyOrder({ "abstractExplainPlan", "hint", "explainScanType", "consistency", "tableName", - "keyRanges", "scanTimeRangeMin", "scanTimeRangeMax", "splitsChunk", "useRoundRobinIterator", - "samplingRate", "hexStringRVCOffset", "iteratorTypeAndScanSize", "estimatedRows", - "estimatedSizeInBytes", "serverWhereFilter", "serverDistinctFilter", "serverMergeColumns", - "serverArrayElementProjection", "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" }) + "keyRanges", "indexName", "indexKind", "saltBuckets", "regionsPlanned", "scanTimeRangeMin", + "scanTimeRangeMax", "splitsChunk", "useRoundRobinIterator", "samplingRate", "hexStringRVCOffset", + "iteratorTypeAndScanSize", "estimatedRows", "estimatedSizeInBytes", "serverWhereFilter", + "serverDistinctFilter", "serverMergeColumns", "serverArrayElementProjection", "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 { // Plan identity and scan-level metadata @@ -54,6 +54,10 @@ public class ExplainPlanAttributes { private final Consistency consistency; private final String tableName; private final String keyRanges; + private final String indexName; + private final String indexKind; + private final Integer saltBuckets; + private final Integer regionsPlanned; private final Long scanTimeRangeMin; private final Long scanTimeRangeMax; private final Integer splitsChunk; @@ -113,6 +117,10 @@ public class ExplainPlanAttributes { this.consistency = null; this.tableName = null; this.keyRanges = null; + this.indexName = null; + this.indexKind = null; + this.saltBuckets = null; + this.regionsPlanned = null; this.scanTimeRangeMin = null; this.scanTimeRangeMax = null; this.splitsChunk = null; @@ -155,8 +163,9 @@ public class ExplainPlanAttributes { } public ExplainPlanAttributes(String abstractExplainPlan, Hint hint, String explainScanType, - Consistency consistency, String tableName, String keyRanges, Long scanTimeRangeMin, - Long scanTimeRangeMax, Integer splitsChunk, boolean useRoundRobinIterator, Double samplingRate, + 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, String serverAggregate, @@ -175,6 +184,10 @@ public class ExplainPlanAttributes { this.consistency = consistency; this.tableName = tableName; this.keyRanges = keyRanges; + this.indexName = indexName; + this.indexKind = indexKind; + this.saltBuckets = saltBuckets; + this.regionsPlanned = regionsPlanned; this.scanTimeRangeMin = scanTimeRangeMin; this.scanTimeRangeMax = scanTimeRangeMax; this.splitsChunk = splitsChunk; @@ -242,6 +255,22 @@ public class ExplainPlanAttributes { return keyRanges; } + public String getIndexName() { + return indexName; + } + + public String getIndexKind() { + return indexKind; + } + + public Integer getSaltBuckets() { + return saltBuckets; + } + + public Integer getRegionsPlanned() { + return regionsPlanned; + } + public Long getScanTimeRangeMin() { return scanTimeRangeMin; } @@ -411,6 +440,10 @@ public class ExplainPlanAttributes { private Consistency consistency; private String tableName; private String keyRanges; + private String indexName; + private String indexKind; + private Integer saltBuckets; + private Integer regionsPlanned; private Long scanTimeRangeMin; private Long scanTimeRangeMax; private Integer splitsChunk; @@ -462,6 +495,10 @@ public class ExplainPlanAttributes { this.consistency = explainPlanAttributes.getConsistency(); this.tableName = explainPlanAttributes.getTableName(); this.keyRanges = explainPlanAttributes.getKeyRanges(); + this.indexName = explainPlanAttributes.getIndexName(); + this.indexKind = explainPlanAttributes.getIndexKind(); + this.saltBuckets = explainPlanAttributes.getSaltBuckets(); + this.regionsPlanned = explainPlanAttributes.getRegionsPlanned(); this.scanTimeRangeMin = explainPlanAttributes.getScanTimeRangeMin(); this.scanTimeRangeMax = explainPlanAttributes.getScanTimeRangeMax(); this.splitsChunk = explainPlanAttributes.getSplitsChunk(); @@ -534,6 +571,26 @@ public class ExplainPlanAttributes { return this; } + public ExplainPlanAttributesBuilder setIndexName(String indexName) { + this.indexName = indexName; + return this; + } + + public ExplainPlanAttributesBuilder setIndexKind(String indexKind) { + this.indexKind = indexKind; + return this; + } + + public ExplainPlanAttributesBuilder setSaltBuckets(Integer saltBuckets) { + this.saltBuckets = saltBuckets; + return this; + } + + public ExplainPlanAttributesBuilder setRegionsPlanned(Integer regionsPlanned) { + this.regionsPlanned = regionsPlanned; + return this; + } + public ExplainPlanAttributesBuilder setScanTimeRangeMin(Long scanTimeRangeMin) { this.scanTimeRangeMin = scanTimeRangeMin; return this; @@ -743,15 +800,16 @@ public class ExplainPlanAttributes { public ExplainPlanAttributes build() { return new ExplainPlanAttributes(abstractExplainPlan, hint, explainScanType, consistency, - tableName, keyRanges, scanTimeRangeMin, scanTimeRangeMax, splitsChunk, - useRoundRobinIterator, samplingRate, hexStringRVCOffset, iteratorTypeAndScanSize, - estimatedRows, estimatedSizeInBytes, serverWhereFilter, serverDistinctFilter, - serverMergeColumns, serverArrayElementProjection, 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); + tableName, keyRanges, indexName, indexKind, saltBuckets, regionsPlanned, scanTimeRangeMin, + scanTimeRangeMax, splitsChunk, useRoundRobinIterator, samplingRate, hexStringRVCOffset, + iteratorTypeAndScanSize, estimatedRows, estimatedSizeInBytes, serverWhereFilter, + serverDistinctFilter, serverMergeColumns, serverArrayElementProjection, 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/iterate/BaseResultIterators.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java index 45e160fac8..7e81d3a534 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 @@ -640,6 +640,11 @@ public abstract class BaseResultIterators extends ExplainTable implements Result else return splits; } + @Override + protected int getSplitCount() { + return splits == null ? 0 : splits.size(); + } + @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 8382100561..1a1f32786f 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 @@ -122,6 +122,33 @@ public abstract class ExplainTable { return buf.toString(); } + /** + * Number of region scan splits the plan will hit, used to render the {@code REGIONS PLANNED} + * per-scan line. + * @return the split count, or 0 when unknown + */ + protected int getSplitCount() { + return 0; + } + + /** + * 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. + * @param table the scanned table or index + * @return the display name with any child-view local-index prefix stripped + */ + private static String getExplainIndexName(PTable table) { + String indexName = table.getName().getString(); + if ( + table.getIndexType() == PTable.IndexType.LOCAL && table.getViewIndexId() != null + && indexName.contains(QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR) + ) { + int lastIndexOf = indexName.lastIndexOf(QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR); + indexName = indexName.substring(lastIndexOf + 1); + } + return indexName; + } + protected void explain(String prefix, List<String> planSteps, ExplainPlanAttributesBuilder explainPlanAttributesBuilder, List<HRegionLocation> regionLocations) { @@ -148,15 +175,7 @@ public abstract class ExplainTable { String tableName = tableRef.getTable().getPhysicalName().getString(); if (tableRef.getTable().getIndexType() == PTable.IndexType.LOCAL) { - String indexName = tableRef.getTable().getName().getString(); - if ( - tableRef.getTable().getViewIndexId() != null - && indexName.contains(QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR) - ) { - int lastIndexOf = indexName.lastIndexOf(QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR); - indexName = indexName.substring(lastIndexOf + 1); - } - tableName = indexName + "(" + tableName + ")"; + tableName = getExplainIndexName(tableRef.getTable()) + "(" + tableName + ")"; } buf.append("OVER ").append(tableName); @@ -178,6 +197,45 @@ public abstract class ExplainTable { explainPlanAttributesBuilder.setKeyRanges(appendKeyRanges()); } } + + PTable.IndexType indexType = tableRef.getTable().getIndexType(); + String explainIndexName = getExplainIndexName(tableRef.getTable()); + String indexKind = null; + if (indexType != null) { + switch (indexType) { + case LOCAL: + indexKind = "LOCAL"; + break; + case GLOBAL: + indexKind = "GLOBAL"; + break; + case UNCOVERED_GLOBAL: + indexKind = "UNCOVERED GLOBAL"; + break; + default: + indexKind = null; + } + } + planSteps.add(" INDEX " + explainIndexName + (indexKind == null ? "" : " " + indexKind)); + Integer bucketNum = tableRef.getTable().getBucketNum(); + if (bucketNum != null) { + planSteps.add(" SALT BUCKETS " + bucketNum); + } + int splitCount = getSplitCount(); + if (splitCount > 0) { + planSteps.add(" REGIONS PLANNED " + splitCount); + } + if (explainPlanAttributesBuilder != null) { + explainPlanAttributesBuilder.setIndexName(explainIndexName); + explainPlanAttributesBuilder.setIndexKind(indexKind); + if (bucketNum != null) { + explainPlanAttributesBuilder.setSaltBuckets(bucketNum); + } + if (splitCount > 0) { + explainPlanAttributesBuilder.setRegionsPlanned(splitCount); + } + } + if (context.getScan() != null && tableRef.getTable().getRowTimestampColPos() != -1) { TimeRange range = context.getScan().getTimeRange(); planSteps.add(" ROW TIMESTAMP FILTER [" + range.getMin() + ", " + range.getMax() + ")"); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortMergeJoinMoreIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortMergeJoinMoreIT.java index 1a57ae39e0..26621c1c9b 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortMergeJoinMoreIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortMergeJoinMoreIT.java @@ -434,8 +434,8 @@ public class SortMergeJoinMoreIT extends ParallelStatsDisabledIT { .serverDistinctFilter("SERVER DISTINCT PREFIX FILTER OVER [BUCKET, TIMESTAMP, LOCATION]") .serverAggregate( "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [BUCKET, TIMESTAMP, LOCATION]") - .clientSortAlgo("CLIENT MERGE SORT").clientSortedBy("[BUCKET, TIMESTAMP]") - .end().rhs().iteratorType("PARALLEL").scanType("SKIP SCAN ON 2 RANGES").table(t[i]) + .clientSortAlgo("CLIENT MERGE SORT").clientSortedBy("[BUCKET, TIMESTAMP]").end().rhs() + .iteratorType("PARALLEL").scanType("SKIP SCAN ON 2 RANGES").table(t[i]) .keyRanges(rhsKeyRanges) .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND SRC_LOCATION = DST_LOCATION") .serverDistinctFilter( diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinGlobalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinGlobalIndexIT.java index 59d69aa160..612768aabc 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinGlobalIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinGlobalIndexIT.java @@ -65,9 +65,8 @@ public class SortMergeJoinGlobalIndexIT extends SortMergeJoinIT { .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"S.:supplier_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end().rhs() .abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(true) - .clientSortedBy("[\"I.0:supplier_id\"]").lhs() - .scanType("FULL SCAN").table(itemIndex).serverSortedBy("[\"I.:item_id\"]") - .clientSortAlgo("CLIENT MERGE SORT").end().rhs() + .clientSortedBy("[\"I.0:supplier_id\"]").lhs().scanType("FULL SCAN").table(itemIndex) + .serverSortedBy("[\"I.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end().rhs() .scanType("FULL SCAN").table(order).serverWhereFilter("SERVER FILTER BY QUANTITY < 5000") .serverSortedBy("[\"O.item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end().end(); } @@ -96,8 +95,8 @@ public class SortMergeJoinGlobalIndexIT extends SortMergeJoinIT { assertPlan(attributes).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(false) .clientRowLimit(4).lhs().scanType("FULL SCAN").table(itemIndex) .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"I.:item_id\"]") - .clientSortAlgo("CLIENT MERGE SORT").end().rhs().scanType("FULL SCAN") - .table(order).serverSortedBy(queryIndex == 0 ? "[\"O.item_id\"]" : "[\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end().rhs().scanType("FULL SCAN").table(order) + .serverSortedBy(queryIndex == 0 ? "[\"O.item_id\"]" : "[\"item_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end(); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinLocalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinLocalIndexIT.java index ec31377e1a..95d5f44a2a 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinLocalIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinLocalIndexIT.java @@ -67,10 +67,9 @@ public class SortMergeJoinLocalIndexIT extends SortMergeJoinIT { .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"S.:supplier_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end().rhs() .abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(true) - .clientSortedBy("[\"I.0:supplier_id\"]").lhs() - .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") - .serverSortedBy("[\"I.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT") - .end().rhs().scanType("FULL SCAN").table(order) + .clientSortedBy("[\"I.0:supplier_id\"]").lhs().scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]").serverSortedBy("[\"I.:item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end().rhs().scanType("FULL SCAN").table(order) .serverWhereFilter("SERVER FILTER BY QUANTITY < 5000").serverSortedBy("[\"O.item_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end().end(); } diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainJsonNormalizer.java b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainJsonNormalizer.java index 38f44fabea..eb96912f95 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainJsonNormalizer.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainJsonNormalizer.java @@ -63,6 +63,9 @@ public final class ExplainJsonNormalizer { if (obj.has("splitsChunk")) { obj.set("splitsChunk", NullNode.getInstance()); } + if (obj.has("regionsPlanned")) { + obj.set("regionsPlanned", NullNode.getInstance()); + } if (obj.has("estimatedRows")) { obj.set("estimatedRows", NullNode.getInstance()); } 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 66fe6b8b71..8e57faa750 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 @@ -109,8 +109,8 @@ 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", - " SERVER FILTER BY (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)"), scanAttrs("POINT LOOKUP ON 1 KEY ", "ATABLE", null).put("serverWhereFilter", "SERVER FILTER BY (X_INTEGER = 2 AND A_INTEGER < 5)")); } @@ -121,7 +121,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"), + text("CLIENT PARALLEL <N>-WAY POINT LOOKUP ON 4 KEYS OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED <N>"), scanAttrs("POINT LOOKUP ON 4 KEYS ", "ATABLE", null)); } @@ -130,8 +131,10 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { verifyQuery("rangeScan", "SELECT a_string FROM atable WHERE organization_id = '00D000000000001'" + " AND entity_id > '00E00000000002' AND entity_id < '00E00000000008'", - text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE" - + " ['00D000000000001','00E00000000002!'] - ['00D000000000001','00E00000000008 ']"), + text( + "CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE" + + " ['00D000000000001','00E00000000002!'] - ['00D000000000001','00E00000000008 ']", + " INDEX ATABLE", " REGIONS PLANNED <N>"), scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001','00E00000000002!'] - ['00D000000000001','00E00000000008 ']")); } @@ -140,7 +143,7 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { public void testSkipScanKeys() throws Exception { verifyQuery("skipScanKeys", "SELECT host FROM ptsdb3 WHERE host IN ('na1','na2','na3')", text("CLIENT PARALLEL <N>-WAY SKIP SCAN ON 3 KEYS OVER PTSDB3 [~'na3'] - [~'na1']", - " SERVER FILTER BY FIRST KEY ONLY"), + " INDEX PTSDB3", " REGIONS PLANNED <N>", " SERVER FILTER BY FIRST KEY ONLY"), scanAttrs("SKIP SCAN ON 3 KEYS ", "PTSDB3", " [~'na3'] - [~'na1']").put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY")); } @@ -154,7 +157,7 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { text( "CLIENT PARALLEL <N>-WAY SKIP SCAN ON 6 RANGES OVER PTSDB" + " ['na1','a','2013-01-01'] - ['na3','b','2013-01-02']", - " SERVER FILTER BY FIRST KEY ONLY"), + " INDEX PTSDB", " REGIONS PLANNED <N>", " SERVER FILTER BY FIRST KEY ONLY"), scanAttrs("SKIP SCAN ON 6 RANGES ", "PTSDB", " ['na1','a','2013-01-01'] - ['na3','b','2013-01-02']").put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY")); @@ -163,15 +166,17 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { @Test public void testFullScan() throws Exception { verifyQuery("fullScan", "SELECT * FROM atable", - text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE"), scanAttrs("FULL SCAN ", "ATABLE", "")); + text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED <N>"), + scanAttrs("FULL SCAN ", "ATABLE", "")); } @Test public void testReverseScan() throws Exception { verifyQuery("reverseScan", "SELECT inst,\"DATE\" FROM ptsdb2 WHERE inst = 'na1' ORDER BY inst DESC, \"DATE\" DESC", - text("CLIENT PARALLEL <N>-WAY REVERSE RANGE SCAN OVER PTSDB2 ['na1']", - " SERVER FILTER BY FIRST KEY ONLY"), + text("CLIENT PARALLEL <N>-WAY REVERSE RANGE SCAN OVER PTSDB2 ['na1']", " INDEX PTSDB2", + " REGIONS PLANNED <N>", " SERVER FILTER BY FIRST KEY ONLY"), scanAttrs("RANGE SCAN ", "PTSDB2", " ['na1']") .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY") .put("clientSortedBy", "REVERSE")); @@ -182,7 +187,7 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { verifyQuery("smallHint", "SELECT /*+ SMALL */ host FROM ptsdb3 WHERE host IN ('na1','na2','na3')", text("CLIENT PARALLEL <N>-WAY SMALL SKIP SCAN ON 3 KEYS OVER PTSDB3 [~'na3'] - [~'na1']", - " SERVER FILTER BY FIRST KEY ONLY"), + " INDEX PTSDB3", " REGIONS PLANNED <N>", " SERVER FILTER BY FIRST KEY ONLY"), scanAttrs("SKIP SCAN ON 3 KEYS ", "PTSDB3", " [~'na3'] - [~'na1']").put("hint", "SMALL") .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY")); } @@ -190,7 +195,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { @Test public void testAggregateSingleRow() throws Exception { verifyQuery("aggregateSingleRow", "SELECT count(*) FROM atable", - text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " SERVER FILTER BY FIRST KEY ONLY", + text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED <N>", " SERVER FILTER BY FIRST KEY ONLY", " SERVER AGGREGATE INTO SINGLE ROW"), scanAttrs("FULL SCAN ", "ATABLE", "") .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY") @@ -200,8 +206,9 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { @Test public void testAggregateOrderedDistinct() throws Exception { verifyQuery("aggregateOrderedDistinct", "SELECT count(1) FROM atable GROUP BY a_string", - text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", - " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]", "CLIENT MERGE SORT"), + text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED <N>", " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]", + "CLIENT MERGE SORT"), scanAttrs("FULL SCAN ", "ATABLE", "") .put("serverAggregate", "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]") .put("clientSortAlgo", "CLIENT MERGE SORT") @@ -213,7 +220,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { verifyQuery("aggregateHashDistinct", "SELECT count(1) FROM atable WHERE a_integer = 1" + " GROUP BY ROUND(a_time,'HOUR',2), entity_id", - text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " SERVER FILTER BY A_INTEGER = 1", + text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED <N>", " SERVER FILTER BY A_INTEGER = 1", " SERVER AGGREGATE INTO DISTINCT ROWS BY [ENTITY_ID, ROUND(A_TIME)]", "CLIENT MERGE SORT"), scanAttrs("FULL SCAN ", "ATABLE", "") @@ -226,8 +234,9 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { @Test public void testTopNSortedBy() throws Exception { verifyQuery("topNSortedBy", "SELECT a_string FROM atable ORDER BY a_string DESC LIMIT 3", - text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", - " SERVER TOP 3 ROWS SORTED BY [A_STRING DESC]", "CLIENT MERGE SORT", "CLIENT LIMIT 3"), + text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED <N>", " SERVER TOP 3 ROWS SORTED BY [A_STRING DESC]", + "CLIENT MERGE SORT", "CLIENT LIMIT 3"), scanAttrs("FULL SCAN ", "ATABLE", "").put("serverSortedBy", "[A_STRING DESC]") .put("serverRowLimit", 3).put("clientRowLimit", 3) .put("clientSortAlgo", "CLIENT MERGE SORT") @@ -238,7 +247,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { public void testClientFilterByMax() throws Exception { verifyQuery("clientFilterByMax", "SELECT count(1) FROM atable GROUP BY a_string, b_string HAVING max(a_string) = 'a'", - text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", + text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED <N>", " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]", "CLIENT MERGE SORT", "CLIENT FILTER BY MAX(A_STRING) = 'a'"), scanAttrs("FULL SCAN ", "ATABLE", "") @@ -254,7 +264,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { "SELECT a_string, b_string FROM atable" + " WHERE organization_id = '00D000000000001' AND entity_id != '00E00000000002'" + " AND x_integer = 2 AND a_integer < 5 LIMIT 10", - text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['00D000000000001']", + text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['00D000000000001']", " INDEX ATABLE", + " REGIONS PLANNED <N>", " SERVER FILTER BY (ENTITY_ID != '00E00000000002' AND X_INTEGER = 2 AND A_INTEGER < 5)", " SERVER 10 ROW LIMIT", "CLIENT 10 ROW LIMIT"), scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']") @@ -267,8 +278,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { @Test public void testArrayElementProjection() throws Exception { verifyQuery("arrayElementProjection", "SELECT a_string_array[1] FROM table_with_array", - text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER TABLE_WITH_ARRAY", - " SERVER ARRAY ELEMENT PROJECTION"), + text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER TABLE_WITH_ARRAY", " INDEX TABLE_WITH_ARRAY", + " REGIONS PLANNED <N>", " SERVER ARRAY ELEMENT PROJECTION"), scanAttrs("FULL SCAN ", "TABLE_WITH_ARRAY", "").put("serverArrayElementProjection", true)); } @@ -278,8 +289,10 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { "SELECT a_string,b_string FROM atable WHERE organization_id = '000000000000001'" + " AND entity_id > '000000000000002' AND entity_id < '000000000000008'" + " AND (organization_id,entity_id) <= ('000000000000001','000000000000005')", - text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE" - + " ['000000000000001','000000000000003'] - ['000000000000001','000000000000005']"), + text( + "CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE" + + " ['000000000000001','000000000000003'] - ['000000000000001','000000000000005']", + " INDEX ATABLE", " REGIONS PLANNED <N>"), scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000001','000000000000003'] - ['000000000000001','000000000000005']")); } @@ -293,6 +306,7 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { text( "CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE" + " ['000000000000003000000000000005'] - [*]", + " INDEX ATABLE", " REGIONS PLANNED <N>", " SERVER FILTER BY (ENTITY_ID > '000000000000002' AND ENTITY_ID < '000000000000008')"), scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000003000000000000005'] - [*]").put( "serverWhereFilter", @@ -304,7 +318,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { verifyQuery("rangeScanNullNotNull", "SELECT host FROM PTSDB WHERE inst IS NULL AND host IS NOT NULL" + " AND \"DATE\" >= to_date('2013-01-01')", - text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER PTSDB [null,not null]", + text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER PTSDB [null,not null]", " INDEX PTSDB", + " REGIONS PLANNED <N>", " SERVER FILTER BY FIRST KEY ONLY AND \"DATE\" >= DATE '2013-01-01 00:00:00.000'"), scanAttrs("RANGE SCAN ", "PTSDB", " [null,not null]").put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY AND \"DATE\" >= DATE '2013-01-01 00:00:00.000'")); @@ -315,7 +330,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { verifyQuery("rangeScanNotNull", "SELECT host FROM PTSDB WHERE inst IS NOT NULL AND host IS NULL" + " AND \"DATE\" >= to_date('2013-01-01')", - text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER PTSDB [not null]", + text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER PTSDB [not null]", " INDEX PTSDB", + " REGIONS PLANNED <N>", " SERVER FILTER BY FIRST KEY ONLY AND (HOST IS NULL" + " AND \"DATE\" >= DATE '2013-01-01 00:00:00.000')"), scanAttrs("RANGE SCAN ", "PTSDB", " [not null]").put("serverWhereFilter", @@ -331,7 +347,7 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { text( "CLIENT PARALLEL <N>-WAY SKIP SCAN ON 2 RANGES OVER PTSDB" + " ['na','a','2013-01-01'] - ['nb','b','2013-01-02']", - " SERVER FILTER BY FIRST KEY ONLY"), + " INDEX PTSDB", " REGIONS PLANNED <N>", " SERVER FILTER BY FIRST KEY ONLY"), scanAttrs("SKIP SCAN ON 2 RANGES ", "PTSDB", " ['na','a','2013-01-01'] - ['nb','b','2013-01-02']").put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY")); @@ -342,6 +358,7 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { verifyQuery("skipScanRegexpRanges", "SELECT inst,host FROM PTSDB WHERE REGEXP_SUBSTR(INST, '[^-]+', 1) IN ('na1', 'na2','na3')", text("CLIENT PARALLEL <N>-WAY SKIP SCAN ON 3 RANGES OVER PTSDB ['na1'] - ['na4']", + " INDEX PTSDB", " REGIONS PLANNED <N>", " SERVER FILTER BY FIRST KEY ONLY AND" + " REGEXP_SUBSTR(INST, '[^-]+', 1) IN ('na1','na2','na3')"), scanAttrs("SKIP SCAN ON 3 RANGES ", "PTSDB", " ['na1'] - ['na4']").put("serverWhereFilter", @@ -353,8 +370,10 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { verifyQuery("rangeScanSubstrBounds", "SELECT a_string FROM atable WHERE organization_id='000000000000001'" + " AND SUBSTR(entity_id,1,3) > '002' AND SUBSTR(entity_id,1,3) <= '003'", - text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE" - + " ['000000000000001','003 '] - ['000000000000001','004 ']"), + text( + "CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE" + + " ['000000000000001','003 '] - ['000000000000001','004 ']", + " INDEX ATABLE", " REGIONS PLANNED <N>"), scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000001','003 '] - ['000000000000001','004 ']")); } @@ -364,17 +383,19 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { verifyQuery("skipScanTwoKeys", "SELECT a_string,b_string FROM atable" + " WHERE organization_id IN ('000000000000001', '000000000000005')", - text("CLIENT PARALLEL <N>-WAY SKIP SCAN ON 2 KEYS OVER ATABLE" - + " ['000000000000001'] - ['000000000000005']"), + text( + "CLIENT PARALLEL <N>-WAY SKIP SCAN ON 2 KEYS OVER ATABLE" + + " ['000000000000001'] - ['000000000000005']", + " INDEX ATABLE", " REGIONS PLANNED <N>"), scanAttrs("SKIP SCAN ON 2 KEYS ", "ATABLE", " ['000000000000001'] - ['000000000000005']")); } @Test public void testGroupByClientLimit() throws Exception { verifyQuery("groupByClientLimit", "SELECT count(1) FROM atable GROUP BY a_string LIMIT 5", - text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", - " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]", "CLIENT MERGE SORT", - "CLIENT 5 ROW LIMIT"), + text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED <N>", " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]", + "CLIENT MERGE SORT", "CLIENT 5 ROW LIMIT"), scanAttrs("FULL SCAN ", "ATABLE", "") .put("serverAggregate", "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]") .put("clientRowLimit", 5).put("clientSortAlgo", "CLIENT MERGE SORT") @@ -386,8 +407,9 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { verifyQuery("topNAscNullsFirstLimit", "SELECT a_string,b_string FROM atable WHERE organization_id = '000000000000001'" + " ORDER BY a_string ASC NULLS FIRST LIMIT 10", - text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['000000000000001']", - " SERVER TOP 10 ROWS SORTED BY [A_STRING]", "CLIENT MERGE SORT", "CLIENT LIMIT 10"), + text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['000000000000001']", " INDEX ATABLE", + " REGIONS PLANNED <N>", " SERVER TOP 10 ROWS SORTED BY [A_STRING]", + "CLIENT MERGE SORT", "CLIENT LIMIT 10"), scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000001']").put("serverSortedBy", "[A_STRING]") .put("serverRowLimit", 10).put("clientRowLimit", 10) .put("clientSortAlgo", "CLIENT MERGE SORT") @@ -399,9 +421,9 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { verifyQuery("topNDescNullsLastLimit", "SELECT a_string,b_string FROM atable WHERE organization_id = '000000000000001'" + " ORDER BY a_string DESC NULLS LAST LIMIT 10", - text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['000000000000001']", - " SERVER TOP 10 ROWS SORTED BY [A_STRING DESC NULLS LAST]", "CLIENT MERGE SORT", - "CLIENT LIMIT 10"), + text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['000000000000001']", " INDEX ATABLE", + " REGIONS PLANNED <N>", " SERVER TOP 10 ROWS SORTED BY [A_STRING DESC NULLS LAST]", + "CLIENT MERGE SORT", "CLIENT LIMIT 10"), scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000001']") .put("serverSortedBy", "[A_STRING DESC NULLS LAST]").put("serverRowLimit", 10) .put("clientRowLimit", 10).put("clientSortAlgo", "CLIENT MERGE SORT") @@ -414,7 +436,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { "SELECT max(a_integer) FROM atable WHERE organization_id = '000000000000001'" + " GROUP BY organization_id,entity_id,ROUND(a_date,'HOUR')" + " ORDER BY entity_id NULLS LAST LIMIT 10", - text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['000000000000001']", + text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['000000000000001']", " INDEX ATABLE", + " REGIONS PLANNED <N>", " SERVER AGGREGATE INTO DISTINCT ROWS BY" + " [ORGANIZATION_ID, ENTITY_ID, ROUND(A_DATE)]", "CLIENT MERGE SORT", "CLIENT 10 ROW LIMIT"), @@ -430,7 +453,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { verifyQuery("clientSortedByHaving", "SELECT count(1) FROM atable WHERE a_integer = 1 GROUP BY a_string,b_string" + " HAVING max(a_string) = 'a' ORDER BY b_string", - text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " SERVER FILTER BY A_INTEGER = 1", + text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED <N>", " SERVER FILTER BY A_INTEGER = 1", " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]", "CLIENT MERGE SORT", "CLIENT FILTER BY MAX(A_STRING) = 'a'", "CLIENT SORTED BY [B_STRING]"), scanAttrs("FULL SCAN ", "ATABLE", "") @@ -457,8 +481,10 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { + " JOIN atable b ON a.organization_id = b.organization_id" + " WHERE a.organization_id = '00D000000000001'", text("SORT-MERGE-JOIN (INNER) TABLES", - " CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['00D000000000001']", "AND", - " CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE"), + " CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['00D000000000001']", + " INDEX ATABLE", " REGIONS PLANNED <N>", "AND", + " CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED <N>"), expected); } @@ -476,8 +502,10 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { "SELECT a.a_string, b.a_string FROM atable a" + " JOIN atable b ON a.organization_id = b.organization_id" + " WHERE a.organization_id = '00D000000000001'", - text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['00D000000000001']", - " PARALLEL INNER-JOIN TABLE 0", " CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", + text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['00D000000000001']", " INDEX ATABLE", + " REGIONS PLANNED <N>", " PARALLEL INNER-JOIN TABLE 0", + " CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED <N>", " DYNAMIC SERVER FILTER BY A.ORGANIZATION_ID IN (B.ORGANIZATION_ID)"), expected); } @@ -497,9 +525,10 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { verifyQuery("hashJoinSemiInSubquery", "SELECT a_string FROM atable" + " WHERE organization_id IN (SELECT organization_id FROM atable WHERE a_integer = 1)", - text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " SKIP-SCAN-JOIN TABLE 0", - " CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", - " SERVER FILTER BY A_INTEGER = 1", + text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED <N>", " SKIP-SCAN-JOIN TABLE 0", + " CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED <N>", " SERVER FILTER BY A_INTEGER = 1", " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [ORGANIZATION_ID]", " DYNAMIC SERVER FILTER BY ATABLE.ORGANIZATION_ID IN ($1.$2)"), expected); @@ -513,7 +542,9 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { + " SELECT a_string FROM atable WHERE organization_id = '00D000000000002'", text("UNION ALL OVER 2 QUERIES", " CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['00D000000000001']", - " CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['00D000000000002']"), + " INDEX ATABLE", " REGIONS PLANNED <N>", + " CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['00D000000000002']", + " INDEX ATABLE", " REGIONS PLANNED <N>"), scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']") .put("abstractExplainPlan", "UNION ALL OVER 2 QUERIES") .set("rhsJoinQueryExplainPlan", rhs)); @@ -534,7 +565,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { + " SELECT organization_id, entity_id, a_string FROM atable" + " WHERE organization_id = '00D000000000001'", false, - text("UPSERT SELECT", "CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['00D000000000001']"), + text("UPSERT SELECT", "CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['00D000000000001']", + " INDEX ATABLE", " REGIONS PLANNED <N>"), scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']").put("abstractExplainPlan", "UPSERT SELECT")); } @@ -546,7 +578,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { + " SELECT organization_id, entity_id, a_string FROM atable" + " WHERE organization_id = '00D000000000001'", true, - text("UPSERT ROWS", "CLIENT PARALLEL <N>-WAY RANGE SCAN OVER ATABLE ['00D000000000001']"), + 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")); } @@ -564,6 +597,7 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { public void testDeleteServer() throws Exception { verifyMutation("deleteServer", "DELETE FROM atable WHERE entity_id = 'abc'", true, text("DELETE ROWS SERVER SELECT", "CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", + " INDEX ATABLE", " REGIONS PLANNED <N>", " SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID = 'abc'"), scanAttrs("FULL SCAN ", "ATABLE", "").put("abstractExplainPlan", "DELETE ROWS SERVER SELECT") .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID = 'abc'")); @@ -573,6 +607,7 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { public void testDeleteClient() throws Exception { verifyMutation("deleteClient", "DELETE FROM atable WHERE entity_id = 'abc'", false, text("DELETE ROWS CLIENT SELECT", "CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", + " INDEX ATABLE", " REGIONS PLANNED <N>", " SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID = 'abc'"), scanAttrs("FULL SCAN ", "ATABLE", "").put("abstractExplainPlan", "DELETE ROWS CLIENT SELECT") .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID = 'abc'")); @@ -581,7 +616,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { @Test public void testSequenceNextValue() throws Exception { verifyQuery("sequenceNextValue", "SELECT NEXT VALUE FOR " + SEQ + " FROM atable", - text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " SERVER FILTER BY FIRST KEY ONLY", + text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED <N>", " SERVER FILTER BY FIRST KEY ONLY", "CLIENT RESERVE VALUES FROM 1 SEQUENCE"), scanAttrs("FULL SCAN ", "ATABLE", "") .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY").put("clientSequenceCount", 1) @@ -591,9 +627,11 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { @Test public void testSaltedTableScan() throws Exception { verifyQuery("saltedTableScan", "SELECT * FROM " + SALTED + " WHERE v = 7", - text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER EO_SALTED", " SERVER FILTER BY V = 7", + text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER EO_SALTED", " INDEX EO_SALTED", + " SALT BUCKETS 4", " REGIONS PLANNED <N>", " SERVER FILTER BY V = 7", "CLIENT MERGE SORT"), - scanAttrs("FULL SCAN ", "EO_SALTED", "").put("serverWhereFilter", "SERVER FILTER BY V = 7") + scanAttrs("FULL SCAN ", "EO_SALTED", "").put("saltBuckets", 4) + .put("serverWhereFilter", "SERVER FILTER BY V = 7") .put("clientSortAlgo", "CLIENT MERGE SORT") .set("clientSteps", clientSteps("CLIENT MERGE SORT"))); } @@ -604,12 +642,56 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { tenantProps.setProperty(DATE_FORMAT_ATTRIB, "yyyy-MM-dd"); tenantProps.setProperty(TENANT_ID_ATTRIB, TENANT_ID); verifyQuery("multiTenantView", "SELECT * FROM " + MT_VIEW + " LIMIT 1", tenantProps, - text("CLIENT SERIAL <N>-WAY RANGE SCAN OVER EO_MT_BASE ['tenant42']", - " SERVER 1 ROW LIMIT", "CLIENT 1 ROW LIMIT"), + text("CLIENT SERIAL <N>-WAY RANGE SCAN OVER EO_MT_BASE ['tenant42']", " INDEX EO_MT_VIEW", + " 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("keyRanges", " ['tenant42']").put("serverRowLimit", 1).put("clientRowLimit", 1) - .set("clientSteps", clientSteps("CLIENT 1 ROW LIMIT"))); + .put("indexName", "EO_MT_VIEW").put("keyRanges", " ['tenant42']").put("serverRowLimit", 1) + .put("clientRowLimit", 1).set("clientSteps", clientSteps("CLIENT 1 ROW LIMIT"))); + } + + @Test + public void testIndexKindGlobal() 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)"); + ExplainPlanTestUtil.assertPlan(conn, "SELECT v1, v2 FROM " + base + " WHERE v1 = 'x'") + .table(idx).indexName(idx).indexKind("GLOBAL"); + } + } + + @Test + public void testIndexKindLocal() 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 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"); + } + } + + @Test + public void testIndexKindUncoveredGlobal() 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 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"); + } } @Test @@ -908,6 +990,10 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { n.putNull("explainScanType"); n.putNull("tableName"); n.putNull("keyRanges"); + n.putNull("indexName"); + n.putNull("indexKind"); + n.putNull("saltBuckets"); + n.putNull("regionsPlanned"); n.putNull("scanTimeRangeMin"); n.putNull("scanTimeRangeMax"); n.putNull("serverWhereFilter"); @@ -957,6 +1043,9 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { n.put("consistency", "STRONG"); n.put("explainScanType", scanType); n.put("tableName", table); + // 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); 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 7c55ecbafc..2f44a07e58 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 @@ -175,6 +175,33 @@ public final class ExplainPlanTestUtil { return this; } + /** Assert the chosen per-scan index (or data table) name. */ + public ExplainPlanAssert indexName(String expected) { + assertEquals(at("indexName"), expected, attributes.getIndexName()); + return this; + } + + /** + * Assert the per-scan index kind token: {@code "LOCAL"}, {@code "GLOBAL"}, or + * {@code "UNCOVERED GLOBAL"} (null for a data-table target). + */ + public ExplainPlanAssert indexKind(String expected) { + assertEquals(at("indexKind"), expected, attributes.getIndexKind()); + return this; + } + + /** Assert the salt bucket count of the scanned table (null when not salted). */ + public ExplainPlanAssert saltBuckets(Integer expected) { + assertEquals(at("saltBuckets"), expected, attributes.getSaltBuckets()); + return this; + } + + /** Assert the number of regions the scan is planned to hit (null when unknown). */ + public ExplainPlanAssert regionsPlanned(Integer expected) { + assertEquals(at("regionsPlanned"), expected, attributes.getRegionsPlanned()); + return this; + } + /** Assert the hex-string row-value-constructor offset marker. */ public ExplainPlanAssert hexStringRVCOffset(String expected) { assertEquals(at("hexStringRVCOffset"), expected, attributes.getHexStringRVCOffset()); diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainTextNormalizer.java b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainTextNormalizer.java index 24c3bee4a9..c8e1d1ef46 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainTextNormalizer.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainTextNormalizer.java @@ -33,9 +33,12 @@ public final class ExplainTextNormalizer { // Matches the iterator parallelism count. private static final Pattern WAY_COUNT = Pattern.compile("\\b\\d+-WAY\\b"); - // Matches the stats-row-count gated row count and byte count. + // Matches the stats row and byte counts. private static final Pattern ROWS_BYTES = Pattern.compile("\\d+ ROWS \\d+ BYTES\\s*"); + // Matches the planned regions count on the REGIONS PLANNED line. + private static final Pattern REGIONS_PLANNED = Pattern.compile("REGIONS PLANNED \\d+"); + // Matches the region locations line. private static final String REGION_LOCATIONS_PREFIX = " (region locations = "; @@ -62,6 +65,7 @@ public final class ExplainTextNormalizer { normalized = CHUNK_COUNT.matcher(normalized).replaceAll("<N>-CHUNK"); normalized = WAY_COUNT.matcher(normalized).replaceAll("<N>-WAY"); normalized = ROWS_BYTES.matcher(normalized).replaceAll(""); + normalized = REGIONS_PLANNED.matcher(normalized).replaceAll("REGIONS PLANNED <N>"); normalized = aliases.rewrite(normalized); out.add(normalized); }
