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 1e32251fbf4c0c21ca7dff104752e88cfb58c92f Author: Andrew Purtell <[email protected]> AuthorDate: Fri Jun 5 17:54:07 2026 -0700 [WIP] Add per-scan INDEX, SALT BUCKETS, and REGIONS PLANNED to EXPLAIN Emit the chosen index name with its kind (GLOBAL/LOCAL/UNCOVERED GLOBAL), salt bucket count, and planned region count for each scan, exposed in both the EXPLAIN text and ExplainPlanAttributes. Update the backward-compat baselines, normalize the environment-dependent REGIONS PLANNED count, and add dedicated tests covering data-table, salted, and all index-kind scans. --- .../phoenix/compile/ExplainPlanAttributes.java | 78 +++++- .../phoenix/iterate/BaseResultIterators.java | 9 + .../org/apache/phoenix/iterate/ExplainTable.java | 92 +++---- .../query/explain/ExplainJsonNormalizer.java | 5 + .../phoenix/query/explain/ExplainPlanTest.java | 276 +++++++++++++++++---- .../query/explain/ExplainTextNormalizer.java | 5 + 6 files changed, 358 insertions(+), 107 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 683369d854..af0feeccc6 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,7 +35,8 @@ import org.apache.phoenix.schema.PColumn; @JsonPropertyOrder({ "abstractExplainPlan", "hint", "explainScanType", "consistency", "tableName", "keyRanges", "scanTimeRangeMin", "scanTimeRangeMax", "splitsChunk", "useRoundRobinIterator", "samplingRate", "hexStringRVCOffset", "iteratorTypeAndScanSize", "estimatedRows", - "estimatedSizeInBytes", "serverWhereFilter", "serverDistinctFilter", "serverMergeColumns", + "estimatedSizeInBytes", "indexName", "indexKind", "saltBuckets", "regionsPlanned", + "serverWhereFilter", "serverDistinctFilter", "serverMergeColumns", "serverArrayElementProjection", "serverAggregate", "serverGroupByLimit", "serverSortedBy", "serverOffset", "serverRowLimit", "clientFilterBy", "clientAggregate", "clientDistinctFilter", "clientAfterAggregate", "clientSortAlgo", "clientSortedBy", "clientOffset", "clientRowLimit", @@ -61,6 +62,12 @@ public class ExplainPlanAttributes { private final Long estimatedRows; private final Long estimatedSizeInBytes; + // Per-scan index and structural attributes + private final String indexName; + private final String indexKind; + private final Integer saltBuckets; + private final Integer regionsPlanned; + // Server-side operations private final String serverWhereFilter; private final String serverDistinctFilter; @@ -114,6 +121,10 @@ public class ExplainPlanAttributes { this.iteratorTypeAndScanSize = null; this.estimatedRows = null; this.estimatedSizeInBytes = null; + this.indexName = null; + this.indexKind = null; + this.saltBuckets = null; + this.regionsPlanned = null; this.serverWhereFilter = null; this.serverDistinctFilter = null; this.serverMergeColumns = null; @@ -147,7 +158,8 @@ public class ExplainPlanAttributes { Consistency consistency, String tableName, String keyRanges, Long scanTimeRangeMin, Long scanTimeRangeMax, Integer splitsChunk, boolean useRoundRobinIterator, Double samplingRate, String hexStringRVCOffset, String iteratorTypeAndScanSize, Long estimatedRows, - Long estimatedSizeInBytes, String serverWhereFilter, String serverDistinctFilter, + Long estimatedSizeInBytes, String indexName, String indexKind, Integer saltBuckets, + Integer regionsPlanned, String serverWhereFilter, String serverDistinctFilter, Set<PColumn> serverMergeColumns, boolean serverArrayElementProjection, String serverAggregate, Integer serverGroupByLimit, String serverSortedBy, Integer serverOffset, Long serverRowLimit, String clientFilterBy, String clientAggregate, String clientDistinctFilter, @@ -172,6 +184,10 @@ public class ExplainPlanAttributes { this.iteratorTypeAndScanSize = iteratorTypeAndScanSize; this.estimatedRows = estimatedRows; this.estimatedSizeInBytes = estimatedSizeInBytes; + this.indexName = indexName; + this.indexKind = indexKind; + this.saltBuckets = saltBuckets; + this.regionsPlanned = regionsPlanned; this.serverWhereFilter = serverWhereFilter; this.serverDistinctFilter = serverDistinctFilter; this.serverMergeColumns = serverMergeColumns; @@ -261,6 +277,22 @@ public class ExplainPlanAttributes { return estimatedSizeInBytes; } + public String getIndexName() { + return indexName; + } + + public String getIndexKind() { + return indexKind; + } + + public Integer getSaltBuckets() { + return saltBuckets; + } + + public Integer getRegionsPlanned() { + return regionsPlanned; + } + public String getServerWhereFilter() { return serverWhereFilter; } @@ -391,6 +423,10 @@ public class ExplainPlanAttributes { private String iteratorTypeAndScanSize; private Long estimatedRows; private Long estimatedSizeInBytes; + private String indexName; + private String indexKind; + private Integer saltBuckets; + private Integer regionsPlanned; private String serverWhereFilter; private String serverDistinctFilter; private Set<PColumn> serverMergeColumns; @@ -439,6 +475,10 @@ public class ExplainPlanAttributes { this.iteratorTypeAndScanSize = explainPlanAttributes.getIteratorTypeAndScanSize(); this.estimatedRows = explainPlanAttributes.getEstimatedRows(); this.estimatedSizeInBytes = explainPlanAttributes.getEstimatedSizeInBytes(); + this.indexName = explainPlanAttributes.getIndexName(); + this.indexKind = explainPlanAttributes.getIndexKind(); + this.saltBuckets = explainPlanAttributes.getSaltBuckets(); + this.regionsPlanned = explainPlanAttributes.getRegionsPlanned(); this.serverWhereFilter = explainPlanAttributes.getServerWhereFilter(); this.serverDistinctFilter = explainPlanAttributes.getServerDistinctFilter(); this.serverMergeColumns = explainPlanAttributes.getServerMergeColumns(); @@ -543,6 +583,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 setServerWhereFilter(String serverWhereFilter) { this.serverWhereFilter = serverWhereFilter; return this; @@ -684,13 +744,13 @@ public class ExplainPlanAttributes { 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, rhsJoinQueryExplainPlan, subPlans, - dynamicServerFilter, afterJoinFilter, joinScannerLimit, sortMergeSkipMerge, regionLocations, - numRegionLocationLookups); + estimatedRows, estimatedSizeInBytes, indexName, indexKind, saltBuckets, regionsPlanned, + serverWhereFilter, serverDistinctFilter, serverMergeColumns, serverArrayElementProjection, + serverAggregate, serverGroupByLimit, serverSortedBy, serverOffset, serverRowLimit, + clientFilterBy, clientAggregate, clientDistinctFilter, clientAfterAggregate, clientSortAlgo, + clientSortedBy, clientOffset, clientRowLimit, clientSequenceCount, clientCursorName, + rhsJoinQueryExplainPlan, subPlans, dynamicServerFilter, afterJoinFilter, joinScannerLimit, + sortMergeSkipMerge, regionLocations, numRegionLocationLookups); } } } 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..1384ab4484 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 @@ -646,6 +646,15 @@ public abstract class BaseResultIterators extends ExplainTable implements Result else return scans; } + public int getSplitCount() { + return splits == null ? 0 : splits.size(); + } + + @Override + protected Integer getRegionsPlanned() { + return splits == null ? 0 : splits.size(); + } + private List<HRegionLocation> getRegionBoundaries(ParallelScanGrouper scanGrouper, byte[] startRegionBoundaryKey, byte[] stopRegionBoundaryKey) throws SQLException { return scanGrouper.getRegionBoundaries(context, physicalTableName, startRegionBoundaryKey, 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 221a69c8dd..9a936dcda8 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 @@ -53,6 +53,7 @@ import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.query.QueryServicesOptions; import org.apache.phoenix.schema.PColumn; import org.apache.phoenix.schema.PTable; +import org.apache.phoenix.schema.PTableType; import org.apache.phoenix.schema.RowKeySchema; import org.apache.phoenix.schema.SortOrder; import org.apache.phoenix.schema.TableRef; @@ -180,6 +181,50 @@ public abstract class ExplainTable { explainPlanAttributesBuilder.setKeyRanges(appendKeyRanges()); } } + PTable table = tableRef.getTable(); + String idxName = table.getName().getString(); + if (table.getIndexType() == PTable.IndexType.LOCAL + && table.getViewIndexId() != null + && idxName.contains(QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR)) { + int lastIdx = idxName.lastIndexOf(QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR); + idxName = idxName.substring(lastIdx + 1); + } + StringBuilder indexBuf = new StringBuilder("INDEX ").append(idxName); + String indexKindStr = null; + if (table.getType() == PTableType.INDEX) { + PTable.IndexType idxType = table.getIndexType(); + if (idxType == PTable.IndexType.LOCAL) { + indexKindStr = "LOCAL"; + } else if (context.isUncoveredIndex() + || idxType == PTable.IndexType.UNCOVERED_GLOBAL) { + indexKindStr = "UNCOVERED GLOBAL"; + } else if (idxType == PTable.IndexType.GLOBAL) { + indexKindStr = "GLOBAL"; + } + if (indexKindStr != null) { + indexBuf.append(' ').append(indexKindStr); + } + } + planSteps.add(" " + indexBuf.toString()); + if (explainPlanAttributesBuilder != null) { + explainPlanAttributesBuilder.setIndexName(idxName); + explainPlanAttributesBuilder.setIndexKind(indexKindStr); + } + Integer bucketNum = table.getBucketNum(); + if (bucketNum != null && bucketNum > 0) { + planSteps.add(" SALT BUCKETS " + bucketNum); + if (explainPlanAttributesBuilder != null) { + explainPlanAttributesBuilder.setSaltBuckets(bucketNum); + } + } + Integer regionsPlannedVal = getRegionsPlanned(); + if (regionsPlannedVal != null) { + planSteps.add(" REGIONS PLANNED " + regionsPlannedVal); + if (explainPlanAttributesBuilder != null) { + explainPlanAttributesBuilder.setRegionsPlanned(regionsPlannedVal); + } + } + if (context.getScan() != null && tableRef.getTable().getRowTimestampColPos() != -1) { TimeRange range = context.getScan().getTimeRange(); planSteps.add(" ROW TIMESTAMP FILTER [" + range.getMin() + ", " + range.getMax() + ")"); @@ -455,49 +500,6 @@ public abstract class ExplainTable { return (useLongViewIndex ? (Long) s : (Short) s) + Short.MAX_VALUE + 2; } - private static class RowKeyValueIterator implements Iterator<byte[]> { - private final RowKeySchema schema; - private ImmutableBytesWritable ptr = new ImmutableBytesWritable(); - private int position = 0; - private final int maxOffset; - private byte[] nextValue; - - public RowKeyValueIterator(RowKeySchema schema, byte[] rowKey) { - this.schema = schema; - this.maxOffset = schema.iterator(rowKey, ptr); - iterate(); - } - - private void iterate() { - if (schema.next(ptr, position++, maxOffset) == null) { - nextValue = null; - } else { - nextValue = ptr.copyBytes(); - } - } - - @Override - public boolean hasNext() { - return nextValue != null; - } - - @Override - public byte[] next() { - if (nextValue == null) { - throw new NoSuchElementException(); - } - byte[] value = nextValue; - iterate(); - return value; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - } - private void appendScanRow(StringBuilder buf, Bound bound) { ScanRanges scanRanges = context.getScanRanges(); Iterator<byte[]> minMaxIterator = Collections.emptyIterator(); @@ -531,6 +533,10 @@ public abstract class ExplainTable { } } + protected Integer getRegionsPlanned() { + return null; + } + private String appendKeyRanges() { final StringBuilder buf = new StringBuilder(); ScanRanges scanRanges = context.getScanRanges(); 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 eccde4303a..22fa3e715f 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 @@ -56,6 +56,11 @@ public final class ExplainJsonNormalizer { if (obj.has("splitsChunk")) { obj.set("splitsChunk", NullNode.getInstance()); } + // The planned region/split count is dependent on the split count that drives <N>-WAY, so it + // is collapsed for comparison. + 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 df26951ff3..f653d3d69d 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 @@ -73,6 +73,11 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { private static final String MT_BASE = "EO_MT_BASE"; private static final String MT_VIEW = "EO_MT_VIEW"; private static final String TENANT_ID = "tenant42"; + // Base table plus three index flavors used to exercise the per-scan INDEX <kind> classifier. + private static final String IDX_BASE = "EO_IDX_BASE"; + private static final String GLOBAL_IDX = "EO_GIDX"; + private static final String LOCAL_IDX = "EO_LIDX"; + private static final String UNCOVERED_IDX = "EO_UIDX"; private static ExplainOracle oracle; private static ObjectMapper mapper; @@ -86,6 +91,14 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { conn.createStatement().execute("CREATE TABLE IF NOT EXISTS " + SALTED + " (k VARCHAR NOT NULL PRIMARY KEY, v INTEGER) SALT_BUCKETS=4"); conn.createStatement().execute("CREATE SEQUENCE IF NOT EXISTS " + SEQ); + conn.createStatement().execute("CREATE TABLE IF NOT EXISTS " + IDX_BASE + + " (k VARCHAR NOT NULL PRIMARY KEY, a INTEGER, b INTEGER)"); + conn.createStatement() + .execute("CREATE INDEX IF NOT EXISTS " + GLOBAL_IDX + " ON " + IDX_BASE + " (a) INCLUDE(b)"); + conn.createStatement() + .execute("CREATE LOCAL INDEX IF NOT EXISTS " + LOCAL_IDX + " ON " + IDX_BASE + " (a)"); + conn.createStatement() + .execute("CREATE UNCOVERED INDEX IF NOT EXISTS " + UNCOVERED_IDX + " ON " + IDX_BASE + " (a)"); conn.createStatement() .execute("CREATE TABLE IF NOT EXISTS " + MT_BASE + " (" + " tenant_id VARCHAR(8) NOT NULL," + " userid INTEGER NOT NULL," + " username VARCHAR NOT NULL," + " col VARCHAR" @@ -109,8 +122,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 +134,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)); } @@ -131,7 +145,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { "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 ']"), + + " ['00D000000000001','00E00000000002!'] - ['00D000000000001','00E00000000008 ']", + " INDEX ATABLE", " REGIONS PLANNED <N>"), scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001','00E00000000002!'] - ['00D000000000001','00E00000000008 ']")); } @@ -140,7 +155,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 +169,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 +178,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 +199,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 +207,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 +218,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")); @@ -212,7 +231,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", "") @@ -224,8 +244,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")); @@ -235,7 +256,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", "") @@ -249,7 +271,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']") @@ -261,8 +284,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)); } @@ -273,7 +296,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { + " 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']"), + + " ['000000000000001','000000000000003'] - ['000000000000001','000000000000005']", + " INDEX ATABLE", " REGIONS PLANNED <N>"), scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000001','000000000000003'] - ['000000000000001','000000000000005']")); } @@ -287,6 +311,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", @@ -298,7 +323,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'")); @@ -309,8 +335,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]", - " SERVER FILTER BY FIRST KEY ONLY AND (HOST IS 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", "SERVER FILTER BY FIRST KEY ONLY AND (HOST IS NULL" @@ -325,7 +351,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")); @@ -336,7 +362,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']", - " SERVER FILTER BY FIRST KEY ONLY AND" + " 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", "SERVER FILTER BY FIRST KEY ONLY AND REGEXP_SUBSTR(INST, '[^-]+', 1) IN ('na1','na2','na3')")); @@ -348,7 +374,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { "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 ']"), + + " ['000000000000001','003 '] - ['000000000000001','004 ']", + " INDEX ATABLE", " REGIONS PLANNED <N>"), scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000001','003 '] - ['000000000000001','004 ']")); } @@ -359,16 +386,17 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { "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']"), + + " ['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")); @@ -379,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")); @@ -391,9 +420,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")); @@ -405,8 +434,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']", - " SERVER AGGREGATE INTO DISTINCT ROWS BY" + 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"), scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000001']") @@ -420,7 +449,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", "") @@ -438,8 +468,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>"), scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']") .put("abstractExplainPlan", "SORT-MERGE-JOIN (INNER)").set("rhsJoinQueryExplainPlan", rhs)); } @@ -458,8 +490,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); } @@ -478,9 +512,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 ($<N>.$<N>)"), expected); @@ -494,7 +529,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)); @@ -515,7 +552,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")); } @@ -527,7 +565,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")); } @@ -545,6 +584,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'")); @@ -554,6 +594,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'")); @@ -562,7 +603,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)); @@ -571,9 +613,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")); } @@ -583,11 +627,127 @@ 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)); + .put("indexName", "EO_MT_VIEW").put("keyRanges", " ['tenant42']").put("serverRowLimit", 1) + .put("clientRowLimit", 1)); + } + + @Test + public void testIndexLineDataTableNoKind() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), defaultProps())) { + ExplainPlan plan = ExplainPlanTestUtil.getExplainPlan(conn, "SELECT * FROM atable"); + ExplainPlanAttributes attrs = plan.getPlanStepsAsAttributes(); + // A data-table scan reports the table as the chosen INDEX and emits no kind token. + assertEquals("ATABLE", attrs.getIndexName()); + assertEquals(null, attrs.getIndexKind()); + assertEquals(null, attrs.getSaltBuckets()); + assertTrue("regionsPlanned should be populated", attrs.getRegionsPlanned() != null); + assertTrue("regionsPlanned should be positive", attrs.getRegionsPlanned() >= 1); + assertTrue("text must carry bare INDEX line", + plan.getPlanSteps().contains(" INDEX ATABLE")); + // No kind token and no SALT BUCKETS line for an unsalted data table. + for (String line : plan.getPlanSteps()) { + assertTrue("unexpected SALT BUCKETS line: " + line, !line.contains("SALT BUCKETS")); + } + } + } + + @Test + public void testSaltBucketsLine() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), defaultProps())) { + ExplainPlan plan = ExplainPlanTestUtil.getExplainPlan(conn, "SELECT * FROM " + SALTED); + ExplainPlanAttributes attrs = plan.getPlanStepsAsAttributes(); + assertEquals(SALTED, attrs.getIndexName()); + assertEquals(Integer.valueOf(4), attrs.getSaltBuckets()); + assertTrue("text must carry SALT BUCKETS 4", + plan.getPlanSteps().contains(" SALT BUCKETS 4")); + // SALT BUCKETS sits between INDEX and REGIONS PLANNED in the per-scan order. + List<String> steps = plan.getPlanSteps(); + int idxLine = steps.indexOf(" INDEX " + SALTED); + int saltLine = steps.indexOf(" SALT BUCKETS 4"); + assertTrue("INDEX line present", idxLine >= 0); + assertTrue("SALT BUCKETS after INDEX", saltLine > idxLine); + } + } + + @Test + public void testRegionsPlannedAlwaysEmitted() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), defaultProps())) { + List<String> steps = + ExplainPlanTestUtil.getPlanSteps(conn, "SELECT * FROM atable WHERE organization_id = '00D'"); + boolean hasRegionsPlanned = false; + for (String line : steps) { + if (line.trim().startsWith("REGIONS PLANNED ")) { + hasRegionsPlanned = true; + // The value is a positive integer. + int n = Integer.parseInt(line.trim().substring("REGIONS PLANNED ".length())); + assertTrue("REGIONS PLANNED value must be positive", n >= 1); + } + } + assertTrue("every scan emits REGIONS PLANNED", hasRegionsPlanned); + } + } + + @Test + public void testIndexKindGlobal() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), defaultProps())) { + // Selecting only covered columns lets the covered global index be chosen. + ExplainPlan plan = ExplainPlanTestUtil.getExplainPlan(conn, + "SELECT /*+ INDEX(" + IDX_BASE + " " + GLOBAL_IDX + ") */ a, b FROM " + IDX_BASE); + ExplainPlanAttributes attrs = plan.getPlanStepsAsAttributes(); + assertEquals(GLOBAL_IDX, attrs.getIndexName()); + assertEquals("GLOBAL", attrs.getIndexKind()); + assertTrue("text must carry INDEX <name> GLOBAL", + plan.getPlanSteps().contains(" INDEX " + GLOBAL_IDX + " GLOBAL")); + } + } + + @Test + public void testIndexKindLocal() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), defaultProps())) { + ExplainPlan plan = ExplainPlanTestUtil.getExplainPlan(conn, + "SELECT /*+ INDEX(" + IDX_BASE + " " + LOCAL_IDX + ") */ a FROM " + IDX_BASE + + " WHERE a = 1"); + ExplainPlanAttributes attrs = plan.getPlanStepsAsAttributes(); + // For a local index the INDEX line carries just the index name with the LOCAL kind token, + // while the OVER clause keeps the disambiguating idx(phys) form. + assertEquals(LOCAL_IDX, attrs.getIndexName()); + assertEquals("LOCAL", attrs.getIndexKind()); + assertTrue("text must carry INDEX <name> LOCAL", + plan.getPlanSteps().contains(" INDEX " + LOCAL_IDX + " LOCAL")); + } + } + + @Test + public void testIndexKindUncoveredGlobal() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), defaultProps())) { + ExplainPlan plan = ExplainPlanTestUtil.getExplainPlan(conn, + "SELECT /*+ INDEX(" + IDX_BASE + " " + UNCOVERED_IDX + ") */ a FROM " + IDX_BASE + + " WHERE a = 1"); + ExplainPlanAttributes attrs = plan.getPlanStepsAsAttributes(); + assertEquals(UNCOVERED_IDX, attrs.getIndexName()); + assertEquals("UNCOVERED GLOBAL", attrs.getIndexKind()); + assertTrue("text must carry INDEX <name> UNCOVERED GLOBAL", + plan.getPlanSteps().contains(" INDEX " + UNCOVERED_IDX + " UNCOVERED GLOBAL")); + } + } + + @Test + public void testTextNormalizerCollapsesRegionsPlanned() { + assertEquals( + Collections.singletonList(" REGIONS PLANNED <N>"), + new ExplainTextNormalizer().normalize(Arrays.asList(" REGIONS PLANNED 20"))); + } + + @Test + public void testJsonNormalizerErasesRegionsPlanned() { + ObjectNode root = mapper.createObjectNode(); + root.put("regionsPlanned", 7); + new ExplainJsonNormalizer().normalize(root); + assertTrue(root.get("regionsPlanned").isNull()); } @Test @@ -861,6 +1021,10 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { n.putNull("splitsChunk"); n.putNull("estimatedRows"); n.putNull("estimatedSizeInBytes"); + n.putNull("indexName"); + n.putNull("indexKind"); + n.putNull("saltBuckets"); + n.putNull("regionsPlanned"); n.putNull("iteratorTypeAndScanSize"); n.putNull("samplingRate"); n.put("useRoundRobinIterator", false); @@ -915,6 +1079,8 @@ public class ExplainPlanTest extends BaseConnectionlessQueryTest { n.put("consistency", "STRONG"); n.put("explainScanType", scanType); n.put("tableName", table); + // For a data table scan the chosen INDEX <name> is the logical table name. + n.put("indexName", table); if (keys != null) { n.put("keyRanges", keys); } 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 446e1fe2fd..9099f197bd 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 @@ -35,6 +35,10 @@ public final class ExplainTextNormalizer { // PARALLEL 400-WAY -> PARALLEL <N>-WAY ; matches the iterator parallelism count. private static final Pattern WAY_COUNT = Pattern.compile("\\b\\d+-WAY\\b"); + // REGIONS PLANNED 20 -> REGIONS PLANNED <N> the planned region/split count is dependent + // on the split count that drives <N>-WAY, so it is collapsed for cross-environment comparison. + private static final Pattern REGIONS_PLANNED = Pattern.compile("REGIONS PLANNED \\d+"); + // 1234 ROWS 5678 BYTES (stats-row-count gated; we strip when present). private static final Pattern ROWS_BYTES = Pattern.compile("\\d+ ROWS \\d+ BYTES\\s*"); @@ -67,6 +71,7 @@ public final class ExplainTextNormalizer { String normalized = line; normalized = CHUNK_COUNT.matcher(normalized).replaceAll("<N>-CHUNK"); normalized = WAY_COUNT.matcher(normalized).replaceAll("<N>-WAY"); + normalized = REGIONS_PLANNED.matcher(normalized).replaceAll("REGIONS PLANNED <N>"); normalized = ROWS_BYTES.matcher(normalized).replaceAll(""); normalized = DYNAMIC_FILTER_ALIAS.matcher(normalized).replaceAll(DYNAMIC_FILTER_ALIAS_PLACEHOLDER);
