diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java index 3d1e9ffdcda..05316748b9b 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java @@ -793,7 +793,9 @@ public MutationState execute() throws SQLException { @Override public ExplainPlan getExplainPlan() throws SQLException { - return new ExplainPlan(Collections.singletonList("DELETE SINGLE ROW")); + ExplainPlanAttributes attributes = + new ExplainPlanAttributesBuilder().setAbstractExplainPlan("DELETE SINGLE ROW").build(); + return new ExplainPlan(Collections.singletonList("DELETE SINGLE ROW"), attributes); } @Override 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 f4ed63f2454..683369d8540 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 @@ -32,58 +32,67 @@ * against. This also makes attribute retrieval easier as an API rather than retrieving list of * Strings containing entire plan. */ -@JsonPropertyOrder({ "abstractExplainPlan", "splitsChunk", "estimatedRows", "estimatedSizeInBytes", - "iteratorTypeAndScanSize", "samplingRate", "useRoundRobinIterator", "hexStringRVCOffset", - "consistency", "hint", "serverSortedBy", "explainScanType", "tableName", "keyRanges", - "scanTimeRangeMin", "scanTimeRangeMax", "serverWhereFilter", "serverDistinctFilter", - "serverOffset", "serverRowLimit", "serverArrayElementProjection", "serverAggregate", - "clientFilterBy", "clientAggregate", "clientSortedBy", "clientAfterAggregate", - "clientDistinctFilter", "clientOffset", "clientRowLimit", "clientSequenceCount", - "clientCursorName", "clientSortAlgo", "rhsJoinQueryExplainPlan", "serverMergeColumns", +@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", "rhsJoinQueryExplainPlan", "subPlans", + "dynamicServerFilter", "afterJoinFilter", "joinScannerLimit", "sortMergeSkipMerge", "regionLocations", "numRegionLocationLookups" }) public class ExplainPlanAttributes { + // Plan identity and scan-level metadata private final String abstractExplainPlan; - private final Integer splitsChunk; - private final Long estimatedRows; - private final Long estimatedSizeInBytes; - private final String iteratorTypeAndScanSize; - private final Double samplingRate; - private final boolean useRoundRobinIterator; - private final String hexStringRVCOffset; - private final Consistency consistency; private final Hint hint; - private final String serverSortedBy; private final String explainScanType; + private final Consistency consistency; private final String tableName; private final String keyRanges; private final Long scanTimeRangeMin; private final Long scanTimeRangeMax; + private final Integer splitsChunk; + private final boolean useRoundRobinIterator; + private final Double samplingRate; + private final String hexStringRVCOffset; + private final String iteratorTypeAndScanSize; + private final Long estimatedRows; + private final Long estimatedSizeInBytes; + + // Server-side operations private final String serverWhereFilter; private final String serverDistinctFilter; - private final Integer serverOffset; - private final Long serverRowLimit; + private final Set serverMergeColumns; private final boolean serverArrayElementProjection; private final String serverAggregate; + private final Integer serverGroupByLimit; + private final String serverSortedBy; + private final Integer serverOffset; + private final Long serverRowLimit; + + // Client-side operations private final String clientFilterBy; private final String clientAggregate; - private final String clientSortedBy; - private final String clientAfterAggregate; private final String clientDistinctFilter; + private final String clientAfterAggregate; + private final String clientSortAlgo; + private final String clientSortedBy; private final Integer clientOffset; private final Integer clientRowLimit; private final Integer clientSequenceCount; private final String clientCursorName; - private final String clientSortAlgo; - // This object represents PlanAttributes object for rhs query - // to be used only by Join queries. In case of Join query, lhs plan is - // represented by 'this' object and rhs plan is represented by - // 'rhsJoinQueryExplainPlan' object (which in turn should - // have null rhsJoinQueryExplainPlan) - // For non-Join queries related Plans, rhsJoinQueryExplainPlan will always - // be null + + // Join / sub-plan private final ExplainPlanAttributes rhsJoinQueryExplainPlan; - private final Set serverMergeColumns; + private final List subPlans; + private final String dynamicServerFilter; + private final String afterJoinFilter; + private final Long joinScannerLimit; + private final boolean sortMergeSkipMerge; + + // Region-location metadata private final List regionLocations; private final int numRegionLocationLookups; @@ -91,89 +100,103 @@ public class ExplainPlanAttributes { private ExplainPlanAttributes() { this.abstractExplainPlan = null; - this.splitsChunk = null; - this.estimatedRows = null; - this.estimatedSizeInBytes = null; - this.iteratorTypeAndScanSize = null; - this.samplingRate = null; - this.useRoundRobinIterator = false; - this.hexStringRVCOffset = null; - this.consistency = null; this.hint = null; - this.serverSortedBy = null; this.explainScanType = null; + this.consistency = null; this.tableName = null; this.keyRanges = null; this.scanTimeRangeMin = null; this.scanTimeRangeMax = null; + this.splitsChunk = null; + this.useRoundRobinIterator = false; + this.samplingRate = null; + this.hexStringRVCOffset = null; + this.iteratorTypeAndScanSize = null; + this.estimatedRows = null; + this.estimatedSizeInBytes = null; this.serverWhereFilter = null; this.serverDistinctFilter = null; - this.serverOffset = null; - this.serverRowLimit = null; + this.serverMergeColumns = null; this.serverArrayElementProjection = false; this.serverAggregate = null; + this.serverGroupByLimit = null; + this.serverSortedBy = null; + this.serverOffset = null; + this.serverRowLimit = null; this.clientFilterBy = null; this.clientAggregate = null; - this.clientSortedBy = null; - this.clientAfterAggregate = null; this.clientDistinctFilter = null; + this.clientAfterAggregate = null; + this.clientSortAlgo = null; + this.clientSortedBy = null; this.clientOffset = null; this.clientRowLimit = null; this.clientSequenceCount = null; this.clientCursorName = null; - this.clientSortAlgo = null; this.rhsJoinQueryExplainPlan = null; - this.serverMergeColumns = null; + this.subPlans = null; + this.dynamicServerFilter = null; + this.afterJoinFilter = null; + this.joinScannerLimit = null; + this.sortMergeSkipMerge = false; this.regionLocations = null; this.numRegionLocationLookups = 0; } - public ExplainPlanAttributes(String abstractExplainPlan, Integer splitsChunk, Long estimatedRows, - Long estimatedSizeInBytes, String iteratorTypeAndScanSize, Double samplingRate, - boolean useRoundRobinIterator, String hexStringRVCOffset, Consistency consistency, Hint hint, - String serverSortedBy, String explainScanType, String tableName, String keyRanges, - Long scanTimeRangeMin, Long scanTimeRangeMax, String serverWhereFilter, - String serverDistinctFilter, Integer serverOffset, Long serverRowLimit, - boolean serverArrayElementProjection, String serverAggregate, String clientFilterBy, - String clientAggregate, String clientSortedBy, String clientAfterAggregate, - String clientDistinctFilter, Integer clientOffset, Integer clientRowLimit, - Integer clientSequenceCount, String clientCursorName, String clientSortAlgo, - ExplainPlanAttributes rhsJoinQueryExplainPlan, Set serverMergeColumns, - List regionLocations, int numRegionLocationLookups) { + public ExplainPlanAttributes(String abstractExplainPlan, Hint hint, String explainScanType, + 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, + Set serverMergeColumns, boolean serverArrayElementProjection, String serverAggregate, + Integer serverGroupByLimit, String serverSortedBy, Integer serverOffset, Long serverRowLimit, + String clientFilterBy, String clientAggregate, String clientDistinctFilter, + String clientAfterAggregate, String clientSortAlgo, String clientSortedBy, Integer clientOffset, + Integer clientRowLimit, Integer clientSequenceCount, String clientCursorName, + ExplainPlanAttributes rhsJoinQueryExplainPlan, List subPlans, + String dynamicServerFilter, String afterJoinFilter, Long joinScannerLimit, + boolean sortMergeSkipMerge, List regionLocations, + int numRegionLocationLookups) { this.abstractExplainPlan = abstractExplainPlan; - this.splitsChunk = splitsChunk; - this.estimatedRows = estimatedRows; - this.estimatedSizeInBytes = estimatedSizeInBytes; - this.iteratorTypeAndScanSize = iteratorTypeAndScanSize; - this.samplingRate = samplingRate; - this.useRoundRobinIterator = useRoundRobinIterator; - this.hexStringRVCOffset = hexStringRVCOffset; - this.consistency = consistency; this.hint = hint; - this.serverSortedBy = serverSortedBy; this.explainScanType = explainScanType; + this.consistency = consistency; this.tableName = tableName; this.keyRanges = keyRanges; this.scanTimeRangeMin = scanTimeRangeMin; this.scanTimeRangeMax = scanTimeRangeMax; + this.splitsChunk = splitsChunk; + this.useRoundRobinIterator = useRoundRobinIterator; + this.samplingRate = samplingRate; + this.hexStringRVCOffset = hexStringRVCOffset; + this.iteratorTypeAndScanSize = iteratorTypeAndScanSize; + this.estimatedRows = estimatedRows; + this.estimatedSizeInBytes = estimatedSizeInBytes; this.serverWhereFilter = serverWhereFilter; this.serverDistinctFilter = serverDistinctFilter; - this.serverOffset = serverOffset; - this.serverRowLimit = serverRowLimit; + this.serverMergeColumns = serverMergeColumns; this.serverArrayElementProjection = serverArrayElementProjection; this.serverAggregate = serverAggregate; + this.serverGroupByLimit = serverGroupByLimit; + this.serverSortedBy = serverSortedBy; + this.serverOffset = serverOffset; + this.serverRowLimit = serverRowLimit; this.clientFilterBy = clientFilterBy; this.clientAggregate = clientAggregate; - this.clientSortedBy = clientSortedBy; - this.clientAfterAggregate = clientAfterAggregate; this.clientDistinctFilter = clientDistinctFilter; + this.clientAfterAggregate = clientAfterAggregate; + this.clientSortAlgo = clientSortAlgo; + this.clientSortedBy = clientSortedBy; this.clientOffset = clientOffset; this.clientRowLimit = clientRowLimit; this.clientSequenceCount = clientSequenceCount; this.clientCursorName = clientCursorName; - this.clientSortAlgo = clientSortAlgo; this.rhsJoinQueryExplainPlan = rhsJoinQueryExplainPlan; - this.serverMergeColumns = serverMergeColumns; + this.subPlans = subPlans; + this.dynamicServerFilter = dynamicServerFilter; + this.afterJoinFilter = afterJoinFilter; + this.joinScannerLimit = joinScannerLimit; + this.sortMergeSkipMerge = sortMergeSkipMerge; this.regionLocations = regionLocations; this.numRegionLocationLookups = numRegionLocationLookups; } @@ -182,64 +205,60 @@ public String getAbstractExplainPlan() { return abstractExplainPlan; } - public Integer getSplitsChunk() { - return splitsChunk; - } - - public Long getEstimatedRows() { - return estimatedRows; + public Hint getHint() { + return hint; } - public Long getEstimatedSizeInBytes() { - return estimatedSizeInBytes; + public String getExplainScanType() { + return explainScanType; } - public String getIteratorTypeAndScanSize() { - return iteratorTypeAndScanSize; + public Consistency getConsistency() { + return consistency; } - public Double getSamplingRate() { - return samplingRate; + public String getTableName() { + return tableName; } - public boolean isUseRoundRobinIterator() { - return useRoundRobinIterator; + public String getKeyRanges() { + return keyRanges; } - public String getHexStringRVCOffset() { - return hexStringRVCOffset; + public Long getScanTimeRangeMin() { + return scanTimeRangeMin; } - public Consistency getConsistency() { - return consistency; + public Long getScanTimeRangeMax() { + return scanTimeRangeMax; } - public Hint getHint() { - return hint; + public Integer getSplitsChunk() { + return splitsChunk; } - public String getServerSortedBy() { - return serverSortedBy; + public boolean isUseRoundRobinIterator() { + return useRoundRobinIterator; } - public String getExplainScanType() { - return explainScanType; + public Double getSamplingRate() { + return samplingRate; } - public String getTableName() { - return tableName; + public String getHexStringRVCOffset() { + return hexStringRVCOffset; } - public String getKeyRanges() { - return keyRanges; + public String getIteratorTypeAndScanSize() { + return iteratorTypeAndScanSize; } - public Long getScanTimeRangeMin() { - return scanTimeRangeMin; + public Long getEstimatedRows() { + return estimatedRows; } - public Long getScanTimeRangeMax() { - return scanTimeRangeMax; + public Long getEstimatedSizeInBytes() { + return estimatedSizeInBytes; } public String getServerWhereFilter() { @@ -250,12 +269,9 @@ public String getServerDistinctFilter() { return serverDistinctFilter; } - public Integer getServerOffset() { - return serverOffset; - } - - public Long getServerRowLimit() { - return serverRowLimit; + @JsonSerialize(using = ServerMergeColumnsSerializer.class) + public Set getServerMergeColumns() { + return serverMergeColumns; } public boolean isServerArrayElementProjection() { @@ -266,6 +282,22 @@ public String getServerAggregate() { return serverAggregate; } + public Integer getServerGroupByLimit() { + return serverGroupByLimit; + } + + public String getServerSortedBy() { + return serverSortedBy; + } + + public Integer getServerOffset() { + return serverOffset; + } + + public Long getServerRowLimit() { + return serverRowLimit; + } + public String getClientFilterBy() { return clientFilterBy; } @@ -274,16 +306,20 @@ public String getClientAggregate() { return clientAggregate; } - public String getClientSortedBy() { - return clientSortedBy; + public String getClientDistinctFilter() { + return clientDistinctFilter; } public String getClientAfterAggregate() { return clientAfterAggregate; } - public String getClientDistinctFilter() { - return clientDistinctFilter; + public String getClientSortAlgo() { + return clientSortAlgo; + } + + public String getClientSortedBy() { + return clientSortedBy; } public Integer getClientOffset() { @@ -302,17 +338,28 @@ public String getClientCursorName() { return clientCursorName; } - public String getClientSortAlgo() { - return clientSortAlgo; - } - public ExplainPlanAttributes getRhsJoinQueryExplainPlan() { return rhsJoinQueryExplainPlan; } - @JsonSerialize(using = ServerMergeColumnsSerializer.class) - public Set getServerMergeColumns() { - return serverMergeColumns; + public List getSubPlans() { + return subPlans; + } + + public String getDynamicServerFilter() { + return dynamicServerFilter; + } + + public String getAfterJoinFilter() { + return afterJoinFilter; + } + + public Long getJoinScannerLimit() { + return joinScannerLimit; + } + + public boolean isSortMergeSkipMerge() { + return sortMergeSkipMerge; } @JsonSerialize(using = RegionLocationsListSerializer.class) @@ -330,39 +377,45 @@ public static ExplainPlanAttributes getDefaultExplainPlan() { public static class ExplainPlanAttributesBuilder { private String abstractExplainPlan; - private Integer splitsChunk; - private Long estimatedRows; - private Long estimatedSizeInBytes; - private String iteratorTypeAndScanSize; - private Double samplingRate; - private boolean useRoundRobinIterator; - private String hexStringRVCOffset; - private Consistency consistency; private HintNode.Hint hint; - private String serverSortedBy; private String explainScanType; + private Consistency consistency; private String tableName; private String keyRanges; private Long scanTimeRangeMin; private Long scanTimeRangeMax; + private Integer splitsChunk; + private boolean useRoundRobinIterator; + private Double samplingRate; + private String hexStringRVCOffset; + private String iteratorTypeAndScanSize; + private Long estimatedRows; + private Long estimatedSizeInBytes; private String serverWhereFilter; private String serverDistinctFilter; - private Integer serverOffset; - private Long serverRowLimit; + private Set serverMergeColumns; private boolean serverArrayElementProjection; private String serverAggregate; + private Integer serverGroupByLimit; + private String serverSortedBy; + private Integer serverOffset; + private Long serverRowLimit; private String clientFilterBy; private String clientAggregate; - private String clientSortedBy; - private String clientAfterAggregate; private String clientDistinctFilter; + private String clientAfterAggregate; + private String clientSortAlgo; + private String clientSortedBy; private Integer clientOffset; private Integer clientRowLimit; private Integer clientSequenceCount; private String clientCursorName; - private String clientSortAlgo; private ExplainPlanAttributes rhsJoinQueryExplainPlan; - private Set serverMergeColumns; + private List subPlans; + private String dynamicServerFilter; + private String afterJoinFilter; + private Long joinScannerLimit; + private boolean sortMergeSkipMerge; private List regionLocations; private int numRegionLocationLookups; @@ -372,39 +425,45 @@ public ExplainPlanAttributesBuilder() { public ExplainPlanAttributesBuilder(ExplainPlanAttributes explainPlanAttributes) { this.abstractExplainPlan = explainPlanAttributes.getAbstractExplainPlan(); - this.splitsChunk = explainPlanAttributes.getSplitsChunk(); - this.estimatedRows = explainPlanAttributes.getEstimatedRows(); - this.estimatedSizeInBytes = explainPlanAttributes.getEstimatedSizeInBytes(); - this.iteratorTypeAndScanSize = explainPlanAttributes.getIteratorTypeAndScanSize(); - this.samplingRate = explainPlanAttributes.getSamplingRate(); - this.useRoundRobinIterator = explainPlanAttributes.isUseRoundRobinIterator(); - this.hexStringRVCOffset = explainPlanAttributes.getHexStringRVCOffset(); - this.consistency = explainPlanAttributes.getConsistency(); this.hint = explainPlanAttributes.getHint(); - this.serverSortedBy = explainPlanAttributes.getServerSortedBy(); this.explainScanType = explainPlanAttributes.getExplainScanType(); + this.consistency = explainPlanAttributes.getConsistency(); this.tableName = explainPlanAttributes.getTableName(); this.keyRanges = explainPlanAttributes.getKeyRanges(); this.scanTimeRangeMin = explainPlanAttributes.getScanTimeRangeMin(); this.scanTimeRangeMax = explainPlanAttributes.getScanTimeRangeMax(); + this.splitsChunk = explainPlanAttributes.getSplitsChunk(); + this.useRoundRobinIterator = explainPlanAttributes.isUseRoundRobinIterator(); + this.samplingRate = explainPlanAttributes.getSamplingRate(); + this.hexStringRVCOffset = explainPlanAttributes.getHexStringRVCOffset(); + this.iteratorTypeAndScanSize = explainPlanAttributes.getIteratorTypeAndScanSize(); + this.estimatedRows = explainPlanAttributes.getEstimatedRows(); + this.estimatedSizeInBytes = explainPlanAttributes.getEstimatedSizeInBytes(); this.serverWhereFilter = explainPlanAttributes.getServerWhereFilter(); this.serverDistinctFilter = explainPlanAttributes.getServerDistinctFilter(); - this.serverOffset = explainPlanAttributes.getServerOffset(); - this.serverRowLimit = explainPlanAttributes.getServerRowLimit(); + this.serverMergeColumns = explainPlanAttributes.getServerMergeColumns(); this.serverArrayElementProjection = explainPlanAttributes.isServerArrayElementProjection(); this.serverAggregate = explainPlanAttributes.getServerAggregate(); + this.serverGroupByLimit = explainPlanAttributes.getServerGroupByLimit(); + this.serverSortedBy = explainPlanAttributes.getServerSortedBy(); + this.serverOffset = explainPlanAttributes.getServerOffset(); + this.serverRowLimit = explainPlanAttributes.getServerRowLimit(); this.clientFilterBy = explainPlanAttributes.getClientFilterBy(); this.clientAggregate = explainPlanAttributes.getClientAggregate(); - this.clientSortedBy = explainPlanAttributes.getClientSortedBy(); - this.clientAfterAggregate = explainPlanAttributes.getClientAfterAggregate(); this.clientDistinctFilter = explainPlanAttributes.getClientDistinctFilter(); + this.clientAfterAggregate = explainPlanAttributes.getClientAfterAggregate(); + this.clientSortAlgo = explainPlanAttributes.getClientSortAlgo(); + this.clientSortedBy = explainPlanAttributes.getClientSortedBy(); this.clientOffset = explainPlanAttributes.getClientOffset(); this.clientRowLimit = explainPlanAttributes.getClientRowLimit(); this.clientSequenceCount = explainPlanAttributes.getClientSequenceCount(); this.clientCursorName = explainPlanAttributes.getClientCursorName(); - this.clientSortAlgo = explainPlanAttributes.getClientSortAlgo(); this.rhsJoinQueryExplainPlan = explainPlanAttributes.getRhsJoinQueryExplainPlan(); - this.serverMergeColumns = explainPlanAttributes.getServerMergeColumns(); + this.subPlans = explainPlanAttributes.getSubPlans(); + this.dynamicServerFilter = explainPlanAttributes.getDynamicServerFilter(); + this.afterJoinFilter = explainPlanAttributes.getAfterJoinFilter(); + this.joinScannerLimit = explainPlanAttributes.getJoinScannerLimit(); + this.sortMergeSkipMerge = explainPlanAttributes.isSortMergeSkipMerge(); this.regionLocations = explainPlanAttributes.getRegionLocations(); this.numRegionLocationLookups = explainPlanAttributes.getNumRegionLocationLookups(); } @@ -414,78 +473,73 @@ public ExplainPlanAttributesBuilder setAbstractExplainPlan(String abstractExplai return this; } - public ExplainPlanAttributesBuilder setSplitsChunk(Integer splitsChunk) { - this.splitsChunk = splitsChunk; - return this; - } - - public ExplainPlanAttributesBuilder setEstimatedRows(Long estimatedRows) { - this.estimatedRows = estimatedRows; + public ExplainPlanAttributesBuilder setHint(HintNode.Hint hint) { + this.hint = hint; return this; } - public ExplainPlanAttributesBuilder setEstimatedSizeInBytes(Long estimatedSizeInBytes) { - this.estimatedSizeInBytes = estimatedSizeInBytes; + public ExplainPlanAttributesBuilder setExplainScanType(String explainScanType) { + this.explainScanType = explainScanType; return this; } - public ExplainPlanAttributesBuilder setIteratorTypeAndScanSize(String iteratorTypeAndScanSize) { - this.iteratorTypeAndScanSize = iteratorTypeAndScanSize; + public ExplainPlanAttributesBuilder setConsistency(Consistency consistency) { + this.consistency = consistency; return this; } - public ExplainPlanAttributesBuilder setSamplingRate(Double samplingRate) { - this.samplingRate = samplingRate; + public ExplainPlanAttributesBuilder setTableName(String tableName) { + this.tableName = tableName; return this; } - public ExplainPlanAttributesBuilder setUseRoundRobinIterator(boolean useRoundRobinIterator) { - this.useRoundRobinIterator = useRoundRobinIterator; + public ExplainPlanAttributesBuilder setKeyRanges(String keyRanges) { + this.keyRanges = keyRanges; return this; } - public ExplainPlanAttributesBuilder setHexStringRVCOffset(String hexStringRVCOffset) { - this.hexStringRVCOffset = hexStringRVCOffset; + public ExplainPlanAttributesBuilder setScanTimeRangeMin(Long scanTimeRangeMin) { + this.scanTimeRangeMin = scanTimeRangeMin; return this; } - public ExplainPlanAttributesBuilder setConsistency(Consistency consistency) { - this.consistency = consistency; + public ExplainPlanAttributesBuilder setScanTimeRangeMax(Long scanTimeRangeMax) { + this.scanTimeRangeMax = scanTimeRangeMax; return this; } - public ExplainPlanAttributesBuilder setHint(HintNode.Hint hint) { - this.hint = hint; + public ExplainPlanAttributesBuilder setSplitsChunk(Integer splitsChunk) { + this.splitsChunk = splitsChunk; return this; } - public ExplainPlanAttributesBuilder setServerSortedBy(String serverSortedBy) { - this.serverSortedBy = serverSortedBy; + public ExplainPlanAttributesBuilder setUseRoundRobinIterator(boolean useRoundRobinIterator) { + this.useRoundRobinIterator = useRoundRobinIterator; return this; } - public ExplainPlanAttributesBuilder setExplainScanType(String explainScanType) { - this.explainScanType = explainScanType; + public ExplainPlanAttributesBuilder setSamplingRate(Double samplingRate) { + this.samplingRate = samplingRate; return this; } - public ExplainPlanAttributesBuilder setTableName(String tableName) { - this.tableName = tableName; + public ExplainPlanAttributesBuilder setHexStringRVCOffset(String hexStringRVCOffset) { + this.hexStringRVCOffset = hexStringRVCOffset; return this; } - public ExplainPlanAttributesBuilder setKeyRanges(String keyRanges) { - this.keyRanges = keyRanges; + public ExplainPlanAttributesBuilder setIteratorTypeAndScanSize(String iteratorTypeAndScanSize) { + this.iteratorTypeAndScanSize = iteratorTypeAndScanSize; return this; } - public ExplainPlanAttributesBuilder setScanTimeRangeMin(Long scanTimeRangeMin) { - this.scanTimeRangeMin = scanTimeRangeMin; + public ExplainPlanAttributesBuilder setEstimatedRows(Long estimatedRows) { + this.estimatedRows = estimatedRows; return this; } - public ExplainPlanAttributesBuilder setScanTimeRangeMax(Long scanTimeRangeMax) { - this.scanTimeRangeMax = scanTimeRangeMax; + public ExplainPlanAttributesBuilder setEstimatedSizeInBytes(Long estimatedSizeInBytes) { + this.estimatedSizeInBytes = estimatedSizeInBytes; return this; } @@ -499,13 +553,8 @@ public ExplainPlanAttributesBuilder setServerDistinctFilter(String serverDistinc return this; } - public ExplainPlanAttributesBuilder setServerOffset(Integer serverOffset) { - this.serverOffset = serverOffset; - return this; - } - - public ExplainPlanAttributesBuilder setServerRowLimit(Long serverRowLimit) { - this.serverRowLimit = serverRowLimit; + public ExplainPlanAttributesBuilder setServerMergeColumns(Set columns) { + this.serverMergeColumns = columns; return this; } @@ -520,6 +569,26 @@ public ExplainPlanAttributesBuilder setServerAggregate(String serverAggregate) { return this; } + public ExplainPlanAttributesBuilder setServerGroupByLimit(Integer serverGroupByLimit) { + this.serverGroupByLimit = serverGroupByLimit; + return this; + } + + public ExplainPlanAttributesBuilder setServerSortedBy(String serverSortedBy) { + this.serverSortedBy = serverSortedBy; + return this; + } + + public ExplainPlanAttributesBuilder setServerOffset(Integer serverOffset) { + this.serverOffset = serverOffset; + return this; + } + + public ExplainPlanAttributesBuilder setServerRowLimit(Long serverRowLimit) { + this.serverRowLimit = serverRowLimit; + return this; + } + public ExplainPlanAttributesBuilder setClientFilterBy(String clientFilterBy) { this.clientFilterBy = clientFilterBy; return this; @@ -530,8 +599,8 @@ public ExplainPlanAttributesBuilder setClientAggregate(String clientAggregate) { return this; } - public ExplainPlanAttributesBuilder setClientSortedBy(String clientSortedBy) { - this.clientSortedBy = clientSortedBy; + public ExplainPlanAttributesBuilder setClientDistinctFilter(String clientDistinctFilter) { + this.clientDistinctFilter = clientDistinctFilter; return this; } @@ -540,8 +609,13 @@ public ExplainPlanAttributesBuilder setClientAfterAggregate(String clientAfterAg return this; } - public ExplainPlanAttributesBuilder setClientDistinctFilter(String clientDistinctFilter) { - this.clientDistinctFilter = clientDistinctFilter; + public ExplainPlanAttributesBuilder setClientSortAlgo(String clientSortAlgo) { + this.clientSortAlgo = clientSortAlgo; + return this; + } + + public ExplainPlanAttributesBuilder setClientSortedBy(String clientSortedBy) { + this.clientSortedBy = clientSortedBy; return this; } @@ -565,19 +639,34 @@ public ExplainPlanAttributesBuilder setClientCursorName(String clientCursorName) return this; } - public ExplainPlanAttributesBuilder setClientSortAlgo(String clientSortAlgo) { - this.clientSortAlgo = clientSortAlgo; - return this; - } - public ExplainPlanAttributesBuilder setRhsJoinQueryExplainPlan(ExplainPlanAttributes rhsJoinQueryExplainPlan) { this.rhsJoinQueryExplainPlan = rhsJoinQueryExplainPlan; return this; } - public ExplainPlanAttributesBuilder setServerMergeColumns(Set columns) { - this.serverMergeColumns = columns; + public ExplainPlanAttributesBuilder setSubPlans(List subPlans) { + this.subPlans = subPlans; + return this; + } + + public ExplainPlanAttributesBuilder setDynamicServerFilter(String dynamicServerFilter) { + this.dynamicServerFilter = dynamicServerFilter; + return this; + } + + public ExplainPlanAttributesBuilder setAfterJoinFilter(String afterJoinFilter) { + this.afterJoinFilter = afterJoinFilter; + return this; + } + + public ExplainPlanAttributesBuilder setJoinScannerLimit(Long joinScannerLimit) { + this.joinScannerLimit = joinScannerLimit; + return this; + } + + public ExplainPlanAttributesBuilder setSortMergeSkipMerge(boolean sortMergeSkipMerge) { + this.sortMergeSkipMerge = sortMergeSkipMerge; return this; } @@ -592,14 +681,16 @@ public ExplainPlanAttributesBuilder setNumRegionLocationLookups(int numRegionLoc } public ExplainPlanAttributes build() { - return new ExplainPlanAttributes(abstractExplainPlan, splitsChunk, estimatedRows, - estimatedSizeInBytes, iteratorTypeAndScanSize, samplingRate, useRoundRobinIterator, - hexStringRVCOffset, consistency, hint, serverSortedBy, explainScanType, tableName, - keyRanges, scanTimeRangeMin, scanTimeRangeMax, serverWhereFilter, serverDistinctFilter, - serverOffset, serverRowLimit, serverArrayElementProjection, serverAggregate, clientFilterBy, - clientAggregate, clientSortedBy, clientAfterAggregate, clientDistinctFilter, clientOffset, - clientRowLimit, clientSequenceCount, clientCursorName, clientSortAlgo, - rhsJoinQueryExplainPlan, serverMergeColumns, regionLocations, numRegionLocationLookups); + 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); } } } diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/GroupByCompiler.java b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/GroupByCompiler.java index 7b090467b3a..06db0584182 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/GroupByCompiler.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/GroupByCompiler.java @@ -164,6 +164,7 @@ public List getOrderPreservingTrackInfos() { return orderPreservingTrackInfos; } + @SuppressWarnings("rawtypes") public GroupBy compile(StatementContext context, QueryPlan innerQueryPlan, Expression whereExpression) throws SQLException { boolean isOrderPreserving = this.isOrderPreserving; @@ -365,6 +366,9 @@ private void explainUtil(List planSteps, Integer limit, planSteps.add(" " + serverAggregate); if (explainPlanAttributesBuilder != null) { explainPlanAttributesBuilder.setServerAggregate(serverAggregate); + if (!isUngroupedAggregate && limit != null) { + explainPlanAttributesBuilder.setServerGroupByLimit(limit); + } } } @@ -448,6 +452,7 @@ public static GroupBy compile(StatementContext context, SelectStatement statemen return groupBy; } + @SuppressWarnings("rawtypes") private static boolean onlyAtEndType(Expression expression) { // Due to the encoding schema of these types, they may only be // used once in a group by and are located at the end of the @@ -456,6 +461,7 @@ private static boolean onlyAtEndType(Expression expression) { return type.isArrayType() || type == PVarbinary.INSTANCE; } + @SuppressWarnings("rawtypes") private static PDataType getGroupByDataType(Expression expression) { return IndexUtil.getIndexColumnDataType(expression.isNullable(), expression.getDataType()); } diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java index 81500776dbb..aa18e0e6cc8 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java @@ -40,6 +40,8 @@ import org.apache.phoenix.cache.ServerCacheClient.ServerCache; import org.apache.phoenix.compile.ColumnProjector; import org.apache.phoenix.compile.ExplainPlan; +import org.apache.phoenix.compile.ExplainPlanAttributes; +import org.apache.phoenix.compile.ExplainPlanAttributes.ExplainPlanAttributesBuilder; import org.apache.phoenix.compile.FromCompiler; import org.apache.phoenix.compile.QueryPlan; import org.apache.phoenix.compile.RowProjector; @@ -313,24 +315,62 @@ private Expression createKeyRangeExpression(Expression lhsExpression, Expression @Override public ExplainPlan getExplainPlan() throws SQLException { - // TODO : Support ExplainPlanAttributes for HashJoinPlan - List planSteps = Lists.newArrayList(delegate.getExplainPlan().getPlanSteps()); + ExplainPlan delegateExplainPlan = delegate.getExplainPlan(); + List planSteps = Lists.newArrayList(delegateExplainPlan.getPlanSteps()); + // Each hash/skip-scan sub-plan is recorded under subPlans. + ExplainPlanAttributes delegateAttributes = delegateExplainPlan.getPlanStepsAsAttributes(); + if (delegateAttributes == null) { + delegateAttributes = ExplainPlanAttributes.getDefaultExplainPlan(); + } + ExplainPlanAttributesBuilder builder = new ExplainPlanAttributesBuilder(delegateAttributes); + List subPlanAttributes = Lists.newArrayList(); int count = subPlans.length; for (int i = 0; i < count; i++) { planSteps.addAll(subPlans[i].getPreSteps(this)); + ExplainPlanAttributes subPlanAttribute = subPlans[i].getPreStepsAsAttributes(this); + if (subPlanAttribute != null) { + subPlanAttributes.add(subPlanAttribute); + } } for (int i = 0; i < count; i++) { - planSteps.addAll(subPlans[i].getPostSteps(this)); + List postSteps = subPlans[i].getPostSteps(this); + planSteps.addAll(postSteps); + for (String step : postSteps) { + String trimmed = step.trim(); + if (trimmed.startsWith("DYNAMIC SERVER FILTER BY")) { + builder.setDynamicServerFilter(trimmed); + } + } } if (joinInfo != null && joinInfo.getPostJoinFilterExpression() != null) { - planSteps.add( - " AFTER-JOIN SERVER FILTER BY " + joinInfo.getPostJoinFilterExpression().toString()); + String afterJoinFilter = + "AFTER-JOIN SERVER FILTER BY " + joinInfo.getPostJoinFilterExpression().toString(); + planSteps.add(" " + afterJoinFilter); + builder.setAfterJoinFilter(afterJoinFilter); } if (joinInfo != null && joinInfo.getLimit() != null) { planSteps.add(" JOIN-SCANNER " + joinInfo.getLimit() + " ROW LIMIT"); + builder.setJoinScannerLimit(joinInfo.getLimit().longValue()); } - return new ExplainPlan(planSteps); + if (!subPlanAttributes.isEmpty()) { + builder.setSubPlans(subPlanAttributes); + } + return new ExplainPlan(planSteps, builder.build()); + } + + /** + * Builds the explain-plan attributes for a hash-join subplan by taking the inner plan's + * attributes (when available) and stamping the supplied join header onto abstractExplainPlan. + */ + private static ExplainPlanAttributes subPlanAttributesWithHeader(QueryPlan plan, String header) + throws SQLException { + ExplainPlanAttributes innerAttributes = plan.getExplainPlan().getPlanStepsAsAttributes(); + ExplainPlanAttributesBuilder builder = + (innerAttributes != null && innerAttributes != ExplainPlanAttributes.getDefaultExplainPlan()) + ? new ExplainPlanAttributesBuilder(innerAttributes) + : new ExplainPlanAttributesBuilder(); + return builder.setAbstractExplainPlan(header).build(); } @Override @@ -430,6 +470,14 @@ public interface SubPlan { public List getPostSteps(HashJoinPlan parent) throws SQLException; + /** + * Returns the explain-plan attributes for this subplan, with its abstractExplainPlan. Returns + * null when no structured attributes are available for the inner plan. + */ + default ExplainPlanAttributes getPreStepsAsAttributes(HashJoinPlan parent) throws SQLException { + return null; + } + public QueryPlan getInnerPlan(); public boolean hasKeyRangeExpression(); @@ -446,6 +494,7 @@ public WhereClauseSubPlan(QueryPlan plan, SelectStatement select, boolean expect this.expectSingleRow = expectSingleRow; } + @SuppressWarnings("rawtypes") @Override public ServerCache execute(HashJoinPlan parent) throws SQLException { List values = Lists. newArrayList(); @@ -508,6 +557,12 @@ public List getPreSteps(HashJoinPlan parent) throws SQLException { return steps; } + @Override + public ExplainPlanAttributes getPreStepsAsAttributes(HashJoinPlan parent) throws SQLException { + String header = "EXECUTE " + (expectSingleRow ? "SINGLE" : "MULTIPLE") + "-ROW SUBQUERY"; + return subPlanAttributesWithHeader(plan, header); + } + @Override public List getPostSteps(HashJoinPlan parent) throws SQLException { return Collections. emptyList(); @@ -646,6 +701,21 @@ public List getPreSteps(HashJoinPlan parent) throws SQLException { return steps; } + @Override + public ExplainPlanAttributes getPreStepsAsAttributes(HashJoinPlan parent) throws SQLException { + boolean earlyEvaluation = parent.joinInfo.earlyEvaluation()[index]; + boolean skipMerge = parent.joinInfo.getSchemas()[index].getFieldCount() == 0; + String header; + if (hashExpressions != null) { + header = "PARALLEL " + parent.joinInfo.getJoinTypes()[index].toString().toUpperCase() + + "-JOIN TABLE " + index + (earlyEvaluation ? "" : "(DELAYED EVALUATION)") + + (skipMerge ? " (SKIP MERGE)" : ""); + } else { + header = "SKIP-SCAN-JOIN TABLE " + index; + } + return subPlanAttributesWithHeader(plan, header); + } + @Override public List getPostSteps(HashJoinPlan parent) throws SQLException { if (keyRangeLhsExpression == null) return Collections. emptyList(); diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java index 0b851902211..ea210c09a4c 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java @@ -196,6 +196,7 @@ public ExplainPlan getExplainPlan() throws SQLException { new ExplainPlanAttributesBuilder(lhsPlanAttributes); lhsPlanBuilder .setAbstractExplainPlan("SORT-MERGE-JOIN (" + joinType.toString().toUpperCase() + ")"); + lhsPlanBuilder.setSortMergeSkipMerge(rhsSchema.getFieldCount() == 0); for (String step : lhsPlanSteps) { steps.add(" " + step); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/AlterSessionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/AlterSessionIT.java index 1fe2d15e799..0f02a5b7a36 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/AlterSessionIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/AlterSessionIT.java @@ -17,20 +17,18 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import java.sql.Connection; import java.sql.DriverManager; -import java.sql.ResultSet; import java.sql.Statement; import java.util.Properties; import org.apache.hadoop.hbase.client.Consistency; import org.apache.phoenix.jdbc.PhoenixConnection; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -58,16 +56,12 @@ public void testUpdateConsistency() throws Exception { try (Connection conn = DriverManager.getConnection(getUrl(), props)) { Statement st = conn.createStatement(); st.execute("alter session set Consistency = 'timeline'"); - ResultSet rs = st.executeQuery("explain select * from " + tableName); assertEquals(Consistency.TIMELINE, conn.unwrap(PhoenixConnection.class).getConsistency()); - String queryPlan = QueryUtil.getExplainPlan(rs); - assertTrue(queryPlan.indexOf("TIMELINE") > 0); + assertPlan(conn, "select * from " + tableName).consistency("TIMELINE"); // turn off timeline read consistency st.execute("alter session set Consistency = 'strong'"); - rs = st.executeQuery("explain select * from " + tableName); - queryPlan = QueryUtil.getExplainPlan(rs); - assertTrue(queryPlan.indexOf("TIMELINE") < 0); + assertPlan(conn, "select * from " + tableName).consistency("STRONG"); } } @@ -77,10 +71,7 @@ public void testSetConsistencyInURL() throws Exception { try (Connection conn = DriverManager.getConnection( getUrl() + PhoenixRuntime.JDBC_PROTOCOL_TERMINATOR + "Consistency=TIMELINE", props)) { assertEquals(Consistency.TIMELINE, ((PhoenixConnection) conn).getConsistency()); - Statement st = conn.createStatement(); - ResultSet rs = st.executeQuery("explain select * from " + tableName); - String queryPlan = QueryUtil.getExplainPlan(rs); - assertTrue(queryPlan.indexOf("TIMELINE") > 0); + assertPlan(conn, "select * from " + tableName).consistency("TIMELINE"); conn.close(); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CDCQueryIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CDCQueryIT.java index 9a86354778d..8be2045a6f6 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CDCQueryIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CDCQueryIT.java @@ -25,10 +25,12 @@ import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.BEFORE_REBUILD_UNKNOWN_INDEX_ROW_COUNT; import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.REBUILT_INDEX_ROW_COUNT; import static org.apache.phoenix.query.QueryConstants.CDC_EVENT_TYPE; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.getExplainAttributes; import static org.apache.phoenix.schema.PTable.QualifierEncodingScheme.NON_ENCODED_QUALIFIERS; import static org.apache.phoenix.schema.PTable.QualifierEncodingScheme.TWO_BYTE_QUALIFIERS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.sql.Connection; @@ -53,6 +55,7 @@ import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.filter.Filter; import org.apache.hadoop.hbase.filter.FilterList; +import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants; import org.apache.phoenix.filter.DistinctPrefixFilter; import org.apache.phoenix.iterate.ResultIterator; @@ -66,7 +69,6 @@ import org.apache.phoenix.util.CDCUtil; import org.apache.phoenix.util.EnvironmentEdgeManager; import org.apache.phoenix.util.ManualEnvironmentEdge; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; @@ -147,10 +149,13 @@ public void beforeTest() { private void cdcIndexShouldNotBeUsedForDataTableQueries(Connection conn, String dataTableName, String cdcName) throws Exception { - ResultSet rs = conn.createStatement().executeQuery( - "EXPLAIN SELECT * FROM " + dataTableName + " WHERE PHOENIX_ROW_TIMESTAMP() < CURRENT_TIME()"); - String explainPlan = QueryUtil.getExplainPlan(rs); - assertFalse(explainPlan.contains(cdcName)); + String sql = + "SELECT * FROM " + dataTableName + " WHERE PHOENIX_ROW_TIMESTAMP() < CURRENT_TIME()"; + ExplainPlanAttributes attributes = getExplainAttributes(conn, sql); + String scannedTable = attributes.getTableName(); + assertNotNull(scannedTable); + assertFalse("CDC index " + cdcName + " should not be used, but plan scanned " + scannedTable, + scannedTable.contains(cdcName)); } private boolean isDistinctPrefixFilterIncludedInFilterList(FilterList filterList) { diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientHashAggregateIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientHashAggregateIT.java index 9bf4caebe7b..ab4333efa72 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientHashAggregateIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientHashAggregateIT.java @@ -17,9 +17,12 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.getExplainAttributes; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.sql.Connection; @@ -28,8 +31,8 @@ import java.sql.ResultSet; import java.sql.Statement; import java.util.Properties; +import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -113,13 +116,15 @@ private String getQuery(String table, boolean hash, boolean swap, boolean sort) private void verifyExplain(Connection conn, String table, boolean swap, boolean sort) throws Exception { - String query = "EXPLAIN " + getQuery(table, true, swap, sort); - Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery(query); - String plan = QueryUtil.getExplainPlan(rs); - rs.close(); - assertTrue(plan != null && plan.contains("CLIENT HASH AGGREGATE")); - assertTrue(plan != null && (sort == plan.contains("CLIENT SORTED BY"))); + String query = getQuery(table, true, swap, sort); + ExplainPlanAttributes attributes = getExplainAttributes(conn, query); + assertNotNull(attributes.getClientAggregate()); + assertTrue(attributes.getClientAggregate().contains("CLIENT HASH AGGREGATE")); + if (sort) { + assertNotNull(attributes.getClientSortedBy()); + } else { + assertNull(attributes.getClientSortedBy()); + } } private void verifyResults(Connection conn, String table, int c1, int c2, boolean swap, diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CsvBulkLoadToolIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CsvBulkLoadToolIT.java index abd6a212e9e..5fa004dd402 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CsvBulkLoadToolIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CsvBulkLoadToolIT.java @@ -18,6 +18,7 @@ package org.apache.phoenix.end2end; import static org.apache.phoenix.query.QueryServices.DATE_FORMAT_ATTRIB; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -45,7 +46,6 @@ import org.apache.phoenix.schema.PTableKey; import org.apache.phoenix.util.DateUtil; import org.apache.phoenix.util.PhoenixRuntime; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; @@ -521,8 +521,7 @@ public void testImportWithDifferentPhysicalName() throws Exception { assertEquals("FirstName 2", rs.getString(2)); String selectFromIndex = "SELECT FIRST_NAME FROM " + fullTableName + " where FIRST_NAME='FirstName 1'"; - rs = stmt.executeQuery("EXPLAIN " + selectFromIndex); - assertTrue(QueryUtil.getExplainPlan(rs).contains(indexTableName)); + assertPlan(conn, selectFromIndex).tableContains(indexTableName); rs = stmt.executeQuery(selectFromIndex); assertTrue(rs.next()); assertEquals("FirstName 1", rs.getString(1)); @@ -538,8 +537,7 @@ public void testImportWithDifferentPhysicalName() throws Exception { "--index-table", indexTableName, "--zookeeper", zkQuorum, "--corruptindexes" }); assertEquals(0, exitCode); selectFromIndex = "SELECT FIRST_NAME FROM " + fullTableName + " where FIRST_NAME='FirstName 3'"; - rs = stmt.executeQuery("EXPLAIN " + selectFromIndex); - assertTrue(QueryUtil.getExplainPlan(rs).contains(indexTableName)); + assertPlan(conn, selectFromIndex).tableContains(indexTableName); rs = stmt.executeQuery(selectFromIndex); assertTrue(rs.next()); assertEquals("FirstName 3", rs.getString(1)); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DeleteIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DeleteIT.java index c2a31842f9b..cdf03f76d4e 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DeleteIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DeleteIT.java @@ -17,6 +17,8 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertMutationPlan; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -52,16 +54,17 @@ import org.apache.hadoop.hbase.regionserver.RegionScanner; import org.apache.hadoop.hbase.util.Bytes; import org.apache.phoenix.compile.DeleteCompiler; +import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.compile.MutationPlan; import org.apache.phoenix.end2end.index.IndexTestUtil; import org.apache.phoenix.jdbc.PhoenixConnection; +import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.jdbc.PhoenixStatement; import org.apache.phoenix.parse.DeleteStatement; import org.apache.phoenix.parse.SQLParser; import org.apache.phoenix.query.ConnectionQueryServices; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -192,20 +195,25 @@ private static void assertIndexUsed(Connection conn, String query, String indexN private static void assertIndexUsed(Connection conn, String query, List binds, String indexName, boolean expectedToBeUsed, boolean local) throws SQLException { - PreparedStatement stmt = conn.prepareStatement("EXPLAIN " + query); + PhoenixPreparedStatement stmt = + conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class); for (int i = 0; i < binds.size(); i++) { stmt.setObject(i + 1, binds.get(i)); } - ResultSet rs = stmt.executeQuery(); - String explainPlan = QueryUtil.getExplainPlan(rs); + boolean isMutation = query.trim().toUpperCase().startsWith("DELETE") + || query.trim().toUpperCase().startsWith("UPSERT"); + ExplainPlanAttributes attributes = + (isMutation ? stmt.compileMutation().getExplainPlan() : stmt.optimizeQuery().getExplainPlan()) + .getPlanStepsAsAttributes(); // It's very difficult currently to check if a local index is being used // This check is brittle as it checks that the index ID appears in the range scan - // TODO: surface QueryPlan from MutationPlan if (local) { - assertEquals(expectedToBeUsed, - explainPlan.contains(indexName + " [1]") || explainPlan.contains(indexName + " [1,")); + String keyRanges = attributes.getKeyRanges(); + boolean used = indexName.equals(attributes.getTableName()) && keyRanges != null + && (keyRanges.startsWith(" [1]") || keyRanges.startsWith(" [1,")); + assertEquals(expectedToBeUsed, used); } else { - assertEquals(expectedToBeUsed, explainPlan.contains(" SCAN OVER " + indexName)); + assertEquals(expectedToBeUsed, indexName.equals(attributes.getTableName())); } } @@ -499,16 +507,18 @@ public void testPointDeleteRowFromTableWithImmutableIndex(boolean localIndex, if (!autoCommit) { con.commit(); } - psDelete = con.prepareStatement("EXPLAIN " + dml); - psDelete.setString(1, "AA"); - psDelete.setString(2, "BB"); - psDelete.setString(3, "CC"); - psDelete.setDate(4, date); - String explainPlan = QueryUtil.getExplainPlan(psDelete.executeQuery()); + PhoenixPreparedStatement explainStmt = + con.prepareStatement(dml).unwrap(PhoenixPreparedStatement.class); + explainStmt.setString(1, "AA"); + explainStmt.setString(2, "BB"); + explainStmt.setString(3, "CC"); + explainStmt.setDate(4, date); + ExplainPlanAttributes attributes = + explainStmt.compileMutation().getExplainPlan().getPlanStepsAsAttributes(); if (addNonPKIndex) { - assertNotEquals("DELETE SINGLE ROW", explainPlan); + assertNotEquals("DELETE SINGLE ROW", attributes.getAbstractExplainPlan()); } else { - assertEquals("DELETE SINGLE ROW", explainPlan); + assertPlan(attributes).abstractExplainPlan("DELETE SINGLE ROW"); } assertDeleted(con, tableName, indexName1, indexName2, indexName3); @@ -927,26 +937,20 @@ public void testDeleteFilterWithMultipleIndexes() throws Exception { } try (Connection conn = DriverManager.getConnection(getUrl(), props)) { conn.setAutoCommit(true); - try (Statement statement = conn.createStatement()) { - ResultSet rs = statement.executeQuery("EXPLAIN " + delete); - String explainPlan = QueryUtil.getExplainPlan(rs); - // Verify index is used for the delete query - IndexToolIT.assertExplainPlan(false, explainPlan, tableName, indexName1); - } + // Verify index is used for the delete query + assertMutationPlan(conn, delete).scanType("RANGE SCAN").table(indexName1); // Created the second index try (Statement statement = conn.createStatement()) { statement.execute(indexDdl2); } + // Verify index is used for the delete query + assertMutationPlan(conn, delete).scanType("RANGE SCAN").table(indexName1); try (Statement statement = conn.createStatement()) { - ResultSet rs = statement.executeQuery("EXPLAIN " + delete); - String explainPlan = QueryUtil.getExplainPlan(rs); - // Verify index is used for the delete query - IndexToolIT.assertExplainPlan(false, explainPlan, tableName, indexName1); statement.executeUpdate(delete); // Count the number of rows String query = "SELECT COUNT(*) from " + tableName; // There should be no rows on the data table - rs = conn.createStatement().executeQuery(query); + ResultSet rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals(0, rs.getInt(1)); query = "SELECT COUNT(*) from " + indexName1; diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DistinctPrefixFilterIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DistinctPrefixFilterIT.java index bebf007fb86..e1eedac0e77 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DistinctPrefixFilterIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DistinctPrefixFilterIT.java @@ -17,10 +17,13 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.getExplainAttributes; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.sql.Connection; @@ -29,8 +32,8 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; +import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -232,8 +235,12 @@ private void testCommonPlans(String testTable, String contains) throws Exception } private void testPlan(String query, boolean optimizable) throws Exception { - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + query); - assertEquals(optimizable, QueryUtil.getExplainPlan(rs).contains(PREFIX)); + ExplainPlanAttributes attributes = getExplainAttributes(conn, query); + if (optimizable) { + assertNotNull(attributes.getServerDistinctFilter()); + } else { + assertNull(attributes.getServerDistinctFilter()); + } } @Test diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/EmptyColumnIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/EmptyColumnIT.java index 5e0033bb4f9..45ba6fc22db 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/EmptyColumnIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/EmptyColumnIT.java @@ -29,10 +29,12 @@ import static org.apache.phoenix.query.PhoenixTestBuilder.SchemaBuilder.TableOptions; import static org.apache.phoenix.query.PhoenixTestBuilder.SchemaBuilder.TenantViewIndexOptions; import static org.apache.phoenix.query.PhoenixTestBuilder.SchemaBuilder.TenantViewOptions; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.getExplainAttributes; import static org.apache.phoenix.util.PhoenixRuntime.TENANT_ID_ATTRIB; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotNull; import java.io.IOException; import java.sql.Connection; @@ -50,7 +52,7 @@ import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.util.Bytes; -import org.apache.phoenix.jdbc.PhoenixResultSet; +import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.query.PhoenixTestBuilder.BasicDataWriter; import org.apache.phoenix.query.PhoenixTestBuilder.DataSupplier; import org.apache.phoenix.query.PhoenixTestBuilder.DataWriter; @@ -60,7 +62,6 @@ import org.apache.phoenix.util.EnvironmentEdgeManager; import org.apache.phoenix.util.IndexUtil; import org.apache.phoenix.util.ManualEnvironmentEdge; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; import org.junit.Ignore; @@ -746,11 +747,10 @@ public void testMaskingWithDistinctPrefixFilter() throws Exception { injectEdge.setValue(EnvironmentEdgeManager.currentTimeMillis() + ttl * 1000 + 2); EnvironmentEdgeManager.injectEdge(injectEdge); String distinctQuery = "SELECT DISTINCT id1 FROM " + dataTableName; + ExplainPlanAttributes attributes = getExplainAttributes(conn, distinctQuery); + assertPlan(attributes).serverWhereFilter("SERVER FILTER BY EMPTY COLUMN ONLY"); + assertNotNull(attributes.getServerDistinctFilter()); try (ResultSet rs = conn.createStatement().executeQuery(distinctQuery)) { - PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class); - String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(explainPlan.contains("SERVER FILTER BY EMPTY COLUMN ONLY")); - assertTrue(explainPlan.contains("SERVER DISTINCT PREFIX FILTER OVER")); // all the rows should have been masked assertFalse(rs.next()); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ExplainPlanWithStatsDisabledIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ExplainPlanWithStatsDisabledIT.java index 2565a3ba80d..7b771c8e1d0 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ExplainPlanWithStatsDisabledIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ExplainPlanWithStatsDisabledIT.java @@ -18,6 +18,7 @@ package org.apache.phoenix.end2end; import static org.apache.phoenix.end2end.ExplainPlanWithStatsEnabledIT.getByteRowEstimates; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -34,7 +35,6 @@ import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -256,12 +256,10 @@ public void testDescTimestampAtBoundary() throws Exception { + " ) IMMUTABLE_ROWS=true\n" + " ,SALT_BUCKETS=20"); String query = "select * from foo where a = 'a' and b >= timestamp '2016-01-28 00:00:00' and b < timestamp '2016-01-29 00:00:00'"; - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + query); - String queryPlan = QueryUtil.getExplainPlan(rs); - assertEquals( - "CLIENT PARALLEL 20-WAY RANGE SCAN OVER FOO [X'00','a',~'2016-01-28 23:59:59.999'] - [X'13','a',~'2016-01-28 00:00:00.000']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT", - queryPlan); + assertPlan(conn, query).scanType("RANGE SCAN").table("FOO") + .keyRanges( + " [X'00','a',~'2016-01-28 23:59:59.999'] -" + " [X'13','a',~'2016-01-28 00:00:00.000']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); } } @@ -278,11 +276,10 @@ public void testUseOfRoundRobinIteratorSurfaced() throws Exception { + " ) IMMUTABLE_ROWS=true\n" + " ,SALT_BUCKETS=20"); String query = "select * from " + tableName + " where a = 'a' and b >= timestamp '2016-01-28 00:00:00' and b < timestamp '2016-01-29 00:00:00'"; - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + query); - String queryPlan = QueryUtil.getExplainPlan(rs); - assertEquals("CLIENT PARALLEL 20-WAY ROUND ROBIN RANGE SCAN OVER " + tableName - + " [X'00','a',~'2016-01-28 23:59:59.999'] - [X'13','a',~'2016-01-28 00:00:00.000']\n" - + " SERVER FILTER BY FIRST KEY ONLY", queryPlan); + assertPlan(conn, query).useRoundRobinIterator(true).scanType("RANGE SCAN").table(tableName) + .keyRanges( + " [X'00','a',~'2016-01-28 23:59:59.999'] -" + " [X'13','a',~'2016-01-28 00:00:00.000']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); } } @@ -346,60 +343,48 @@ public void testRangeScanWithMetadataLookup() throws Exception { ResultSet rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals(53, rs.getInt(1)); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - String queryPlan = QueryUtil.getExplainPlan(rs); - assertEquals( - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + tableName + " [*] - ['b']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER AGGREGATE INTO SINGLE ROW", - queryPlan); ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) .optimizeQuery().getExplainPlan(); ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); + assertPlan(planAttributes).scanType("RANGE SCAN").table(tableName).keyRanges(" [*] - ['b']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW"); assertEquals(2, planAttributes.getNumRegionLocationLookups()); query = "select count(*) from " + tableName + " where PK1 <= 'cd'"; rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals(128, rs.getInt(1)); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - queryPlan = QueryUtil.getExplainPlan(rs); - assertEquals( - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + tableName + " [*] - ['cd']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER AGGREGATE INTO SINGLE ROW", - queryPlan); plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() .getExplainPlan(); planAttributes = plan.getPlanStepsAsAttributes(); + assertPlan(planAttributes).scanType("RANGE SCAN").table(tableName).keyRanges(" [*] - ['cd']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW"); assertEquals(3, planAttributes.getNumRegionLocationLookups()); query = "select count(*) from " + tableName + " where PK1 LIKE 'ef%'"; rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals(25, rs.getInt(1)); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - queryPlan = QueryUtil.getExplainPlan(rs); - assertEquals( - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + tableName + " ['ef'] - ['eg']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER AGGREGATE INTO SINGLE ROW", - queryPlan); plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() .getExplainPlan(); planAttributes = plan.getPlanStepsAsAttributes(); + assertPlan(planAttributes).scanType("RANGE SCAN").table(tableName) + .keyRanges(" ['ef'] - ['eg']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW"); assertEquals(1, planAttributes.getNumRegionLocationLookups()); query = "select count(*) from " + tableName + " where PK1 > 'de'"; rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals(75, rs.getInt(1)); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - queryPlan = QueryUtil.getExplainPlan(rs); - assertEquals( - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + tableName + " ['de'] - [*]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER AGGREGATE INTO SINGLE ROW", - queryPlan); plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() .getExplainPlan(); planAttributes = plan.getPlanStepsAsAttributes(); + assertPlan(planAttributes).scanType("RANGE SCAN").table(tableName).keyRanges(" ['de'] - [*]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW"); assertEquals(1, planAttributes.getNumRegionLocationLookups()); } } @@ -494,28 +479,23 @@ public void testMultiTenantWithMetadataLookup() throws Exception { assertTrue(rs.next()); assertEquals(50, rs.getInt(1)); - rs = tenantConn.createStatement().executeQuery("EXPLAIN " + query); - String queryPlan = QueryUtil.getExplainPlan(rs); - assertEquals( - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + tableName + " ['ab12']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER AGGREGATE INTO SINGLE ROW", - queryPlan); ExplainPlan plan = tenantConn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) .optimizeQuery().getExplainPlan(); ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); + assertPlan(planAttributes).scanType("RANGE SCAN").table(tableName).keyRanges(" ['ab12']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW"); assertEquals(1, planAttributes.getNumRegionLocationLookups()); } try (Connection tenantConn = getTenantConnection("cd12")) { String query = "select * from " + view03 + " order by col2"; - ResultSet rs = tenantConn.createStatement().executeQuery("EXPLAIN " + query); - String queryPlan = QueryUtil.getExplainPlan(rs); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + tableName + " ['cd12']\n" - + " SERVER SORTED BY [COL2]\n" + "CLIENT MERGE SORT", queryPlan); ExplainPlan plan = tenantConn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) .optimizeQuery().getExplainPlan(); ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); + assertPlan(planAttributes).scanType("RANGE SCAN").table(tableName).keyRanges(" ['cd12']") + .serverSortedBy("[COL2]").clientSortAlgo("CLIENT MERGE SORT"); assertEquals(1, planAttributes.getNumRegionLocationLookups()); } @@ -528,13 +508,11 @@ public void testMultiTenantWithMetadataLookup() throws Exception { } assertEquals(25, c); - rs = tenantConn.createStatement().executeQuery("EXPLAIN " + query); - String queryPlan = QueryUtil.getExplainPlan(rs); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + tableName + " ['de12']\n" - + " SERVER FILTER BY COL1 = 'col101'", queryPlan); ExplainPlan plan = tenantConn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) .optimizeQuery().getExplainPlan(); ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); + assertPlan(planAttributes).scanType("RANGE SCAN").table(tableName).keyRanges(" ['de12']") + .serverWhereFilter("SERVER FILTER BY COL1 = 'col101'"); assertEquals(1, planAttributes.getNumRegionLocationLookups()); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexBuildTimestampIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexBuildTimestampIT.java index e7ab684e8ba..3491b0f2a1b 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexBuildTimestampIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexBuildTimestampIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -41,8 +42,8 @@ import org.apache.phoenix.schema.PTable; import org.apache.phoenix.util.EnvironmentEdge; import org.apache.phoenix.util.EnvironmentEdgeManager; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; +import org.apache.phoenix.util.SchemaUtil; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -136,11 +137,13 @@ public static synchronized Collection data() { public static void assertExplainPlan(Connection conn, boolean localIndex, String selectSql, String dataTableFullName, String indexTableFullName) throws SQLException { - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql); - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - - IndexToolIT.assertExplainPlan(localIndex, actualExplainPlan, dataTableFullName, - indexTableFullName); + // Verify the query is served by a RANGE SCAN over the index table. For a local index the + // scanned name carries the data table in parentheses, e.g. IDX(DATA). + String expectedTable = localIndex + ? SchemaUtil.normalizeIdentifier(indexTableFullName) + "(" + + SchemaUtil.normalizeIdentifier(dataTableFullName) + ")" + : SchemaUtil.normalizeIdentifier(indexTableFullName); + assertPlan(conn, selectSql).scanType("RANGE SCAN").tableContains(expectedTable); } private class MyClock extends EnvironmentEdge { diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolIT.java index 23f083f0964..948607a24ff 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolIT.java @@ -38,6 +38,8 @@ import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.BEFORE_REPAIR_EXTRA_VERIFIED_INDEX_ROW_COUNT; import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.REBUILT_INDEX_ROW_COUNT; import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.SCANNED_DATA_ROW_COUNT; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.getExplainAttributes; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -104,7 +106,6 @@ import org.apache.phoenix.util.IndexScrutiny; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; @@ -779,16 +780,18 @@ public void testCaseSensitiveNames() throws Exception { "SELECT ID FROM %s WHERE LPAD(UPPER(NAME, 'en_US'),8,'x')||'_xyz' = 'xxUNAME2_xyz'", qDataTableName); - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql); - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - // assert we are pulling from data table. - assertEquals(String.format( - "CLIENT PARALLEL 1-WAY FULL SCAN OVER %s\n" - + " SERVER FILTER BY (LPAD(UPPER(NAME, 'en_US'), 8, 'x') || '_xyz') = 'xxUNAME2_xyz'", - dataTableNameForExplain), actualExplainPlan.replaceAll(":", ".")); - - rs = stmt1.executeQuery(selectSql); + ExplainPlanAttributes dataAttributes = getExplainAttributes(conn, selectSql); + assertPlan(dataAttributes).scanType("FULL SCAN").serverWhereFilter( + "SERVER FILTER BY (LPAD(UPPER(NAME, 'en_US'), 8, 'x') || '_xyz') = 'xxUNAME2_xyz'"); + // Table names are case-sensitive and (under namespace mapping) carry a ':' separator, so + // compare against the normalized ('.') form rather than via tableContains(). + String dataScanTable = dataAttributes.getTableName() == null + ? null + : dataAttributes.getTableName().replaceAll(":", "."); + assertEquals(dataTableNameForExplain, dataScanTable); + + ResultSet rs = stmt1.executeQuery(selectSql); assertTrue(rs.next()); assertEquals(2, rs.getInt(1)); assertFalse(rs.next()); @@ -803,12 +806,19 @@ public void testCaseSensitiveNames() throws Exception { conn.commit(); // assert we are pulling from index table. - rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql); - actualExplainPlan = QueryUtil.getExplainPlan(rs); - // Because the explain plan doesn't include double-quotes around case-sensitive table names, - // we need to tell assertExplainPlan to not normalize our table names. - assertExplainPlan(localIndex, actualExplainPlan, dataTableNameForExplain, - indexTableNameForExplain, false); + ExplainPlanAttributes indexAttributes = getExplainAttributes(conn, selectSql); + assertPlan(indexAttributes).scanType("RANGE SCAN"); + // The explain plan doesn't include double-quotes around case-sensitive table names and (under + // namespace mapping) uses a ':' separator, so compare against the non-normalized ('.') form. + String indexScanTable = indexAttributes.getTableName() == null + ? null + : indexAttributes.getTableName().replaceAll(":", "."); + String expectedIndexTable = localIndex + ? indexTableNameForExplain + "(" + dataTableNameForExplain + ")" + : indexTableNameForExplain; + assertTrue( + "expected scanned table <" + indexScanTable + "> to use index <" + expectedIndexTable + ">", + indexScanTable != null && indexScanTable.contains(expectedIndexTable)); rs = conn.createStatement().executeQuery(selectSql); assertTrue(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/LogicalTableNameExtendedIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/LogicalTableNameExtendedIT.java index 4b8496d9cc0..5277863842c 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/LogicalTableNameExtendedIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/LogicalTableNameExtendedIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; @@ -232,8 +233,7 @@ public void testHint() throws Exception { } String indexSelect = "SELECT /*+ INDEX(" + tableName + " " + hintedIndex + ")*/ V1,V2,V3 FROM " + tableName; - rs = conn.createStatement().executeQuery("EXPLAIN " + indexSelect); - assertEquals(true, QueryUtil.getExplainPlan(rs).contains(hintedIndex)); + assertPlan(conn, indexSelect).tableContains(hintedIndex); rs = conn.createStatement().executeQuery(indexSelect); assertEquals(true, rs.next()); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/LogicalTableNameIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/LogicalTableNameIT.java index d8f9bec76b4..49c9c6b3b60 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/LogicalTableNameIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/LogicalTableNameIT.java @@ -19,6 +19,7 @@ import static org.apache.phoenix.mapreduce.index.PhoenixScrutinyJobCounters.INVALID_ROW_COUNT; import static org.apache.phoenix.mapreduce.index.PhoenixScrutinyJobCounters.VALID_ROW_COUNT; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.MetaDataUtil.VIEW_INDEX_TABLE_PREFIX; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.*; @@ -42,7 +43,6 @@ import org.apache.phoenix.query.QueryConstants; import org.apache.phoenix.schema.PTable; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.SchemaUtil; import org.junit.BeforeClass; import org.junit.Ignore; @@ -50,16 +50,12 @@ import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.phoenix.thirdparty.com.google.common.collect.Lists; @RunWith(Parameterized.class) @Category(NeedsOwnMiniClusterTest.class) public class LogicalTableNameIT extends LogicalTableNameBaseIT { - private static final Logger LOGGER = LoggerFactory.getLogger(LogicalTableNameIT.class); - protected boolean createChildAfterRename; private boolean immutable; private Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); @@ -213,8 +209,6 @@ public void testUpdatePhysicalIndexTableName_runScrutiny() throws Exception { String schemaName = "S_" + generateUniqueName(); String tableName = "TBL_" + generateUniqueName(); String indexName = "IDX_" + generateUniqueName(); - String fullTableName = SchemaUtil.getTableName(schemaName, tableName); - String fullIndexName = SchemaUtil.getTableName(schemaName, indexName); try (Connection conn = getConnection(props)) { try (Connection conn2 = getConnection(props)) { test_IndexTableChange(conn, conn2, schemaName, tableName, indexName, @@ -411,7 +405,6 @@ public void testWith2LevelViewsBaseTablePhysicalNameChange() throws Exception { String upsert = "UPSERT INTO " + fullLevel2ViewName + " (PK1, V1, VIEW_COL1, CHV2) VALUES (?,?,?,?)"; PreparedStatement upsertStmt = conn.prepareStatement(upsert); - ArrayList row = new ArrayList<>(); upsertStmt.setString(1, "PK10"); upsertStmt.setString(2, "V10"); upsertStmt.setString(3, "VIEW_COL1_10"); @@ -426,8 +419,7 @@ public void testWith2LevelViewsBaseTablePhysicalNameChange() throws Exception { String indexSelect = "SELECT chv2, V1, VIEW_COL1 FROM " + fullLevel2ViewName + " WHERE chv2='CHV210'"; - rs = conn2.createStatement().executeQuery("EXPLAIN " + indexSelect); - assertEquals(true, QueryUtil.getExplainPlan(rs).contains(VIEW_INDEX_TABLE_PREFIX)); + assertPlan(conn2, indexSelect).tableContains(VIEW_INDEX_TABLE_PREFIX); rs = conn2.createStatement().executeQuery(indexSelect); assertEquals(true, rs.next()); assertEquals(false, rs.next()); @@ -476,8 +468,7 @@ public void testHashJoin() throws Exception { } Object[] arr = HashJoinGlobalIndexIT.data().toArray(); String[] indexDDL = ((String[][]) arr[0])[0]; - String[] plans = ((String[][]) arr[0])[1]; - HashJoinGlobalIndexIT hjgit = new HashJoinGlobalIndexIT(indexDDL, plans); + HashJoinGlobalIndexIT hjgit = new HashJoinGlobalIndexIT(indexDDL); hjgit.createSchema(); hjgit.testInnerJoin(false); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/MaxLookbackExtendedIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MaxLookbackExtendedIT.java index 38a2c113db2..4df082dd5a8 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/MaxLookbackExtendedIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MaxLookbackExtendedIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.assertRawCellCount; import static org.apache.phoenix.util.TestUtil.assertRawRowCount; import static org.apache.phoenix.util.TestUtil.assertRowExistsAtSCN; @@ -52,7 +53,6 @@ import org.apache.phoenix.util.EnvironmentEdgeManager; import org.apache.phoenix.util.ManualEnvironmentEdge; import org.apache.phoenix.util.PhoenixRuntime; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; @@ -685,8 +685,8 @@ private void createIndex(String dataTableName, String indexTableName, int indexV public static void assertExplainPlan(Connection conn, String selectSql, String dataTableFullName, String indexTableFullName) throws SQLException { - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql); - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - IndexToolIT.assertExplainPlan(false, actualExplainPlan, dataTableFullName, indexTableFullName); + // Verify the query is served by a RANGE SCAN over the (global) index table. + assertPlan(conn, selectSql).scanType("RANGE SCAN") + .tableContains(SchemaUtil.normalizeIdentifier(indexTableFullName)); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/MaxLookbackIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MaxLookbackIT.java index 881a1198a34..fc1baa642e6 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/MaxLookbackIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MaxLookbackIT.java @@ -18,6 +18,7 @@ package org.apache.phoenix.end2end; import static org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants.PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.assertRawCellCount; import static org.apache.phoenix.util.TestUtil.assertRawRowCount; import static org.apache.phoenix.util.TestUtil.assertRowExistsAtSCN; @@ -28,7 +29,6 @@ import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; -import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Map; @@ -44,8 +44,8 @@ import org.apache.phoenix.util.EnvironmentEdgeManager; import org.apache.phoenix.util.ManualEnvironmentEdge; import org.apache.phoenix.util.PhoenixRuntime; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; +import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; import org.junit.After; import org.junit.Assert; @@ -390,9 +390,9 @@ private void createIndex(String dataTableName, String indexTableName, int indexV public static void assertExplainPlan(Connection conn, String selectSql, String dataTableFullName, String indexTableFullName) throws SQLException { - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql); - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - IndexToolIT.assertExplainPlan(false, actualExplainPlan, dataTableFullName, indexTableFullName); + // Verify the query is served by a RANGE SCAN over the (global) index table. + assertPlan(conn, selectSql).scanType("RANGE SCAN") + .tableContains(SchemaUtil.normalizeIdentifier(indexTableFullName)); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/OnDuplicateKeyIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/OnDuplicateKeyIT.java index 754933b5103..c004ccb40fd 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/OnDuplicateKeyIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/OnDuplicateKeyIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -48,7 +49,7 @@ import org.apache.phoenix.util.EnvironmentEdgeManager; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; +import org.apache.phoenix.util.SchemaUtil; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -561,10 +562,13 @@ public void run() { ResultSet rs; String selectSql = "SELECT * FROM " + tableName + " WHERE counter1 >= 0"; if (isIndexCreated) { - rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql); - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - IndexToolIT.assertExplainPlan(this.indexDDL.contains("local"), actualExplainPlan, tableName, - tableName + "_IDX"); + // Verify the query is served by a RANGE SCAN over the index table. For a local index the + // scanned name carries the data table in parentheses, e.g. IDX(DATA). + boolean localIndex = this.indexDDL.contains("local"); + String index = SchemaUtil.normalizeIdentifier(tableName + "_IDX"); + String expectedTable = + localIndex ? index + "(" + SchemaUtil.normalizeIdentifier(tableName) + ")" : index; + assertPlan(conn, selectSql).scanType("RANGE SCAN").tableContains(expectedTable); } rs = conn.createStatement().executeQuery(selectSql); assertTrue(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ProjectArrayElemAfterHashJoinIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ProjectArrayElemAfterHashJoinIT.java index 40545af925d..0b882f3a16c 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ProjectArrayElemAfterHashJoinIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ProjectArrayElemAfterHashJoinIT.java @@ -17,6 +17,8 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.getExplainAttributes; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -28,8 +30,8 @@ import java.sql.ResultSet; import java.sql.Statement; import java.util.Properties; +import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -109,18 +111,12 @@ private String getQuery(String table, boolean fullArray, boolean hashJoin) { private void verifyExplain(Connection conn, String table, boolean fullArray, boolean hashJoin) throws Exception { - String query = "EXPLAIN " + getQuery(table, fullArray, hashJoin); - Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery(query); - - try { - String plan = QueryUtil.getExplainPlan(rs); - assertTrue(plan != null); - assertTrue(fullArray || plan.contains("SERVER ARRAY ELEMENT PROJECTION")); - assertTrue(hashJoin == plan.contains("JOIN")); - } finally { - rs.close(); + String query = getQuery(table, fullArray, hashJoin); + ExplainPlanAttributes attributes = getExplainAttributes(conn, query); + if (!fullArray) { + assertPlan(attributes).serverArrayElementProjection(true); } + assertPlan(attributes).subPlanCount(hashJoin ? 1 : 0); } private void verifyResults(Connection conn, String table, boolean fullArray, boolean hashJoin) diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithTableSampleIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithTableSampleIT.java index 045e20aabfc..ddbf213c45c 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithTableSampleIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithTableSampleIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -26,12 +27,8 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.Properties; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.exception.PhoenixParserException; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.TestUtil; import org.junit.Before; import org.junit.Test; @@ -56,7 +53,7 @@ public void testSingleQueryWrongSyntax() throws Exception { prepareTableWithValues(conn, 100); String query = "SELECT i1, i2 FROM " + tableName + " tablesample 15 "; - ResultSet rs = conn.createStatement().executeQuery(query); + conn.createStatement().executeQuery(query); } finally { conn.close(); } @@ -70,7 +67,7 @@ public void testSingleQueryWrongSamplingRate() throws Exception { prepareTableWithValues(conn, 100); String query = "SELECT i1, i2 FROM " + tableName + " tablesample (175) "; - ResultSet rs = conn.createStatement().executeQuery(query); + conn.createStatement().executeQuery(query); } finally { conn.close(); } @@ -214,14 +211,8 @@ public void testExplainSingleQuery() throws Exception { try (Connection conn = DriverManager.getConnection(getUrl(), props)) { prepareTableWithValues(conn, 100); String query = "SELECT i1, i2 FROM " + tableName + " tablesample (45) "; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(0.45D, explainPlanAttributes.getSamplingRate(), 0D); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); + assertPlan(conn, query).iteratorType("PARALLEL").scanType("FULL SCAN").samplingRate(0.45d) + .table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); } } @@ -231,17 +222,14 @@ public void testExplainSingleQueryWithUnion() throws Exception { Connection conn = DriverManager.getConnection(getUrl(), props); try { prepareTableWithValues(conn, 100); - String query = "EXPLAIN SELECT * FROM " + tableName - + " tablesample (100) where i1<2 union all SELECT * FROM " + tableName - + " tablesample (2) where i2<6000"; - ResultSet rs = conn.createStatement().executeQuery(query); - - assertEquals( - "UNION ALL OVER 2 QUERIES\n" + " CLIENT PARALLEL 1-WAY 1.0-SAMPLED RANGE SCAN OVER " - + tableName + " [*] - [2]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " CLIENT PARALLEL 1-WAY 0.02-SAMPLED FULL SCAN OVER " + tableName + "\n" - + " SERVER FILTER BY FIRST KEY ONLY AND I2 < 6000", - QueryUtil.getExplainPlan(rs)); + String query = + "SELECT * FROM " + tableName + " tablesample (100) where i1<2 union all SELECT * FROM " + + tableName + " tablesample (2) where i2<6000"; + assertPlan(conn, query).abstractExplainPlan("UNION ALL OVER 2 QUERIES").scanType("RANGE SCAN") + .table(tableName).keyRanges(" [*] - [2]").samplingRate(1.0d) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").rhs().scanType("FULL SCAN") + .table(tableName).samplingRate(0.02d) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND I2 < 6000").end(); } finally { conn.close(); } @@ -253,17 +241,16 @@ public void testExplainSingleQueryWithJoins() throws Exception { Connection conn = DriverManager.getConnection(getUrl(), props); try { prepareTableWithValues(conn, 100); - String query = "EXPLAIN SELECT count(*) FROM " + tableName + " as A tablesample (45), " + String query = "SELECT count(*) FROM " + tableName + " as A tablesample (45), " + joinedTableName + " as B tablesample (75) where A.i1=B.i1"; - System.out.println(query); - ResultSet rs = conn.createStatement().executeQuery(query); - assertEquals("CLIENT PARALLEL 1-WAY 0.45-SAMPLED FULL SCAN OVER " + tableName + "\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER AGGREGATE INTO SINGLE ROW\n" - + " PARALLEL INNER-JOIN TABLE 0 (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY 0.75-SAMPLED FULL SCAN OVER " + joinedTableName + "\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " DYNAMIC SERVER FILTER BY A.I1 IN (B.I1)", QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).scanType("FULL SCAN").table(tableName).samplingRate(0.45d) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY A.I1 IN (B.I1)").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 (SKIP MERGE)").scanType("FULL SCAN") + .table(joinedTableName).samplingRate(0.75d) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); } finally { conn.close(); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/RTrimFunctionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/RTrimFunctionIT.java index b15d4d424e2..e0f04eeab6c 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/RTrimFunctionIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/RTrimFunctionIT.java @@ -17,8 +17,8 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; -import static org.junit.Assert.assertTrue; import java.sql.Connection; import java.sql.DriverManager; @@ -28,7 +28,6 @@ import java.util.Properties; import org.apache.phoenix.schema.SortOrder; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -66,8 +65,7 @@ private void testWithFixedLengthPK(SortOrder sortOrder, List expectedRes ResultSet rs = conn.createStatement().executeQuery(query); assertValueEqualsResultSet(rs, expectedResults); - rs = conn.createStatement().executeQuery("explain " + query); - assertTrue(QueryUtil.getExplainPlan(rs).contains("RANGE SCAN OVER " + tableName)); + assertPlan(conn, query).scanType("RANGE SCAN").table(tableName); conn.close(); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorOffsetIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorOffsetIT.java index 1df20890569..590c793db99 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorOffsetIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorOffsetIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -41,7 +42,6 @@ import org.apache.phoenix.schema.RowValueConstructorOffsetNotCoercibleException; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -1079,16 +1079,8 @@ public void testIndexMultiColumnsIndexedFixedLengthNullLiteralsRVCOffset() throw @Test public void testOffsetExplain() throws SQLException { - String sql = - "EXPLAIN SELECT * FROM " + DATA_TABLE_NAME + " LIMIT 2 OFFSET (k1,k2,k3)=(2, 3, 2)"; - try (Statement statement = conn.createStatement(); ResultSet rs = statement.executeQuery(sql)) { - StringBuilder explainStringBuilder = new StringBuilder(); - while (rs.next()) { - String explain = rs.getString(1); - explainStringBuilder.append(explain); - } - assertTrue(explainStringBuilder.toString().contains("With RVC Offset")); - } + String sql = "SELECT * FROM " + DATA_TABLE_NAME + " LIMIT 2 OFFSET (k1,k2,k3)=(2, 3, 2)"; + assertPlan(conn, sql).hexStringRVCOffset("0x828383"); } @Test @@ -1271,12 +1263,8 @@ public void testRVCOffsetWithNotApplicableIndexHint() throws Exception { "SELECT /*+ INDEX(%s %s)*/ %s FROM %s " + "WHERE t_id = 'b' AND k1 = 2 AND k2 = 3 OFFSET (%s)=('a', 1, 2)", TABLE_NAME, INDEX_NAME, TABLE_ROW_KEY, TABLE_NAME, TABLE_ROW_KEY); - try (Statement statement = conn.createStatement()) { - ResultSet rs = statement.executeQuery("EXPLAIN " + sql); - String actualQueryPlan = QueryUtil.getExplainPlan(rs); - // As hinted plan is not applicable so use data plan which is point lookup - assertTrue(actualQueryPlan.contains("POINT LOOKUP ON 1 KEY OVER " + TABLE_NAME)); - } + // As hinted plan is not applicable so use data plan which is point lookup + assertPlan(conn, sql).scanType("POINT LOOKUP ON 1 KEY").table(TABLE_NAME); } @Test diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ServerPagingIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ServerPagingIT.java index b86793badbe..9072ae302b4 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ServerPagingIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ServerPagingIT.java @@ -17,11 +17,11 @@ */ package org.apache.phoenix.end2end; -import static org.apache.phoenix.end2end.index.GlobalIndexCheckerIT.assertExplainPlan; import static org.apache.phoenix.end2end.index.GlobalIndexCheckerIT.assertExplainPlanWithLimit; import static org.apache.phoenix.end2end.index.GlobalIndexCheckerIT.commitWithException; import static org.apache.phoenix.monitoring.GlobalClientMetrics.GLOBAL_PAGED_ROWS_COUNTER; import static org.apache.phoenix.query.QueryServices.USE_BLOOMFILTER_FOR_MULTIKEY_POINTLOOKUP; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -658,7 +658,7 @@ public void testUncoveredQuery() throws Exception { selectSql = "SELECT count(val3) from " + dataTableName + " where val1 > '0' GROUP BY val1"; // Verify that we will read from the index table - assertExplainPlan(conn, selectSql, dataTableName, indexTableName); + assertPlan(conn, selectSql).scanType("RANGE SCAN").tableContains(indexTableName); rs = conn.createStatement().executeQuery(selectSql); assertTrue(rs.next()); assertEquals(2, rs.getInt(1)); @@ -668,7 +668,7 @@ public void testUncoveredQuery() throws Exception { selectSql = "SELECT count(val3) from " + dataTableName + " where val1 > '0'"; // Verify that we will read from the index table - assertExplainPlan(conn, selectSql, dataTableName, indexTableName); + assertPlan(conn, selectSql).scanType("RANGE SCAN").tableContains(indexTableName); rs = conn.createStatement().executeQuery(selectSql); assertTrue(rs.next()); assertEquals(3, rs.getInt(1)); @@ -676,7 +676,7 @@ public void testUncoveredQuery() throws Exception { // Run an order by query where the uncovered index should be used selectSql = "SELECT val3 from " + dataTableName + " where val1 > '0' ORDER BY val1"; // Verify that we will read from the index table - assertExplainPlan(conn, selectSql, dataTableName, indexTableName); + assertPlan(conn, selectSql).scanType("RANGE SCAN").tableContains(indexTableName); rs = conn.createStatement().executeQuery(selectSql); assertTrue(rs.next()); assertEquals("abcd", rs.getString(1)); 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 24a5099134b..bfb01ff0f9f 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 @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.apache.phoenix.util.TestUtil.assertResultSet; import static org.junit.Assert.assertEquals; @@ -30,7 +31,6 @@ import java.sql.Statement; import java.util.Properties; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -419,40 +419,31 @@ public void testBug2894() throws Exception { + " ) L\n" + " ON L.BUCKET = E.BUCKET AND L.TIMESTAMP = E.TIMESTAMP\n" + " ) C\n" + " GROUP BY C.BUCKET, C.TIMESTAMP ORDER BY C.BUCKET, C.TIMESTAMP"; - String p = i == 0 - ? "SORT-MERGE-JOIN (INNER) TABLES\n" - + " CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER " + eventCountTableName - + " [X'00','5SEC',~1462993520000000000,'Tr/Bal'] - [X'01','5SEC',~1462993420000000000,'Tr/Bal']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER DISTINCT PREFIX FILTER OVER [BUCKET, TIMESTAMP, LOCATION]\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [BUCKET, TIMESTAMP, LOCATION]\n" - + " CLIENT MERGE SORT\n" + " CLIENT SORTED BY [BUCKET, TIMESTAMP]\n" - + "AND (SKIP MERGE)\n" + " CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER " + t[i] - + " [X'00','5SEC',~1462993520000000000,'Tr/Bal'] - [X'01','5SEC',~1462993420000000000,'Tr/Bal']\n" - + " SERVER FILTER BY FIRST KEY ONLY AND SRC_LOCATION = DST_LOCATION\n" - + " SERVER DISTINCT PREFIX FILTER OVER [BUCKET, \"TIMESTAMP\", SRC_LOCATION, DST_LOCATION]\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [BUCKET, \"TIMESTAMP\", SRC_LOCATION, DST_LOCATION]\n" - + " CLIENT MERGE SORT\n" + " CLIENT SORTED BY [BUCKET, \"TIMESTAMP\"]\n" - + "CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY [E.BUCKET, E.TIMESTAMP]" - : "SORT-MERGE-JOIN (INNER) TABLES\n" - + " CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER " + eventCountTableName - + " [X'00','5SEC',~1462993520000000000,'Tr/Bal'] - [X'01','5SEC',~1462993420000000000,'Tr/Bal']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER DISTINCT PREFIX FILTER OVER [BUCKET, TIMESTAMP, LOCATION]\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [BUCKET, TIMESTAMP, LOCATION]\n" - + " CLIENT MERGE SORT\n" + " CLIENT SORTED BY [BUCKET, TIMESTAMP]\n" - + "AND (SKIP MERGE)\n" + " CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER " + t[i] - + " [X'00','5SEC',1462993420000000001,'Tr/Bal'] - [X'01','5SEC',1462993520000000000,'Tr/Bal']\n" - + " SERVER FILTER BY FIRST KEY ONLY AND SRC_LOCATION = DST_LOCATION\n" - + " SERVER DISTINCT PREFIX FILTER OVER [BUCKET, \"TIMESTAMP\", SRC_LOCATION, DST_LOCATION]\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [BUCKET, \"TIMESTAMP\", SRC_LOCATION, DST_LOCATION]\n" - + " CLIENT MERGE SORT\n" - + "CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY [E.BUCKET, E.TIMESTAMP]"; - - ResultSet rs = conn.createStatement().executeQuery("explain " + q); - assertEquals(p, QueryUtil.getExplainPlan(rs)); - - rs = conn.createStatement().executeQuery(q); + String lhsKeyRanges = + " [X'00','5SEC',~1462993520000000000,'Tr/Bal'] - [X'01','5SEC',~1462993420000000000,'Tr/Bal']"; + String rhsKeyRanges = i == 0 + ? lhsKeyRanges + : " [X'00','5SEC',1462993420000000001,'Tr/Bal'] - [X'01','5SEC',1462993520000000000,'Tr/Bal']"; + String rhsClientSortedBy = i == 0 ? "[BUCKET, \"TIMESTAMP\"]" : null; + + assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(true) + .iteratorType("PARALLEL").scanType("SKIP SCAN ON 2 RANGES").table(eventCountTableName) + .keyRanges(lhsKeyRanges).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverDistinctFilter("SERVER DISTINCT PREFIX FILTER OVER [BUCKET, TIMESTAMP, LOCATION]") + .serverAggregate( + "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [BUCKET, TIMESTAMP, LOCATION]") + .clientSortAlgo("CLIENT MERGE SORT").clientSortedBy("[BUCKET, TIMESTAMP]") + .clientAggregate("CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY [E.BUCKET, E.TIMESTAMP]") + .rhs().iteratorType("PARALLEL").scanType("SKIP SCAN ON 2 RANGES").table(t[i]) + .keyRanges(rhsKeyRanges) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND SRC_LOCATION = DST_LOCATION") + .serverDistinctFilter( + "SERVER DISTINCT PREFIX FILTER OVER [BUCKET, \"TIMESTAMP\", SRC_LOCATION, DST_LOCATION]") + .serverAggregate( + "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [BUCKET, \"TIMESTAMP\", SRC_LOCATION, DST_LOCATION]") + .clientSortAlgo("CLIENT MERGE SORT").clientSortedBy(rhsClientSortedBy).end(); + + ResultSet rs = conn.createStatement().executeQuery(q); assertTrue(rs.next()); assertEquals("5SEC", rs.getString(1)); assertEquals(1462993430000000000L, rs.getLong(2)); @@ -721,12 +712,8 @@ private static void verifyQueryPlanAndResultForBug4508(Connection conn, String p + "JOIN " + peopleTable + " ds ON ds.PERSON_ID = l.LOCALID"; for (String q : new String[] { query1, query2 }) { - ResultSet rs = conn.createStatement().executeQuery("explain " + q); - String plan = QueryUtil.getExplainPlan(rs); - assertFalse("Tables should not be sorted over their PKs:\n" + plan, - plan.contains("SERVER SORTED BY")); - - rs = conn.createStatement().executeQuery(q); + assertPlan(conn, q).serverSortedBy(null).rhs().serverSortedBy(null).end(); + ResultSet rs = conn.createStatement().executeQuery(q); assertTrue(rs.next()); assertEquals(2, rs.getInt(1)); assertFalse(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SubBinaryFunctionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SubBinaryFunctionIT.java index c2acef86dcf..dc19d5afef7 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SubBinaryFunctionIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SubBinaryFunctionIT.java @@ -17,12 +17,13 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; + import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import org.apache.phoenix.util.QueryUtil; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -379,14 +380,11 @@ public void testExplainPlanWithSubBinaryFunctionInPK() throws Exception { count++; } Assert.assertEquals(2, count); - rs = conn.createStatement().executeQuery("EXPLAIN " + sql); - String plan = QueryUtil.getExplainPlan(rs); - Assert.assertTrue(plan.contains("RANGE SCAN OVER " + tableName + " [1,X'01'] - [1,X'02']")); + assertPlan(conn, sql).scanType("RANGE SCAN").table(tableName) + .keyRanges(" [1,X'01'] - [1,X'02']"); sql = "SELECT * FROM " + tableName + " WHERE id = 1 AND SUBBINARY(VAR_BIN_COL, 2, 1) = X'01'"; - rs = conn.createStatement().executeQuery("EXPLAIN " + sql); - plan = QueryUtil.getExplainPlan(rs); - Assert.assertTrue(plan.contains("RANGE SCAN OVER " + tableName + " [1]")); + assertPlan(conn, sql).scanType("RANGE SCAN").table(tableName).keyRanges(" [1]"); } private void upsertRow(PreparedStatement stmt, int id, byte[] b1, byte[] b2) throws SQLException { diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TableTTLIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TableTTLIT.java index dcefcb48669..5ba04afeac0 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TableTTLIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TableTTLIT.java @@ -22,6 +22,7 @@ import static org.apache.phoenix.query.QueryConstants.CDC_PRE_IMAGE; import static org.apache.phoenix.query.QueryConstants.CDC_TTL_DELETE_EVENT_TYPE; import static org.apache.phoenix.query.QueryConstants.CDC_UPSERT_EVENT_TYPE; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.junit.Assert.*; import com.fasterxml.jackson.databind.ObjectMapper; @@ -45,7 +46,6 @@ import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants; import org.apache.phoenix.hbase.index.IndexRegionObserver; import org.apache.phoenix.jdbc.PhoenixConnection; -import org.apache.phoenix.jdbc.PhoenixResultSet; import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.schema.PTable; @@ -575,9 +575,7 @@ public void testDeleteFamilyVersion() throws Exception { String expectedValue; String dql = "select val1, val2 from " + tableName + " where id = 'a1'"; try (ResultSet rs = conn.createStatement().executeQuery(dql)) { - PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class); - String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertFalse(explainPlan.contains(indexName)); + assertPlan(conn, dql).table(tableName); assertTrue(rs.next()); indexColumnValue = rs.getString(1); expectedValue = rs.getString(2); @@ -609,9 +607,7 @@ public void testDeleteFamilyVersion() throws Exception { // do a read on the index which should trigger a read repair dql = "select val2 from " + tableName + " where val1 = '" + indexColumnValue + "'"; try (ResultSet rs = conn.createStatement().executeQuery(dql)) { - PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class); - String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(explainPlan.contains(indexName)); + assertPlan(conn, dql).tableContains(indexName); assertTrue(rs.next()); assertEquals(rs.getString(1), expectedValue); assertFalse(rs.next()); @@ -622,9 +618,7 @@ public void testDeleteFamilyVersion() throws Exception { TestUtil.dumpTable(conn, TableName.valueOf(indexName)); // run the same query again after compaction try (ResultSet rs = conn.createStatement().executeQuery(dql)) { - PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class); - String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(explainPlan.contains(indexName)); + assertPlan(conn, dql).tableContains(indexName); assertTrue(rs.next()); assertEquals(rs.getString(1), expectedValue); assertFalse(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDDLIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDDLIT.java index 07ca7495754..338daf1e3cc 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDDLIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDDLIT.java @@ -24,6 +24,7 @@ import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.ORDINAL_POSITION; import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA; import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_FUNCTION_TABLE; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.schema.PTableType.SYSTEM; import static org.apache.phoenix.schema.PTableType.TABLE; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; @@ -61,7 +62,6 @@ import org.apache.phoenix.util.MetaDataUtil; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.StringUtil; import org.apache.phoenix.util.TestUtil; @@ -694,19 +694,14 @@ public void testIndexHintWithTenantView() throws Exception { String sql = "SELECT /*+ INDEX(" + fullGrandChildViewName + " " + viewIndexName + ")*/ " + "val2, id2, val1, id3, id1 FROM " + fullGrandChildViewName + " WHERE id2 = 'a2' AND (id1 = 'a1' OR id1 = 'b1') AND id3 = 3"; - ResultSet rs = stmt.executeQuery("EXPLAIN " + sql); - String actualQueryPlan = QueryUtil.getExplainPlan(rs); - assertTrue(actualQueryPlan - .contains("1-WAY POINT LOOKUP ON 2 KEYS OVER " + physicalViewIndexTableName)); - rs = stmt.executeQuery(sql); + assertPlan(conn, sql).scanType("POINT LOOKUP ON 2 KEYS") + .tableContains(physicalViewIndexTableName); + ResultSet rs = stmt.executeQuery(sql); assertTrue(rs.next()); assertFalse(rs.next()); sql = "SELECT val2, id2, val1, id3, id1 FROM " + fullGrandChildViewName + " WHERE id2 = 'a2' AND (id1 = 'a1' OR id1 = 'b1') AND id3 = 3"; - rs = stmt.executeQuery("EXPLAIN " + sql); - actualQueryPlan = QueryUtil.getExplainPlan(rs); - assertTrue( - actualQueryPlan.contains("1-WAY POINT LOOKUP ON 2 KEYS OVER " + fullDataTableName)); + assertPlan(conn, sql).scanType("POINT LOOKUP ON 2 KEYS").table(fullDataTableName); rs = stmt.executeQuery(sql); assertTrue(rs.next()); assertFalse(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDMLIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDMLIT.java index dff28f344bd..938a9003c83 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDMLIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDMLIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.apache.phoenix.util.TestUtil.analyzeTable; import static org.apache.phoenix.util.TestUtil.getAllSplits; @@ -31,12 +32,10 @@ import java.sql.ResultSet; import java.util.List; import java.util.Properties; -import org.apache.phoenix.jdbc.PhoenixResultSet; import org.apache.phoenix.query.KeyRange; import org.apache.phoenix.schema.TableNotFoundException; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -90,9 +89,7 @@ public void testPointLookupOnBaseTable() throws Exception { String dql = String.format("SELECT * FROM %s where org_id='%s' AND kp='%s' LIMIT 1", tableName, tenantId, kp); try (ResultSet rs = conn.createStatement().executeQuery(dql)) { - PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class); - String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(explainPlan.contains("POINT LOOKUP ON 1 KEY")); + assertPlan(conn, dql).scanType("POINT LOOKUP ON 1 KEY"); assertTrue(rs.next()); assertEquals(tenantId, rs.getString(1)); assertEquals(kp, rs.getString(2)); @@ -100,9 +97,7 @@ public void testPointLookupOnBaseTable() throws Exception { dql = String.format("SELECT count(*) FROM %s where org_id='%s' AND kp='%s'", tableName, tenantId, kp); try (ResultSet rs = conn.createStatement().executeQuery(dql)) { - PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class); - String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(explainPlan.contains("POINT LOOKUP ON 1 KEY")); + assertPlan(conn, dql).scanType("POINT LOOKUP ON 1 KEY"); assertTrue(rs.next()); assertEquals(nRows, rs.getInt(1)); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectIT.java index 1bf090cdae1..ebeb76ca3e9 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectIT.java @@ -19,6 +19,7 @@ import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED; import static org.apache.phoenix.monitoring.GlobalClientMetrics.GLOBAL_OPEN_PHOENIX_CONNECTIONS; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertMutationPlan; import static org.apache.phoenix.util.PhoenixRuntime.TENANT_ID_ATTRIB; import static org.apache.phoenix.util.PhoenixRuntime.UPSERT_BATCH_SIZE_ATTRIB; import static org.apache.phoenix.util.TestUtil.A_VALUE; @@ -51,6 +52,7 @@ import org.apache.phoenix.compile.QueryPlan; import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants; import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.jdbc.PhoenixResultSet; import org.apache.phoenix.jdbc.PhoenixStatement; import org.apache.phoenix.monitoring.GlobalMetric; @@ -60,7 +62,6 @@ import org.apache.phoenix.util.EnvironmentEdgeManager; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; import org.apache.phoenix.util.TestUtil; import org.junit.After; @@ -182,12 +183,10 @@ private void testUpsertSelect(boolean createIndex, boolean saltTable) throws Exc + "SELECT substr(entity_id, 4), substr(entity_id, 1, 3), organization_id, " + "a_string FROM " + aTable + " WHERE ?=a_string"; if (createIndex) { // Confirm index is used - try (PreparedStatement upsertStmt = conn.prepareStatement("EXPLAIN " + upsert)) { + try (PhoenixPreparedStatement upsertStmt = + conn.prepareStatement(upsert).unwrap(PhoenixPreparedStatement.class)) { upsertStmt.setString(1, tenantId); - ResultSet ers = upsertStmt.executeQuery(); - assertTrue(ers.next()); - String explainPlan = QueryUtil.getExplainPlan(ers); - assertTrue(explainPlan.contains(" SCAN OVER " + indexName)); + assertMutationPlan(upsertStmt).tableContains(indexName); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectWithRegionMovesIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectWithRegionMovesIT.java index c2ffbc30a57..3098be41994 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectWithRegionMovesIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectWithRegionMovesIT.java @@ -18,6 +18,7 @@ package org.apache.phoenix.end2end; import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertMutationPlan; import static org.apache.phoenix.util.PhoenixRuntime.TENANT_ID_ATTRIB; import static org.apache.phoenix.util.PhoenixRuntime.UPSERT_BATCH_SIZE_ATTRIB; import static org.apache.phoenix.util.TestUtil.A_VALUE; @@ -52,6 +53,7 @@ import org.apache.phoenix.compile.QueryPlan; import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants; import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.jdbc.PhoenixResultSet; import org.apache.phoenix.jdbc.PhoenixStatement; import org.apache.phoenix.monitoring.GlobalMetric; @@ -61,7 +63,6 @@ import org.apache.phoenix.util.EnvironmentEdgeManager; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; import org.apache.phoenix.util.TestUtil; import org.junit.After; @@ -190,12 +191,10 @@ private void testUpsertSelect(boolean createIndex, boolean saltTable) throws Exc + "SELECT substr(entity_id, 4), substr(entity_id, 1, 3), organization_id, " + "a_string FROM " + aTable + " WHERE ?=a_string"; if (createIndex) { // Confirm index is used - try (PreparedStatement upsertStmt = conn.prepareStatement("EXPLAIN " + upsert)) { + try (PhoenixPreparedStatement upsertStmt = + conn.prepareStatement(upsert).unwrap(PhoenixPreparedStatement.class)) { upsertStmt.setString(1, tenantId); - ResultSet ers = upsertStmt.executeQuery(); - assertTrue(ers.next()); - String explainPlan = QueryUtil.getExplainPlan(ers); - assertTrue(explainPlan.contains(" SCAN OVER " + indexName)); + assertMutationPlan(upsertStmt).tableContains(indexName); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewMetadataIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewMetadataIT.java index b3f28a08e93..85c248663ba 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewMetadataIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewMetadataIT.java @@ -32,6 +32,7 @@ import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TENANT_ID; import static org.apache.phoenix.query.QueryServices.DROP_METADATA_ATTRIB; import static org.apache.phoenix.query.QueryServicesOptions.DEFAULT_TASK_HANDLING_MAX_INTERVAL_MS; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.getPlanSteps; import static org.apache.phoenix.schema.PTable.TaskType.DROP_CHILD_VIEWS; import static org.apache.phoenix.thirdparty.com.google.common.collect.Lists.newArrayListWithExpectedSize; import static org.apache.phoenix.util.ByteUtil.EMPTY_BYTE_ARRAY; @@ -947,13 +948,13 @@ private void helpTestQueryForViewOnTableThatHasIndex(Statement s1, Statement s2, // Create a index on the table s1.execute("create index " + indexName + " ON " + tableName + " (col2)"); - try (ResultSet rs = s2.executeQuery("explain select /*+ INDEX(" + viewName + " " + indexName - + ") */ * from " + viewName + " where col2 = 'aaa'")) { - String explainPlan = QueryUtil.getExplainPlan(rs); - - // check if the query uses the index - assertTrue(explainPlan.contains(indexName)); - } + String sql = "select /*+ INDEX(" + viewName + " " + indexName + ") */ * from " + viewName + + " where col2 = 'aaa'"; + // This query produces a SKIP-SCAN-JOIN where the index is scanned in a sub-plan. Assert that + // the index name appears in the plan steps. + List planSteps = getPlanSteps(s2.getConnection(), sql); + assertTrue("Expected plan to use index " + indexName + " but was: " + planSteps, + planSteps.toString().contains(indexName)); } @Test diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseImmutableIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseImmutableIndexIT.java index aaeeea6d07f..bb0788f4203 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseImmutableIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseImmutableIndexIT.java @@ -17,8 +17,8 @@ */ package org.apache.phoenix.end2end.index; -import static org.apache.phoenix.end2end.IndexToolIT.assertExplainPlan; import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.IMMUTABLE_STORAGE_SCHEME; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.schema.PTable.ImmutableStorageScheme.SINGLE_CELL_ARRAY_WITH_OFFSETS; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.apache.phoenix.util.TestUtil.getRowCount; @@ -71,7 +71,6 @@ import org.apache.phoenix.transaction.TransactionFactory; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; import org.junit.Ignore; @@ -465,9 +464,9 @@ public void testGlobalImmutableIndexDelete() throws Exception { admin.truncateTable(TableName.valueOf(fullTableName), true); String selectFromIndex = "SELECT long_pk, varchar_pk, long_col1 FROM " + TABLE_NAME + " WHERE varchar_pk='varchar2' AND long_pk=2"; - rs = conn.createStatement().executeQuery("EXPLAIN " + selectFromIndex); - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - assertExplainPlan(false, actualExplainPlan, fullTableName, fullIndexName); + // Verify the query is served by a RANGE SCAN over the index table. + assertPlan(conn, selectFromIndex).scanType("RANGE SCAN") + .tableContains(SchemaUtil.normalizeIdentifier(fullIndexName)); rs = conn.createStatement().executeQuery(selectFromIndex); assertFalse(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseIndexIT.java index 4dca35db987..48b64bc1cc8 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseIndexIT.java @@ -18,6 +18,7 @@ package org.apache.phoenix.end2end.index; import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.ROW5; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; @@ -80,7 +81,6 @@ import org.apache.phoenix.util.EnvironmentEdgeManager; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; import org.apache.phoenix.util.TransactionUtil; @@ -1547,12 +1547,12 @@ public void testSelectUncoveredWithCoveredField() throws Exception { query = "SELECT /*+ INDEX(" + fullTableName + " " + indexName + ")*/ " + columns + " from " + fullTableName + " where int_col1=2 and long_col1=2"; - rs = stmt.executeQuery("Explain " + query); - String explainPlan = QueryUtil.getExplainPlan(rs); - assertEquals("bad plan with columns:" + columns, "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + fullIndexName + " [2]\n" - + " SERVER MERGE [A.VARCHAR_COL1, A.CHAR_COL1, A.DECIMAL_COL1, A.DATE1, B.VARCHAR_COL2, B.CHAR_COL2, B.INT_COL2, B.DECIMAL_COL2, B.DATE2]\n" - + " SERVER FILTER BY A.\"LONG_COL1\" = 2", explainPlan); + assertPlan(conn, query).scanType("RANGE SCAN").tableContains(fullIndexName) + .keyRanges(" [2]") + .serverMergeColumns( + "[A.VARCHAR_COL1, A.CHAR_COL1, A.DECIMAL_COL1, A.DATE1, B.VARCHAR_COL2, B.CHAR_COL2," + + " B.INT_COL2, B.DECIMAL_COL2, B.DATE2]") + .serverWhereFilter("SERVER FILTER BY A.\"LONG_COL1\" = 2"); rs = stmt.executeQuery(query); assertTrue(rs.next()); // Test the projector thoroughly diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseIndexWithRegionMovesIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseIndexWithRegionMovesIT.java index 329cb4df55e..0aeff042aa8 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseIndexWithRegionMovesIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/BaseIndexWithRegionMovesIT.java @@ -18,6 +18,7 @@ package org.apache.phoenix.end2end.index; import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.ROW5; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; @@ -80,7 +81,6 @@ import org.apache.phoenix.util.EnvironmentEdgeManager; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; import org.apache.phoenix.util.TransactionUtil; @@ -1673,14 +1673,12 @@ public void testSelectUncoveredWithCoveredField() throws Exception { query = "SELECT /*+ INDEX(" + fullTableName + " " + indexName + ")*/ " + columns + " from " + fullTableName + " where int_col1=2 and long_col1=2"; - rs = stmt.executeQuery("Explain " + query); - String explainPlan = QueryUtil.getExplainPlan(rs); - assertEquals("bad plan with columns:" + columns, - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullIndexName + " [2]\n" - + " SERVER MERGE [A.VARCHAR_COL1, A.CHAR_COL1, A.DECIMAL_COL1," - + " A.DATE1, B.VARCHAR_COL2, B.CHAR_COL2, B.INT_COL2, " + "B.DECIMAL_COL2, B.DATE2]\n" - + " SERVER FILTER BY A.\"LONG_COL1\" = 2", - explainPlan); + assertPlan(conn, query).scanType("RANGE SCAN").tableContains(fullIndexName) + .keyRanges(" [2]") + .serverMergeColumns( + "[A.VARCHAR_COL1, A.CHAR_COL1, A.DECIMAL_COL1, A.DATE1, B.VARCHAR_COL2, B.CHAR_COL2," + + " B.INT_COL2, B.DECIMAL_COL2, B.DATE2]") + .serverWhereFilter("SERVER FILTER BY A.\"LONG_COL1\" = 2"); rs = stmt.executeQuery(query); assertTrue(rs.next()); moveRegionsOfTable(fullTableName); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java index 8d7d4c546ad..e9f4832144d 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java @@ -29,9 +29,12 @@ import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.BEFORE_REBUILD_VALID_INDEX_ROW_COUNT; import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.REBUILT_INDEX_ROW_COUNT; import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.SCANNED_DATA_ROW_COUNT; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.getExplainAttributes; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -52,10 +55,10 @@ import java.util.Properties; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.mapreduce.CounterGroup; +import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.end2end.IndexToolIT; import org.apache.phoenix.end2end.NeedsOwnMiniClusterTest; import org.apache.phoenix.hbase.index.IndexRegionObserver; -import org.apache.phoenix.jdbc.PhoenixResultSet; import org.apache.phoenix.mapreduce.index.IndexTool; import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.QueryServices; @@ -65,6 +68,7 @@ import org.apache.phoenix.util.PropertiesUtil; import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; +import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; import org.junit.After; import org.junit.Assume; @@ -140,9 +144,14 @@ public void unsetFailForTesting() throws Exception { public static void assertExplainPlan(Connection conn, String selectSql, String dataTableFullName, String indexTableFullName) throws SQLException { - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql); - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - IndexToolIT.assertExplainPlan(false, actualExplainPlan, dataTableFullName, indexTableFullName); + // Verify the query is served by a RANGE SCAN over the index table not the data table. + ExplainPlanAttributes attributes = getExplainAttributes(conn, selectSql); + assertPlan(attributes).scanType("RANGE SCAN"); + String actualTable = + attributes.getTableName() == null ? null : attributes.getTableName().replaceAll(":", "."); + String expectedTable = SchemaUtil.normalizeIdentifier(indexTableFullName); + assertTrue("expected scanned table <" + actualTable + "> to use index <" + expectedTable + ">", + actualTable != null && actualTable.contains(expectedTable)); } public static void assertExplainPlanWithLimit(Connection conn, String selectSql, @@ -278,9 +287,7 @@ public void testPhoenixRowTimestamp() throws Exception { + dataTableName + " WHERE val1 = 'bc' AND " + "PHOENIX_ROW_TIMESTAMP() > TO_DATE('" + after.toString() + "','yyyy-MM-dd HH:mm:ss.SSS', '" + timeZoneID + "')"; // Verify that we will read from the data table - rs = conn.createStatement().executeQuery("EXPLAIN " + noIndexQuery); - String explainPlan = QueryUtil.getExplainPlan(rs); - assertTrue(explainPlan.contains("FULL SCAN OVER " + dataTableName)); + assertPlan(conn, noIndexQuery).scanType("FULL SCAN").table(dataTableName); rs = conn.createStatement().executeQuery(noIndexQuery); assertTrue(rs.next()); assertEquals("bc", rs.getString(1)); @@ -739,10 +746,8 @@ public void testPartialRowUpdateForImmutable() throws Exception { // now read the same row from data table selectSql = "SELECT * from " + dataTableName + " WHERE id = 'a'"; + assertPlan(conn, selectSql).table(dataTableName); try (ResultSet rs = conn.createStatement().executeQuery(selectSql)) { - PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class); - String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(explainPlan.contains(dataTableName)); assertTrue(rs.next()); assertEquals("a", rs.getString(1)); assertEquals("ab", rs.getString(2)); @@ -1264,11 +1269,7 @@ public void testUnverifiedIndexRowWithSkipScanFilter() throws Exception { String selectSql = "SELECT id, val1, val3 from " + dataTableName + " WHERE val1 IN ('ab', 'bcc') "; // Verify that we will read from the index table - try (ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql)) { - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - String expectedExplainPlan = String.format("SKIP SCAN ON 2 KEYS OVER %s", indexName); - assertTrue(actualExplainPlan.contains(expectedExplainPlan)); - } + assertPlan(conn, selectSql).scanType("SKIP SCAN ON 2 KEYS").table(indexName); try (ResultSet rs = conn.createStatement().executeQuery(selectSql)) { assertTrue(rs.next()); assertEquals("a", rs.getString("id")); @@ -1289,16 +1290,11 @@ public void testUnverifiedIndexRowWithSkipScanFilter() throws Exception { selectSql = "SELECT id, val3 from " + dataTableName + " WHERE val1 IN ('bc') AND val2 IN ('bcd', 'xcdf') AND val3 = 'bcde' "; - // Verify that we will read from the index table - try (ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql)) { - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - String expectedExplainPlan = String.format("SKIP SCAN ON 2 KEYS OVER %s", indexName); - String filter = "SERVER FILTER BY"; - assertTrue(String.format("actual=%s", actualExplainPlan), - actualExplainPlan.contains(expectedExplainPlan)); - assertTrue(String.format("actual=%s", actualExplainPlan), - actualExplainPlan.contains(filter)); - } + // Verify that we will read from the index table with a server filter + ExplainPlanAttributes skipScanAttributes = getExplainAttributes(conn, selectSql); + assertPlan(skipScanAttributes).scanType("SKIP SCAN ON 2 KEYS").table(indexName); + assertNotNull("expected a server filter, plan=" + skipScanAttributes, + skipScanAttributes.getServerWhereFilter()); try (ResultSet rs = conn.createStatement().executeQuery(selectSql)) { assertTrue(rs.next()); assertEquals("b", rs.getString("id")); @@ -1351,9 +1347,7 @@ public void testUnverifiedIndexRowWithSkipScanFilter2() throws Exception { List expectedIDs = Lists.newArrayList("a", "d"); List actualIDs = Lists.newArrayList(); try (ResultSet rs = conn.createStatement().executeQuery(selectSql)) { - PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class); - String actualExplainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(actualExplainPlan.contains(indexName)); + assertPlan(conn, selectSql).table(indexName); while (rs.next()) { actualIDs.add(rs.getString("id")); } @@ -1367,9 +1361,7 @@ public void testUnverifiedIndexRowWithSkipScanFilter2() throws Exception { expectedIDs = Lists.newArrayList("e", "g"); actualIDs = Lists.newArrayList(); try (ResultSet rs = conn.createStatement().executeQuery(selectSql)) { - PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class); - String actualExplainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(actualExplainPlan.contains(indexName)); + assertPlan(conn, selectSql).table(indexName); while (rs.next()) { actualIDs.add(rs.getString("id")); } @@ -1532,10 +1524,10 @@ public void testUncoveredIndexWithDistinctPrefixFilter() throws Exception { private void verifyDistinctQueryOnIndex(Connection conn, String indexName, String query, List expectedValues) throws SQLException, IOException { try (ResultSet rs = conn.createStatement().executeQuery(query)) { - PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class); - String actualExplainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(actualExplainPlan.contains(indexName)); - assertTrue(actualExplainPlan, actualExplainPlan.contains("SERVER DISTINCT PREFIX FILTER")); + ExplainPlanAttributes attributes = getExplainAttributes(conn, query); + assertPlan(attributes).table(indexName); + assertNotNull("expected a server distinct prefix filter, plan=" + attributes, + attributes.getServerDistinctFilter()); List actualValues = Lists.newArrayList(); while (rs.next()) { actualValues.add(rs.getString(1)); @@ -1572,17 +1564,14 @@ public void testUnverifiedIndexRowWithFirstKeyOnlyFilter() throws Exception { String selectSql = "SELECT id, val3 from " + dataTableName + " WHERE val1 = 'bc' and val2 = 'bcd' "; - // Verify that we will read from the index table - try (ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql)) { - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - String expectedExplainPlan = String.format("RANGE SCAN OVER %s", indexName); - String filter = - String.format("SERVER FILTER BY %s ONLY AND", encoded ? "FIRST KEY" : "EMPTY COLUMN"); - assertTrue(String.format("actual=%s", actualExplainPlan), - actualExplainPlan.contains(expectedExplainPlan)); - assertTrue(String.format("actual=%s", actualExplainPlan), - actualExplainPlan.contains(filter)); - } + // Verify that we will read from the index table with a first-key-only/empty-column filter + ExplainPlanAttributes attributes = getExplainAttributes(conn, selectSql); + assertPlan(attributes).scanType("RANGE SCAN").table(indexName); + String expectedFilter = + String.format("SERVER FILTER BY %s ONLY AND", encoded ? "FIRST KEY" : "EMPTY COLUMN"); + assertTrue("serverWhereFilter=" + attributes.getServerWhereFilter(), + attributes.getServerWhereFilter() != null + && attributes.getServerWhereFilter().contains(expectedFilter)); try (ResultSet rs = conn.createStatement().executeQuery(selectSql)) { assertTrue(rs.next()); assertEquals("b", rs.getString("id")); @@ -1617,19 +1606,15 @@ public void testIndexRowWithNullIncludedColumnAndFilter() throws Exception { String dql = String.format("select id, val2 from %s where val1='ab' and val3='abcd'", dataTableName); + assertPlan(conn, dql).table(indexName); try (ResultSet rs = conn.createStatement().executeQuery(dql)) { - PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class); - String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(explainPlan.contains(indexName)); assertFalse(rs.next()); } dql = String.format("select id, val2 from %s where val1='ab' and val3 is null", dataTableName); + assertPlan(conn, dql).table(indexName); try (ResultSet rs = conn.createStatement().executeQuery(dql)) { - PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class); - String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(explainPlan.contains(indexName)); assertTrue(rs.next()); assertEquals("abc", rs.getString("val2")); } @@ -1642,10 +1627,8 @@ public void testIndexRowWithNullIncludedColumnAndFilter() throws Exception { dql = String.format("select id, val2 from %s where val1='ac' and val3 is null", dataTableName); + assertPlan(conn, dql).table(indexName); try (ResultSet rs = conn.createStatement().executeQuery(dql)) { - PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class); - String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(explainPlan.contains(indexName)); assertTrue(rs.next()); assertNull(rs.getString("val2")); } @@ -1730,10 +1713,8 @@ public void testIndexToolWithMultipleDeleteFamilyMarkers() throws Exception { // delete family marker on the unverified index row String dql = String.format("select id, val2, val3 from %s where val1='ab'", dataTableName); + assertPlan(conn, dql).table(indexName); try (ResultSet rs = conn.createStatement().executeQuery(dql)) { - PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class); - String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(explainPlan.contains(indexName)); assertFalse(rs.next()); } } @@ -1884,10 +1865,10 @@ public void testWithDistinctPrefixFilter() throws Exception { waitForEventualConsistency(); String distinctQuery = "SELECT DISTINCT val1 FROM " + dataTableName; try (ResultSet rs = conn.createStatement().executeQuery(distinctQuery)) { - PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class); - String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(explainPlan.contains(indexTableName)); - assertTrue(explainPlan.contains("SERVER DISTINCT PREFIX FILTER OVER")); + ExplainPlanAttributes attributes = getExplainAttributes(conn, distinctQuery); + assertPlan(attributes).table(indexTableName); + assertNotNull("expected a server distinct prefix filter, plan=" + attributes, + attributes.getServerDistinctFilter()); List actualValues = Lists.newArrayList(); while (rs.next()) { actualValues.add(rs.getString(1)); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerWithRegionMovesIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerWithRegionMovesIT.java index 6583df2d84b..2806197b173 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerWithRegionMovesIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerWithRegionMovesIT.java @@ -30,9 +30,12 @@ import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.BEFORE_REBUILD_VALID_INDEX_ROW_COUNT; import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.REBUILT_INDEX_ROW_COUNT; import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.SCANNED_DATA_ROW_COUNT; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.getExplainAttributes; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -58,6 +61,7 @@ import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.RegionInfo; import org.apache.hadoop.hbase.client.RegionStatesCount; +import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants; import org.apache.phoenix.end2end.IndexToolIT; import org.apache.phoenix.end2end.NeedsOwnMiniClusterTest; @@ -72,6 +76,7 @@ import org.apache.phoenix.util.PropertiesUtil; import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; +import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; import org.junit.After; import org.junit.Before; @@ -251,9 +256,14 @@ public void unsetFailForTesting() throws Exception { public static void assertExplainPlan(Connection conn, String selectSql, String dataTableFullName, String indexTableFullName) throws SQLException { - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql); - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - IndexToolIT.assertExplainPlan(false, actualExplainPlan, dataTableFullName, indexTableFullName); + // Verify the query is served by a RANGE SCAN over the index table and not the data table. + ExplainPlanAttributes attributes = getExplainAttributes(conn, selectSql); + assertPlan(attributes).scanType("RANGE SCAN"); + String actualTable = + attributes.getTableName() == null ? null : attributes.getTableName().replaceAll(":", "."); + String expectedTable = SchemaUtil.normalizeIdentifier(indexTableFullName); + assertTrue("expected scanned table <" + actualTable + "> to use index <" + expectedTable + ">", + actualTable != null && actualTable.contains(expectedTable)); } public static void assertExplainPlanWithLimit(Connection conn, String selectSql, @@ -398,9 +408,7 @@ public void testPhoenixRowTimestamp() throws Exception { + dataTableName + " WHERE val1 = 'bc' AND " + "PHOENIX_ROW_TIMESTAMP() > TO_DATE('" + after.toString() + "','yyyy-MM-dd HH:mm:ss.SSS', '" + timeZoneID + "')"; // Verify that we will read from the data table - rs = conn.createStatement().executeQuery("EXPLAIN " + noIndexQuery); - String explainPlan = QueryUtil.getExplainPlan(rs); - assertTrue(explainPlan.contains("FULL SCAN OVER " + dataTableName)); + assertPlan(conn, noIndexQuery).scanType("FULL SCAN").table(dataTableName); rs = conn.createStatement().executeQuery(noIndexQuery); assertTrue(rs.next()); moveRegionsOfTable(dataTableName); @@ -1447,11 +1455,7 @@ public void testUnverifiedIndexRowWithSkipScanFilter() throws Exception { String selectSql = "SELECT id, val1, val3 from " + dataTableName + " WHERE val1 IN ('ab', 'bcc') "; // Verify that we will read from the index table - try (ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql)) { - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - String expectedExplainPlan = String.format("SKIP SCAN ON 2 KEYS OVER %s", indexName); - assertTrue(actualExplainPlan.contains(expectedExplainPlan)); - } + assertPlan(conn, selectSql).scanType("SKIP SCAN ON 2 KEYS").table(indexName); try (ResultSet rs = conn.createStatement().executeQuery(selectSql)) { assertTrue(rs.next()); assertEquals("a", rs.getString("id")); @@ -1474,16 +1478,11 @@ public void testUnverifiedIndexRowWithSkipScanFilter() throws Exception { selectSql = "SELECT id, val3 from " + dataTableName + " WHERE val1 IN ('bc') AND val2 IN ('bcd', 'xcdf') AND val3 = 'bcde' "; - // Verify that we will read from the index table - try (ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql)) { - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - String expectedExplainPlan = String.format("SKIP SCAN ON 2 KEYS OVER %s", indexName); - String filter = "SERVER FILTER BY"; - assertTrue(String.format("actual=%s", actualExplainPlan), - actualExplainPlan.contains(expectedExplainPlan)); - assertTrue(String.format("actual=%s", actualExplainPlan), - actualExplainPlan.contains(filter)); - } + // Verify that we will read from the index table with a server filter + ExplainPlanAttributes skipScanAttributes = getExplainAttributes(conn, selectSql); + assertPlan(skipScanAttributes).scanType("SKIP SCAN ON 2 KEYS").table(indexName); + assertNotNull("expected a server filter, plan=" + skipScanAttributes, + skipScanAttributes.getServerWhereFilter()); try (ResultSet rs = conn.createStatement().executeQuery(selectSql)) { assertTrue(rs.next()); moveRegionsOfTable(dataTableName); @@ -1525,17 +1524,14 @@ public void testUnverifiedIndexRowWithFirstKeyOnlyFilter() throws Exception { String selectSql = "SELECT id, val3 from " + dataTableName + " WHERE val1 = 'bc' and val2 = 'bcd' "; - // Verify that we will read from the index table - try (ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql)) { - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - String expectedExplainPlan = String.format("RANGE SCAN OVER %s", indexName); - String filter = - String.format("SERVER FILTER BY %s ONLY AND", encoded ? "FIRST KEY" : "EMPTY COLUMN"); - assertTrue(String.format("actual=%s", actualExplainPlan), - actualExplainPlan.contains(expectedExplainPlan)); - assertTrue(String.format("actual=%s", actualExplainPlan), - actualExplainPlan.contains(filter)); - } + // Verify that we will read from the index table with a first-key-only/empty-column filter + ExplainPlanAttributes attributes = getExplainAttributes(conn, selectSql); + assertPlan(attributes).scanType("RANGE SCAN").table(indexName); + String expectedFilter = + String.format("SERVER FILTER BY %s ONLY AND", encoded ? "FIRST KEY" : "EMPTY COLUMN"); + assertTrue("serverWhereFilter=" + attributes.getServerWhereFilter(), + attributes.getServerWhereFilter() != null + && attributes.getServerWhereFilter().contains(expectedFilter)); try (ResultSet rs = conn.createStatement().executeQuery(selectSql)) { assertTrue(rs.next()); moveRegionsOfTable(dataTableName); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexOptimizationIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexOptimizationIT.java index fe7a1e8e4b8..a9a43e32042 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexOptimizationIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexOptimizationIT.java @@ -17,6 +17,8 @@ */ package org.apache.phoenix.end2end.index; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertMutationPlan; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -28,8 +30,8 @@ import java.util.regex.Pattern; import org.apache.phoenix.end2end.NeedsOwnMiniClusterTest; import org.apache.phoenix.end2end.ParallelStatsDisabledIT; +import org.apache.phoenix.query.explain.ExplainPlanTestUtil.ExplainPlanAssert; import org.apache.phoenix.util.PhoenixRuntime; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.SchemaUtil; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -78,13 +80,11 @@ public void testIndexDeleteOptimizationWithLocalIndex() throws Exception { + " SELECT TO_CHAR(rand()*100),rand()*10000,rand()*10000,rand()*10000,TO_CHAR(rand()*100) FROM " + dataTableName); } - ResultSet rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - String expected = "DELETE ROWS CLIENT SELECT\n" + "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + indexTableName + "L" + "(" + dataTableName + ") [1,*] - [1,100]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT"; - String actual = QueryUtil.getExplainPlan(rs); - assertEquals(expected, actual); - rs = conn1.createStatement().executeQuery("SELECT COUNT(*) FROM " + dataTableName); + assertMutationPlan(conn1, query).abstractExplainPlan("DELETE ROWS CLIENT SELECT") + .scanType("RANGE SCAN").table(indexTableName + "L(" + dataTableName + ")") + .keyRanges(" [1,*] - [1,100]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .clientSortAlgo("CLIENT MERGE SORT"); + ResultSet rs = conn1.createStatement().executeQuery("SELECT COUNT(*) FROM " + dataTableName); rs.next(); int count = rs.getInt(1); int deleted = conn1.createStatement().executeUpdate(query); @@ -153,14 +153,10 @@ private void testOptimization(String dataTableName, String dataTableFullName, String query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ * FROM " + dataTableName + " where v1='a'"; - ResultSet rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - - String expected = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexTableName + " ['a']\n" - + " SERVER MERGE [0.K3]\n" + " SERVER FILTER BY FIRST KEY ONLY"; - String actual = QueryUtil.getExplainPlan(rs); - assertTrue("Expected:\n" + expected + "\nbut got\n" + actual, actual.equals(expected)); + assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName).keyRanges(" ['a']") + .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); - rs = conn1.createStatement().executeQuery(query); + ResultSet rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals("f", rs.getString("t_id")); assertEquals(1, rs.getInt("k1")); @@ -175,12 +171,8 @@ private void testOptimization(String dataTableName, String dataTableFullName, query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ * FROM " + dataTableName + " where v1='a'"; - rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - - expected = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexTableName + " ['a']\n" - + " SERVER MERGE [0.K3]\n" + " SERVER FILTER BY FIRST KEY ONLY"; - actual = QueryUtil.getExplainPlan(rs); - assertTrue("Expected:\n" + expected + "\nbut got\n" + actual, actual.equals(expected)); + assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName).keyRanges(" ['a']") + .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -199,13 +191,9 @@ private void testOptimization(String dataTableName, String dataTableFullName, query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ * FROM " + dataTableName + " where v1='a' limit 1"; - rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - - expected = "CLIENT SERIAL 1-WAY RANGE SCAN OVER " + indexTableName + " ['a']\n" - + " SERVER MERGE [0.K3]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER 1 ROW LIMIT\n" + "CLIENT 1 ROW LIMIT"; - actual = QueryUtil.getExplainPlan(rs); - assertTrue("Expected:\n" + expected + "\nbut got\n" + actual, actual.equals(expected)); + assertPlan(conn1, query).iteratorType("SERIAL").scanType("RANGE SCAN").table(indexTableName) + .keyRanges(" ['a']").serverMergeColumns("[0.K3]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverRowLimit(1L).clientRowLimit(1); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -219,12 +207,9 @@ private void testOptimization(String dataTableName, String dataTableFullName, query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ t_id, k1, k2, k3, V1 from " + dataTableFullName + " where v1<='z' and k3 > 1 order by V1,t_id"; - rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - - expected = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexTableName + " [*] - ['z']\n" - + " SERVER MERGE [0.K3]\n" + " SERVER FILTER BY FIRST KEY ONLY AND \"K3\" > 1"; - actual = QueryUtil.getExplainPlan(rs); - assertTrue("Expected:\n" + expected + "\nbut got\n" + actual, actual.equals(expected)); + assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName) + .keyRanges(" [*] - ['z']").serverMergeColumns("[0.K3]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND \"K3\" > 1"); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -251,18 +236,24 @@ private void testOptimization(String dataTableName, String dataTableFullName, query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + "), NO_INDEX_SERVER_MERGE */ t_id, k1, k2, k3, V1 from " + dataTableFullName + " where v1<='z' and k3 > 1 order by V1,t_id"; - rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - expected = "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + dataTableName + "\n" - + " SERVER FILTER BY K3 > 1\n" + " SERVER SORTED BY \\[" + dataTableName + "\\.V1, " - + dataTableName + "\\.T_ID\\]\n" + "CLIENT MERGE SORT\n" + " SKIP-SCAN-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexTableName - + " \\[\\*\\] - \\['z'\\]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " DYNAMIC SERVER FILTER BY \\(\"" + dataTableName + "\\.T_ID\", \"" + dataTableName - + "\\.K1\", \"" + dataTableName + ExplainPlanAssert skipScanJoinPlan = assertPlan(conn1, query).scanType("FULL SCAN") + .table(dataTableName).serverWhereFilter("SERVER FILTER BY K3 > 1") + .serverSortedBy("[" + dataTableName + ".V1, " + dataTableName + ".T_ID]") + .clientSortAlgo("CLIENT MERGE SORT"); + skipScanJoinPlan.subPlanCount(1).subPlan(0).abstractExplainPlan("SKIP-SCAN-JOIN TABLE 0") + .scanType("RANGE SCAN").table(indexTableName).keyRanges(" [*] - ['z']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); + // The dynamic server filter references compiler-generated positional aliases ($N.$N) whose + // numbers are not stable, so match the structural shape of the attribute with a regex. + String dynamicServerFilter = skipScanJoinPlan.attributes().getDynamicServerFilter(); + String dynamicServerFilterPattern = "DYNAMIC SERVER FILTER BY \\(\"" + dataTableName + + "\\.T_ID\", \"" + dataTableName + "\\.K1\", \"" + dataTableName + "\\.K2\"\\) IN \\(\\(\\$\\d+\\.\\$\\d+, \\$\\d+\\.\\$\\d+, \\$\\d+\\.\\$\\d+\\)\\)"; - actual = QueryUtil.getExplainPlan(rs); - assertTrue("Expected:\n" + expected + "\nbut got\n" + actual, - Pattern.matches(expected, actual)); + assertTrue( + "Expected dynamic server filter to match:\n" + dynamicServerFilterPattern + "\nbut got\n" + + dynamicServerFilter, + dynamicServerFilter != null + && Pattern.matches(dynamicServerFilterPattern, dynamicServerFilter)); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -287,14 +278,11 @@ private void testOptimization(String dataTableName, String dataTableFullName, query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ t_id, V1, k3 from " + dataTableFullName + " where v1 <='z' group by v1,t_id, k3"; - rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - expected = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexTableName + " [*] - ['z']\n" - + " SERVER MERGE [0.K3]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"V1\", \"T_ID\", \"K3\"]\n" - + "CLIENT MERGE SORT"; - - actual = QueryUtil.getExplainPlan(rs); - assertTrue("Expected:\n" + expected + "\nbut got\n" + actual, actual.equals(expected)); + assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName) + .keyRanges(" [*] - ['z']").serverMergeColumns("[0.K3]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"V1\", \"T_ID\", \"K3\"]") + .clientSortAlgo("CLIENT MERGE SORT"); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -319,12 +307,10 @@ private void testOptimization(String dataTableName, String dataTableFullName, query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ v1,sum(k3) from " + dataTableFullName + " where v1 <='z' group by v1 order by v1"; - rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - expected = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexTableName + " [*] - ['z']\n" - + " SERVER MERGE [0.K3]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"V1\"]"; - actual = QueryUtil.getExplainPlan(rs); - assertTrue("Expected:\n" + expected + "\nbut got\n" + actual, actual.equals(expected)); + assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName) + .keyRanges(" [*] - ['z']").serverMergeColumns("[0.K3]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"V1\"]"); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -356,14 +342,11 @@ private void testOptimizationTenantSpecific(String dataTableName, String indexTa String query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ k1,k2,k3,v1 FROM " + dataTableName + " where v1='a'"; - ResultSet rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - - String actual = QueryUtil.getExplainPlan(rs); - String expected = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexTableName - + " ['tid1','a']\n" + " SERVER MERGE [0.K3]\n" + " SERVER FILTER BY FIRST KEY ONLY"; - assertTrue("Expected:\n" + expected + "\nbut got\n" + actual, actual.equals(expected)); + assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName) + .keyRanges(" ['tid1','a']").serverMergeColumns("[0.K3]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); - rs = conn1.createStatement().executeQuery(query); + ResultSet rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals(1, rs.getInt("k1")); assertEquals(2, rs.getInt("k2")); @@ -406,19 +389,15 @@ public void testGlobalIndexOptimizationOnSharedIndex() throws Exception { String query = "SELECT /*+ INDEX(" + viewName + " " + viewIndex + ")*/ t_id,k1,k2,k3,v1 FROM " + viewName + " where k1 IN (1,2) and k2 IN (3,4)"; - rs = conn1.createStatement().executeQuery("EXPLAIN " + query); /** * This inner "_IDX_" + dataTableName use skipScan, and all the whereExpressions are already * in SkipScanFilter, so there is no other RowKeyComparisonFilter needed. */ - String actual = QueryUtil.getExplainPlan(rs); - String expected = "CLIENT PARALLEL 1-WAY SKIP SCAN ON 2 KEYS OVER _IDX_" + dataTableName - + " [" + Short.MIN_VALUE + ",1] - [" + Short.MIN_VALUE + ",2]\n" - + " SERVER MERGE [0.K3]\n" + " SERVER FILTER BY FIRST KEY ONLY"; - - assertEquals(expected, actual); + assertPlan(conn1, query).scanType("SKIP SCAN ON 2 KEYS").table("_IDX_" + dataTableName) + .keyRanges(" [" + Short.MIN_VALUE + ",1] - [" + Short.MIN_VALUE + ",2]") + .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -455,12 +434,10 @@ public void testNoGlobalIndexOptimization() throws Exception { // All columns available in index String query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ t_id, k1, k2, V1 FROM " + dataTableName + " where v1='a'"; - ResultSet rs = conn1.createStatement().executeQuery("EXPLAIN " + query); + assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName).keyRanges(" ['a']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexTableName + " ['a']\n" - + " SERVER FILTER BY FIRST KEY ONLY", QueryUtil.getExplainPlan(rs)); - - rs = conn1.createStatement().executeQuery(query); + ResultSet rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals("f", rs.getString("t_id")); assertEquals(1, rs.getInt("k1")); @@ -473,10 +450,8 @@ public void testNoGlobalIndexOptimization() throws Exception { // No INDEX hint specified query = "SELECT t_id, k1, k2, k3, V1 FROM " + dataTableName + " where v1='a'"; - rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER " + dataTableName + "\n" - + " SERVER FILTER BY V1 = 'a'", QueryUtil.getExplainPlan(rs)); + assertPlan(conn1, query).scanType("FULL SCAN").table(dataTableName) + .serverWhereFilter("SERVER FILTER BY V1 = 'a'"); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -493,12 +468,8 @@ public void testNoGlobalIndexOptimization() throws Exception { // No where clause query = "SELECT t_id, k1, k2, k3, V1 from " + dataTableFullName + " order by V1,t_id"; - rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - - assertEquals( - "CLIENT PARALLEL 4-WAY FULL SCAN OVER " + dataTableName + "\n" - + " SERVER SORTED BY [V1, T_ID]\n" + "CLIENT MERGE SORT", - QueryUtil.getExplainPlan(rs)); + assertPlan(conn1, query).scanType("FULL SCAN").table(dataTableName) + .serverSortedBy("[V1, T_ID]").clientSortAlgo("CLIENT MERGE SORT"); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -530,11 +501,9 @@ public void testNoGlobalIndexOptimization() throws Exception { // No where clause in index scan query = "SELECT t_id, k1, k2, k3, V1 from " + dataTableFullName + " where k3 > 1 order by V1,t_id"; - rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - - assertEquals("CLIENT PARALLEL 4-WAY FULL SCAN OVER " + dataTableName + "\n" - + " SERVER FILTER BY K3 > 1\n" + " SERVER SORTED BY [V1, T_ID]\n" - + "CLIENT MERGE SORT", QueryUtil.getExplainPlan(rs)); + assertPlan(conn1, query).scanType("FULL SCAN").table(dataTableName) + .serverWhereFilter("SERVER FILTER BY K3 > 1").serverSortedBy("[V1, T_ID]") + .clientSortAlgo("CLIENT MERGE SORT"); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexMaintenanceIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexMaintenanceIT.java index 0995ced87b5..e8b5500325a 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexMaintenanceIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexMaintenanceIT.java @@ -18,6 +18,7 @@ package org.apache.phoenix.end2end.index; import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.INDEX_DATA_SCHEMA; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; @@ -35,10 +36,10 @@ import org.apache.commons.lang3.StringUtils; import org.apache.phoenix.end2end.ParallelStatsDisabledIT; import org.apache.phoenix.end2end.ParallelStatsDisabledTest; +import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.QueryConstants; import org.apache.phoenix.util.DateUtil; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.TestUtil; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -157,17 +158,20 @@ private void helpTestCreateAndUpdate(boolean mutable, boolean localIndex) throws stmt.setDate(5, date); // verify that the query does a range scan on the index table - ResultSet rs = stmt.executeQuery("EXPLAIN " + whereSql); - assertEquals(localIndex - ? "CLIENT PARALLEL 1-WAY RANGE SCAN OVER INDEX_TEST." + indexName + "(" + fullDataTableName - + ")" - + " [1,'VARCHAR1_CHAR1 _A.VARCHAR1_B.CHAR1 ',3,'2015-01-02 00:00:00.000',1,420,156,800,000,1,420,156,800,000]\nCLIENT MERGE SORT" - : "CLIENT PARALLEL 1-WAY RANGE SCAN OVER INDEX_TEST." + indexName - + " ['VARCHAR1_CHAR1 _A.VARCHAR1_B.CHAR1 ',3,'2015-01-02 00:00:00.000',1,420,156,800,000,1,420,156,800,000]", - QueryUtil.getExplainPlan(rs)); + if (localIndex) { + assertPlan(stmt.unwrap(PhoenixPreparedStatement.class)).scanType("RANGE SCAN") + .tableContains("INDEX_TEST." + indexName + "(" + fullDataTableName + ")") + .keyRanges( + " [1,'VARCHAR1_CHAR1 _A.VARCHAR1_B.CHAR1 ',3,'2015-01-02 00:00:00.000',1,420,156,800,000,1,420,156,800,000]") + .clientSortAlgo("CLIENT MERGE SORT"); + } else { + assertPlan(stmt.unwrap(PhoenixPreparedStatement.class)).scanType("RANGE SCAN") + .tableContains("INDEX_TEST." + indexName).keyRanges( + " ['VARCHAR1_CHAR1 _A.VARCHAR1_B.CHAR1 ',3,'2015-01-02 00:00:00.000',1,420,156,800,000,1,420,156,800,000]"); + } // verify that the correct results are returned - rs = stmt.executeQuery(); + ResultSet rs = stmt.executeQuery(); assertTrue(rs.next()); assertEquals(1, rs.getInt(1)); assertEquals(1, rs.getInt(2)); @@ -179,13 +183,14 @@ private void helpTestCreateAndUpdate(boolean mutable, boolean localIndex) throws + "decimal_pk+int_pk+decimal_col2+int_col1, " + "date_pk+1, date1+1, date2+1, " + "varchar_pk, char_pk, int_pk, long_pk, decimal_pk, " + "long_col1, long_col2 " + "from " + fullDataTableName; - rs = conn.createStatement().executeQuery("EXPLAIN " + indexSelectSql); - assertEquals( - localIndex - ? "CLIENT PARALLEL 1-WAY RANGE SCAN OVER INDEX_TEST." + indexName + "(" - + fullDataTableName + ") [1]\nCLIENT MERGE SORT" - : "CLIENT PARALLEL 1-WAY FULL SCAN OVER INDEX_TEST." + indexName, - QueryUtil.getExplainPlan(rs)); + if (localIndex) { + assertPlan(conn, indexSelectSql).scanType("RANGE SCAN") + .tableContains("INDEX_TEST." + indexName + "(" + fullDataTableName + ")") + .keyRanges(" [1]").clientSortAlgo("CLIENT MERGE SORT"); + } else { + assertPlan(conn, indexSelectSql).scanType("FULL SCAN") + .tableContains("INDEX_TEST." + indexName); + } rs = conn.createStatement().executeQuery(indexSelectSql); verifyResult(rs, 1); verifyResult(rs, 2); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexUsageIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexUsageIT.java index c75d1107d79..6d88aa6d08f 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexUsageIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexUsageIT.java @@ -18,6 +18,7 @@ package org.apache.phoenix.end2end.index; import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.INDEX_DATA_SCHEMA; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; @@ -44,7 +45,6 @@ import org.apache.phoenix.query.QueryConstants; import org.apache.phoenix.util.DateUtil; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.TestUtil; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -696,11 +696,9 @@ private void helpTestViewUsesTableIndex(boolean immutable) throws Exception { conn.createStatement().execute("ALTER VIEW " + viewName + " DROP COLUMN s4"); // i2 cannot be used since s4 has been dropped from the view, so i1 will be used - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - String queryPlan = QueryUtil.getExplainPlan(rs); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName1 + " [1]\n" - + " SERVER FILTER BY FIRST KEY ONLY AND ((\"S2\" || '_' || \"S3\") = 'abc_cab' AND \"S1\" = 'foo')", - queryPlan); + assertPlan(conn, query).scanType("RANGE SCAN").tableContains(indexName1).keyRanges(" [1]") + .serverWhereFilter( + "SERVER FILTER BY FIRST KEY ONLY AND ((\"S2\" || '_' || \"S3\") = 'abc_cab' AND \"S1\" = 'foo')"); rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals("abc_cab", rs.getString(1)); @@ -860,21 +858,20 @@ public void helpTestIndexExpressionWithJoin(boolean mutable, boolean localIndex) query = "select c.c_customer_sk from " + tableName + " c " + "left outer join " + tableName + " c2 on c.c_customer_sk = c2.c_customer_sk " + "where c.c_customer_sk || c.c_first_name = '1David'"; - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - String explainPlan = QueryUtil.getExplainPlan(rs); if (localIndex) { - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName + "(" + tableName - + ") [1,'1David']\n" + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 0 (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName + "(" + tableName - + ") [1]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " CLIENT MERGE SORT", explainPlan); + assertPlan(conn, query).scanType("RANGE SCAN") + .tableContains(indexName + "(" + tableName + ")").keyRanges(" [1,'1David']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT") + .subPlanCount(1).subPlan(0).scanType("RANGE SCAN") + .tableContains(indexName + "(" + tableName + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT") + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 (SKIP MERGE)").end(); } else { - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName + " ['1David']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " PARALLEL LEFT-JOIN TABLE 0 (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + indexName + "\n" - + " SERVER FILTER BY FIRST KEY ONLY", explainPlan); + assertPlan(conn, query).scanType("RANGE SCAN").tableContains(indexName) + .keyRanges(" ['1David']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .subPlanCount(1).subPlan(0).scanType("FULL SCAN").tableContains(indexName) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 (SKIP MERGE)").end(); } rs = conn.createStatement().executeQuery(query); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java index ea8f752afde..fffacd4fc34 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java @@ -18,6 +18,7 @@ package org.apache.phoenix.end2end.index; import static org.apache.phoenix.end2end.ExplainPlanWithStatsEnabledIT.getByteRowEstimates; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.MetaDataUtil.getViewIndexSequenceName; import static org.apache.phoenix.util.MetaDataUtil.getViewIndexSequenceSchemaName; import static org.junit.Assert.assertArrayEquals; @@ -56,14 +57,11 @@ import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.CommonFSUtils; import org.apache.hadoop.hbase.util.Pair; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.compile.QueryPlan; import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants; import org.apache.phoenix.end2end.ExplainPlanWithStatsEnabledIT.Estimate; import org.apache.phoenix.hbase.index.IndexRegionSplitPolicy; import org.apache.phoenix.jdbc.PhoenixConnection; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.jdbc.PhoenixResultSet; import org.apache.phoenix.jdbc.PhoenixStatement; import org.apache.phoenix.query.QueryConstants; @@ -73,7 +71,6 @@ import org.apache.phoenix.schema.PTableKey; import org.apache.phoenix.schema.TableNotFoundException; import org.apache.phoenix.util.EnvironmentEdgeManager; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; import org.junit.Test; @@ -438,54 +435,29 @@ public void testUseUncoveredLocalIndexWithPrefix() throws Exception { .execute("CREATE LOCAL INDEX " + indexName + " ON " + tableName + "(pk1,pk2,v1,v2)"); // 1. same prefix length, no other restrictions, but v3 is in the SELECT. Use the main table. - ExplainPlan explainPlan = - conn.prepareStatement("SELECT * FROM " + tableName + " WHERE pk1 = 3 AND pk2 = 4") - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = explainPlan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(physicalTableName.toString(), explainPlanAttributes.getTableName()); - assertEquals(" [3,4]", explainPlanAttributes.getKeyRanges()); + assertPlan(conn, "SELECT * FROM " + tableName + " WHERE pk1 = 3 AND pk2 = 4") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(physicalTableName.toString()) + .keyRanges(" [3,4]"); // 2. same prefix length, no other restrictions. Only index columns used. Use the index. - explainPlan = - conn.prepareStatement("SELECT v2 FROM " + tableName + " WHERE pk1 = 3 AND pk2 = 4") - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - explainPlanAttributes = explainPlan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName + "(" + indexPhysicalTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1,3,4]", explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + assertPlan(conn, "SELECT v2 FROM " + tableName + " WHERE pk1 = 3 AND pk2 = 4") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,3,4]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); // 3. same prefix length, but there's a column not on the index - explainPlan = - conn.prepareStatement("SELECT v2 FROM " + tableName + " WHERE pk1 = 3 AND pk2 = 4 AND v3 = 1") - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - explainPlanAttributes = explainPlan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(physicalTableName.toString(), explainPlanAttributes.getTableName()); - assertEquals(" [3,4]", explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY V3 = 1", explainPlanAttributes.getServerWhereFilter()); + assertPlan(conn, "SELECT v2 FROM " + tableName + " WHERE pk1 = 3 AND pk2 = 4 AND v3 = 1") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(physicalTableName.toString()) + .keyRanges(" [3,4]").serverWhereFilter("SERVER FILTER BY V3 = 1"); // 4. Longer prefix on the index, use it. - explainPlan = conn - .prepareStatement( - "SELECT v2 FROM " + tableName + " WHERE pk1 = 3 AND pk2 = 4 AND v1 = 3 AND v3 = 1") - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - explainPlanAttributes = explainPlan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName + "(" + indexPhysicalTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1,3,4,3]", explainPlanAttributes.getKeyRanges()); - assertEquals("[0.V3]", explainPlanAttributes.getServerMergeColumns().toString()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY AND \"V3\" = 1", - explainPlanAttributes.getServerWhereFilter()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + assertPlan(conn, + "SELECT v2 FROM " + tableName + " WHERE pk1 = 3 AND pk2 = 4 AND v1 = 3 AND v3 = 1") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,3,4,3]") + .serverMergeColumns("[0.V3]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND \"V3\" = 1") + .clientSortAlgo("CLIENT MERGE SORT"); } @Test @@ -511,13 +483,11 @@ public void testUseUncoveredLocalIndexWithSplitPrefix() throws Exception { .execute("CREATE LOCAL INDEX " + indexName + " ON " + tableName + "(pk1,pk3)"); // 1. Still use the index - ResultSet rs = conn.createStatement().executeQuery( - "EXPLAIN SELECT pk1, pk2, pk3, v1 FROM " + tableName + " WHERE pk1 = 2 AND pk3 = 3"); - assertEquals("CLIENT PARALLEL 16-WAY RANGE SCAN OVER " + fullIndexName + "(" - + indexPhysicalTableName + ") [1,2,3]\n" + " SERVER MERGE [0.V1]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT", - QueryUtil.getExplainPlan(rs)); - rs.close(); + assertPlan(conn, "SELECT pk1, pk2, pk3, v1 FROM " + tableName + " WHERE pk1 = 2 AND pk3 = 3") + .iteratorType("PARALLEL").scanType("RANGE SCAN") + .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,2,3]") + .serverMergeColumns("[0.V1]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .clientSortAlgo("CLIENT MERGE SORT"); } @Test @@ -541,90 +511,69 @@ public void testUseUncoveredLocalIndex() throws Exception { .execute("CREATE LOCAL INDEX " + indexName + " ON " + tableName + "(v2, v3, v4)"); // 1. COUNT(*) should still use the index - fewer bytes to scan - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN SELECT COUNT(*) FROM " + tableName); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullIndexName + "(" - + indexPhysicalTableName + ") [1]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO SINGLE ROW", QueryUtil.getExplainPlan(rs)); - rs.close(); + assertPlan(conn, "SELECT COUNT(*) FROM " + tableName).iteratorType("PARALLEL 1-WAY") + .scanType("RANGE SCAN").table(fullIndexName + "(" + indexPhysicalTableName + ")") + .keyRanges(" [1]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW"); // 2. All column projected, no filtering by indexed column, not using the index - rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " + tableName); - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER " + physicalTableName, - QueryUtil.getExplainPlan(rs)); - rs.close(); + assertPlan(conn, "SELECT * FROM " + tableName).iteratorType("PARALLEL 1-WAY") + .scanType("FULL SCAN").table(physicalTableName.toString()); // 3. if the index can avoid a sort operation, use it - rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " + tableName + " ORDER BY v2"); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullIndexName + "(" - + indexPhysicalTableName + ") [1]\n" + " SERVER MERGE [0.V1]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT", - QueryUtil.getExplainPlan(rs)); - rs.close(); + assertPlan(conn, "SELECT * FROM " + tableName + " ORDER BY v2").iteratorType("PARALLEL 1-WAY") + .scanType("RANGE SCAN").table(fullIndexName + "(" + indexPhysicalTableName + ")") + .keyRanges(" [1]").serverMergeColumns("[0.V1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); // 4. but can't use the index if not ORDERing by a prefix of the index key. - rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " + tableName + " ORDER BY v3"); - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER " + physicalTableName + "\n" - + " SERVER SORTED BY [V3]\n" + "CLIENT MERGE SORT", QueryUtil.getExplainPlan(rs)); - rs.close(); + assertPlan(conn, "SELECT * FROM " + tableName + " ORDER BY v3").iteratorType("PARALLEL 1-WAY") + .scanType("FULL SCAN").table(physicalTableName.toString()).serverSortedBy("[V3]") + .clientSortAlgo("CLIENT MERGE SORT"); // 5. If we pin the prefix of the index key we use the index avoiding sorting on the postfix - rs = conn.createStatement() - .executeQuery("EXPLAIN SELECT * FROM " + tableName + " WHERE v2 = 2 ORDER BY v3"); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullIndexName + "(" - + indexPhysicalTableName + ") [1,2]\n" + " SERVER MERGE [0.V1]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT", - QueryUtil.getExplainPlan(rs)); - rs.close(); + assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v2 = 2 ORDER BY v3") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,2]") + .serverMergeColumns("[0.V1]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .clientSortAlgo("CLIENT MERGE SORT"); // 6. Filtering by a non-indexed column will not use the index - rs = - conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " + tableName + " WHERE v1 = 3"); - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER " + physicalTableName + "\n" - + " SERVER FILTER BY V1 = 3.0", QueryUtil.getExplainPlan(rs)); - rs.close(); + assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v1 = 3").iteratorType("PARALLEL 1-WAY") + .scanType("FULL SCAN").table(physicalTableName.toString()) + .serverWhereFilter("SERVER FILTER BY V1 = 3.0"); // 7. Also don't use an index if not filtering on a prefix of the key - rs = - conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " + tableName + " WHERE v3 = 1"); - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER " + physicalTableName + "\n" - + " SERVER FILTER BY V3 = 1", QueryUtil.getExplainPlan(rs)); - rs.close(); + assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v3 = 1").iteratorType("PARALLEL 1-WAY") + .scanType("FULL SCAN").table(physicalTableName.toString()) + .serverWhereFilter("SERVER FILTER BY V3 = 1"); // 8. Filtering along a prefix of the index key can use the index - rs = - conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " + tableName + " WHERE v2 = 2"); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullIndexName + "(" - + indexPhysicalTableName + ") [1,2]\n" + " SERVER MERGE [0.V1]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT", - QueryUtil.getExplainPlan(rs)); - rs.close(); + assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v2 = 2").iteratorType("PARALLEL 1-WAY") + .scanType("RANGE SCAN").table(fullIndexName + "(" + indexPhysicalTableName + ")") + .keyRanges(" [1,2]").serverMergeColumns("[0.V1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); // 9. Make sure a gap in the index columns still uses the index as long as a prefix is specified - rs = conn.createStatement() - .executeQuery("EXPLAIN SELECT * FROM " + tableName + " WHERE v2 = 2 AND v4 = 4"); - assertEquals( - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullIndexName + "(" + indexPhysicalTableName - + ") [1,2]\n" + " SERVER MERGE [0.V1]\n" - + " SERVER FILTER BY FIRST KEY ONLY AND TO_INTEGER(\"V4\") = 4\n" + "CLIENT MERGE SORT", - QueryUtil.getExplainPlan(rs)); - rs.close(); + assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v2 = 2 AND v4 = 4") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,2]") + .serverMergeColumns("[0.V1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND TO_INTEGER(\"V4\") = 4") + .clientSortAlgo("CLIENT MERGE SORT"); // 10. Use index even when also filtering on non-indexed column - rs = conn.createStatement() - .executeQuery("EXPLAIN SELECT * FROM " + tableName + " WHERE v2 = 2 AND v1 = 3"); - assertEquals( - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullIndexName + "(" + indexPhysicalTableName - + ") [1,2]\n" + " SERVER MERGE [0.V1]\n" - + " SERVER FILTER BY FIRST KEY ONLY AND \"V1\" = 3.0\n" + "CLIENT MERGE SORT", - QueryUtil.getExplainPlan(rs)); - rs.close(); + assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v2 = 2 AND v1 = 3") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,2]") + .serverMergeColumns("[0.V1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND \"V1\" = 3.0") + .clientSortAlgo("CLIENT MERGE SORT"); // 11. Another case of not using a prefix of the index key - rs = conn.createStatement() - .executeQuery("EXPLAIN SELECT * FROM " + tableName + " WHERE v1 = 3 AND v3 = 1 AND v4 = 1"); - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER " + physicalTableName + "\n" - + " SERVER FILTER BY (V1 = 3.0 AND V3 = 1 AND V4 = 1)", QueryUtil.getExplainPlan(rs)); - rs.close(); + assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v1 = 3 AND v3 = 1 AND v4 = 1") + .iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(physicalTableName.toString()) + .serverWhereFilter("SERVER FILTER BY (V1 = 3.0 AND V3 = 1 AND V4 = 1)"); } @Test @@ -829,19 +778,12 @@ public void testLocalIndexUsedForUncoveredOrderBy() throws Exception { .execute("CREATE LOCAL INDEX " + indexName + " ON " + tableName + "(v1)"); String query = "SELECT * FROM " + tableName + " ORDER BY V1"; - ResultSet rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - - Admin admin = - driver.getConnectionQueryServices(getUrl(), TestUtil.TEST_PROPERTIES).getAdmin(); - int numRegions = admin.getRegions(physicalTableName).size(); - - assertEquals( - "CLIENT PARALLEL " + numRegions + "-WAY RANGE SCAN OVER " + fullIndexName + "(" - + indexPhysicalTableName + ") [1]\n" + " SERVER MERGE [0.K3]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT", - QueryUtil.getExplainPlan(rs)); + assertPlan(conn1, query).scanType("RANGE SCAN") + .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1]") + .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .clientSortAlgo("CLIENT MERGE SORT"); - rs = conn1.createStatement().executeQuery(query); + ResultSet rs = conn1.createStatement().executeQuery(query); String v = ""; int i = 0; while (rs.next()) { @@ -855,12 +797,10 @@ public void testLocalIndexUsedForUncoveredOrderBy() throws Exception { rs.close(); query = "SELECT * FROM " + tableName + " ORDER BY V1 DESC NULLS LAST"; - rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - assertEquals( - "CLIENT PARALLEL " + numRegions + "-WAY REVERSE RANGE SCAN OVER " + fullIndexName + "(" - + indexPhysicalTableName + ") [1]\n" + " SERVER MERGE [0.K3]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT", - QueryUtil.getExplainPlan(rs)); + assertPlan(conn1, query).scanType("RANGE SCAN").clientSortedBy("REVERSE") + .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1]") + .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .clientSortAlgo("CLIENT MERGE SORT"); rs = conn1.createStatement().executeQuery(query); v = "zz"; @@ -898,17 +838,11 @@ public void testLocalIndexReverseScanShouldReturnAllRows() throws Exception { .execute("CREATE LOCAL INDEX " + indexName + " ON " + tableName + "(v1)"); String query = "SELECT V1 FROM " + tableName + " ORDER BY V1 DESC NULLS LAST"; - ResultSet rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - - Admin admin = - driver.getConnectionQueryServices(getUrl(), TestUtil.TEST_PROPERTIES).getAdmin(); - int numRegions = admin.getRegions(physicalTableName).size(); + assertPlan(conn1, query).scanType("RANGE SCAN").clientSortedBy("REVERSE") + .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); - assertEquals("CLIENT PARALLEL " + numRegions + "-WAY REVERSE RANGE SCAN OVER " + fullIndexName - + "(" + indexPhysicalTableName + ") [1]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + "CLIENT MERGE SORT", QueryUtil.getExplainPlan(rs)); - - rs = conn1.createStatement().executeQuery(query); + ResultSet rs = conn1.createStatement().executeQuery(query); String v = "zz"; int i = 0; while (rs.next()) { @@ -948,18 +882,11 @@ public void testLocalIndexScanJoinColumnsFromDataTable() throws Exception { ResultSet rs = conn1.createStatement().executeQuery("SELECT COUNT(*) FROM " + indexTableName); assertTrue(rs.next()); - Admin admin = - driver.getConnectionQueryServices(getUrl(), TestUtil.TEST_PROPERTIES).getAdmin(); - int numRegions = admin.getRegions(physicalTableName).size(); - String query = "SELECT t_id, k1, k2, k3, V1 FROM " + tableName + " where v1='a'"; - rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - - assertEquals( - "CLIENT PARALLEL " + numRegions + "-WAY RANGE SCAN OVER " + fullIndexName + "(" - + indexPhysicalTableName + ") [1,'a']\n" + " SERVER MERGE [0.K3]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT", - QueryUtil.getExplainPlan(rs)); + assertPlan(conn1, query).scanType("RANGE SCAN") + .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,'a']") + .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .clientSortAlgo("CLIENT MERGE SORT"); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -975,13 +902,10 @@ public void testLocalIndexScanJoinColumnsFromDataTable() throws Exception { assertFalse(rs.next()); query = "SELECT t_id, k1, k2, k3, V1 from " + tableName + " where v1<='z' order by V1,t_id"; - rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - - assertEquals( - "CLIENT PARALLEL " + numRegions + "-WAY RANGE SCAN OVER " + fullIndexName + "(" - + indexPhysicalTableName + ") [1,*] - [1,'z']\n" + " SERVER MERGE [0.K3]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT", - QueryUtil.getExplainPlan(rs)); + assertPlan(conn1, query).scanType("RANGE SCAN") + .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,*] - [1,'z']") + .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .clientSortAlgo("CLIENT MERGE SORT"); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -1010,13 +934,11 @@ public void testLocalIndexScanJoinColumnsFromDataTable() throws Exception { assertEquals("z", rs.getString("V1")); query = "SELECT t_id, V1, k3 from " + tableName + " where v1 <='z' group by v1,t_id, k3"; - rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - - assertEquals("CLIENT PARALLEL " + numRegions + "-WAY RANGE SCAN OVER " + fullIndexName + "(" - + indexPhysicalTableName + ") [1,*] - [1,'z']\n" + " SERVER MERGE [0.K3]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"V1\", \"T_ID\", \"K3\"]\nCLIENT MERGE SORT", - QueryUtil.getExplainPlan(rs)); + assertPlan(conn1, query).scanType("RANGE SCAN") + .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,*] - [1,'z']") + .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"V1\", \"T_ID\", \"K3\"]") + .clientSortAlgo("CLIENT MERGE SORT"); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -1038,13 +960,11 @@ public void testLocalIndexScanJoinColumnsFromDataTable() throws Exception { query = "SELECT v1,sum(k3) from " + tableName + " where v1 <='z' group by v1 order by v1"; - rs = conn1.createStatement().executeQuery("EXPLAIN " + query); - assertEquals( - "CLIENT PARALLEL " + numRegions + "-WAY RANGE SCAN OVER " + fullIndexName + "(" - + indexPhysicalTableName + ") [1,*] - [1,'z']\n" + " SERVER MERGE [0.K3]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"V1\"]\nCLIENT MERGE SORT", - QueryUtil.getExplainPlan(rs)); + assertPlan(conn1, query).scanType("RANGE SCAN") + .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,*] - [1,'z']") + .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"V1\"]") + .clientSortAlgo("CLIENT MERGE SORT"); PhoenixStatement stmt = conn1.createStatement().unwrap(PhoenixStatement.class); rs = stmt.executeQuery(query); @@ -1086,10 +1006,10 @@ public void testIndexPlanSelectionIfBothGlobalAndLocalIndexesHasSameColumnsAndOr conn1.createStatement() .execute("CREATE INDEX " + indexName + "2" + " ON " + tableName + "(v1)"); String query = "SELECT t_id, k1, k2,V1 FROM " + tableName + " where v1='a'"; - ResultSet rs1 = conn1.createStatement().executeQuery("EXPLAIN " + query); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + SchemaUtil.getPhysicalTableName(Bytes.toBytes(indexTableName), isNamespaceMapped) + "2" - + " ['a']\n" + " SERVER FILTER BY FIRST KEY ONLY", QueryUtil.getExplainPlan(rs1)); + assertPlan(conn1, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table( + SchemaUtil.getPhysicalTableName(Bytes.toBytes(indexTableName), isNamespaceMapped) + "2") + .keyRanges(" ['a']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); conn1.close(); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/MutableIndexFailureIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/MutableIndexFailureIT.java index 13a514f3304..9f2a51379a5 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/MutableIndexFailureIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/MutableIndexFailureIT.java @@ -18,6 +18,7 @@ package org.apache.phoenix.end2end.index; import static org.apache.hadoop.hbase.coprocessor.CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -511,18 +512,15 @@ private void validateDataWithIndex(Connection conn, String fullTableName, String String query = "SELECT /*+ INDEX(" + fullTableName + " " + SchemaUtil.getTableNameFromFullName(fullIndexName) + ") */ k,v1 FROM " + fullTableName; ResultSet rs = conn.createStatement().executeQuery(query); - String expectedPlan = - " OVER " - + (localIndex - ? fullIndexName + "(" - + Bytes.toString(SchemaUtil - .getPhysicalTableName(fullTableName.getBytes(), isNamespaceMapped).getName()) - + ")" - : SchemaUtil.getPhysicalTableName(fullIndexName.getBytes(), isNamespaceMapped) - .getNameAsString()); - String explainPlan = - QueryUtil.getExplainPlan(conn.createStatement().executeQuery("EXPLAIN " + query)); - assertTrue(explainPlan, explainPlan.contains(expectedPlan)); + String expectedTable = localIndex + ? fullIndexName + "(" + + Bytes.toString( + SchemaUtil.getPhysicalTableName(fullTableName.getBytes(), isNamespaceMapped).getName()) + + ")" + : SchemaUtil.getPhysicalTableName(fullIndexName.getBytes(), isNamespaceMapped) + .getNameAsString(); + assertPlan(conn, query).scanType(localIndex ? "RANGE SCAN" : "FULL SCAN") + .tableContains(expectedTable); if (transactional) { // failed commit does not get retried assertTrue(rs.next()); assertEquals("a", rs.getString(1)); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/MutableIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/MutableIndexIT.java index 116cc574db8..450eafbce81 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/MutableIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/MutableIndexIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end.index; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -44,7 +45,6 @@ import org.apache.phoenix.util.IndexScrutiny; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; import org.junit.Test; @@ -115,16 +115,16 @@ public void testCoveredColumnUpdates() throws Exception { + fullTableName + " (char_col1 ASC, int_col1 ASC) INCLUDE (long_col1, long_col2)"); String query = "SELECT char_col1, int_col1, long_col2 from " + fullTableName; - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + query); if (localIndex) { - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullIndexName + "(" + fullTableName - + ") [1]\nCLIENT MERGE SORT", QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1]") + .clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER " + fullIndexName, - QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(fullIndexName); } - rs = conn.createStatement().executeQuery(query); + ResultSet rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals("chara", rs.getString(1)); assertEquals(2, rs.getInt(2)); @@ -185,11 +185,11 @@ public void testCoveredColumnUpdates() throws Exception { assertFalse(rs.next()); if (localIndex) { query = "SELECT b.* from " + fullTableName + " where int_col1 = 4 AND char_col1 = 'chara'"; - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullIndexName + "(" + fullTableName - + ") [1,'chara',4]\n" - + " SERVER MERGE [B.VARCHAR_COL2, B.CHAR_COL2, B.INT_COL2, B.DECIMAL_COL2, B.DATE_COL]\n" - + "CLIENT MERGE SORT", QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1,'chara',4]") + .serverMergeColumns( + "[B.VARCHAR_COL2, B.CHAR_COL2, B.INT_COL2, B.DECIMAL_COL2, B.DATE_COL]") + .clientSortAlgo("CLIENT MERGE SORT"); rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals("varchar_b", rs.getString(1)); @@ -286,13 +286,13 @@ public void testCoveredColumns() throws Exception { assertFalse(rs.next()); query = "SELECT * FROM " + fullTableName; - rs = conn.createStatement().executeQuery("EXPLAIN " + query); if (localIndex) { - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullIndexName + "(" + fullTableName - + ") [1]\nCLIENT MERGE SORT", QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1]") + .clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER " + fullIndexName, - QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(fullIndexName); } rs = conn.createStatement().executeQuery(query); @@ -309,13 +309,13 @@ public void testCoveredColumns() throws Exception { conn.commit(); query = "SELECT * FROM " + fullTableName; - rs = conn.createStatement().executeQuery("EXPLAIN " + query); if (localIndex) { - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullIndexName + "(" + fullTableName - + ") [1]\nCLIENT MERGE SORT", QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1]") + .clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER " + fullIndexName, - QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(fullIndexName); } rs = conn.createStatement().executeQuery(query); @@ -332,13 +332,13 @@ public void testCoveredColumns() throws Exception { conn.commit(); query = "SELECT * FROM " + fullTableName; - rs = conn.createStatement().executeQuery("EXPLAIN " + query); if (localIndex) { - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullIndexName + "(" + fullTableName - + ") [1]\nCLIENT MERGE SORT", QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1]") + .clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER " + fullIndexName, - QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(fullIndexName); } rs = conn.createStatement().executeQuery(query); @@ -405,16 +405,16 @@ public void testCompoundIndexKey() throws Exception { assertFalse(rs.next()); query = "SELECT * FROM " + fullTableName; - rs = conn.createStatement().executeQuery("EXPLAIN " + query); if (localIndex) { - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullIndexName + "(" + fullTableName - + ") [1]\n" + " SERVER FILTER BY " + (columnEncoded ? "FIRST KEY" : "EMPTY COLUMN") - + " ONLY\n" + "CLIENT MERGE SORT", QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1]") + .serverWhereFilter( + "SERVER FILTER BY " + (columnEncoded ? "FIRST KEY" : "EMPTY COLUMN") + " ONLY") + .clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals( - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + fullIndexName + "\n" + " SERVER FILTER BY " - + (columnEncoded ? "FIRST KEY" : "EMPTY COLUMN") + " ONLY", - QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(fullIndexName).serverWhereFilter( + "SERVER FILTER BY " + (columnEncoded ? "FIRST KEY" : "EMPTY COLUMN") + " ONLY"); } // make sure the data table looks like what we expect rs = conn.createStatement().executeQuery(query); @@ -534,16 +534,16 @@ public void testMultipleUpdatesToSingleRow() throws Exception { assertFalse(rs.next()); query = "SELECT * FROM " + fullTableName; - rs = conn.createStatement().executeQuery("EXPLAIN " + query); if (localIndex) { - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullIndexName + "(" + fullTableName - + ") [1]\n" + " SERVER FILTER BY " + (columnEncoded ? "FIRST KEY" : "EMPTY COLUMN") - + " ONLY\n" + "CLIENT MERGE SORT", QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1]") + .serverWhereFilter( + "SERVER FILTER BY " + (columnEncoded ? "FIRST KEY" : "EMPTY COLUMN") + " ONLY") + .clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals( - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + fullIndexName + "\n" + " SERVER FILTER BY " - + (columnEncoded ? "FIRST KEY" : "EMPTY COLUMN") + " ONLY", - QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(fullIndexName).serverWhereFilter( + "SERVER FILTER BY " + (columnEncoded ? "FIRST KEY" : "EMPTY COLUMN") + " ONLY"); } // check that the data table matches as expected diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/PartialIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/PartialIndexIT.java index aef493217b8..265a03a184b 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/PartialIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/PartialIndexIT.java @@ -59,12 +59,12 @@ import org.apache.phoenix.mapreduce.index.IndexTool; import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.QueryServices; +import org.apache.phoenix.query.explain.ExplainPlanTestUtil; import org.apache.phoenix.schema.ColumnNotFoundException; import org.apache.phoenix.schema.PTable; import org.apache.phoenix.util.EnvironmentEdgeManager; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; import org.junit.After; import org.junit.Assert; @@ -1014,26 +1014,23 @@ public void testPartialIndexWithIndexHint() throws Exception { // Index hint provided and query plan using partial index is usable String selectSql = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ " + "A from " + dataTableName + " WHERE id2 = 100 AND id1 = 'id12'"; - ResultSet rs = stmt.executeQuery("EXPLAIN " + selectSql); - String actualQueryPlan = QueryUtil.getExplainPlan(rs); - assertTrue(actualQueryPlan.contains("POINT LOOKUP ON 1 KEY OVER " + indexTableName)); - rs = stmt.executeQuery(selectSql); + ExplainPlanTestUtil.assertPlan(conn, selectSql).scanType("POINT LOOKUP ON 1 KEY") + .table(indexTableName); + ResultSet rs = stmt.executeQuery(selectSql); assertTrue(rs.next()); assertEquals(2, rs.getInt(1)); assertFalse(rs.next()); // Index hint provided but query plan using partial index is not usable so, no data selectSql = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ " + "A from " + dataTableName + " WHERE id2 = 10 AND id1 = 'id11'"; - rs = stmt.executeQuery("EXPLAIN " + selectSql); - actualQueryPlan = QueryUtil.getExplainPlan(rs); - assertTrue(actualQueryPlan.contains("POINT LOOKUP ON 1 KEY OVER " + indexTableName)); + ExplainPlanTestUtil.assertPlan(conn, selectSql).scanType("POINT LOOKUP ON 1 KEY") + .table(indexTableName); rs = stmt.executeQuery(selectSql); assertFalse(rs.next()); // No index hint so, use data table only as its point lookup selectSql = "SELECT A from " + dataTableName + " WHERE id2 = 10 AND id1 = 'id11'"; - rs = stmt.executeQuery("EXPLAIN " + selectSql); - actualQueryPlan = QueryUtil.getExplainPlan(rs); - assertTrue(actualQueryPlan.contains("POINT LOOKUP ON 1 KEY OVER " + dataTableName)); + ExplainPlanTestUtil.assertPlan(conn, selectSql).scanType("POINT LOOKUP ON 1 KEY") + .table(dataTableName); rs = stmt.executeQuery(selectSql); assertTrue(rs.next()); assertEquals(1, rs.getInt(1)); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/PartialSystemCatalogIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/PartialSystemCatalogIndexIT.java index 8184f923962..0b3dce61128 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/PartialSystemCatalogIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/PartialSystemCatalogIndexIT.java @@ -22,6 +22,7 @@ import static org.apache.phoenix.query.PhoenixTestBuilder.DDLDefaults.COLUMN_TYPES; import static org.apache.phoenix.query.PhoenixTestBuilder.DDLDefaults.TENANT_VIEW_COLUMNS; import static org.apache.phoenix.query.QueryServices.SYSTEM_CATALOG_INDEXES_ENABLED; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -31,16 +32,12 @@ import java.sql.Connection; import java.sql.DriverManager; -import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Properties; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.HBaseTestingUtility; @@ -570,19 +567,6 @@ static void assertTaskColumns(Connection conn, String expectedStatus, PTable.Tas } } - private List getExplain(String query, Properties props) throws SQLException { - List explainPlan = new ArrayList<>(); - try (Connection conn = DriverManager.getConnection(getUrl(), props); - PreparedStatement statement = conn.prepareStatement("EXPLAIN " + query); - ResultSet rs = statement.executeQuery()) { - while (rs.next()) { - String plan = rs.getString(1); - explainPlan.add(plan); - } - } - return explainPlan; - } - protected PhoenixTestBuilder.SchemaBuilder createLevel2TenantViewWithGlobalLevelTTL(int globalTTL, PhoenixTestBuilder.SchemaBuilder.TenantViewOptions tenantViewOptions, PhoenixTestBuilder.SchemaBuilder.TenantViewIndexOptions tenantViewIndexOptions, @@ -823,24 +807,20 @@ public void testIndexesOfViewAndIndexHeadersCondition() throws Exception { * Testing explain plans */ - List plans = getExplain( - "select TENANT_ID, TABLE_SCHEM, TABLE_NAME, COLUMN_NAME, COLUMN_FAMILY, TABLE_TYPE FROM SYSTEM.CATALOG WHERE TABLE_TYPE = 'v' ", - new Properties()); - assertEquals( - String.format("CLIENT PARALLEL 1-WAY FULL SCAN OVER %s", FULL_SYS_VIEW_HDR_TEST_INDEX_NAME), - plans.get(0)); - - plans = getExplain( - "select VIEW_INDEX_ID, VIEW_INDEX_ID_DATA_TYPE FROM SYSTEM.CATALOG WHERE TABLE_TYPE = 'i' AND LINK_TYPE IS NULL AND VIEW_INDEX_ID IS NOT NULL", - new Properties()); - assertEquals(String.format("CLIENT PARALLEL 1-WAY FULL SCAN OVER %s", - FULL_SYS_VIEW_INDEX_HDR_TEST_INDEX_NAME), plans.get(0)); - - plans = getExplain( - "select ROW_KEY_MATCHER, TTL, TABLE_NAME FROM SYSTEM.CATALOG WHERE TABLE_TYPE = 'v' AND ROW_KEY_MATCHER IS NOT NULL", - new Properties()); - assertEquals(String.format("CLIENT PARALLEL 1-WAY RANGE SCAN OVER %s [not null]", - FULL_SYS_ROW_KEY_MATCHER_TEST_INDEX_NAME), plans.get(0)); + try (Connection conn = DriverManager.getConnection(getUrl())) { + assertPlan(conn, + "select TENANT_ID, TABLE_SCHEM, TABLE_NAME, COLUMN_NAME, COLUMN_FAMILY, TABLE_TYPE FROM SYSTEM.CATALOG WHERE TABLE_TYPE = 'v' ") + .scanType("FULL SCAN").tableContains(FULL_SYS_VIEW_HDR_TEST_INDEX_NAME); + + assertPlan(conn, + "select VIEW_INDEX_ID, VIEW_INDEX_ID_DATA_TYPE FROM SYSTEM.CATALOG WHERE TABLE_TYPE = 'i' AND LINK_TYPE IS NULL AND VIEW_INDEX_ID IS NOT NULL") + .scanType("FULL SCAN").tableContains(FULL_SYS_VIEW_INDEX_HDR_TEST_INDEX_NAME); + + assertPlan(conn, + "select ROW_KEY_MATCHER, TTL, TABLE_NAME FROM SYSTEM.CATALOG WHERE TABLE_TYPE = 'v' AND ROW_KEY_MATCHER IS NOT NULL") + .scanType("RANGE SCAN").tableContains(FULL_SYS_ROW_KEY_MATCHER_TEST_INDEX_NAME) + .keyRanges(" [not null]"); + } /** * Testing cleanup of SYS_INDEX rows after dropping tables and views diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SaltedIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SaltedIndexIT.java index 4b8f928609f..c908e50b922 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SaltedIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SaltedIndexIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end.index; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -35,7 +36,6 @@ import org.apache.phoenix.schema.PTableKey; import org.apache.phoenix.schema.types.PVarbinary; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; import org.junit.Test; @@ -158,15 +158,18 @@ private void testMutableTableIndexMaintanence(String dataTableName, String dataT assertEquals("y", rs.getString(2)); assertFalse(rs.next()); - String expectedPlan; - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - expectedPlan = indexSaltBuckets == null - ? "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexTableFullName + " [~'y']\n" - + " SERVER FILTER BY FIRST KEY ONLY" - : ("CLIENT PARALLEL 4-WAY RANGE SCAN OVER " + indexTableFullName + " [X'00',~'y'] - [" - + PVarbinary.INSTANCE.toStringLiteral(new byte[] { (byte) (indexSaltBuckets - 1) }) - + ",~'y']\n" + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT"); - assertEquals(expectedPlan, QueryUtil.getExplainPlan(rs)); + if (indexSaltBuckets == null) { + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(indexTableFullName).keyRanges(" [~'y']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + } else { + assertPlan(conn, query).iteratorType("PARALLEL 4-WAY").scanType("RANGE SCAN") + .table(indexTableFullName) + .keyRanges(" [X'00',~'y'] - [" + + PVarbinary.INSTANCE.toStringLiteral(new byte[] { (byte) (indexSaltBuckets - 1) }) + + ",~'y']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); + } // Will use index, so rows returned in DESC order. // This is not a bug, though, because we can @@ -180,14 +183,18 @@ private void testMutableTableIndexMaintanence(String dataTableName, String dataT assertEquals("a", rs.getString(1)); assertEquals("x", rs.getString(2)); assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - expectedPlan = indexSaltBuckets == null - ? "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexTableFullName + " [*] - [~'x']\n" - + " SERVER FILTER BY FIRST KEY ONLY" - : ("CLIENT PARALLEL 4-WAY RANGE SCAN OVER " + indexTableFullName + " [X'00',*] - [" - + PVarbinary.INSTANCE.toStringLiteral(new byte[] { (byte) (indexSaltBuckets - 1) }) - + ",~'x']\n" + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT"); - assertEquals(expectedPlan, QueryUtil.getExplainPlan(rs)); + if (indexSaltBuckets == null) { + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(indexTableFullName).keyRanges(" [*] - [~'x']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + } else { + assertPlan(conn, query).iteratorType("PARALLEL 4-WAY").scanType("RANGE SCAN") + .table(indexTableFullName) + .keyRanges(" [X'00',*] - [" + + PVarbinary.INSTANCE.toStringLiteral(new byte[] { (byte) (indexSaltBuckets - 1) }) + + ",~'x']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); + } // Use data table, since point lookup trumps order by query = "SELECT k,v FROM " + dataTableFullName + " WHERE k = 'a' ORDER BY v"; @@ -196,14 +203,8 @@ private void testMutableTableIndexMaintanence(String dataTableName, String dataT assertEquals("a", rs.getString(1)); assertEquals("x", rs.getString(2)); assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - expectedPlan = tableSaltBuckets == null - ? "CLIENT PARALLEL 1-WAY POINT LOOKUP ON 1 KEY OVER " + dataTableFullName + "\n" - + " SERVER SORTED BY [V]\n" + "CLIENT MERGE SORT" - : "CLIENT PARALLEL 1-WAY POINT LOOKUP ON 1 KEY OVER " + dataTableFullName + "\n" - + " SERVER SORTED BY [V]\n" + "CLIENT MERGE SORT"; - String explainPlan2 = QueryUtil.getExplainPlan(rs); - assertEquals(expectedPlan, explainPlan2); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("POINT LOOKUP ON 1 KEY") + .table(dataTableFullName).serverSortedBy("[V]").clientSortAlgo("CLIENT MERGE SORT"); // Will use data table now, since there's a LIMIT clause and // we're able to optimize out the ORDER BY, unless the data @@ -217,26 +218,26 @@ private void testMutableTableIndexMaintanence(String dataTableName, String dataT assertEquals("b", rs.getString(1)); assertEquals("y", rs.getString(2)); assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - expectedPlan = tableSaltBuckets == null - ? "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + dataTableFullName + "\n" - + " SERVER FILTER BY V >= 'x'\n" + " SERVER 2 ROW LIMIT\n" + "CLIENT 2 ROW LIMIT" - : "CLIENT PARALLEL 3-WAY FULL SCAN OVER " + dataTableFullName + "\n" - + " SERVER FILTER BY V >= 'x'\n" + " SERVER 2 ROW LIMIT\n" + "CLIENT MERGE SORT\n" - + "CLIENT 2 ROW LIMIT"; - String explainPlan = QueryUtil.getExplainPlan(rs); - assertEquals(expectedPlan, explainPlan); + if (tableSaltBuckets == null) { + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(dataTableFullName).serverWhereFilter("SERVER FILTER BY V >= 'x'").serverRowLimit(2L) + .clientRowLimit(2); + } else { + assertPlan(conn, query).iteratorType("PARALLEL 3-WAY").scanType("FULL SCAN") + .table(dataTableFullName).serverWhereFilter("SERVER FILTER BY V >= 'x'").serverRowLimit(2L) + .clientSortAlgo("CLIENT MERGE SORT").clientRowLimit(2); + } // PHOENIX-6604 query = "SELECT * FROM " + dataTableFullName + " ORDER BY v DESC LIMIT 1"; - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - expectedPlan = indexSaltBuckets == null - ? "CLIENT SERIAL 1-WAY FULL SCAN OVER " + indexTableFullName + "\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER 1 ROW LIMIT\n" - + "CLIENT 1 ROW LIMIT" - : "CLIENT PARALLEL 4-WAY FULL SCAN OVER " + indexTableFullName + "\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER 1 ROW LIMIT\n" - + "CLIENT MERGE SORT\n" + "CLIENT 1 ROW LIMIT"; - assertEquals(expectedPlan, QueryUtil.getExplainPlan(rs)); + if (indexSaltBuckets == null) { + assertPlan(conn, query).iteratorType("SERIAL 1-WAY").scanType("FULL SCAN") + .table(indexTableFullName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverRowLimit(1L).clientRowLimit(1); + } else { + assertPlan(conn, query).iteratorType("PARALLEL 4-WAY").scanType("FULL SCAN") + .table(indexTableFullName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverRowLimit(1L).clientSortAlgo("CLIENT MERGE SORT").clientRowLimit(1); + } } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SingleCellIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SingleCellIndexIT.java index 94572eabbf9..c62d7fec753 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SingleCellIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SingleCellIndexIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end.index; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.schema.PTable.ImmutableStorageScheme.ONE_CELL_PER_COLUMN; import static org.apache.phoenix.schema.PTable.ImmutableStorageScheme.SINGLE_CELL_ARRAY_WITH_OFFSETS; import static org.apache.phoenix.schema.PTable.QualifierEncodingScheme.NON_ENCODED_QUALIFIERS; @@ -53,7 +54,6 @@ import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.SchemaUtil; import org.junit.Before; import org.junit.Test; @@ -108,11 +108,9 @@ public void testCreateOneCellTableAndSingleCellIndex() throws Exception { String selectFromData = "SELECT /*+ NO_INDEX */ PK1, INT_PK, V1, V2, V4 FROM " + tableName + " where V2 >= 3 and V4 LIKE 'V4%'"; - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectFromData); - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - assertTrue(actualExplainPlan.contains(tableName)); + assertPlan(conn, selectFromData).table(tableName); - rs = conn.createStatement().executeQuery(selectFromData); + ResultSet rs = conn.createStatement().executeQuery(selectFromData); assertTrue(rs.next()); assertEquals("PK2", rs.getString(1)); assertEquals(2, rs.getInt(2)); @@ -123,9 +121,7 @@ public void testCreateOneCellTableAndSingleCellIndex() throws Exception { String selectFromIndex = "SELECT PK1, INT_PK, V1, V2, V4 FROM " + tableName + " where V2 >= 3 and V4 LIKE 'V4%'"; - rs = conn.createStatement().executeQuery("EXPLAIN " + selectFromIndex); - actualExplainPlan = QueryUtil.getExplainPlan(rs); - assertTrue(actualExplainPlan.contains(idxName)); + assertPlan(conn, selectFromIndex).table(idxName); rs = conn.createStatement().executeQuery(selectFromIndex); assertTrue(rs.next()); @@ -166,11 +162,9 @@ public void testAddColumns() throws Exception { String selectFromIndex = "SELECT PK1, INT_PK, V1, V2, V4, V_NEW FROM " + tableName + " where V1='V199' AND V2=100"; - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectFromIndex); - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - assertTrue(actualExplainPlan.contains(idxName)); + assertPlan(conn, selectFromIndex).table(idxName); - rs = conn.createStatement().executeQuery(selectFromIndex); + ResultSet rs = conn.createStatement().executeQuery(selectFromIndex); assertTrue(rs.next()); assertEquals("PK99", rs.getString(1)); assertEquals(99, rs.getInt(2)); @@ -207,9 +201,7 @@ public void testDropColumns() throws Exception { assertFalse(rs.next()); String selectFromIndex = "SELECT PK1, INT_PK, V1, V4 FROM " + tableName + " where V1='V11'"; - rs = conn.createStatement().executeQuery("EXPLAIN " + selectFromIndex); - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - assertTrue(actualExplainPlan.contains(idxName)); + assertPlan(conn, selectFromIndex).table(idxName); rs = conn.createStatement().executeQuery(selectFromIndex); assertTrue(rs.next()); @@ -408,9 +400,7 @@ public void testMultipleColumnFamilies() throws Exception { String selectFromIndex = "SELECT PK1, INT_PK, A.V2, B.V3, A.V4, B.V5 FROM " + tableName + " where A.V4='V42' and B.V3 >= 3"; - rs = conn.createStatement().executeQuery("EXPLAIN " + selectFromIndex); - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - assertTrue(actualExplainPlan.contains(idxName)); + assertPlan(conn, selectFromIndex).table(idxName); rs = conn.createStatement().executeQuery(selectFromIndex); assertTrue(rs.next()); assertEquals("PK2", rs.getString(1)); @@ -442,11 +432,9 @@ public void testPartialUpdateSingleCellTable() throws Exception { conn.commit(); String selectFromData = "SELECT /*+ NO_INDEX */ PK1, INT_PK, V1, V2, V4 FROM " + tableName + " where INT_PK = 1 and V4 LIKE 'UpdatedV4'"; - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectFromData); - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - assertTrue(actualExplainPlan.contains(tableName)); + assertPlan(conn, selectFromData).table(tableName); - rs = conn.createStatement().executeQuery(selectFromData); + ResultSet rs = conn.createStatement().executeQuery(selectFromData); assertTrue(rs.next()); assertEquals("PK1", rs.getString(1)); assertEquals(1, rs.getInt(2)); @@ -456,9 +444,7 @@ public void testPartialUpdateSingleCellTable() throws Exception { String selectFromIndex = "SELECT PK1, INT_PK, V1, V2, V4 FROM " + tableName + " where V2 >= 2 and V4 = 'UpdatedV4'"; - rs = conn.createStatement().executeQuery("EXPLAIN " + selectFromIndex); - actualExplainPlan = QueryUtil.getExplainPlan(rs); - assertTrue(actualExplainPlan.contains(idxName)); + assertPlan(conn, selectFromIndex).table(idxName); rs = conn.createStatement().executeQuery(selectFromIndex); assertTrue(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/UncoveredGlobalIndexRegionScanner2IT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/UncoveredGlobalIndexRegionScanner2IT.java index 555f87ff0cb..5be82ccdd97 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/UncoveredGlobalIndexRegionScanner2IT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/UncoveredGlobalIndexRegionScanner2IT.java @@ -20,6 +20,7 @@ import static org.apache.phoenix.end2end.index.GlobalIndexCheckerIT.assertExplainPlan; import static org.apache.phoenix.end2end.index.GlobalIndexCheckerIT.assertExplainPlanWithLimit; import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -66,7 +67,6 @@ import org.apache.phoenix.query.KeyRange; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.EnvironmentEdgeManager; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; import org.apache.phoenix.util.TestUtil; import org.junit.After; @@ -377,9 +377,7 @@ public void testUncoveredQueryWithPhoenixRowTimestamp() throws Exception { + dataTableName + " WHERE val1 = 'bc' AND " + "PHOENIX_ROW_TIMESTAMP() > TO_DATE('" + after + "','yyyy-MM-dd HH:mm:ss.SSS', '" + timeZoneID + "')"; // Verify that we will read from the data table - rs = conn.createStatement().executeQuery("EXPLAIN " + noIndexQuery); - String explainPlan = QueryUtil.getExplainPlan(rs); - assertTrue(explainPlan.contains("FULL SCAN OVER " + dataTableName)); + assertPlan(conn, noIndexQuery).scanType("FULL SCAN").table(dataTableName); rs = conn.createStatement().executeQuery(noIndexQuery); assertTrue(rs.next()); assertEquals("bc", rs.getString(1)); @@ -587,9 +585,8 @@ public void testUncoveredQueryWithPhoenixRowTimestampAndAllPkCols() throws Excep + dataTableName + " WHERE val1 = 'bc' AND " + "PHOENIX_ROW_TIMESTAMP() > TO_DATE('" + after + "','yyyy-MM-dd HH:mm:ss.SSS', '" + timeZoneID + "')"; // Verify that we will read from the data table - rs = conn.createStatement().executeQuery("EXPLAIN " + noIndexQuery); - String explainPlan = QueryUtil.getExplainPlan(rs); - assertTrue(explainPlan.contains(salted ? "RANGE" : "FULL" + " SCAN OVER " + dataTableName)); + assertPlan(conn, noIndexQuery).scanType(salted ? "RANGE SCAN" : "FULL SCAN") + .table(dataTableName); rs = conn.createStatement().executeQuery(noIndexQuery); assertTrue(rs.next()); assertEquals("bc", rs.getString(1)); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/UncoveredGlobalIndexRegionScannerIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/UncoveredGlobalIndexRegionScannerIT.java index 641fcaa2eaf..79772ff1f0a 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/UncoveredGlobalIndexRegionScannerIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/UncoveredGlobalIndexRegionScannerIT.java @@ -19,6 +19,7 @@ import static org.apache.phoenix.end2end.index.GlobalIndexCheckerIT.assertExplainPlan; import static org.apache.phoenix.end2end.index.GlobalIndexCheckerIT.assertExplainPlanWithLimit; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -48,7 +49,6 @@ import org.apache.phoenix.query.KeyRange; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.EnvironmentEdgeManager; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; @@ -241,9 +241,7 @@ public void testUncoveredQueryWithPhoenixRowTimestamp() throws Exception { + dataTableName + " WHERE val1 = 'bc' AND " + "PHOENIX_ROW_TIMESTAMP() > TO_DATE('" + after + "','yyyy-MM-dd HH:mm:ss.SSS', '" + timeZoneID + "')"; // Verify that we will read from the data table - rs = conn.createStatement().executeQuery("EXPLAIN " + noIndexQuery); - String explainPlan = QueryUtil.getExplainPlan(rs); - assertTrue(explainPlan.contains("FULL SCAN OVER " + dataTableName)); + assertPlan(conn, noIndexQuery).scanType("FULL SCAN").table(dataTableName); rs = conn.createStatement().executeQuery(noIndexQuery); assertTrue(rs.next()); assertEquals("bc", rs.getString(1)); @@ -421,9 +419,8 @@ public void testUncoveredQueryWithPhoenixRowTimestampAndAllPkCols() throws Excep + dataTableName + " WHERE val1 = 'bc' AND " + "PHOENIX_ROW_TIMESTAMP() > TO_DATE('" + after + "','yyyy-MM-dd HH:mm:ss.SSS', '" + timeZoneID + "')"; // Verify that we will read from the data table - rs = conn.createStatement().executeQuery("EXPLAIN " + noIndexQuery); - String explainPlan = QueryUtil.getExplainPlan(rs); - assertTrue(explainPlan.contains(salted ? "RANGE" : "FULL" + " SCAN OVER " + dataTableName)); + assertPlan(conn, noIndexQuery).scanType(salted ? "RANGE SCAN" : "FULL SCAN") + .table(dataTableName); rs = conn.createStatement().executeQuery(noIndexQuery); assertTrue(rs.next()); assertEquals("bc", rs.getString(1)); @@ -867,24 +864,18 @@ public void testPointLookup() throws Exception { // Index hint is incorrect as full index name with schema is used String sql = "SELECT /*+ INDEX(" + fullDataTableName + " " + fullIndexName + ")*/ val2, val3 from " + fullDataTableName + " WHERE id = 'a'"; - ResultSet rs = stmt.executeQuery("EXPLAIN " + sql); - String actualQueryPlan = QueryUtil.getExplainPlan(rs); - assertTrue(actualQueryPlan.contains("POINT LOOKUP ON 1 KEY OVER " + fullDataTableName)); - rs = stmt.executeQuery(sql); + assertPlan(conn, sql).scanType("POINT LOOKUP ON 1 KEY").table(fullDataTableName); + ResultSet rs = stmt.executeQuery(sql); assertTrue(rs.next()); // No explicit index hint and being point lookup no index will be used sql = "SELECT val2, val3 from " + fullDataTableName + " WHERE id = 'a'"; - rs = stmt.executeQuery("EXPLAIN " + sql); - actualQueryPlan = QueryUtil.getExplainPlan(rs); - assertTrue(actualQueryPlan.contains("POINT LOOKUP ON 1 KEY OVER " + fullDataTableName)); + assertPlan(conn, sql).scanType("POINT LOOKUP ON 1 KEY").table(fullDataTableName); rs = stmt.executeQuery(sql); assertTrue(rs.next()); // Index hint with point lookup over data table, still index should be used sql = "SELECT /*+ INDEX(" + fullDataTableName + " " + indexName + ")*/ val2, val3 from " + fullDataTableName + " WHERE id = 'a'"; - rs = stmt.executeQuery("EXPLAIN " + sql); - actualQueryPlan = QueryUtil.getExplainPlan(rs); - assertTrue(actualQueryPlan.contains("FULL SCAN OVER " + fullIndexName)); + assertPlan(conn, sql).scanType("FULL SCAN").table(fullIndexName); rs = stmt.executeQuery(sql); assertTrue(rs.next()); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java index b7d0e0e45da..6da69d4316d 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java @@ -22,6 +22,7 @@ import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TABLE_NAME; import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TABLE_SCHEM; import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.VIEW_INDEX_ID_DATA_TYPE; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.MetaDataUtil.getViewIndexSequenceName; import static org.apache.phoenix.util.MetaDataUtil.getViewIndexSequenceSchemaName; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; @@ -71,7 +72,6 @@ import org.apache.phoenix.util.MetaDataUtil; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; import org.junit.Assert; @@ -256,14 +256,12 @@ public void testMultiTenantViewLocalIndex() throws Exception { conn1.commit(); String sql = "SELECT * FROM " + fullViewName + " WHERE v2 = 100"; - ResultSet rs = conn1.prepareStatement("EXPLAIN " + sql).executeQuery(); - assertEquals( - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + fullIndexName + "(" - + SchemaUtil.getPhysicalTableName(Bytes.toBytes(fullTableName), isNamespaceMapped) - + ") [1,'10',100]\n" + " SERVER MERGE [0.V1]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT", - QueryUtil.getExplainPlan(rs)); - rs = conn1.prepareStatement(sql).executeQuery(); + assertPlan(conn1, sql).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "(" + + SchemaUtil.getPhysicalTableName(Bytes.toBytes(fullTableName), isNamespaceMapped) + ")") + .keyRanges(" [1,'10',100]").serverMergeColumns("[0.V1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); + ResultSet rs = conn1.prepareStatement(sql).executeQuery(); assertTrue(rs.next()); assertFalse(rs.next()); @@ -569,9 +567,7 @@ private void createTableForRowKeyTestsAndVerify(Connection conn, String viewPkCo String select = "SELECT " + lastViewPKCol + " FROM " + childViewName + " WHERE TEXT1='text1' LIMIT 10"; - ResultSet rs1 = conn2.createStatement().executeQuery("EXPLAIN " + select); - String actualExplainPlan = QueryUtil.getExplainPlan(rs1); - assertTrue(actualExplainPlan.contains("_IDX_" + fullTableName)); + assertPlan(conn2, select).table("_IDX_" + fullTableName); ResultSet rs = conn2.createStatement().executeQuery(select); assertTrue(rs.next()); @@ -585,11 +581,11 @@ private void assertRowCount(Connection conn, String fullTableName, String fullBa assertTrue(rs.next()); assertEquals(expectedCount, rs.getInt(1)); // Ensure that index is being used - rs = stmt.executeQuery("EXPLAIN SELECT COUNT(*) FROM " + fullTableName); if (fullBaseName != null) { // Uses index and finds correct number of rows - assertTrue(QueryUtil.getExplainPlan(rs).startsWith("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + Bytes.toString(MetaDataUtil.getViewIndexPhysicalName(Bytes.toBytes(fullBaseName))))); + assertPlan(conn, "SELECT COUNT(*) FROM " + fullTableName).iteratorType("PARALLEL 1-WAY") + .scanType("RANGE SCAN") + .table(Bytes.toString(MetaDataUtil.getViewIndexPhysicalName(Bytes.toBytes(fullBaseName)))); } // Force it not to use index and still finds correct number of rows diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/txn/TxWriteFailureIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/txn/TxWriteFailureIT.java index 659f1b01ef1..95811171b8b 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/txn/TxWriteFailureIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/txn/TxWriteFailureIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end.index.txn; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -43,7 +44,6 @@ import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; import org.apache.phoenix.util.SchemaUtil; import org.junit.Before; @@ -159,9 +159,7 @@ private void helpTestWriteFailure(boolean indexTableWriteFailure) throws SQLExce // verify that only k3,v3 exists in the data table String dataSql = "SELECT k, v1 FROM " + dataTableFullName + " order by k"; - rs = conn.createStatement().executeQuery("EXPLAIN " + dataSql); - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER " + dataTableFullName, - QueryUtil.getExplainPlan(rs)); + assertPlan(conn, dataSql).scanType("FULL SCAN").table(dataTableFullName); rs = conn.createStatement().executeQuery(dataSql); assertTrue(rs.next()); assertEquals("k3", rs.getString(1)); @@ -170,15 +168,10 @@ private void helpTestWriteFailure(boolean indexTableWriteFailure) throws SQLExce // verify the only k3,v3 exists in the index table String indexSql = "SELECT k, v1 FROM " + dataTableFullName + " order by v1"; - rs = conn.createStatement().executeQuery("EXPLAIN " + indexSql); if (localIndex) { - assertEquals( - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexFullName + "(" + dataTableFullName - + ") [1]\n" + " SERVER FILTER BY EMPTY COLUMN ONLY\n" + "CLIENT MERGE SORT", - QueryUtil.getExplainPlan(rs)); + assertPlan(conn, indexSql).scanType("RANGE SCAN").tableContains(indexFullName); } else { - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER " + indexFullName - + "\n SERVER FILTER BY EMPTY COLUMN ONLY", QueryUtil.getExplainPlan(rs)); + assertPlan(conn, indexSql).scanType("FULL SCAN").tableContains(indexFullName); } rs = conn.createStatement().executeQuery(indexSql); assertTrue(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/BaseJoinIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/BaseJoinIT.java index a050c16f291..0a07e2b4300 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/BaseJoinIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/BaseJoinIT.java @@ -18,8 +18,6 @@ package org.apache.phoenix.end2end.join; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import java.sql.Connection; import java.sql.Date; @@ -30,13 +28,11 @@ import java.text.SimpleDateFormat; import java.util.Map; import java.util.Properties; -import java.util.regex.Pattern; import org.apache.phoenix.cache.ServerCacheClient; import org.apache.phoenix.end2end.ParallelStatsDisabledIT; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.PropertiesUtil; import org.apache.phoenix.util.SchemaUtil; -import org.apache.phoenix.util.StringUtil; import org.junit.Before; import org.apache.phoenix.thirdparty.com.google.common.collect.ImmutableMap; @@ -104,18 +100,15 @@ public abstract class BaseJoinIT extends ParallelStatsDisabledIT { protected String seqName; private String schemaName; protected final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - protected final String[] plans; private final String[] indexDDL; private final Map virtualNameToRealNameMap = Maps.newHashMap(); - public BaseJoinIT(String[] indexDDL, String[] plans) { + public BaseJoinIT(String[] indexDDL) { this.indexDDL = indexDDL; - this.plans = plans; } public BaseJoinIT() { this.indexDDL = new String[0]; - this.plans = new String[0]; } protected String getSchemaName() { @@ -164,33 +157,6 @@ public void createSchema() throws SQLException { } } - private String translateToVirtualPlan(String actualPlan) { - int size = getTableNameMap().size(); - String[] virtualNames = new String[size + 1]; - String[] realNames = new String[size + 1]; - int count = 0; - for (Map.Entry entry : getTableNameMap().entrySet()) { - virtualNames[count] = entry.getKey(); - realNames[count] = entry.getValue(); - count++; - } - realNames[count] = getSchemaName(); - virtualNames[count] = JOIN_SCHEMA; - String convertedPlan = StringUtil.replace(actualPlan, realNames, virtualNames); - return convertedPlan; - } - - protected void assertPlansMatch(String virtualPlanRegEx, String actualPlan) { - String convertedPlan = translateToVirtualPlan(actualPlan); - assertTrue("\"" + convertedPlan + "\" does not match \"" + virtualPlanRegEx + "\"", - Pattern.matches(virtualPlanRegEx, convertedPlan)); - } - - protected void assertPlansEqual(String virtualPlan, String actualPlan) { - String convertedPlan = translateToVirtualPlan(actualPlan); - assertEquals(virtualPlan, convertedPlan); - } - private static void initValues(Connection conn, String virtualName, String realName) throws Exception { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinGlobalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinGlobalIndexIT.java index dd8f4b144c0..8c6c5bc7591 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinGlobalIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinGlobalIndexIT.java @@ -17,10 +17,15 @@ */ package org.apache.phoenix.end2end.join; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; + +import java.sql.Connection; import java.util.Collection; import java.util.List; import java.util.Map; +import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.end2end.ParallelStatsDisabledTest; +import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; @@ -46,8 +51,300 @@ protected Map getTableNameMap() { return virtualNameToRealNameMap; } - public HashJoinGlobalIndexIT(String[] indexDDL, String[] plans) { - super(indexDDL, plans); + public HashJoinGlobalIndexIT(String[] indexDDL) { + super(indexDDL); + } + + @Override + protected void assertLeftJoinWithAggPlan1(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String idxItem = getSchemaName() + ".idx_item"; + assertPlan(conn, query).scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"I.0:NAME\"]") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(idxItem) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); + } + + @Override + protected void assertLeftJoinWithAggPlan2(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String idxItem = getSchemaName() + ".idx_item"; + assertPlan(conn, query).scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"I.:item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").clientSortedBy("[SUM(O.QUANTITY) DESC]").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN") + .table(idxItem).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); + } + + @Override + protected void assertLeftJoinWithAggPlan3(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.item_id\"]") + .clientSortedBy("[SUM(O.QUANTITY) DESC NULLS LAST, \"I.item_id\"]").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertRightJoinWithAggPlan1(Connection conn, String query) throws Exception { + String idxItem = getSchemaName() + ".idx_item"; + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(idxItem) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.0:NAME\"]") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0") + .scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertRightJoinWithAggPlan2(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.item_id\"]") + .clientSortedBy("[SUM(O.QUANTITY) DESC NULLS LAST, \"I.item_id\"]").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertJoinWithWildcardPlan(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item).subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(supplier) + .end(); + } + + @Override + protected void assertJoinPlanWithIndexPlan1(Connection conn, String query) throws Exception { + String idxItem = getSchemaName() + ".idx_item"; + String idxSupplier = getSchemaName() + ".idx_supplier"; + assertPlan(conn, query).scanType("RANGE SCAN").table(idxItem).keyRanges(" ['T1'] - ['T5']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("RANGE SCAN").table(idxSupplier) + .keyRanges(" ['S1'] - ['S5']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); + } + + @Override + protected void assertJoinPlanWithIndexPlan2(Connection conn, String query) throws Exception { + String idxItem = getSchemaName() + ".idx_item"; + String idxSupplier = getSchemaName() + ".idx_supplier"; + assertPlan(conn, query).scanType("SKIP SCAN ON 2 KEYS").table(idxItem) + .keyRanges(" ['T1'] - ['T5']").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("SKIP SCAN ON 2 KEYS") + .table(idxSupplier).keyRanges(" ['S1'] - ['S5']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); + } + + @Override + protected void assertSkipMergeOptimizationPlan(Connection conn, String query) throws Exception { + String idxItem = getSchemaName() + ".idx_item"; + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String idxSupplier = getSchemaName() + ".idx_supplier"; + assertPlan(conn, query).scanType("FULL SCAN").table(idxItem).subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 (SKIP MERGE)").scanType("FULL SCAN") + .table(order).serverWhereFilter("SERVER FILTER BY QUANTITY < 5000").end().subPlan(1) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1").scanType("FULL SCAN").table(idxSupplier) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); + } + + @Override + protected void assertSelfJoinPlan1(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String idxItem = getSchemaName() + ".idx_item"; + assertPlan(conn, query).scanType("FULL SCAN").table(item) + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"I1.item_id\" IN (\"I2.:item_id\")") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("FULL SCAN").table(idxItem).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .end(); + } + + @Override + protected void assertSelfJoinPlan2(Connection conn, String query) throws Exception { + String idxItem = getSchemaName() + ".idx_item"; + assertPlan(conn, query).scanType("FULL SCAN").table(idxItem) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverSortedBy("[\"I1.0:NAME\", \"I2.0:NAME\"]").clientSortAlgo("CLIENT MERGE SORT") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("FULL SCAN").table(idxItem).end(); + } + + @Override + protected void assertStarJoinPlan(Connection conn, String query, boolean noStarJoin) + throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String idxCustomer = getSchemaName() + ".idx_customer"; + String idxItem = getSchemaName() + ".idx_item"; + if (!noStarJoin) { + assertPlan(conn, query).scanType("FULL SCAN").table(order).subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(idxCustomer) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end().subPlan(1) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1").scanType("FULL SCAN").table(idxItem) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); + } else { + assertPlan(conn, query).scanType("FULL SCAN").table(idxItem) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"O.order_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(order) + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("FULL SCAN").table(idxCustomer) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end().end(); + } + } + + @Override + protected void assertSubJoinPlan(Connection conn, String query) throws Exception { + String customer = getTableName(conn, JOIN_CUSTOMER_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String idxItem = getSchemaName() + ".idx_item"; + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("RANGE SCAN").table(customer) + .keyRanges(" [*] - ['0000000005']").serverSortedBy("[\"C.customer_id\", \"I.0:NAME\"]") + .clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"C.customer_id\" IN (\"O.customer_id\")") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("FULL SCAN").table(order) + .serverWhereFilter("SERVER FILTER BY \"order_id\" != '000000000000003'").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN") + .table(idxItem).serverWhereFilter("SERVER FILTER BY \"NAME\" != 'T3'").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN") + .table(supplier).end().end().end(); + } + + @Override + protected void assertSubqueryAggPlan1(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String idxItem = getSchemaName() + ".idx_item"; + assertPlan(conn, query).scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [I.NAME]") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(idxItem) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); + } + + @Override + protected void assertSubqueryAggPlan2(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String idxItem = getSchemaName() + ".idx_item"; + assertPlan(conn, query).scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [O.IID]") + .clientSortAlgo("CLIENT MERGE SORT").clientSortedBy("[SUM(O.QUANTITY) DESC]").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 (SKIP MERGE)") + .scanType("FULL SCAN").table(idxItem).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .end(); + } + + @Override + protected void assertSubqueryAggPlan3(Connection conn, String query) throws Exception { + String idxItem = getSchemaName() + ".idx_item"; + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(idxItem) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverSortedBy("[O.Q DESC NULLS LAST, I.IID]").clientSortAlgo("CLIENT MERGE SORT") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0") + .scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end(); + } + + @Override + protected void assertSubqueryAggPlan4(Connection conn, String query) throws Exception { + String idxItem = getSchemaName() + ".idx_item"; + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(idxItem) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[O.Q DESC, I.IID]") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end(); + } + + @Override + protected void assertNestedSubqueriesPlan(Connection conn, String query) throws Exception { + String customer = getTableName(conn, JOIN_CUSTOMER_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String idxItem = getSchemaName() + ".idx_item"; + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("RANGE SCAN").table(customer) + .keyRanges(" [*] - ['0000000005']").serverSortedBy("[C.CID, QO.INAME]") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(order) + .serverWhereFilter("SERVER FILTER BY \"order_id\" != '000000000000003'").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN") + .table(idxItem).serverWhereFilter("SERVER FILTER BY \"NAME\" != 'T3'").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN") + .table(supplier).end().end().end(); + } + + @Override + protected void assertJoinWithLimitPlan1(Connection conn, String query) throws Exception { + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String idxItem = getSchemaName() + ".idx_item"; + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).iteratorType("SERIAL").scanType("FULL SCAN").table(supplier) + .serverRowLimit(4L).clientRowLimit(4).joinScannerLimit(4L).subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(idxItem).end() + .subPlan(1).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 1(DELAYED EVALUATION)") + .scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertJoinWithLimitPlan2(Connection conn, String query) throws Exception { + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String idxItem = getSchemaName() + ".idx_item"; + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(supplier).clientRowLimit(4) + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"S.supplier_id\" IN (\"I.0:supplier_id\")") + .joinScannerLimit(4L).subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(idxItem).end() + .subPlan(1).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1(DELAYED EVALUATION)") + .scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertSetMaxRowsPlan(Connection conn, String query) throws Exception { + String idxItem = getSchemaName() + ".idx_item"; + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + PhoenixPreparedStatement statement = + conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class); + statement.setMaxRows(4); + ExplainPlanAttributes attributes = + statement.optimizeQuery().getExplainPlan().getPlanStepsAsAttributes(); + assertPlan(attributes).scanType("FULL SCAN").table(idxItem) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientRowLimit(4).joinScannerLimit(4L) + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertJoinWithOffsetPlan1(Connection conn, String query) throws Exception { + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String idxItem = getSchemaName() + ".idx_item"; + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).iteratorType("SERIAL").scanType("FULL SCAN").table(supplier) + .serverOffset(2).serverRowLimit(3L).clientRowLimit(1).joinScannerLimit(3L).subPlanCount(2) + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN") + .table(idxItem).end().subPlan(1) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 1(DELAYED EVALUATION)").scanType("FULL SCAN") + .table(order).end(); + } + + @Override + protected void assertJoinWithOffsetPlan2(Connection conn, String query) throws Exception { + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String idxItem = getSchemaName() + ".idx_item"; + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).iteratorType("SERIAL").scanType("FULL SCAN").table(supplier) + .serverOffset(2).clientRowLimit(1) + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"S.supplier_id\" IN (\"I.0:supplier_id\")") + .joinScannerLimit(3L).subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(idxItem).end() + .subPlan(1).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1(DELAYED EVALUATION)") + .scanType("FULL SCAN").table(order).end(); } @Parameters(name = "HashJoinGlobalIndexIT_{index}") // name is used by failsafe as file name in @@ -58,278 +355,7 @@ public static synchronized Collection data() { { "CREATE INDEX \"idx_customer\" ON " + JOIN_CUSTOMER_TABLE_FULL_NAME + " (name)", "CREATE INDEX \"idx_item\" ON " + JOIN_ITEM_TABLE_FULL_NAME + " (name) INCLUDE (price, discount1, discount2, \"supplier_id\", description)", - "CREATE INDEX \"idx_supplier\" ON " + JOIN_SUPPLIER_TABLE_FULL_NAME + " (name)" }, - { - /* - * testLeftJoinWithAggregation() SELECT i.name, sum(quantity) FROM joinOrderTable o LEFT - * JOIN joinItemTable i ON o.item_id = i.item_id GROUP BY i.name ORDER BY i.name - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"I.0:NAME\"]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SCHEMA + ".idx_item\n" + " SERVER FILTER BY FIRST KEY ONLY", - /* - * testLeftJoinWithAggregation() SELECT i.item_id iid, sum(quantity) q FROM joinOrderTable o - * LEFT JOIN joinItemTable i ON o.item_id = i.item_id GROUP BY i.item_id ORDER BY q DESC" - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"I.:item_id\"]\n" + "CLIENT MERGE SORT\n" - + "CLIENT SORTED BY [SUM(O.QUANTITY) DESC]\n" + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY", - /* - * testLeftJoinWithAggregation() SELECT i.item_id iid, sum(quantity) q FROM joinItemTable i - * LEFT JOIN joinOrderTable o ON o.item_id = i.item_id GROUP BY i.item_id ORDER BY q DESC - * NULLS LAST, iid - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.item_id\"]\n" - + "CLIENT SORTED BY [SUM(O.QUANTITY) DESC NULLS LAST, \"I.item_id\"]\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME, - /* - * testRightJoinWithAggregation() SELECT i.name, sum(quantity) FROM joinOrderTable o RIGHT - * JOIN joinItemTable i ON o.item_id = i.item_id GROUP BY i.name ORDER BY i.name - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.0:NAME\"]\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME, - /* - * testRightJoinWithAggregation() SELECT i.item_id iid, sum(quantity) q FROM joinOrderTable - * o RIGHT JOIN joinItemTable i ON o.item_id = i.item_id GROUP BY i.item_id ORDER BY q DESC - * NULLS LAST, iid - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.item_id\"]\n" - + "CLIENT SORTED BY [SUM(O.QUANTITY) DESC NULLS LAST, \"I.item_id\"]\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME, - /* - * testJoinWithWildcard() SELECT * FROM joinItemTable LEFT JOIN joinSupplierTable supp ON - * joinItemTable.supplier_id = supp.supplier_id ORDER BY item_id - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SUPPLIER_TABLE_FULL_NAME, - /* - * testJoinPlanWithIndex() SELECT item.item_id, item.name, supp.supplier_id, supp.name FROM - * joinItemTable item LEFT JOIN joinSupplierTable supp ON substr(item.name, 2, 1) = - * substr(supp.name, 2, 1) AND (supp.name BETWEEN 'S1' AND 'S5') WHERE item.name BETWEEN - * 'T1' AND 'T5' - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_SCHEMA + ".idx_item ['T1'] - ['T5']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_SCHEMA - + ".idx_supplier ['S1'] - ['S5']\n" + " SERVER FILTER BY FIRST KEY ONLY", - /* - * testJoinPlanWithIndex() SELECT item.item_id, item.name, supp.supplier_id, supp.name FROM - * joinItemTable item INNER JOIN joinSupplierTable supp ON item.supplier_id = - * supp.supplier_id WHERE (item.name = 'T1' OR item.name = 'T5') AND (supp.name = 'S1' OR - * supp.name = 'S5') - */ - "CLIENT PARALLEL 1-WAY SKIP SCAN ON 2 KEYS OVER " + JOIN_SCHEMA - + ".idx_item ['T1'] - ['T5']\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY SKIP SCAN ON 2 KEYS OVER " + JOIN_SCHEMA - + ".idx_supplier ['S1'] - ['S5']\n" + " SERVER FILTER BY FIRST KEY ONLY", - /* - * testJoinWithSkipMergeOptimization() SELECT s.name FROM joinItemTable i JOIN - * joinOrderTable o ON o.item_id = i.item_id AND quantity < 5000 JOIN joinSupplierTable s ON - * i.supplier_id = s.supplier_id - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " PARALLEL INNER-JOIN TABLE 0 (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY QUANTITY < 5000\n" + " PARALLEL INNER-JOIN TABLE 1\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_supplier\n" - + " SERVER FILTER BY FIRST KEY ONLY", - /* - * testSelfJoin() SELECT i2.item_id, i1.name FROM joinItemTable i1 JOIN joinItemTable i2 ON - * i1.item_id = i2.item_id ORDER BY i1.item_id - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SCHEMA + ".idx_item\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " DYNAMIC SERVER FILTER BY \"I1.item_id\" IN (\"I2.:item_id\")", - /* - * testSelfJoin() SELECT i1.name, i2.name FROM joinItemTable i1 JOIN joinItemTable i2 ON - * i1.item_id = i2.supplier_id ORDER BY i1.name, i2.name - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [\"I1.0:NAME\", \"I2.0:NAME\"]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SCHEMA + ".idx_item", - /* - * testStarJoin() SELECT order_id, c.name, i.name iname, quantity, o.date FROM - * joinOrderTable o JOIN joinCustomerTable c ON o.customer_id = c.customer_id JOIN - * joinItemTable i ON o.item_id = i.item_id ORDER BY order_id - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SCHEMA + ".idx_customer\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " PARALLEL INNER-JOIN TABLE 1\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SCHEMA + ".idx_item\n" + " SERVER FILTER BY FIRST KEY ONLY", - /* - * testStarJoin() SELECT (*NO_STAR_JOIN*) order_id, c.name, i.name iname, quantity, o.date - * FROM joinOrderTable o JOIN joinCustomerTable c ON o.customer_id = c.customer_id JOIN - * joinItemTable i ON o.item_id = i.item_id ORDER BY order_id - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER SORTED BY [\"O.order_id\"]\n" - + "CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA - + ".idx_customer\n" + " SERVER FILTER BY FIRST KEY ONLY", - /* - * testSubJoin() SELECT * FROM joinCustomerTable c INNER JOIN (joinOrderTable o INNER JOIN - * (joinSupplierTable s RIGHT JOIN joinItemTable i ON i.supplier_id = s.supplier_id) ON - * o.item_id = i.item_id) ON c.customer_id = o.customer_id WHERE c.customer_id <= - * '0000000005' AND order_id != '000000000000003' AND i.name != 'T3' ORDER BY c.customer_id, - * i.name - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_CUSTOMER_TABLE_FULL_NAME - + " [*] - ['0000000005']\n" + " SERVER SORTED BY [\"C.customer_id\", \"I.0:NAME\"]\n" - + "CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY \"order_id\" != '000000000000003'\n" - + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY \"NAME\" != 'T3'\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " DYNAMIC SERVER FILTER BY \"C.customer_id\" IN (\"O.customer_id\")", - /* - * testJoinWithSubqueryAndAggregation() SELECT i.name, sum(quantity) FROM joinOrderTable o - * LEFT JOIN (SELECT name, item_id iid FROM joinItemTable) AS i ON o.item_id = i.iid GROUP - * BY i.name ORDER BY i.name - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [I.NAME]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SCHEMA + ".idx_item\n" + " SERVER FILTER BY FIRST KEY ONLY", - /* - * testJoinWithSubqueryAndAggregation() SELECT o.iid, sum(o.quantity) q FROM (SELECT item_id - * iid, quantity FROM joinOrderTable) AS o LEFT JOIN (SELECT item_id FROM joinItemTable) AS - * i ON o.iid = i.item_id GROUP BY o.iid ORDER BY q DESC - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [O.IID]\n" + "CLIENT MERGE SORT\n" - + "CLIENT SORTED BY [SUM(O.QUANTITY) DESC]\n" - + " PARALLEL LEFT-JOIN TABLE 0 (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY", - /* - * testJoinWithSubqueryAndAggregation() SELECT i.iid, o.q FROM (SELECT item_id iid FROM - * joinItemTable) AS i LEFT JOIN (SELECT item_id iid, sum(quantity) q FROM joinOrderTable - * GROUP BY item_id) AS o ON o.iid = i.iid ORDER BY o.q DESC NULLS LAST, i.iid - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [O.Q DESC NULLS LAST, I.IID]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]\n" - + " CLIENT MERGE SORT", - /* - * testJoinWithSubqueryAndAggregation() SELECT i.iid, o.q FROM (SELECT item_id iid, - * sum(quantity) q FROM joinOrderTable GROUP BY item_id) AS o JOIN (SELECT item_id iid FROM - * joinItemTable) AS i ON o.iid = i.iid ORDER BY o.q DESC, i.iid - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER SORTED BY [O.Q DESC, I.IID]\n" - + "CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]\n" - + " CLIENT MERGE SORT", - /* - * testNestedSubqueries() SELECT * FROM (SELECT customer_id cid, name, phone, address, - * loc_id, date FROM joinCustomerTable) AS c INNER JOIN (SELECT o.oid ooid, o.cid ocid, - * o.iid oiid, o.price * o.quantity, o.date odate, qi.iiid iiid, qi.iname iname, qi.iprice - * iprice, qi.idiscount1 idiscount1, qi.idiscount2 idiscount2, qi.isid isid, qi.idescription - * idescription, qi.ssid ssid, qi.sname sname, qi.sphone sphone, qi.saddress saddress, - * qi.sloc_id sloc_id FROM (SELECT item_id iid, customer_id cid, order_id oid, price, - * quantity, date FROM joinOrderTable) AS o INNER JOIN (SELECT i.iid iiid, i.name iname, - * i.price iprice, i.discount1 idiscount1, i.discount2 idiscount2, i.sid isid, i.description - * idescription, s.sid ssid, s.name sname, s.phone sphone, s.address saddress, s.loc_id - * sloc_id FROM (SELECT supplier_id sid, name, phone, address, loc_id FROM - * joinSupplierTable) AS s RIGHT JOIN (SELECT item_id iid, name, price, discount1, - * discount2, supplier_id sid, description FROM joinItemTable) AS i ON i.sid = s.sid) as qi - * ON o.iid = qi.iiid) as qo ON c.cid = qo.ocid WHERE c.cid <= '0000000005' AND qo.ooid != - * '000000000000003' AND qo.iname != 'T3' ORDER BY c.cid, qo.iname - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_CUSTOMER_TABLE_FULL_NAME - + " [*] - ['0000000005']\n" + " SERVER SORTED BY [C.CID, QO.INAME]\n" - + "CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY \"order_id\" != '000000000000003'\n" - + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY \"NAME\" != 'T3'\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SUPPLIER_TABLE_FULL_NAME, - /* - * testJoinWithLimit() SELECT order_id, i.name, s.name, s.address, quantity FROM - * joinSupplierTable s LEFT JOIN joinItemTable i ON i.supplier_id = s.supplier_id LEFT JOIN - * joinOrderTable o ON o.item_id = i.item_id LIMIT 4 - */ - "CLIENT SERIAL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " SERVER 4 ROW LIMIT\n" + "CLIENT 4 ROW LIMIT\n" + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " PARALLEL LEFT-JOIN TABLE 1(DELAYED EVALUATION)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " JOIN-SCANNER 4 ROW LIMIT", - /* - * testJoinWithLimit() SELECT order_id, i.name, s.name, s.address, quantity FROM - * joinSupplierTable s JOIN joinItemTable i ON i.supplier_id = s.supplier_id JOIN - * joinOrderTable o ON o.item_id = i.item_id LIMIT 4 - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + "CLIENT 4 ROW LIMIT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " PARALLEL INNER-JOIN TABLE 1(DELAYED EVALUATION)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " DYNAMIC SERVER FILTER BY \"S.supplier_id\" IN (\"I.0:supplier_id\")\n" - + " JOIN-SCANNER 4 ROW LIMIT", - /* - * testJoinWithSetMaxRows() statement.setMaxRows(4); SELECT order_id, i.name, quantity FROM - * joinItemTable i JOIN joinOrderTable o ON o.item_id = i.item_id; SELECT o.order_id, - * i.name, o.quantity FROM joinItemTable i JOIN (SELECT order_id, item_id, quantity FROM - * joinOrderTable) o ON o.item_id = i.item_id; - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT 4 ROW LIMIT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME + "\n" + " JOIN-SCANNER 4 ROW LIMIT", - /* - * testJoinWithLimit() SELECT order_id, i.name, s.name, s.address, quantity FROM - * joinSupplierTable s LEFT JOIN joinItemTable i ON i.supplier_id = s.supplier_id LEFT JOIN - * joinOrderTable o ON o.item_id = i.item_id LIMIT 1 OFFSET 2 - */ - "CLIENT SERIAL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " SERVER OFFSET 2\n" + " SERVER 3 ROW LIMIT\n" + "CLIENT 1 ROW LIMIT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SCHEMA + ".idx_item\n" + " PARALLEL LEFT-JOIN TABLE 1(DELAYED EVALUATION)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " JOIN-SCANNER 3 ROW LIMIT", - /* - * testJoinWithLimit() SELECT order_id, i.name, s.name, s.address, quantity FROM - * joinSupplierTable s JOIN joinItemTable i ON i.supplier_id = s.supplier_id JOIN - * joinOrderTable o ON o.item_id = i.item_id LIMIT 1 OFFSET 2 - */ - "CLIENT SERIAL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " SERVER OFFSET 2\n" + "CLIENT 1 ROW LIMIT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " PARALLEL INNER-JOIN TABLE 1(DELAYED EVALUATION)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " DYNAMIC SERVER FILTER BY \"S.supplier_id\" IN (\"I.0:supplier_id\")\n" - + " JOIN-SCANNER 3 ROW LIMIT", } }); + "CREATE INDEX \"idx_supplier\" ON " + JOIN_SUPPLIER_TABLE_FULL_NAME + " (name)" } }); return testCases; } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinIT.java index cfbc0c94504..7105a29dd55 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinIT.java @@ -40,7 +40,6 @@ import org.apache.phoenix.exception.SQLExceptionCode; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -48,10 +47,327 @@ @RunWith(Parameterized.class) public abstract class HashJoinIT extends BaseJoinIT { - public HashJoinIT(String[] indexDDL, String[] plans) { - super(indexDDL, plans); + public HashJoinIT(String[] indexDDL) { + super(indexDDL); } + /* + * The expected EXPLAIN plan for each of the queries below differs per index configuration, so + * each concrete subclass supplies the attribute-based assertions via these hooks. + */ + + /** + * {@link #testLeftJoinWithAggregation()}: + * + *
+   * SELECT i.name, sum(quantity) FROM joinOrderTable o
+   *   LEFT JOIN joinItemTable i ON o.item_id = i.item_id
+   *   GROUP BY i.name ORDER BY i.name
+   * 
+ */ + protected abstract void assertLeftJoinWithAggPlan1(Connection conn, String query) + throws Exception; + + /** + * {@link #testLeftJoinWithAggregation()}: + * + *
+   * SELECT i.item_id iid, sum(quantity) q FROM joinOrderTable o
+   *   LEFT JOIN joinItemTable i ON o.item_id = i.item_id
+   *   GROUP BY i.item_id ORDER BY q DESC
+   * 
+ */ + protected abstract void assertLeftJoinWithAggPlan2(Connection conn, String query) + throws Exception; + + /** + * {@link #testLeftJoinWithAggregation()}: + * + *
+   * SELECT i.item_id iid, sum(quantity) q FROM joinItemTable i
+   *   LEFT JOIN joinOrderTable o ON o.item_id = i.item_id
+   *   GROUP BY i.item_id ORDER BY q DESC NULLS LAST, iid
+   * 
+ */ + protected abstract void assertLeftJoinWithAggPlan3(Connection conn, String query) + throws Exception; + + /** + * {@link #testRightJoinWithAggregation()}: + * + *
+   * SELECT i.name, sum(quantity) FROM joinOrderTable o
+   *   RIGHT JOIN joinItemTable i ON o.item_id = i.item_id
+   *   GROUP BY i.name ORDER BY i.name
+   * 
+ */ + protected abstract void assertRightJoinWithAggPlan1(Connection conn, String query) + throws Exception; + + /** + * {@link #testRightJoinWithAggregation()}: + * + *
+   * SELECT i.item_id iid, sum(quantity) q FROM joinOrderTable o
+   *   RIGHT JOIN joinItemTable i ON o.item_id = i.item_id
+   *   GROUP BY i.item_id ORDER BY q DESC NULLS LAST, iid
+   * 
+ */ + protected abstract void assertRightJoinWithAggPlan2(Connection conn, String query) + throws Exception; + + /** + * {@link #testJoinWithWildcard()}: + * + *
+   * SELECT * FROM joinItemTable
+   *   LEFT JOIN joinSupplierTable supp ON joinItemTable.supplier_id = supp.supplier_id
+   *   ORDER BY item_id
+   * 
+ */ + protected abstract void assertJoinWithWildcardPlan(Connection conn, String query) + throws Exception; + + /** + * {@link #testJoinPlanWithIndex()}: + * + *
+   * SELECT item.item_id, item.name, supp.supplier_id, supp.name FROM joinItemTable item
+   *   LEFT JOIN joinSupplierTable supp
+   *     ON substr(item.name, 2, 1) = substr(supp.name, 2, 1)
+   *     AND (supp.name BETWEEN 'S1' AND 'S5')
+   *   WHERE item.name BETWEEN 'T1' AND 'T5'
+   * 
+ */ + protected abstract void assertJoinPlanWithIndexPlan1(Connection conn, String query) + throws Exception; + + /** + * {@link #testJoinPlanWithIndex()}: + * + *
+   * SELECT item.item_id, item.name, supp.supplier_id, supp.name FROM joinItemTable item
+   *   INNER JOIN joinSupplierTable supp ON item.supplier_id = supp.supplier_id
+   *   WHERE (item.name = 'T1' OR item.name = 'T5')
+   *     AND (supp.name = 'S1' OR supp.name = 'S5')
+   * 
+ */ + protected abstract void assertJoinPlanWithIndexPlan2(Connection conn, String query) + throws Exception; + + /** + * {@link #testJoinWithSkipMergeOptimization()}: + * + *
+   * SELECT s.name FROM joinItemTable i
+   *   JOIN joinOrderTable o ON o.item_id = i.item_id AND quantity < 5000
+   *   JOIN joinSupplierTable s ON i.supplier_id = s.supplier_id
+   * 
+ */ + protected abstract void assertSkipMergeOptimizationPlan(Connection conn, String query) + throws Exception; + + /** + * {@link #testSelfJoin()}: + * + *
+   * SELECT i2.item_id, i1.name FROM joinItemTable i1
+   *   JOIN joinItemTable i2 ON i1.item_id = i2.item_id
+   *   ORDER BY i1.item_id
+   * 
+ */ + protected abstract void assertSelfJoinPlan1(Connection conn, String query) throws Exception; + + /** + * {@link #testSelfJoin()}: + * + *
+   * SELECT i1.name, i2.name FROM joinItemTable i1
+   *   JOIN joinItemTable i2 ON i1.item_id = i2.supplier_id
+   *   ORDER BY i1.name, i2.name
+   * 
+ */ + protected abstract void assertSelfJoinPlan2(Connection conn, String query) throws Exception; + + /** + * {@link #testStarJoin()}. {@code noStarJoin} selects the {@code NO_STAR_JOIN} variant. + * + *
+   * SELECT order_id, c.name, i.name iname, quantity, o.date FROM joinOrderTable o
+   *   JOIN joinCustomerTable c ON o.customer_id = c.customer_id
+   *   JOIN joinItemTable i ON o.item_id = i.item_id
+   *   ORDER BY order_id
+   *
+   * -- noStarJoin variant adds the NO_STAR_JOIN hint:
+   * SELECT /*+ NO_STAR_JOIN*/ order_id, c.name, i.name iname, quantity, o.date
+   *   FROM joinOrderTable o
+   *   JOIN joinCustomerTable c ON o.customer_id = c.customer_id
+   *   JOIN joinItemTable i ON o.item_id = i.item_id
+   *   ORDER BY order_id
+   * 
+ */ + protected abstract void assertStarJoinPlan(Connection conn, String query, boolean noStarJoin) + throws Exception; + + /** + * {@link #testSubJoin()}: + * + *
+   * SELECT * FROM joinCustomerTable c
+   *   INNER JOIN (joinOrderTable o
+   *     INNER JOIN (joinSupplierTable s
+   *       RIGHT JOIN joinItemTable i ON i.supplier_id = s.supplier_id)
+   *     ON o.item_id = i.item_id)
+   *   ON c.customer_id = o.customer_id
+   *   WHERE c.customer_id <= '0000000005'
+   *     AND order_id != '000000000000003' AND i.name != 'T3'
+   *   ORDER BY c.customer_id, i.name
+   * 
+ */ + protected abstract void assertSubJoinPlan(Connection conn, String query) throws Exception; + + /** + * {@link #testJoinWithSubqueryAndAggregation()}: + * + *
+   * SELECT i.name, sum(quantity) FROM joinOrderTable o
+   *   LEFT JOIN (SELECT name, item_id iid FROM joinItemTable) AS i ON o.item_id = i.iid
+   *   GROUP BY i.name ORDER BY i.name
+   * 
+ */ + protected abstract void assertSubqueryAggPlan1(Connection conn, String query) throws Exception; + + /** + * {@link #testJoinWithSubqueryAndAggregation()}: + * + *
+   * SELECT o.iid, sum(o.quantity) q
+   *   FROM (SELECT item_id iid, quantity FROM joinOrderTable) AS o
+   *   LEFT JOIN (SELECT item_id FROM joinItemTable) AS i ON o.iid = i.item_id
+   *   GROUP BY o.iid ORDER BY q DESC
+   * 
+ */ + protected abstract void assertSubqueryAggPlan2(Connection conn, String query) throws Exception; + + /** + * {@link #testJoinWithSubqueryAndAggregation()}: + * + *
+   * SELECT i.iid, o.q
+   *   FROM (SELECT item_id iid FROM joinItemTable) AS i
+   *   LEFT JOIN (SELECT item_id iid, sum(quantity) q FROM joinOrderTable GROUP BY item_id) AS o
+   *     ON o.iid = i.iid
+   *   ORDER BY o.q DESC NULLS LAST, i.iid
+   * 
+ */ + protected abstract void assertSubqueryAggPlan3(Connection conn, String query) throws Exception; + + /** + * {@link #testJoinWithSubqueryAndAggregation()}: + * + *
+   * SELECT i.iid, o.q
+   *   FROM (SELECT item_id iid, sum(quantity) q FROM joinOrderTable GROUP BY item_id) AS o
+   *   JOIN (SELECT item_id iid FROM joinItemTable) AS i ON o.iid = i.iid
+   *   ORDER BY o.q DESC, i.iid
+   * 
+ */ + protected abstract void assertSubqueryAggPlan4(Connection conn, String query) throws Exception; + + /** + * {@link #testNestedSubqueries()}: + * + *
+   * SELECT * FROM
+   *   (SELECT customer_id cid, name, phone, address, loc_id, date FROM joinCustomerTable) AS c
+   *   INNER JOIN
+   *   (SELECT o.oid ooid, o.cid ocid, o.iid oiid, o.price * o.quantity, o.date odate,
+   *           qi.iiid iiid, qi.iname iname, qi.iprice iprice, qi.idiscount1 idiscount1,
+   *           qi.idiscount2 idiscount2, qi.isid isid, qi.idescription idescription,
+   *           qi.ssid ssid, qi.sname sname, qi.sphone sphone, qi.saddress saddress,
+   *           qi.sloc_id sloc_id
+   *      FROM (SELECT item_id iid, customer_id cid, order_id oid, price, quantity, date
+   *              FROM joinOrderTable) AS o
+   *      INNER JOIN
+   *      (SELECT i.iid iiid, i.name iname, i.price iprice, i.discount1 idiscount1,
+   *              i.discount2 idiscount2, i.sid isid, i.description idescription,
+   *              s.sid ssid, s.name sname, s.phone sphone, s.address saddress, s.loc_id sloc_id
+   *         FROM (SELECT supplier_id sid, name, phone, address, loc_id FROM joinSupplierTable) AS s
+   *         RIGHT JOIN (SELECT item_id iid, name, price, discount1, discount2, supplier_id sid,
+   *                            description FROM joinItemTable) AS i ON i.sid = s.sid) as qi
+   *      ON o.iid = qi.iiid) as qo
+   *   ON c.cid = qo.ocid
+   *   WHERE c.cid <= '0000000005' AND qo.ooid != '000000000000003' AND qo.iname != 'T3'
+   *   ORDER BY c.cid, qo.iname
+   * 
+ */ + protected abstract void assertNestedSubqueriesPlan(Connection conn, String query) + throws Exception; + + /** + * {@link #testJoinWithLimit()}: + * + *
+   * SELECT order_id, i.name, s.name, s.address, quantity FROM joinSupplierTable s
+   *   LEFT JOIN joinItemTable i ON i.supplier_id = s.supplier_id
+   *   LEFT JOIN joinOrderTable o ON o.item_id = i.item_id
+   *   LIMIT 4
+   * 
+ */ + protected abstract void assertJoinWithLimitPlan1(Connection conn, String query) throws Exception; + + /** + * {@link #testJoinWithLimit()}: + * + *
+   * SELECT order_id, i.name, s.name, s.address, quantity FROM joinSupplierTable s
+   *   JOIN joinItemTable i ON i.supplier_id = s.supplier_id
+   *   JOIN joinOrderTable o ON o.item_id = i.item_id
+   *   LIMIT 4
+   * 
+ */ + protected abstract void assertJoinWithLimitPlan2(Connection conn, String query) throws Exception; + + /** + * Assert the EXPLAIN plan for {@link #testJoinWithSetMaxRows()} (with a max-rows limit of 4). The + * {@code CLIENT 4 ROW LIMIT} comes from {@link java.sql.Statement#setMaxRows(int)} rather than + * the SQL, so subclasses must compile via a {@code PhoenixPreparedStatement}. + * + *
+   * statement.setMaxRows(4);
+   * SELECT order_id, i.name, quantity FROM joinItemTable i
+   *   JOIN joinOrderTable o ON o.item_id = i.item_id
+   *
+   * SELECT o.order_id, i.name, o.quantity FROM joinItemTable i
+   *   JOIN (SELECT order_id, item_id, quantity FROM joinOrderTable) o ON o.item_id = i.item_id
+   * 
+ */ + protected abstract void assertSetMaxRowsPlan(Connection conn, String query) throws Exception; + + /** + * {@link #testJoinWithOffset()}: + * + *
+   * SELECT order_id, i.name, s.name, s.address, quantity FROM joinSupplierTable s
+   *   LEFT JOIN joinItemTable i ON i.supplier_id = s.supplier_id
+   *   LEFT JOIN joinOrderTable o ON o.item_id = i.item_id
+   *   LIMIT 1 OFFSET 2
+   * 
+ */ + protected abstract void assertJoinWithOffsetPlan1(Connection conn, String query) throws Exception; + + /** + * {@link #testJoinWithOffset()}: + * + *
+   * SELECT order_id, i.name, s.name, s.address, quantity FROM joinSupplierTable s
+   *   JOIN joinItemTable i ON i.supplier_id = s.supplier_id
+   *   JOIN joinOrderTable o ON o.item_id = i.item_id
+   *   LIMIT 1 OFFSET 2
+   * 
+ */ + protected abstract void assertJoinWithOffsetPlan2(Connection conn, String query) throws Exception; + public void testInnerJoin(boolean renamePhysicalTable) throws Exception { Connection conn = getConnection(); String tableName1 = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); @@ -536,8 +852,7 @@ public void testStarJoin() throws Exception { assertFalse(rs.next()); if (i < 4) { - rs = conn.createStatement().executeQuery("EXPLAIN " + query[i]); - assertPlansEqual(plans[11 + (i / 2)], QueryUtil.getExplainPlan(rs)); + assertStarJoinPlan(conn, query[i], i >= 2); } } } finally { @@ -575,8 +890,7 @@ public void testLeftJoinWithAggregation() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query1); - assertPlansEqual(plans[0], QueryUtil.getExplainPlan(rs)); + assertLeftJoinWithAggPlan1(conn, query1); statement = conn.prepareStatement(query2); rs = statement.executeQuery(); @@ -595,8 +909,7 @@ public void testLeftJoinWithAggregation() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query2); - assertPlansEqual(plans[1], QueryUtil.getExplainPlan(rs)); + assertLeftJoinWithAggPlan2(conn, query2); statement = conn.prepareStatement(query3); rs = statement.executeQuery(); @@ -624,8 +937,7 @@ public void testLeftJoinWithAggregation() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query3); - assertPlansEqual(plans[2], QueryUtil.getExplainPlan(rs)); + assertLeftJoinWithAggPlan3(conn, query3); } finally { conn.close(); } @@ -668,8 +980,7 @@ public void testRightJoinWithAggregation() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query1); - assertPlansEqual(plans[3], QueryUtil.getExplainPlan(rs)); + assertRightJoinWithAggPlan1(conn, query1); statement = conn.prepareStatement(query2); rs = statement.executeQuery(); @@ -697,8 +1008,7 @@ public void testRightJoinWithAggregation() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query2); - assertPlansEqual(plans[4], QueryUtil.getExplainPlan(rs)); + assertRightJoinWithAggPlan2(conn, query2); } finally { conn.close(); } @@ -1181,8 +1491,7 @@ public void testJoinWithWildcard() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - assertPlansEqual(plans[5], QueryUtil.getExplainPlan(rs)); + assertJoinWithWildcardPlan(conn, query); } finally { conn.close(); } @@ -1490,8 +1799,7 @@ public void testJoinPlanWithIndex() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query1); - assertPlansEqual(plans[6], QueryUtil.getExplainPlan(rs)); + assertJoinPlanWithIndexPlan1(conn, query1); statement = conn.prepareStatement(query2); rs = statement.executeQuery(); @@ -1508,8 +1816,7 @@ public void testJoinPlanWithIndex() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query2); - assertPlansEqual(plans[7], QueryUtil.getExplainPlan(rs)); + assertJoinPlanWithIndexPlan2(conn, query2); } finally { conn.close(); } @@ -1537,8 +1844,7 @@ public void testJoinWithSkipMergeOptimization() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - assertPlansEqual(plans[8], QueryUtil.getExplainPlan(rs)); + assertSkipMergeOptimizationPlan(conn, query); } finally { conn.close(); } @@ -1581,8 +1887,7 @@ public void testSelfJoin() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query1); - assertPlansEqual(plans[9], QueryUtil.getExplainPlan(rs)); + assertSelfJoinPlan1(conn, query1); statement = conn.prepareStatement(query2); rs = statement.executeQuery(); @@ -1607,8 +1912,7 @@ public void testSelfJoin() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query2); - assertPlansEqual(plans[10], QueryUtil.getExplainPlan(rs)); + assertSelfJoinPlan2(conn, query2); } finally { conn.close(); } @@ -1889,8 +2193,7 @@ public void testSubJoin() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query2); - assertPlansEqual(plans[13], QueryUtil.getExplainPlan(rs)); + assertSubJoinPlan(conn, query2); } finally { conn.close(); } @@ -2057,8 +2360,7 @@ public void testJoinWithSubqueryAndAggregation() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query1); - assertPlansEqual(plans[14], QueryUtil.getExplainPlan(rs)); + assertSubqueryAggPlan1(conn, query1); statement = conn.prepareStatement(query2); rs = statement.executeQuery(); @@ -2077,8 +2379,7 @@ public void testJoinWithSubqueryAndAggregation() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query2); - assertPlansEqual(plans[15], QueryUtil.getExplainPlan(rs)); + assertSubqueryAggPlan2(conn, query2); statement = conn.prepareStatement(query3); rs = statement.executeQuery(); @@ -2106,8 +2407,7 @@ public void testJoinWithSubqueryAndAggregation() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query3); - assertPlansEqual(plans[16], QueryUtil.getExplainPlan(rs)); + assertSubqueryAggPlan3(conn, query3); statement = conn.prepareStatement(query4); rs = statement.executeQuery(); @@ -2126,8 +2426,7 @@ public void testJoinWithSubqueryAndAggregation() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query4); - assertPlansEqual(plans[17], QueryUtil.getExplainPlan(rs)); + assertSubqueryAggPlan4(conn, query4); } finally { conn.close(); } @@ -2263,8 +2562,7 @@ public void testNestedSubqueries() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query2); - assertPlansEqual(plans[18], QueryUtil.getExplainPlan(rs)); + assertNestedSubqueriesPlan(conn, query2); } finally { conn.close(); } @@ -2315,8 +2613,7 @@ public void testJoinWithLimit() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query1); - assertPlansEqual(plans[19], QueryUtil.getExplainPlan(rs)); + assertJoinWithLimitPlan1(conn, query1); statement = conn.prepareStatement(query2); rs = statement.executeQuery(); @@ -2347,8 +2644,7 @@ public void testJoinWithLimit() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query2); - assertPlansEqual(plans[20], QueryUtil.getExplainPlan(rs)); + assertJoinWithLimitPlan2(conn, query2); } finally { conn.close(); } @@ -2381,8 +2677,7 @@ public void testJoinWithOffset() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query1); - assertPlansEqual(plans[22], QueryUtil.getExplainPlan(rs)); + assertJoinWithOffsetPlan1(conn, query1); statement = conn.prepareStatement(query2); rs = statement.executeQuery(); @@ -2395,8 +2690,7 @@ public void testJoinWithOffset() throws Exception { assertEquals(rs.getInt(5), 5000); assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query2); - assertPlansEqual(plans[23], QueryUtil.getExplainPlan(rs)); + assertJoinWithOffsetPlan2(conn, query2); } finally { conn.close(); } @@ -2499,8 +2793,7 @@ public void testJoinWithSetMaxRows() throws Exception { assertFalse(rs.next()); - rs = statement.executeQuery("EXPLAIN " + query); - assertPlansEqual(plans[21], QueryUtil.getExplainPlan(rs)); + assertSetMaxRowsPlan(conn, query); } } finally { conn.close(); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinLocalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinLocalIndexIT.java index 0dbe28fe898..a44ac276e9f 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinLocalIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinLocalIndexIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end.join; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -30,9 +31,11 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.end2end.ParallelStatsDisabledTest; +import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; +import org.apache.phoenix.util.SchemaUtil; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -62,8 +65,354 @@ protected Map getTableNameMap() { return virtualNameToRealNameMap; } - public HashJoinLocalIndexIT(String[] indexDDL, String[] plans) { - super(indexDDL, plans); + public HashJoinLocalIndexIT(String[] indexDDL) { + super(indexDDL); + } + + @Override + protected void assertLeftJoinWithAggPlan1(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + assertPlan(conn, query).scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"I.0:NAME\"]") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT") + .end(); + } + + @Override + protected void assertLeftJoinWithAggPlan2(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + assertPlan(conn, query).scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"I.:item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").clientSortedBy("[SUM(O.QUANTITY) DESC]").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT") + .end(); + } + + @Override + protected void assertLeftJoinWithAggPlan3(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.item_id\"]") + .clientSortedBy("[SUM(O.QUANTITY) DESC NULLS LAST, \"I.item_id\"]").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertRightJoinWithAggPlan1(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("RANGE SCAN").table(itemIndex + "(" + item + ")") + .keyRanges(" [1]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.0:NAME\"]") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertRightJoinWithAggPlan2(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.item_id\"]") + .clientSortedBy("[SUM(O.QUANTITY) DESC NULLS LAST, \"I.item_id\"]").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertJoinWithWildcardPlan(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item).subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(supplier) + .end(); + } + + @Override + protected void assertJoinPlanWithIndexPlan1(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String supplierIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_SUPPLIER_INDEX); + assertPlan(conn, query).scanType("RANGE SCAN").table(itemIndex + "(" + item + ")") + .keyRanges(" [1,'T1'] - [1,'T5']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("RANGE SCAN") + .table(supplierIndex + "(" + supplier + ")").keyRanges(" [1,'S1'] - [1,'S5']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT") + .end(); + } + + @Override + protected void assertJoinPlanWithIndexPlan2(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String supplierIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_SUPPLIER_INDEX); + assertPlan(conn, query).scanType("SKIP SCAN ON 2 KEYS").table(itemIndex + "(" + item + ")") + .keyRanges(" [1,'T1'] - [1,'T5']").clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("SKIP SCAN ON 2 KEYS") + .table(supplierIndex + "(" + supplier + ")").keyRanges(" [1,'S1'] - [1,'S5']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT") + .end(); + } + + @Override + protected void assertSkipMergeOptimizationPlan(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String supplierIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_SUPPLIER_INDEX); + assertPlan(conn, query).scanType("RANGE SCAN").table(itemIndex + "(" + item + ")") + .keyRanges(" [1]").clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"I.:item_id\" IN (\"O.item_id\")") + .subPlanCount(2).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 (SKIP MERGE)") + .scanType("FULL SCAN").table(order).serverWhereFilter("SERVER FILTER BY QUANTITY < 5000") + .end().subPlan(1).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1").scanType("RANGE SCAN") + .table(supplierIndex + "(" + supplier + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT") + .end(); + } + + @Override + protected void assertSelfJoinPlan1(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + assertPlan(conn, query).scanType("FULL SCAN").table(item) + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"I1.item_id\" IN (\"I2.:item_id\")") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT") + .end(); + } + + @Override + protected void assertSelfJoinPlan2(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + assertPlan(conn, query).scanType("RANGE SCAN").table(itemIndex + "(" + item + ")") + .keyRanges(" [1]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverSortedBy("[\"I1.0:NAME\", \"I2.0:NAME\"]").clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"I1.:item_id\" IN (\"I2.0:supplier_id\")") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .clientSortAlgo("CLIENT MERGE SORT").end(); + } + + @Override + protected void assertStarJoinPlan(Connection conn, String query, boolean noStarJoin) + throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + String customer = getTableName(conn, JOIN_CUSTOMER_TABLE_FULL_NAME); + String customerIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_CUSTOMER_INDEX); + if (!noStarJoin) { + assertPlan(conn, query).scanType("FULL SCAN").table(order).subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("RANGE SCAN") + .table(customerIndex + "(" + customer + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT") + .end().subPlan(1).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1").scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT") + .end(); + } else { + assertPlan(conn, query).scanType("RANGE SCAN").table(itemIndex + "(" + item + ")") + .keyRanges(" [1]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverSortedBy("[\"O.order_id\"]").clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"I.:item_id\" IN (\"O.item_id\")") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("FULL SCAN").table(order).subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("RANGE SCAN") + .table(customerIndex + "(" + customer + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT") + .end().end(); + } + } + + @Override + protected void assertSubJoinPlan(Connection conn, String query) throws Exception { + String customer = getTableName(conn, JOIN_CUSTOMER_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("RANGE SCAN").table(customer) + .keyRanges(" [*] - ['0000000005']").serverSortedBy("[\"C.customer_id\", \"I.0:NAME\"]") + .clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"C.customer_id\" IN (\"O.customer_id\")") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("FULL SCAN").table(order) + .serverWhereFilter("SERVER FILTER BY \"order_id\" != '000000000000003'").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY \"NAME\" != 'T3'").clientSortAlgo("CLIENT MERGE SORT") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0") + .scanType("FULL SCAN").table(supplier).end().end().end(); + } + + @Override + protected void assertSubqueryAggPlan1(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + assertPlan(conn, query).scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [I.NAME]") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT") + .end(); + } + + @Override + protected void assertSubqueryAggPlan2(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + assertPlan(conn, query).scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [O.IID]") + .clientSortAlgo("CLIENT MERGE SORT").clientSortedBy("[SUM(O.QUANTITY) DESC]").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 (SKIP MERGE)") + .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT") + .end(); + } + + @Override + protected void assertSubqueryAggPlan3(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("RANGE SCAN").table(itemIndex + "(" + item + ")") + .keyRanges(" [1]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverSortedBy("[O.Q DESC NULLS LAST, I.IID]").clientSortAlgo("CLIENT MERGE SORT") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0") + .scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end(); + } + + @Override + protected void assertSubqueryAggPlan4(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("RANGE SCAN").table(itemIndex + "(" + item + ")") + .keyRanges(" [1]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverSortedBy("[O.Q DESC, I.IID]").clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN") + .table(order).serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end(); + } + + @Override + protected void assertNestedSubqueriesPlan(Connection conn, String query) throws Exception { + String customer = getTableName(conn, JOIN_CUSTOMER_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("RANGE SCAN").table(customer) + .keyRanges(" [*] - ['0000000005']").serverSortedBy("[C.CID, QO.INAME]") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(order) + .serverWhereFilter("SERVER FILTER BY \"order_id\" != '000000000000003'").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY \"NAME\" != 'T3'").clientSortAlgo("CLIENT MERGE SORT") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0") + .scanType("FULL SCAN").table(supplier).end().end().end(); + } + + @Override + protected void assertJoinWithLimitPlan1(Connection conn, String query) throws Exception { + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).iteratorType("SERIAL").scanType("FULL SCAN").table(supplier) + .serverRowLimit(4L).clientRowLimit(4).joinScannerLimit(4L).subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]").clientSortAlgo("CLIENT MERGE SORT") + .end().subPlan(1).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 1(DELAYED EVALUATION)") + .scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertJoinWithLimitPlan2(Connection conn, String query) throws Exception { + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(supplier).clientRowLimit(4) + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"S.supplier_id\" IN (\"I.0:supplier_id\")") + .joinScannerLimit(4L).subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]").clientSortAlgo("CLIENT MERGE SORT") + .end().subPlan(1).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1(DELAYED EVALUATION)") + .scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertSetMaxRowsPlan(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + PhoenixPreparedStatement statement = + conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class); + statement.setMaxRows(4); + ExplainPlanAttributes attributes = + statement.optimizeQuery().getExplainPlan().getPlanStepsAsAttributes(); + assertPlan(attributes).scanType("RANGE SCAN").table(itemIndex + "(" + item + ")") + .keyRanges(" [1]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .clientSortAlgo("CLIENT MERGE SORT").clientRowLimit(4) + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"I.:item_id\" IN (\"O.item_id\")") + .joinScannerLimit(4L).subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertJoinWithOffsetPlan1(Connection conn, String query) throws Exception { + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).iteratorType("SERIAL").scanType("FULL SCAN").table(supplier) + .serverOffset(2).serverRowLimit(3L).clientRowLimit(1).joinScannerLimit(3L).subPlanCount(2) + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]").clientSortAlgo("CLIENT MERGE SORT") + .end().subPlan(1).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 1(DELAYED EVALUATION)") + .scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertJoinWithOffsetPlan2(Connection conn, String query) throws Exception { + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).iteratorType("SERIAL").scanType("FULL SCAN").table(supplier) + .serverOffset(2).clientRowLimit(1) + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"S.supplier_id\" IN (\"I.0:supplier_id\")") + .joinScannerLimit(3L).subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]").clientSortAlgo("CLIENT MERGE SORT") + .end().subPlan(1).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1(DELAYED EVALUATION)") + .scanType("FULL SCAN").table(order).end(); } @Parameters(name = "HashJoinLocalIndexIT_{index}") // name is used by failsafe as file name in @@ -76,347 +425,7 @@ public static synchronized Collection data() { "CREATE LOCAL INDEX " + JOIN_ITEM_INDEX + " ON " + JOIN_ITEM_TABLE_FULL_NAME + " (name) " + "INCLUDE (price, discount1, discount2, \"supplier_id\", description)", "CREATE LOCAL INDEX " + JOIN_SUPPLIER_INDEX + " ON " + JOIN_SUPPLIER_TABLE_FULL_NAME - + " (name)" }, - { - /* - * testLeftJoinWithAggregation() SELECT i.name, sum(quantity) FROM joinOrderTable o LEFT - * JOIN joinItemTable i ON o.item_id = i.item_id GROUP BY i.name ORDER BY i.name - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"I.0:NAME\"]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_ITEM_INDEX_FULL_NAME + "(" + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " CLIENT MERGE SORT", - /* - * testLeftJoinWithAggregation() SELECT i.item_id iid, sum(quantity) q FROM joinOrderTable o - * LEFT JOIN joinItemTable i ON o.item_id = i.item_id GROUP BY i.item_id ORDER BY q DESC" - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"I.:item_id\"]\n" + "CLIENT MERGE SORT\n" - + "CLIENT SORTED BY [SUM(O.QUANTITY) DESC]\n" + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " CLIENT MERGE SORT", - /* - * testLeftJoinWithAggregation() SELECT i.item_id iid, sum(quantity) q FROM joinItemTable i - * LEFT JOIN joinOrderTable o ON o.item_id = i.item_id GROUP BY i.item_id ORDER BY q DESC - * NULLS LAST, iid - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.item_id\"]\n" - + "CLIENT SORTED BY [SUM(O.QUANTITY) DESC NULLS LAST, \"I.item_id\"]\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME, - /* - * testRightJoinWithAggregation() SELECT i.name, sum(quantity) FROM joinOrderTable o RIGHT - * JOIN joinItemTable i ON o.item_id = i.item_id GROUP BY i.name ORDER BY i.name - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.0:NAME\"]\n" - + "CLIENT MERGE SORT\n" + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME, - /* - * testRightJoinWithAggregation() SELECT i.item_id iid, sum(quantity) q FROM joinOrderTable - * o RIGHT JOIN joinItemTable i ON o.item_id = i.item_id GROUP BY i.item_id ORDER BY q DESC - * NULLS LAST, iid - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.item_id\"]\n" - + "CLIENT SORTED BY [SUM(O.QUANTITY) DESC NULLS LAST, \"I.item_id\"]\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME, - /* - * testJoinWithWildcard() SELECT * FROM joinItemTable LEFT JOIN joinSupplierTable supp ON - * joinItemTable.supplier_id = supp.supplier_id ORDER BY item_id - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SUPPLIER_TABLE_FULL_NAME, - /* - * testJoinPlanWithIndex() SELECT item.item_id, item.name, supp.supplier_id, supp.name FROM - * joinItemTable item LEFT JOIN joinSupplierTable supp ON substr(item.name, 2, 1) = - * substr(supp.name, 2, 1) AND (supp.name BETWEEN 'S1' AND 'S5') WHERE item.name BETWEEN - * 'T1' AND 'T5' - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1,'T1'] - [1,'T5']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_SUPPLIER_INDEX_FULL_NAME + "(" + JOIN_SUPPLIER_TABLE_FULL_NAME - + ") [1,'S1'] - [1,'S5']\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " CLIENT MERGE SORT", - /* - * testJoinPlanWithIndex() SELECT item.item_id, item.name, supp.supplier_id, supp.name FROM - * joinItemTable item INNER JOIN joinSupplierTable supp ON item.supplier_id = - * supp.supplier_id WHERE (item.name = 'T1' OR item.name = 'T5') AND (supp.name = 'S1' OR - * supp.name = 'S5') - */ - "CLIENT PARALLEL 1-WAY SKIP SCAN ON 2 KEYS OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1,'T1'] - [1,'T5']\n" + "CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY SKIP SCAN ON 2 KEYS OVER " - + JOIN_SUPPLIER_INDEX_FULL_NAME + "(" + JOIN_SUPPLIER_TABLE_FULL_NAME - + ") [1,'S1'] - [1,'S5']\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " CLIENT MERGE SORT", - /* - * testJoinWithSkipMergeOptimization() SELECT s.name FROM joinItemTable i JOIN - * joinOrderTable o ON o.item_id = i.item_id AND quantity < 5000 JOIN joinSupplierTable s ON - * i.supplier_id = s.supplier_id - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0 (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY QUANTITY < 5000\n" + " PARALLEL INNER-JOIN TABLE 1\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_SUPPLIER_INDEX_FULL_NAME + "(" - + JOIN_SUPPLIER_TABLE_FULL_NAME + ") [1]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " CLIENT MERGE SORT\n" - + " DYNAMIC SERVER FILTER BY \"I.:item_id\" IN (\"O.item_id\")", - /* - * testSelfJoin() SELECT i2.item_id, i1.name FROM joinItemTable i1 JOIN joinItemTable i2 ON - * i1.item_id = i2.item_id ORDER BY i1.item_id - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_ITEM_INDEX_FULL_NAME + "(" + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " CLIENT MERGE SORT\n" - + " DYNAMIC SERVER FILTER BY \"I1.item_id\" IN (\"I2.:item_id\")", - /* - * testSelfJoin() SELECT i1.name, i2.name FROM joinItemTable i1 JOIN joinItemTable i2 ON - * i1.item_id = i2.supplier_id ORDER BY i1.name, i2.name - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [\"I1.0:NAME\", \"I2.0:NAME\"]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_ITEM_INDEX_FULL_NAME + "(" + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" - + " CLIENT MERGE SORT\n" - + " DYNAMIC SERVER FILTER BY \"I1.:item_id\" IN (\"I2.0:supplier_id\")", - /* - * testStarJoin() SELECT order_id, c.name, i.name iname, quantity, o.date FROM - * joinOrderTable o JOIN joinCustomerTable c ON o.customer_id = c.customer_id JOIN - * joinItemTable i ON o.item_id = i.item_id ORDER BY order_id - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_CUSTOMER_INDEX_FULL_NAME + "(" + JOIN_CUSTOMER_TABLE_FULL_NAME + ") [1]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 1\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_ITEM_INDEX_FULL_NAME + "(" + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " CLIENT MERGE SORT", - /* - * testStarJoin() SELECT (*NO_STAR_JOIN*) order_id, c.name, i.name iname, quantity, o.date - * FROM joinOrderTable o JOIN joinCustomerTable c ON o.customer_id = c.customer_id JOIN - * joinItemTable i ON o.item_id = i.item_id ORDER BY order_id - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [\"O.order_id\"]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME + "\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_CUSTOMER_INDEX_FULL_NAME - + "(" + JOIN_CUSTOMER_TABLE_FULL_NAME + ") [1]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " CLIENT MERGE SORT\n" - + " DYNAMIC SERVER FILTER BY \"I.:item_id\" IN (\"O.item_id\")", - /* - * testSubJoin() SELECT * FROM joinCustomerTable c INNER JOIN (joinOrderTable o INNER JOIN - * (joinSupplierTable s RIGHT JOIN joinItemTable i ON i.supplier_id = s.supplier_id) ON - * o.item_id = i.item_id) ON c.customer_id = o.customer_id WHERE c.customer_id <= - * '0000000005' AND order_id != '000000000000003' AND i.name != 'T3' ORDER BY c.customer_id, - * i.name - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_CUSTOMER_TABLE_FULL_NAME - + " [*] - ['0000000005']\n" + " SERVER SORTED BY [\"C.customer_id\", \"I.0:NAME\"]\n" - + "CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY \"order_id\" != '000000000000003'\n" - + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME - + "(" + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" - + " SERVER FILTER BY \"NAME\" != 'T3'\n" - + " CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " DYNAMIC SERVER FILTER BY \"C.customer_id\" IN (\"O.customer_id\")", - /* - * testJoinWithSubqueryAndAggregation() SELECT i.name, sum(quantity) FROM joinOrderTable o - * LEFT JOIN (SELECT name, item_id iid FROM joinItemTable) AS i ON o.item_id = i.iid GROUP - * BY i.name ORDER BY i.name - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [I.NAME]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_ITEM_INDEX_FULL_NAME + "(" + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " CLIENT MERGE SORT", - /* - * testJoinWithSubqueryAndAggregation() SELECT o.iid, sum(o.quantity) q FROM (SELECT item_id - * iid, quantity FROM joinOrderTable) AS o LEFT JOIN (SELECT item_id FROM joinItemTable) AS - * i ON o.iid = i.item_id GROUP BY o.iid ORDER BY q DESC - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [O.IID]\n" + "CLIENT MERGE SORT\n" - + "CLIENT SORTED BY [SUM(O.QUANTITY) DESC]\n" - + " PARALLEL LEFT-JOIN TABLE 0 (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " CLIENT MERGE SORT", - /* - * testJoinWithSubqueryAndAggregation() SELECT i.iid, o.q FROM (SELECT item_id iid FROM - * joinItemTable) AS i LEFT JOIN (SELECT item_id iid, sum(quantity) q FROM joinOrderTable - * GROUP BY item_id) AS o ON o.iid = i.iid ORDER BY o.q DESC NULLS LAST, i.iid - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [O.Q DESC NULLS LAST, I.IID]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]\n" - + " CLIENT MERGE SORT", - /* - * testJoinWithSubqueryAndAggregation() SELECT i.iid, o.q FROM (SELECT item_id iid, - * sum(quantity) q FROM joinOrderTable GROUP BY item_id) AS o JOIN (SELECT item_id iid FROM - * joinItemTable) AS i ON o.iid = i.iid ORDER BY o.q DESC, i.iid - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [O.Q DESC, I.IID]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]\n" - + " CLIENT MERGE SORT", - /* - * testNestedSubqueries() SELECT * FROM (SELECT customer_id cid, name, phone, address, - * loc_id, date FROM joinCustomerTable) AS c INNER JOIN (SELECT o.oid ooid, o.cid ocid, - * o.iid oiid, o.price * o.quantity, o.date odate, qi.iiid iiid, qi.iname iname, qi.iprice - * iprice, qi.idiscount1 idiscount1, qi.idiscount2 idiscount2, qi.isid isid, qi.idescription - * idescription, qi.ssid ssid, qi.sname sname, qi.sphone sphone, qi.saddress saddress, - * qi.sloc_id sloc_id FROM (SELECT item_id iid, customer_id cid, order_id oid, price, - * quantity, date FROM joinOrderTable) AS o INNER JOIN (SELECT i.iid iiid, i.name iname, - * i.price iprice, i.discount1 idiscount1, i.discount2 idiscount2, i.sid isid, i.description - * idescription, s.sid ssid, s.name sname, s.phone sphone, s.address saddress, s.loc_id - * sloc_id FROM (SELECT supplier_id sid, name, phone, address, loc_id FROM - * joinSupplierTable) AS s RIGHT JOIN (SELECT item_id iid, name, price, discount1, - * discount2, supplier_id sid, description FROM joinItemTable) AS i ON i.sid = s.sid) as qi - * ON o.iid = qi.iiid) as qo ON c.cid = qo.ocid WHERE c.cid <= '0000000005' AND qo.ooid != - * '000000000000003' AND qo.iname != 'T3' ORDER BY c.cid, qo.iname - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_CUSTOMER_TABLE_FULL_NAME - + " [*] - ['0000000005']\n" + " SERVER SORTED BY [C.CID, QO.INAME]\n" - + "CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY \"order_id\" != '000000000000003'\n" - + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME - + "(" + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" - + " SERVER FILTER BY \"NAME\" != 'T3'\n" - + " CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SUPPLIER_TABLE_FULL_NAME, - /* - * testJoinWithLimit() SELECT order_id, i.name, s.name, s.address, quantity FROM - * joinSupplierTable s LEFT JOIN joinItemTable i ON i.supplier_id = s.supplier_id LEFT JOIN - * joinOrderTable o ON o.item_id = i.item_id LIMIT 4 - */ - "CLIENT SERIAL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " SERVER 4 ROW LIMIT\n" + "CLIENT 4 ROW LIMIT\n" + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" + " CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 1(DELAYED EVALUATION)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " JOIN-SCANNER 4 ROW LIMIT", - /* - * testJoinWithLimit() SELECT order_id, i.name, s.name, s.address, quantity FROM - * joinSupplierTable s JOIN joinItemTable i ON i.supplier_id = s.supplier_id JOIN - * joinOrderTable o ON o.item_id = i.item_id LIMIT 4 - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + "CLIENT 4 ROW LIMIT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" + " CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 1(DELAYED EVALUATION)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " DYNAMIC SERVER FILTER BY \"S.supplier_id\" IN (\"I.0:supplier_id\")\n" - + " JOIN-SCANNER 4 ROW LIMIT", - /* - * testJoinWithSetMaxRows() statement.setMaxRows(4); SELECT order_id, i.name, quantity FROM - * joinItemTable i JOIN joinOrderTable o ON o.item_id = i.item_id; SELECT o.order_id, - * i.name, o.quantity FROM joinItemTable i JOIN (SELECT order_id, item_id, quantity FROM - * joinOrderTable) o ON o.item_id = i.item_id; - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + "CLIENT MERGE SORT\n" + "CLIENT 4 ROW LIMIT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " DYNAMIC SERVER FILTER BY \"I.:item_id\" IN (\"O.item_id\")\n" - + " JOIN-SCANNER 4 ROW LIMIT", - /* - * testJoinWithOffset() SELECT order_id, i.name, s.name, s.address, quantity FROM - * joinSupplierTable s LEFT JOIN joinItemTable i ON i.supplier_id = s.supplier_id LEFT JOIN - * joinOrderTable o ON o.item_id = i.item_id LIMIT 1 OFFSET 2 - */ - "CLIENT SERIAL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " SERVER OFFSET 2\n" + " SERVER 3 ROW LIMIT\n" + "CLIENT 1 ROW LIMIT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_ITEM_INDEX_FULL_NAME + "(" + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" - + " CLIENT MERGE SORT\n" + " PARALLEL LEFT-JOIN TABLE 1(DELAYED EVALUATION)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " JOIN-SCANNER 3 ROW LIMIT", - /* - * testJoinWithOffset() SELECT order_id, i.name, s.name, s.address, quantity FROM - * joinSupplierTable s JOIN joinItemTable i ON i.supplier_id = s.supplier_id JOIN - * joinOrderTable o ON o.item_id = i.item_id LIMIT 1 OFFSET 2 - */ - "CLIENT SERIAL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " SERVER OFFSET 2\n" + "CLIENT 1 ROW LIMIT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" + " CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 1(DELAYED EVALUATION)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " DYNAMIC SERVER FILTER BY \"S.supplier_id\" IN (\"I.0:supplier_id\")\n" - + " JOIN-SCANNER 3 ROW LIMIT", - /* - * testJoinWithLocalIndex() SELECT phone, i.name FROM joinSupplierTable s JOIN joinItemTable - * i ON s.supplier_id = i.supplier_id WHERE s.name = 'S1' AND i.name < 'T6' - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_SUPPLIER_INDEX_FULL_NAME + "(" - + JOIN_SUPPLIER_TABLE_FULL_NAME + ") [1,'S1']\n" + " SERVER MERGE [0.PHONE]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_ITEM_INDEX_FULL_NAME + "(" + JOIN_ITEM_TABLE_FULL_NAME + ") [1,*] - [1,'T6']\n" - + " CLIENT MERGE SORT\n" - + " DYNAMIC SERVER FILTER BY \"S.:supplier_id\" IN (\"I.0:supplier_id\")", - - /* - * testJoinWithLocalIndex() SELECT phone, max(i.name) FROM joinSupplierTable s JOIN - * joinItemTable i ON s."supplier_id" = i."supplier_id" WHERE s.name = 'S1' AND i.name < - * 'T6' GROUP BY phone - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_SUPPLIER_INDEX_FULL_NAME + "(" - + JOIN_SUPPLIER_TABLE_FULL_NAME + ") [1,'S1']\n" + " SERVER MERGE [0.PHONE]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"S.PHONE\"]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_ITEM_INDEX_FULL_NAME + "(" + JOIN_ITEM_TABLE_FULL_NAME + ") [1,*] - [1,'T6']\n" - + " CLIENT MERGE SORT\n" - + " DYNAMIC SERVER FILTER BY \"S.:supplier_id\" IN (\"I.0:supplier_id\")", - - /* - * testJoinWithLocalIndex() SELECT max(phone), max(i.name) FROM joinSupplierTable s LEFT - * JOIN joinItemTable i ON s."supplier_id" = i."supplier_id" AND i.name < 'T6' WHERE s.name - * <= 'S3' - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_SUPPLIER_INDEX_FULL_NAME + "(" - + JOIN_SUPPLIER_TABLE_FULL_NAME + ") [1,*] - [1,'S3']\n" + " SERVER MERGE [0.PHONE]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER AGGREGATE INTO SINGLE ROW\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_ITEM_INDEX_FULL_NAME + "(" + JOIN_ITEM_TABLE_FULL_NAME + ") [1,*] - [1,'T6']\n" - + " CLIENT MERGE SORT", } }); + + " (name)" } }); return testCases; } @@ -425,10 +434,13 @@ public void testJoinWithLocalIndex() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); Connection conn = DriverManager.getConnection(getUrl(), props); try { - String query = - "select phone, i.name from " + getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME) - + " s join " + getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME) - + " i on s.\"supplier_id\" = i.\"supplier_id\" where s.name = 'S1' and i.name < 'T6'"; + String supplierTable = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String itemTable = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String supplierIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_SUPPLIER_INDEX); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + + String query = "select phone, i.name from " + supplierTable + " s join " + itemTable + + " i on s.\"supplier_id\" = i.\"supplier_id\" where s.name = 'S1' and i.name < 'T6'"; System.out.println("1)\n" + query); PreparedStatement statement = conn.prepareStatement(query); ResultSet rs = statement.executeQuery(); @@ -437,11 +449,16 @@ public void testJoinWithLocalIndex() throws Exception { assertTrue(rs.next()); assertEquals(rs.getString(1), "888-888-1111"); assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - assertPlansEqual(plans[24], QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).scanType("RANGE SCAN") + .table(supplierIndex + "(" + supplierTable + ")").keyRanges(" [1,'S1']") + .serverMergeColumns("[0.PHONE]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"S.:supplier_id\" IN (\"I.0:supplier_id\")") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("RANGE SCAN").table(itemIndex + "(" + itemTable + ")") + .keyRanges(" [1,*] - [1,'T6']").clientSortAlgo("CLIENT MERGE SORT").end(); - query = "select phone, max(i.name) from " + getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME) - + " s join " + getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME) + query = "select phone, max(i.name) from " + supplierTable + " s join " + itemTable + " i on s.\"supplier_id\" = i.\"supplier_id\" where s.name = 'S1' and i.name < 'T6' group by phone"; statement = conn.prepareStatement(query); rs = statement.executeQuery(); @@ -449,21 +466,31 @@ public void testJoinWithLocalIndex() throws Exception { assertEquals(rs.getString(1), "888-888-1111"); assertEquals(rs.getString(2), "T2"); assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - assertPlansEqual(plans[25], QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).scanType("RANGE SCAN") + .table(supplierIndex + "(" + supplierTable + ")").keyRanges(" [1,'S1']") + .serverMergeColumns("[0.PHONE]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"S.PHONE\"]") + .clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"S.:supplier_id\" IN (\"I.0:supplier_id\")") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("RANGE SCAN").table(itemIndex + "(" + itemTable + ")") + .keyRanges(" [1,*] - [1,'T6']").clientSortAlgo("CLIENT MERGE SORT").end(); - query = - "select max(phone), max(i.name) from " + getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME) - + " s left join " + getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME) - + " i on s.\"supplier_id\" = i.\"supplier_id\" and i.name < 'T6' where s.name <= 'S3'"; + query = "select max(phone), max(i.name) from " + supplierTable + " s left join " + itemTable + + " i on s.\"supplier_id\" = i.\"supplier_id\" and i.name < 'T6' where s.name <= 'S3'"; statement = conn.prepareStatement(query); rs = statement.executeQuery(); assertTrue(rs.next()); assertEquals(rs.getString(1), "888-888-3333"); assertEquals(rs.getString(2), "T4"); assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - assertPlansEqual(plans[26], QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).scanType("RANGE SCAN") + .table(supplierIndex + "(" + supplierTable + ")").keyRanges(" [1,*] - [1,'S3']") + .serverMergeColumns("[0.PHONE]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("RANGE SCAN") + .table(itemIndex + "(" + itemTable + ")").keyRanges(" [1,*] - [1,'T6']") + .clientSortAlgo("CLIENT MERGE SORT").end(); } finally { conn.close(); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinMoreIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinMoreIT.java index 85d04a10efe..16f0144c691 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinMoreIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinMoreIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end.join; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -33,48 +34,12 @@ import org.apache.phoenix.end2end.ParallelStatsDisabledIT; import org.apache.phoenix.end2end.ParallelStatsDisabledTest; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.TestUtil; import org.junit.Test; import org.junit.experimental.categories.Category; @Category(ParallelStatsDisabledTest.class) public class HashJoinMoreIT extends ParallelStatsDisabledIT { - private final String[] plans = new String[] { - /* - * testJoinWithKeyRangeOptimization() SELECT lhs.col0, lhs.col1, lhs.col2, rhs.col0, rhs.col1, - * rhs.col2 FROM TEMP_TABLE_COMPOSITE_PK lhs JOIN TEMP_TABLE_COMPOSITE_PK rhs ON lhs.col1 = - * rhs.col2 - */ - "CLIENT PARALLEL 4-WAY FULL SCAN OVER %s\n" + "CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 4-WAY FULL SCAN OVER %s\n" - + " CLIENT MERGE SORT", - /* - * testJoinWithKeyRangeOptimization() SELECT lhs.col0, lhs.col1, lhs.col2, rhs.col0, rhs.col1, - * rhs.col2 FROM TEMP_TABLE_COMPOSITE_PK lhs JOIN TEMP_TABLE_COMPOSITE_PK rhs ON lhs.col0 = - * rhs.col2 - */ - "CLIENT PARALLEL 4-WAY FULL SCAN OVER %s\n" + "CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 4-WAY FULL SCAN OVER %s\n" - + " CLIENT MERGE SORT\n" + " DYNAMIC SERVER FILTER BY LHS.COL0 IN (RHS.COL2)", - /* - * testJoinWithKeyRangeOptimization() SELECT lhs.col0, lhs.col1, lhs.col2, rhs.col0, rhs.col1, - * rhs.col2 FROM TEMP_TABLE_COMPOSITE_PK lhs JOIN TEMP_TABLE_COMPOSITE_PK rhs ON lhs.col0 = - * rhs.col1 AND lhs.col1 = rhs.col2 - */ - "CLIENT PARALLEL 4-WAY FULL SCAN OVER %s\n" + "CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 4-WAY FULL SCAN OVER %s\n" - + " CLIENT MERGE SORT\n" - + " DYNAMIC SERVER FILTER BY (LHS.COL0, LHS.COL1) IN ((RHS.COL1, RHS.COL2))", - /* - * testJoinWithKeyRangeOptimization() SELECT lhs.col0, lhs.col1, lhs.col2, rhs.col0, rhs.col1, - * rhs.col2 FROM TEMP_TABLE_COMPOSITE_PK lhs JOIN TEMP_TABLE_COMPOSITE_PK rhs ON lhs.col0 = - * rhs.col1 AND lhs.col2 = rhs.col3 - 1 AND lhs.col1 = rhs.col2 - */ - "CLIENT PARALLEL 4-WAY FULL SCAN OVER %s\n" + "CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 4-WAY FULL SCAN OVER %s\n" - + " CLIENT MERGE SORT\n" - + " DYNAMIC SERVER FILTER BY (LHS.COL0, LHS.COL1, LHS.COL2) IN ((RHS.COL1, RHS.COL2, TO_INTEGER((RHS.COL3 - 1))))", }; @Test public void testJoinOverSaltedTables() throws Exception { @@ -327,9 +292,10 @@ public void testJoinWithKeyRangeOptimization() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - assertEquals(String.format(plans[0], tempTableWithCompositePK, tempTableWithCompositePK), - QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).scanType("FULL SCAN").table(tempTableWithCompositePK) + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN") + .table(tempTableWithCompositePK).clientSortAlgo("CLIENT MERGE SORT").end(); // Two parts of PK but only one leading part query = @@ -350,9 +316,11 @@ public void testJoinWithKeyRangeOptimization() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - assertEquals(String.format(plans[1], tempTableWithCompositePK, tempTableWithCompositePK), - QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).scanType("FULL SCAN").table(tempTableWithCompositePK) + .clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY LHS.COL0 IN (RHS.COL2)").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN") + .table(tempTableWithCompositePK).clientSortAlgo("CLIENT MERGE SORT").end(); // Two leading parts of PK query = @@ -382,9 +350,13 @@ public void testJoinWithKeyRangeOptimization() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - assertEquals(String.format(plans[2], tempTableWithCompositePK, tempTableWithCompositePK), - QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).scanType("FULL SCAN").table(tempTableWithCompositePK) + .clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter( + "DYNAMIC SERVER FILTER BY (LHS.COL0, LHS.COL1) IN ((RHS.COL1, RHS.COL2))") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("FULL SCAN").table(tempTableWithCompositePK).clientSortAlgo("CLIENT MERGE SORT") + .end(); // All parts of PK query = @@ -414,9 +386,13 @@ public void testJoinWithKeyRangeOptimization() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - assertEquals(String.format(plans[3], tempTableWithCompositePK, tempTableWithCompositePK), - QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).scanType("FULL SCAN").table(tempTableWithCompositePK) + .clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter( + "DYNAMIC SERVER FILTER BY (LHS.COL0, LHS.COL1, LHS.COL2) IN ((RHS.COL1, RHS.COL2, TO_INTEGER((RHS.COL3 - 1))))") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("FULL SCAN").table(tempTableWithCompositePK).clientSortAlgo("CLIENT MERGE SORT") + .end(); } finally { conn.close(); } @@ -800,28 +776,21 @@ public void testBug2894() throws Exception { + " ON L.BUCKET = E.BUCKET AND L.\"TIMESTAMP\" = E.\"TIMESTAMP\"\n" + " ) C\n" + " GROUP BY C.BUCKET, C.\"TIMESTAMP\""; - String p = i == 0 - ? "CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER EVENT_COUNT [X'00','5SEC',~1462993520000000000,'Tr/Bal'] - [X'01','5SEC',~1462993420000000000,'Tr/Bal']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"E.TIMESTAMP\", E.BUCKET]\n" - + "CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0 (SKIP MERGE)\n" - + " CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER " + t[i] - + " [X'00','5SEC',~1462993520000000000,'Tr/Bal'] - [X'01','5SEC',~1462993420000000000,'Tr/Bal']\n" - + " SERVER FILTER BY FIRST KEY ONLY AND SRC_LOCATION = DST_LOCATION\n" - + " CLIENT MERGE SORT" - : "CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER EVENT_COUNT [X'00','5SEC',~1462993520000000000,'Tr/Bal'] - [X'01','5SEC',~1462993420000000000,'Tr/Bal']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"E.TIMESTAMP\", E.BUCKET]\n" - + "CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0 (SKIP MERGE)\n" - + " CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER " + t[i] - + " [X'00','5SEC',1462993420000000001,'Tr/Bal'] - [X'01','5SEC',1462993520000000000,'Tr/Bal']\n" - + " SERVER FILTER BY FIRST KEY ONLY AND SRC_LOCATION = DST_LOCATION\n" - + " CLIENT MERGE SORT"; - - ResultSet rs = conn.createStatement().executeQuery("explain " + q); - assertEquals(p, QueryUtil.getExplainPlan(rs)); - - rs = conn.createStatement().executeQuery(q); + String innerKeyRanges = i == 0 + ? " [X'00','5SEC',~1462993520000000000,'Tr/Bal'] - [X'01','5SEC',~1462993420000000000,'Tr/Bal']" + : " [X'00','5SEC',1462993420000000001,'Tr/Bal'] - [X'01','5SEC',1462993520000000000,'Tr/Bal']"; + + assertPlan(conn, q).scanType("SKIP SCAN ON 2 RANGES").table("EVENT_COUNT").keyRanges( + " [X'00','5SEC',~1462993520000000000,'Tr/Bal'] - [X'01','5SEC',~1462993420000000000,'Tr/Bal']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"E.TIMESTAMP\", E.BUCKET]") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 (SKIP MERGE)") + .scanType("SKIP SCAN ON 2 RANGES").table(t[i]).keyRanges(innerKeyRanges) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND SRC_LOCATION = DST_LOCATION") + .clientSortAlgo("CLIENT MERGE SORT").end(); + + ResultSet rs = conn.createStatement().executeQuery(q); assertTrue(rs.next()); assertEquals("5SEC", rs.getString(1)); assertEquals(1462993520000000000L, rs.getLong(2)); @@ -920,12 +889,8 @@ public void testBug2961() throws Exception { + "FROM ( SELECT ACCOUNT_ID, BUCKET_ID, OBJECT_ID, MAX(OBJECT_VERSION) AS MAXVER " + " FROM test2961 GROUP BY ACCOUNT_ID, BUCKET_ID, OBJECT_ID) AS X " + " INNER JOIN test2961 AS OBJ ON X.ACCOUNT_ID = OBJ.ACCOUNT_ID AND X.BUCKET_ID = OBJ.BUCKET_ID AND X.OBJECT_ID = OBJ.OBJECT_ID AND X.MAXVER = OBJ.OBJECT_VERSION"; - rs = conn.createStatement().executeQuery("explain " + q); - String plan = QueryUtil.getExplainPlan(rs); - String dynamicFilter = - "DYNAMIC SERVER FILTER BY (OBJ.ACCOUNT_ID, OBJ.BUCKET_ID, OBJ.OBJECT_ID, OBJ.OBJECT_VERSION) IN ((X.ACCOUNT_ID, X.BUCKET_ID, X.OBJECT_ID, X.MAXVER))"; - assertTrue("Expected '" + dynamicFilter + "' to be used for the query, but got:\n" + plan, - plan.contains(dynamicFilter)); + assertPlan(conn, q).dynamicServerFilter( + "DYNAMIC SERVER FILTER BY (OBJ.ACCOUNT_ID, OBJ.BUCKET_ID, OBJ.OBJECT_ID, OBJ.OBJECT_VERSION) IN ((X.ACCOUNT_ID, X.BUCKET_ID, X.OBJECT_ID, X.MAXVER))"); rs = conn.createStatement().executeQuery(q); assertTrue(rs.next()); assertEquals("2222", rs.getString(4)); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinNoIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinNoIndexIT.java index da637f0c1f0..166cc338adf 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinNoIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinNoIndexIT.java @@ -17,10 +17,15 @@ */ package org.apache.phoenix.end2end.join; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; + +import java.sql.Connection; import java.util.Collection; import java.util.List; import java.util.Map; +import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.end2end.ParallelStatsDisabledTest; +import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; @@ -46,283 +51,300 @@ protected Map getTableNameMap() { return virtualNameToRealNameMap; } - public HashJoinNoIndexIT(String[] indexDDL, String[] plans) { - super(indexDDL, plans); + public HashJoinNoIndexIT(String[] indexDDL) { + super(indexDDL); + } + + @Override + protected void assertLeftJoinWithAggPlan1(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [I.NAME]") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(item).end(); + } + + @Override + protected void assertLeftJoinWithAggPlan2(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"I.item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").clientSortedBy("[SUM(O.QUANTITY) DESC]").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN") + .table(item).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); + } + + @Override + protected void assertLeftJoinWithAggPlan3(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.item_id\"]") + .clientSortedBy("[SUM(O.QUANTITY) DESC NULLS LAST, \"I.item_id\"]").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertRightJoinWithAggPlan1(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [I.NAME]") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertRightJoinWithAggPlan2(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.item_id\"]") + .clientSortedBy("[SUM(O.QUANTITY) DESC NULLS LAST, \"I.item_id\"]").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertJoinWithWildcardPlan(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item).subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(supplier) + .end(); + } + + @Override + protected void assertJoinPlanWithIndexPlan1(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item) + .serverWhereFilter("SERVER FILTER BY (NAME >= 'T1' AND NAME <= 'T5')").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN") + .table(supplier).serverWhereFilter("SERVER FILTER BY (NAME >= 'S1' AND NAME <= 'S5')").end(); + } + + @Override + protected void assertJoinPlanWithIndexPlan2(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item) + .serverWhereFilter("SERVER FILTER BY (NAME = 'T1' OR NAME = 'T5')").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(supplier) + .serverWhereFilter("SERVER FILTER BY (NAME = 'S1' OR NAME = 'S5')").end(); + } + + @Override + protected void assertSkipMergeOptimizationPlan(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item) + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"I.item_id\" IN (\"O.item_id\")") + .subPlanCount(2).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 (SKIP MERGE)") + .scanType("FULL SCAN").table(order).serverWhereFilter("SERVER FILTER BY QUANTITY < 5000") + .end().subPlan(1).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1").scanType("FULL SCAN") + .table(supplier).end(); + } + + @Override + protected void assertSelfJoinPlan1(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item) + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"I1.item_id\" IN (\"I2.item_id\")") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("FULL SCAN").table(item).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); + } + + @Override + protected void assertSelfJoinPlan2(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item).serverSortedBy("[I1.NAME, I2.NAME]") + .clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"I1.item_id\" IN (\"I2.supplier_id\")") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("FULL SCAN").table(item).end(); + } + + @Override + protected void assertStarJoinPlan(Connection conn, String query, boolean noStarJoin) + throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String customer = getTableName(conn, JOIN_CUSTOMER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + if (!noStarJoin) { + assertPlan(conn, query).scanType("FULL SCAN").table(order).subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(customer) + .end().subPlan(1).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1").scanType("FULL SCAN") + .table(item).end(); + } else { + assertPlan(conn, query).scanType("FULL SCAN").table(item).serverSortedBy("[\"O.order_id\"]") + .clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"I.item_id\" IN (\"O.item_id\")") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("FULL SCAN").table(order).subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(customer) + .end().end(); + } + } + + @Override + protected void assertSubJoinPlan(Connection conn, String query) throws Exception { + String customer = getTableName(conn, JOIN_CUSTOMER_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("RANGE SCAN").table(customer) + .keyRanges(" [*] - ['0000000005']").serverSortedBy("[\"C.customer_id\", I.NAME]") + .clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"C.customer_id\" IN (\"O.customer_id\")") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .scanType("FULL SCAN").table(order) + .serverWhereFilter("SERVER FILTER BY \"order_id\" != '000000000000003'").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN") + .table(item).serverWhereFilter("SERVER FILTER BY NAME != 'T3'").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(supplier).end() + .end().end(); + } + + @Override + protected void assertSubqueryAggPlan1(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [I.NAME]") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(item).end(); + } + + @Override + protected void assertSubqueryAggPlan2(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [O.IID]") + .clientSortAlgo("CLIENT MERGE SORT").clientSortedBy("[SUM(O.QUANTITY) DESC]").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 (SKIP MERGE)") + .scanType("FULL SCAN").table(item).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); + } + + @Override + protected void assertSubqueryAggPlan3(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverSortedBy("[O.Q DESC NULLS LAST, I.IID]").clientSortAlgo("CLIENT MERGE SORT") + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0") + .scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end(); + } + + @Override + protected void assertSubqueryAggPlan4(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(item) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[O.Q DESC, I.IID]") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end(); + } + + @Override + protected void assertNestedSubqueriesPlan(Connection conn, String query) throws Exception { + String customer = getTableName(conn, JOIN_CUSTOMER_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("RANGE SCAN").table(customer) + .keyRanges(" [*] - ['0000000005']").serverSortedBy("[C.CID, QO.INAME]") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(order) + .serverWhereFilter("SERVER FILTER BY \"order_id\" != '000000000000003'").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN") + .table(item).serverWhereFilter("SERVER FILTER BY NAME != 'T3'").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(supplier).end() + .end().end(); + } + + @Override + protected void assertJoinWithLimitPlan1(Connection conn, String query) throws Exception { + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).iteratorType("SERIAL").scanType("FULL SCAN").table(supplier) + .serverRowLimit(4L).clientRowLimit(4).joinScannerLimit(4L).subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN").table(item).end() + .subPlan(1).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 1(DELAYED EVALUATION)") + .scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertJoinWithLimitPlan2(Connection conn, String query) throws Exception { + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).scanType("FULL SCAN").table(supplier).clientRowLimit(4) + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"S.supplier_id\" IN (\"I.supplier_id\")") + .joinScannerLimit(4L).subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(item).end() + .subPlan(1).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1(DELAYED EVALUATION)") + .scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertSetMaxRowsPlan(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + PhoenixPreparedStatement statement = + conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class); + statement.setMaxRows(4); + ExplainPlanAttributes attributes = + statement.optimizeQuery().getExplainPlan().getPlanStepsAsAttributes(); + assertPlan(attributes).scanType("FULL SCAN").table(item).clientRowLimit(4) + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"I.item_id\" IN (\"O.item_id\")") + .joinScannerLimit(4L).subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(order).end(); + } + + @Override + protected void assertJoinWithOffsetPlan1(Connection conn, String query) throws Exception { + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).iteratorType("SERIAL").scanType("FULL SCAN").table(supplier) + .serverOffset(2).serverRowLimit(3L).clientRowLimit(1).joinScannerLimit(3L).subPlanCount(2) + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN") + .table(item).end().subPlan(1) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 1(DELAYED EVALUATION)").scanType("FULL SCAN") + .table(order).end(); + } + + @Override + protected void assertJoinWithOffsetPlan2(Connection conn, String query) throws Exception { + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).iteratorType("SERIAL").scanType("FULL SCAN").table(supplier) + .serverOffset(2).clientRowLimit(1) + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"S.supplier_id\" IN (\"I.supplier_id\")") + .joinScannerLimit(3L).subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(item).end() + .subPlan(1).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1(DELAYED EVALUATION)") + .scanType("FULL SCAN").table(order).end(); } @Parameters(name = "HashJoinNoIndexIT_{index}") // name is used by failsafe as file name in // reports public static synchronized Collection data() { List testCases = Lists.newArrayList(); - testCases.add(new String[][] { {}, { - /* - * testLeftJoinWithAggregation() SELECT i.name, sum(quantity) FROM joinOrderTable o LEFT JOIN - * joinItemTable i ON o.item_id = i.item_id GROUP BY i.name ORDER BY i.name - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [I.NAME]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ITEM_TABLE_FULL_NAME, - /* - * testLeftJoinWithAggregation() SELECT i.item_id iid, sum(quantity) q FROM joinOrderTable o - * LEFT JOIN joinItemTable i ON o.item_id = i.item_id GROUP BY i.item_id ORDER BY q DESC" - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"I.item_id\"]\n" + "CLIENT MERGE SORT\n" - + "CLIENT SORTED BY [SUM(O.QUANTITY) DESC]\n" + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY FIRST KEY ONLY", - /* - * testLeftJoinWithAggregation() SELECT i.item_id iid, sum(quantity) q FROM joinItemTable i - * LEFT JOIN joinOrderTable o ON o.item_id = i.item_id GROUP BY i.item_id ORDER BY q DESC - * NULLS LAST, iid - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.item_id\"]\n" - + "CLIENT SORTED BY [SUM(O.QUANTITY) DESC NULLS LAST, \"I.item_id\"]\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME, - /* - * testRightJoinWithAggregation() SELECT i.name, sum(quantity) FROM joinOrderTable o RIGHT - * JOIN joinItemTable i ON o.item_id = i.item_id GROUP BY i.name ORDER BY i.name - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [I.NAME]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME, - /* - * testRightJoinWithAggregation() SELECT i.item_id iid, sum(quantity) q FROM joinOrderTable o - * RIGHT JOIN joinItemTable i ON o.item_id = i.item_id GROUP BY i.item_id ORDER BY q DESC - * NULLS LAST, iid - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.item_id\"]\n" - + "CLIENT SORTED BY [SUM(O.QUANTITY) DESC NULLS LAST, \"I.item_id\"]\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME, - /* - * testJoinWithWildcard() SELECT * FROM joinItemTable LEFT JOIN joinSupplierTable supp ON - * joinItemTable.supplier_id = supp.supplier_id ORDER BY item_id - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SUPPLIER_TABLE_FULL_NAME, - /* - * testJoinPlanWithIndex() SELECT item.item_id, item.name, supp.supplier_id, supp.name FROM - * joinItemTable item LEFT JOIN joinSupplierTable supp ON substr(item.name, 2, 1) = - * substr(supp.name, 2, 1) AND (supp.name BETWEEN 'S1' AND 'S5') WHERE item.name BETWEEN 'T1' - * AND 'T5' - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY (NAME >= 'T1' AND NAME <= 'T5')\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY (NAME >= 'S1' AND NAME <= 'S5')", - /* - * testJoinPlanWithIndex() SELECT item.item_id, item.name, supp.supplier_id, supp.name FROM - * joinItemTable item INNER JOIN joinSupplierTable supp ON item.supplier_id = supp.supplier_id - * WHERE (item.name = 'T1' OR item.name = 'T5') AND (supp.name = 'S1' OR supp.name = 'S5') - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY (NAME = 'T1' OR NAME = 'T5')\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY (NAME = 'S1' OR NAME = 'S5')", - /* - * testJoinWithSkipMergeOptimization() SELECT s.name FROM joinItemTable i JOIN joinOrderTable - * o ON o.item_id = i.item_id AND quantity < 5000 JOIN joinSupplierTable s ON i.supplier_id = - * s.supplier_id - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " PARALLEL INNER-JOIN TABLE 0 (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY QUANTITY < 5000\n" + " PARALLEL INNER-JOIN TABLE 1\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " DYNAMIC SERVER FILTER BY \"I.item_id\" IN (\"O.item_id\")", - /* - * testSelfJoin() SELECT i2.item_id, i1.name FROM joinItemTable i1 JOIN joinItemTable i2 ON - * i1.item_id = i2.item_id ORDER BY i1.item_id - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ITEM_TABLE_FULL_NAME + "\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " DYNAMIC SERVER FILTER BY \"I1.item_id\" IN (\"I2.item_id\")", - /* - * testSelfJoin() SELECT i1.name, i2.name FROM joinItemTable i1 JOIN joinItemTable i2 ON - * i1.item_id = i2.supplier_id ORDER BY i1.name, i2.name - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER SORTED BY [I1.NAME, I2.NAME]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " DYNAMIC SERVER FILTER BY \"I1.item_id\" IN (\"I2.supplier_id\")", - /* - * testStarJoin() SELECT order_id, c.name, i.name iname, quantity, o.date FROM joinOrderTable - * o JOIN joinCustomerTable c ON o.customer_id = c.customer_id JOIN joinItemTable i ON - * o.item_id = i.item_id ORDER BY order_id - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_CUSTOMER_TABLE_FULL_NAME + "\n" + " PARALLEL INNER-JOIN TABLE 1\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME, - /* - * testStarJoin() SELECT (*NO_STAR_JOIN*) order_id, c.name, i.name iname, quantity, o.date - * FROM joinOrderTable o JOIN joinCustomerTable c ON o.customer_id = c.customer_id JOIN - * joinItemTable i ON o.item_id = i.item_id ORDER BY order_id - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER SORTED BY [\"O.order_id\"]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME + "\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_CUSTOMER_TABLE_FULL_NAME - + "\n" + " DYNAMIC SERVER FILTER BY \"I.item_id\" IN (\"O.item_id\")", - /* - * testSubJoin() SELECT * FROM joinCustomerTable c INNER JOIN (joinOrderTable o INNER JOIN - * (joinSupplierTable s RIGHT JOIN joinItemTable i ON i.supplier_id = s.supplier_id) ON - * o.item_id = i.item_id) ON c.customer_id = o.customer_id WHERE c.customer_id <= '0000000005' - * AND order_id != '000000000000003' AND i.name != 'T3' ORDER BY c.customer_id, i.name - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_CUSTOMER_TABLE_FULL_NAME - + " [*] - ['0000000005']\n" + " SERVER SORTED BY [\"C.customer_id\", I.NAME]\n" - + "CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY \"order_id\" != '000000000000003'\n" - + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY NAME != 'T3'\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " DYNAMIC SERVER FILTER BY \"C.customer_id\" IN (\"O.customer_id\")", - /* - * testJoinWithSubqueryAndAggregation() SELECT i.name, sum(quantity) FROM joinOrderTable o - * LEFT JOIN (SELECT name, item_id iid FROM joinItemTable) AS i ON o.item_id = i.iid GROUP BY - * i.name ORDER BY i.name - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [I.NAME]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ITEM_TABLE_FULL_NAME, - /* - * testJoinWithSubqueryAndAggregation() SELECT o.iid, sum(o.quantity) q FROM (SELECT item_id - * iid, quantity FROM joinOrderTable) AS o LEFT JOIN (SELECT item_id FROM joinItemTable) AS i - * ON o.iid = i.item_id GROUP BY o.iid ORDER BY q DESC - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [O.IID]\n" + "CLIENT MERGE SORT\n" - + "CLIENT SORTED BY [SUM(O.QUANTITY) DESC]\n" - + " PARALLEL LEFT-JOIN TABLE 0 (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY FIRST KEY ONLY", - /* - * testJoinWithSubqueryAndAggregation() SELECT i.iid, o.q FROM (SELECT item_id iid FROM - * joinItemTable) AS i LEFT JOIN (SELECT item_id iid, sum(quantity) q FROM joinOrderTable - * GROUP BY item_id) AS o ON o.iid = i.iid ORDER BY o.q DESC NULLS LAST, i.iid - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [O.Q DESC NULLS LAST, I.IID]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]\n" - + " CLIENT MERGE SORT", - /* - * testJoinWithSubqueryAndAggregation() SELECT i.iid, o.q FROM (SELECT item_id iid, - * sum(quantity) q FROM joinOrderTable GROUP BY item_id) AS o JOIN (SELECT item_id iid FROM - * joinItemTable) AS i ON o.iid = i.iid ORDER BY o.q DESC, i.iid - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER SORTED BY [O.Q DESC, I.IID]\n" - + "CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]\n" - + " CLIENT MERGE SORT", - /* - * testNestedSubqueries() SELECT * FROM (SELECT customer_id cid, name, phone, address, loc_id, - * date FROM joinCustomerTable) AS c INNER JOIN (SELECT o.oid ooid, o.cid ocid, o.iid oiid, - * o.price * o.quantity, o.date odate, qi.iiid iiid, qi.iname iname, qi.iprice iprice, - * qi.idiscount1 idiscount1, qi.idiscount2 idiscount2, qi.isid isid, qi.idescription - * idescription, qi.ssid ssid, qi.sname sname, qi.sphone sphone, qi.saddress saddress, - * qi.sloc_id sloc_id FROM (SELECT item_id iid, customer_id cid, order_id oid, price, - * quantity, date FROM joinOrderTable) AS o INNER JOIN (SELECT i.iid iiid, i.name iname, - * i.price iprice, i.discount1 idiscount1, i.discount2 idiscount2, i.sid isid, i.description - * idescription, s.sid ssid, s.name sname, s.phone sphone, s.address saddress, s.loc_id - * sloc_id FROM (SELECT supplier_id sid, name, phone, address, loc_id FROM joinSupplierTable) - * AS s RIGHT JOIN (SELECT item_id iid, name, price, discount1, discount2, supplier_id sid, - * description FROM joinItemTable) AS i ON i.sid = s.sid) as qi ON o.iid = qi.iiid) as qo ON - * c.cid = qo.ocid WHERE c.cid <= '0000000005' AND qo.ooid != '000000000000003' AND qo.iname - * != 'T3' ORDER BY c.cid, qo.iname - */ - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_CUSTOMER_TABLE_FULL_NAME - + " [*] - ['0000000005']\n" + " SERVER SORTED BY [C.CID, QO.INAME]\n" - + "CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY \"order_id\" != '000000000000003'\n" - + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY NAME != 'T3'\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SUPPLIER_TABLE_FULL_NAME, - /* - * testJoinWithLimit() SELECT order_id, i.name, s.name, s.address, quantity FROM - * joinSupplierTable s LEFT JOIN joinItemTable i ON i.supplier_id = s.supplier_id LEFT JOIN - * joinOrderTable o ON o.item_id = i.item_id LIMIT 4 - */ - "CLIENT SERIAL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " SERVER 4 ROW LIMIT\n" + "CLIENT 4 ROW LIMIT\n" + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " PARALLEL LEFT-JOIN TABLE 1(DELAYED EVALUATION)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " JOIN-SCANNER 4 ROW LIMIT", - /* - * testJoinWithLimit() SELECT order_id, i.name, s.name, s.address, quantity FROM - * joinSupplierTable s JOIN joinItemTable i ON i.supplier_id = s.supplier_id JOIN - * joinOrderTable o ON o.item_id = i.item_id LIMIT 4 - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + "CLIENT 4 ROW LIMIT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " PARALLEL INNER-JOIN TABLE 1(DELAYED EVALUATION)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " DYNAMIC SERVER FILTER BY \"S.supplier_id\" IN (\"I.supplier_id\")\n" - + " JOIN-SCANNER 4 ROW LIMIT", - /* - * testJoinWithSetMaxRows() statement.setMaxRows(4); SELECT order_id, i.name, quantity FROM - * joinItemTable i JOIN joinOrderTable o ON o.item_id = i.item_id; SELECT o.order_id, i.name, - * o.quantity FROM joinItemTable i JOIN (SELECT order_id, item_id, quantity FROM - * joinOrderTable) o ON o.item_id = i.item_id; - */ - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + "CLIENT 4 ROW LIMIT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " DYNAMIC SERVER FILTER BY \"I.item_id\" IN (\"O.item_id\")\n" - + " JOIN-SCANNER 4 ROW LIMIT", - /* - * testJoinWithOffset() SELECT order_id, i.name, s.name, s.address, quantity FROM - * joinSupplierTable s LEFT JOIN joinItemTable i ON i.supplier_id = s.supplier_id LEFT JOIN - * joinOrderTable o ON o.item_id = i.item_id LIMIT 1 OFFSET 2 - */ - "CLIENT SERIAL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " SERVER OFFSET 2\n" + " SERVER 3 ROW LIMIT\n" + "CLIENT 1 ROW LIMIT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ITEM_TABLE_FULL_NAME + "\n" + " PARALLEL LEFT-JOIN TABLE 1(DELAYED EVALUATION)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " JOIN-SCANNER 3 ROW LIMIT", - /* - * testJoinWithOffset() SELECT order_id, i.name, s.name, s.address, quantity FROM - * joinSupplierTable s JOIN joinItemTable i ON i.supplier_id = s.supplier_id JOIN - * joinOrderTable o ON o.item_id = i.item_id LIMIT 1 OFFSET 2 - */ - "CLIENT SERIAL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " SERVER OFFSET 2\n" + "CLIENT 1 ROW LIMIT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " PARALLEL INNER-JOIN TABLE 1(DELAYED EVALUATION)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " DYNAMIC SERVER FILTER BY \"S.supplier_id\" IN (\"I.supplier_id\")\n" - + " JOIN-SCANNER 3 ROW LIMIT", } }); + testCases.add(new String[][] { {} }); return testCases; } } 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 5d3a88cafe2..633b91424f5 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 @@ -17,10 +17,15 @@ */ package org.apache.phoenix.end2end.join; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; + +import java.sql.Connection; import java.util.Collection; import java.util.List; import java.util.Map; +import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.end2end.ParallelStatsDisabledTest; +import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; @@ -46,8 +51,53 @@ protected Map getTableNameMap() { return virtualNameToRealNameMap; } - public SortMergeJoinGlobalIndexIT(String[] indexDDL, String[] plans) { - super(indexDDL, plans); + public SortMergeJoinGlobalIndexIT(String[] indexDDL) { + super(indexDDL); + } + + @Override + protected void assertSkipMergeOptimizationPlan(Connection conn, String query) throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String itemIndex = getSchemaName() + ".idx_item"; + String supplierIndex = getSchemaName() + ".idx_supplier"; + assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)").scanType("FULL SCAN") + .table(supplierIndex).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverSortedBy("[\"S.:supplier_id\"]").clientSortAlgo("CLIENT MERGE SORT") + .sortMergeSkipMerge(false).rhs().abstractExplainPlan("SORT-MERGE-JOIN (INNER)") + .scanType("FULL SCAN").table(itemIndex).serverSortedBy("[\"I.:item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").sortMergeSkipMerge(true) + .clientSortedBy("[\"I.0:supplier_id\"]").rhs().scanType("FULL SCAN").table(order) + .serverWhereFilter("SERVER FILTER BY QUANTITY < 5000").serverSortedBy("[\"O.item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end().end(); + } + + @Override + protected void assertSelfJoinPlan(Connection conn, String query) throws Exception { + String itemIndex = getSchemaName() + ".idx_item"; + assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").scanType("FULL SCAN") + .table(itemIndex).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverSortedBy("[\"I1.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT") + .sortMergeSkipMerge(false).rhs().scanType("FULL SCAN").table(itemIndex) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"I2.:item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end(); + } + + @Override + protected void assertSetMaxRowsPlan(Connection conn, String query, int queryIndex) + throws Exception { + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String itemIndex = getSchemaName() + ".idx_item"; + PhoenixPreparedStatement statement = + conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class); + statement.setMaxRows(4); + ExplainPlanAttributes attributes = + statement.optimizeQuery().getExplainPlan().getPlanStepsAsAttributes(); + assertPlan(attributes).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").scanType("FULL SCAN") + .table(itemIndex).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverSortedBy("[\"I.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT") + .sortMergeSkipMerge(false).clientRowLimit(4).rhs().scanType("FULL SCAN").table(order) + .serverSortedBy(queryIndex == 0 ? "[\"O.item_id\"]" : "[\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end(); } @Parameters(name = "SortMergeJoinGlobalIndexIT_{index}") // name is used by failsafe as file name @@ -58,32 +108,7 @@ public static synchronized Collection data() { { "CREATE INDEX \"idx_customer\" ON " + JOIN_CUSTOMER_TABLE_FULL_NAME + " (name)", "CREATE INDEX \"idx_item\" ON " + JOIN_ITEM_TABLE_FULL_NAME + " (name) INCLUDE (price, discount1, discount2, \"supplier_id\", description)", - "CREATE INDEX \"idx_supplier\" ON " + JOIN_SUPPLIER_TABLE_FULL_NAME + " (name)" }, - { "SORT-MERGE-JOIN (LEFT) TABLES\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SCHEMA + ".idx_supplier\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [\"S.:supplier_id\"]\n" + " CLIENT MERGE SORT\n" + "AND\n" - + " SORT-MERGE-JOIN (INNER) TABLES\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SCHEMA + ".idx_item\n" + " SERVER SORTED BY [\"I.:item_id\"]\n" - + " CLIENT MERGE SORT\n" + " AND (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER FILTER BY QUANTITY < 5000\n" - + " SERVER SORTED BY [\"O.item_id\"]\n" + " CLIENT MERGE SORT\n" - + " CLIENT SORTED BY [\"I.0:supplier_id\"]", - - "SORT-MERGE-JOIN (INNER) TABLES\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SCHEMA + ".idx_item\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [\"I.:item_id\"]\n" + " CLIENT MERGE SORT\n" + "AND\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER SORTED BY [\"O.item_id\"]\n" + " CLIENT MERGE SORT\n" - + "CLIENT 4 ROW LIMIT", - - "SORT-MERGE-JOIN (INNER) TABLES\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER Join.idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [\"I1.:item_id\"]\n" + " CLIENT MERGE SORT\n" + "AND\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER Join.idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [\"I2.:item_id\"]\n" + " CLIENT MERGE SORT" } }); + "CREATE INDEX \"idx_supplier\" ON " + JOIN_SUPPLIER_TABLE_FULL_NAME + " (name)" } }); return testCases; } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinIT.java index 014a0a8d496..ff65bdcbd67 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinIT.java @@ -40,7 +40,6 @@ import org.apache.phoenix.exception.SQLExceptionCode; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -48,10 +47,58 @@ @RunWith(Parameterized.class) public abstract class SortMergeJoinIT extends BaseJoinIT { - public SortMergeJoinIT(String[] indexDDL, String[] plans) { - super(indexDDL, plans); + public SortMergeJoinIT(String[] indexDDL) { + super(indexDDL); } + /* + * The expected EXPLAIN plan for each of the queries below differs per index configuration, so + * each concrete subclass supplies the attribute-based assertions via these hooks. + */ + + /** + * {@link #testJoinWithSkipMergeOptimization()}: + * + *
+   * SELECT /*+ USE_SORT_MERGE_JOIN*/ s.name FROM joinItemTable i
+   *   JOIN joinOrderTable o ON o.item_id = i.item_id AND quantity < 5000
+   *   RIGHT JOIN joinSupplierTable s ON i.supplier_id = s.supplier_id
+   * 
+ */ + protected abstract void assertSkipMergeOptimizationPlan(Connection conn, String query) + throws Exception; + + /** + * {@link #testSelfJoin()}: + * + *
+   * SELECT /*+ USE_SORT_MERGE_JOIN*/ i2.item_id, i1.name FROM joinItemTable i1
+   *   JOIN joinItemTable i2 ON i1.item_id = i2.item_id
+   *   ORDER BY i1.item_id
+   * 
+ */ + protected abstract void assertSelfJoinPlan(Connection conn, String query) throws Exception; + + /** + * Assert the EXPLAIN plan for {@link #testJoinWithSetMaxRows()} (with a max-rows limit of 4). The + * {@code CLIENT 4 ROW LIMIT} comes from {@link java.sql.Statement#setMaxRows(int)} rather than + * the SQL, so subclasses must compile via a {@code PhoenixPreparedStatement}. + * + *
+   * statement.setMaxRows(4);
+   *
+   * // queryIndex 0:
+   * SELECT /*+ USE_SORT_MERGE_JOIN*/ order_id, i.name, quantity FROM joinItemTable i
+   *   JOIN joinOrderTable o ON o.item_id = i.item_id
+   *
+   * // queryIndex 1:
+   * SELECT /*+ USE_SORT_MERGE_JOIN*/ o.order_id, i.name, o.quantity FROM joinItemTable i
+   *   JOIN (SELECT order_id, item_id, quantity FROM joinOrderTable) o ON o.item_id = i.item_id
+   * 
+ */ + protected abstract void assertSetMaxRowsPlan(Connection conn, String query, int queryIndex) + throws Exception; + @Test public void testDefaultJoin() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); @@ -1663,8 +1710,7 @@ public void testJoinWithSkipMergeOptimization() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - assertPlansEqual(plans[0], QueryUtil.getExplainPlan(rs)); + assertSkipMergeOptimizationPlan(conn, query); } finally { conn.close(); } @@ -1707,8 +1753,7 @@ public void testSelfJoin() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query1); - assertPlansEqual(plans[2], QueryUtil.getExplainPlan(rs)); + assertSelfJoinPlan(conn, query1); statement = conn.prepareStatement(query2); rs = statement.executeQuery(); @@ -2581,9 +2626,7 @@ public void testJoinWithSetMaxRows() throws Exception { assertFalse(rs.next()); - rs = statement.executeQuery("EXPLAIN " + query); - assertPlansEqual(i == 0 ? plans[1] : plans[1].replaceFirst("O\\.item_id", "item_id"), - QueryUtil.getExplainPlan(rs)); + assertSetMaxRowsPlan(conn, query, i); } } finally { conn.close(); 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 8b03fdc7729..272fee7a49a 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 @@ -17,10 +17,16 @@ */ package org.apache.phoenix.end2end.join; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; + +import java.sql.Connection; import java.util.Collection; import java.util.List; import java.util.Map; +import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.end2end.ParallelStatsDisabledTest; +import org.apache.phoenix.jdbc.PhoenixPreparedStatement; +import org.apache.phoenix.util.SchemaUtil; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; @@ -45,48 +51,73 @@ protected Map getTableNameMap() { return virtualNameToRealNameMap; } - public SortMergeJoinLocalIndexIT(String[] indexDDL, String[] plans) { - super(indexDDL, plans); + public SortMergeJoinLocalIndexIT(String[] indexDDL) { + super(indexDDL); + } + + @Override + protected void assertSkipMergeOptimizationPlan(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + String supplierIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_SUPPLIER_INDEX); + assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)").scanType("RANGE SCAN") + .table(supplierIndex + "(" + supplier + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"S.:supplier_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").sortMergeSkipMerge(false).rhs() + .abstractExplainPlan("SORT-MERGE-JOIN (INNER)").scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]").serverSortedBy("[\"I.:item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").sortMergeSkipMerge(true) + .clientSortedBy("[\"I.0:supplier_id\"]").rhs().scanType("FULL SCAN").table(order) + .serverWhereFilter("SERVER FILTER BY QUANTITY < 5000").serverSortedBy("[\"O.item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end().end(); + } + + @Override + protected void assertSelfJoinPlan(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"I1.:item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").sortMergeSkipMerge(false).rhs().scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"I2.:item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end(); + } + + @Override + protected void assertSetMaxRowsPlan(Connection conn, String query, int queryIndex) + throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); + PhoenixPreparedStatement statement = + conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class); + statement.setMaxRows(4); + ExplainPlanAttributes attributes = + statement.optimizeQuery().getExplainPlan().getPlanStepsAsAttributes(); + assertPlan(attributes).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"I.:item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").sortMergeSkipMerge(false).clientRowLimit(4).rhs() + .scanType("FULL SCAN").table(order) + .serverSortedBy(queryIndex == 0 ? "[\"O.item_id\"]" : "[\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end(); } @Parameters(name = "SortMergeJoinLocalIndexIT_{index}") // name is used by failsafe as file name // in reports public static synchronized Collection data() { List testCases = Lists.newArrayList(); - testCases.add(new String[][] { - { "CREATE LOCAL INDEX " + JOIN_CUSTOMER_INDEX + " ON " + JOIN_CUSTOMER_TABLE_FULL_NAME + testCases.add(new String[][] { { + "CREATE LOCAL INDEX " + JOIN_CUSTOMER_INDEX + " ON " + JOIN_CUSTOMER_TABLE_FULL_NAME + "(name)", - "CREATE LOCAL INDEX " + JOIN_ITEM_INDEX + " ON " + JOIN_ITEM_TABLE_FULL_NAME + "(name) " - + "INCLUDE (price, discount1, discount2, \"supplier_id\", description)", - "CREATE LOCAL INDEX " + JOIN_SUPPLIER_INDEX + " ON " + JOIN_SUPPLIER_TABLE_FULL_NAME - + " (name)" }, - { "SORT-MERGE-JOIN (LEFT) TABLES\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_SUPPLIER_INDEX_FULL_NAME + "(" + JOIN_SUPPLIER_TABLE_FULL_NAME + ") [1]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [\"S.:supplier_id\"]\n" + " CLIENT MERGE SORT\n" + "AND\n" - + " SORT-MERGE-JOIN (INNER) TABLES\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_ITEM_INDEX_FULL_NAME + "(" + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" - + " SERVER SORTED BY [\"I.:item_id\"]\n" + " CLIENT MERGE SORT\n" - + " AND (SKIP MERGE)\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME + "\n" + " SERVER FILTER BY QUANTITY < 5000\n" - + " SERVER SORTED BY [\"O.item_id\"]\n" + " CLIENT MERGE SORT\n" - + " CLIENT SORTED BY [\"I.0:supplier_id\"]", - - "SORT-MERGE-JOIN (INNER) TABLES\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_ITEM_INDEX_FULL_NAME + "(" + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [\"I.:item_id\"]\n" + " CLIENT MERGE SORT\n" + "AND\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER SORTED BY [\"O.item_id\"]\n" + " CLIENT MERGE SORT\n" - + "CLIENT 4 ROW LIMIT", - - "SORT-MERGE-JOIN (INNER) TABLES\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_ITEM_INDEX_FULL_NAME + "(" + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [\"I1.:item_id\"]\n" + " CLIENT MERGE SORT\n" + "AND\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [\"I2.:item_id\"]\n" + " CLIENT MERGE SORT" } }); + "CREATE LOCAL INDEX " + JOIN_ITEM_INDEX + " ON " + JOIN_ITEM_TABLE_FULL_NAME + "(name) " + + "INCLUDE (price, discount1, discount2, \"supplier_id\", description)", + "CREATE LOCAL INDEX " + JOIN_SUPPLIER_INDEX + " ON " + JOIN_SUPPLIER_TABLE_FULL_NAME + + " (name)" } }); return testCases; } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinNoIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinNoIndexIT.java index 73def45f5d9..8bf2ac3dfa9 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinNoIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinNoIndexIT.java @@ -17,10 +17,15 @@ */ package org.apache.phoenix.end2end.join; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; + +import java.sql.Connection; import java.util.Collection; import java.util.List; import java.util.Map; +import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.end2end.ParallelStatsDisabledTest; +import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; @@ -46,31 +51,52 @@ protected Map getTableNameMap() { return virtualNameToRealNameMap; } - public SortMergeJoinNoIndexIT(String[] indexDDL, String[] plans) { - super(indexDDL, plans); + public SortMergeJoinNoIndexIT(String[] indexDDL) { + super(indexDDL); + } + + @Override + protected void assertSkipMergeOptimizationPlan(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String supplier = getTableName(conn, JOIN_SUPPLIER_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)").scanType("FULL SCAN") + .table(supplier).sortMergeSkipMerge(false).rhs() + .abstractExplainPlan("SORT-MERGE-JOIN (INNER)").scanType("FULL SCAN").table(item) + .sortMergeSkipMerge(true).clientSortedBy("[\"I.supplier_id\"]").rhs().scanType("FULL SCAN") + .table(order).serverWhereFilter("SERVER FILTER BY QUANTITY < 5000") + .serverSortedBy("[\"O.item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end().end(); + } + + @Override + protected void assertSelfJoinPlan(Connection conn, String query) throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").scanType("FULL SCAN") + .table(item).sortMergeSkipMerge(false).rhs().scanType("FULL SCAN").table(item) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); + } + + @Override + protected void assertSetMaxRowsPlan(Connection conn, String query, int queryIndex) + throws Exception { + String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); + String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); + PhoenixPreparedStatement statement = + conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class); + statement.setMaxRows(4); + ExplainPlanAttributes attributes = + statement.optimizeQuery().getExplainPlan().getPlanStepsAsAttributes(); + assertPlan(attributes).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").scanType("FULL SCAN") + .table(item).sortMergeSkipMerge(false).clientRowLimit(4).rhs().scanType("FULL SCAN") + .table(order).serverSortedBy(queryIndex == 0 ? "[\"O.item_id\"]" : "[\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end(); } @Parameters(name = "SortMergeJoinNoIndexIT_{index}") // name is used by failsafe as file name in // reports public static synchronized Collection data() { List testCases = Lists.newArrayList(); - testCases.add(new String[][] { {}, - { "SORT-MERGE-JOIN (LEFT) TABLES\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" + "AND\n" + " SORT-MERGE-JOIN (INNER) TABLES\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " AND (SKIP MERGE)\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME + "\n" + " SERVER FILTER BY QUANTITY < 5000\n" - + " SERVER SORTED BY [\"O.item_id\"]\n" + " CLIENT MERGE SORT\n" - + " CLIENT SORTED BY [\"I.supplier_id\"]", - - "SORT-MERGE-JOIN (INNER) TABLES\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ITEM_TABLE_FULL_NAME + "\n" + "AND\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME + "\n" + " SERVER SORTED BY [\"O.item_id\"]\n" - + " CLIENT MERGE SORT\n" + "CLIENT 4 ROW LIMIT", - - "SORT-MERGE-JOIN (INNER) TABLES\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ITEM_TABLE_FULL_NAME + "\n" + "AND\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ITEM_TABLE_FULL_NAME + "\n" + " SERVER FILTER BY FIRST KEY ONLY" } }); + testCases.add(new String[][] { {} }); return testCases; } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinNoSpoolingIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinNoSpoolingIT.java index 8f5e453c9cc..c5efcc4eb0b 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinNoSpoolingIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinNoSpoolingIT.java @@ -43,8 +43,8 @@ @Category(NeedsOwnMiniClusterTest.class) public class SortMergeJoinNoSpoolingIT extends SortMergeJoinNoIndexIT { - public SortMergeJoinNoSpoolingIT(String[] indexDDL, String[] plans) { - super(indexDDL, plans); + public SortMergeJoinNoSpoolingIT(String[] indexDDL) { + super(indexDDL); } @Parameters(name = "SortMergeJoinNoSpoolingIT_{index}") // name is used by failsafe as file name diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SubqueryIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SubqueryIT.java index 4234253cd98..c0e1d1b74d8 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SubqueryIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SubqueryIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end.join; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -35,7 +36,6 @@ import java.util.Properties; import org.apache.phoenix.end2end.ParallelStatsDisabledTest; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -47,211 +47,26 @@ @Category(ParallelStatsDisabledTest.class) @RunWith(Parameterized.class) public class SubqueryIT extends BaseJoinIT { - public SubqueryIT(String[] indexDDL, String[] plans) { - super(indexDDL, plans); + public SubqueryIT(String[] indexDDL) { + super(indexDDL); } @Parameters public static synchronized Collection data() { List testCases = Lists.newArrayList(); - testCases.add(new String[][] { {}, - { "CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ITEM_TABLE_FULL_NAME + "\n" + " SERVER SORTED BY \\[I.NAME\\]\n" - + "CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " SKIP-SCAN-JOIN TABLE 1\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME + " \\['000000000000001'\\] - \\[\\*\\]\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" - + " DYNAMIC SERVER FILTER BY \"I.item_id\" IN \\(\\$\\d+.\\$\\d+\\)", - - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" - + " SERVER SORTED BY [I.NAME]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " PARALLEL SEMI-JOIN TABLE 1(DELAYED EVALUATION) (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]\n" - + " CLIENT MERGE SORT", - - "CLIENT PARALLEL 4-WAY FULL SCAN OVER " + JOIN_COITEM_TABLE_FULL_NAME + "\n" - + "CLIENT MERGE SORT\n" + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\".+.item_id\", .+.NAME\\]\n" - + " CLIENT MERGE SORT\n" - + " PARALLEL ANTI-JOIN TABLE 0 \\(SKIP MERGE\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + "\n" + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" + " PARALLEL LEFT-JOIN TABLE 1\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\".+.item_id\", .+.NAME\\]\n" - + " CLIENT MERGE SORT\n" + " SKIP-SCAN-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + "\n" + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" + " DYNAMIC SERVER FILTER BY \"" - + JOIN_ITEM_TABLE_FULL_NAME + ".item_id\" IN \\(\\$\\d+.\\$\\d+\\)\n" - + " AFTER-JOIN SERVER FILTER BY \\(\\$\\d+.\\$\\d+ IS NOT NULL OR \\$\\d+.\\$\\d+ IS NOT NULL\\)", - - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER SORTED BY [I.NAME]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL ANTI-JOIN TABLE 0 (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]\n" - + " CLIENT MERGE SORT", - - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_CUSTOMER_TABLE_FULL_NAME + "\n" - + " SKIP-SCAN-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"O.customer_id\"\\]\n" - + " CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + "\n" + " PARALLEL LEFT-JOIN TABLE 1\\(DELAYED EVALUATION\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + "\n" + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" - + " DYNAMIC SERVER FILTER BY \"I.item_id\" IN \\(\"O.item_id\"\\)\n" - + " AFTER-JOIN SERVER FILTER BY \\(I.NAME = 'T2' OR O.QUANTITY > \\$\\d+.\\$\\d+\\)\n" - + " DYNAMIC SERVER FILTER BY \"" + JOIN_CUSTOMER_TABLE_FULL_NAME - + ".customer_id\" IN \\(\\$\\d+.\\$\\d+\\)" } }); + testCases.add(new String[][] { {} }); testCases.add(new String[][] { { "CREATE INDEX \"idx_customer\" ON " + JOIN_CUSTOMER_TABLE_FULL_NAME + " (name)", "CREATE INDEX \"idx_item\" ON " + JOIN_ITEM_TABLE_FULL_NAME + " (name) INCLUDE (price, discount1, discount2, \"supplier_id\", description)", - "CREATE INDEX \"idx_supplier\" ON " + JOIN_SUPPLIER_TABLE_FULL_NAME + " (name)" }, - { "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SCHEMA + ".idx_supplier\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " PARALLEL SEMI-JOIN TABLE 1 \\(SKIP MERGE\\)\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + " \\['000000000000001'\\] - \\[\\*\\]\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT", - - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_supplier\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER SORTED BY [\"I.0:NAME\"]\n" - + "CLIENT MERGE SORT\n" + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " PARALLEL SEMI-JOIN TABLE 1(DELAYED EVALUATION) (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]\n" - + " CLIENT MERGE SORT", - - "CLIENT PARALLEL 4-WAY FULL SCAN OVER " + JOIN_COITEM_TABLE_FULL_NAME + "\n" - + "CLIENT MERGE SORT\n" + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY \\[\".+.0:NAME\", \".+.:item_id\"\\]\n" - + " PARALLEL ANTI-JOIN TABLE 0 \\(SKIP MERGE\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + "\n" + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" + " PARALLEL LEFT-JOIN TABLE 1\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY \\[\".+.0:NAME\", \".+.:item_id\"\\]\n" - + " PARALLEL SEMI-JOIN TABLE 0 \\(SKIP MERGE\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + "\n" + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" - + " AFTER-JOIN SERVER FILTER BY \\(\\$\\d+.\\$\\d+ IS NOT NULL OR \\$\\d+.\\$\\d+ IS NOT NULL\\)", - - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " PARALLEL ANTI-JOIN TABLE 0 (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]\n" - + " CLIENT MERGE SORT", - - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_customer\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " PARALLEL SEMI-JOIN TABLE 0 \\(SKIP MERGE\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"O.customer_id\"\\]\n" - + " CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + "\n" + " PARALLEL LEFT-JOIN TABLE 1\\(DELAYED EVALUATION\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + "\n" + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" - + " AFTER-JOIN SERVER FILTER BY \\(\"I.0:NAME\" = 'T2' OR O.QUANTITY > \\$\\d+.\\$\\d+\\)" } }); - testCases.add(new String[][] { - { "CREATE LOCAL INDEX " + JOIN_CUSTOMER_INDEX + " ON " + JOIN_CUSTOMER_TABLE_FULL_NAME + "CREATE INDEX \"idx_supplier\" ON " + JOIN_SUPPLIER_TABLE_FULL_NAME + " (name)" } }); + testCases.add(new String[][] { { + "CREATE LOCAL INDEX " + JOIN_CUSTOMER_INDEX + " ON " + JOIN_CUSTOMER_TABLE_FULL_NAME + " (name)", - "CREATE LOCAL INDEX " + JOIN_ITEM_INDEX + " ON " + JOIN_ITEM_TABLE_FULL_NAME + " " - + "(name) INCLUDE (price, discount1, discount2, \"supplier_id\", description)", - "CREATE LOCAL INDEX " + JOIN_SUPPLIER_INDEX + " ON " + JOIN_SUPPLIER_TABLE_FULL_NAME - + " (name)" }, - { "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "\\(" - + JOIN_ITEM_TABLE_FULL_NAME + "\\) \\[1\\]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_SUPPLIER_INDEX_FULL_NAME + "\\(" + JOIN_SUPPLIER_TABLE_FULL_NAME + "\\) \\[1\\]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " CLIENT MERGE SORT\n" - + " PARALLEL SEMI-JOIN TABLE 1 \\(SKIP MERGE\\)\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + " \\['000000000000001'\\] - \\[\\*\\]\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" - + " DYNAMIC SERVER FILTER BY \"I.:item_id\" IN \\(\\$\\d+.\\$\\d+\\)", - - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_SUPPLIER_INDEX_FULL_NAME + "(" - + JOIN_SUPPLIER_TABLE_FULL_NAME + ") [1]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [\"I.0:NAME\"]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_ITEM_INDEX_FULL_NAME + "(" + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" - + " CLIENT MERGE SORT\n" - + " PARALLEL SEMI-JOIN TABLE 1(DELAYED EVALUATION) (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]\n" - + " CLIENT MERGE SORT", - - "CLIENT PARALLEL 4-WAY FULL SCAN OVER " + JOIN_COITEM_TABLE_FULL_NAME + "\n" - + "CLIENT MERGE SORT\n" + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "\\(" - + JOIN_ITEM_TABLE_FULL_NAME + "\\) \\[1\\]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY \\[\".+.0:NAME\", \".+.:item_id\"\\]\n" - + " CLIENT MERGE SORT\n" - + " PARALLEL ANTI-JOIN TABLE 0 \\(SKIP MERGE\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + "\n" + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" + " PARALLEL LEFT-JOIN TABLE 1\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "\\(" - + JOIN_ITEM_TABLE_FULL_NAME + "\\) \\[1\\]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY \\[\".+.0:NAME\", \".+.:item_id\"\\]\n" - + " CLIENT MERGE SORT\n" - + " PARALLEL SEMI-JOIN TABLE 0 \\(SKIP MERGE\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + "\n" + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" + " DYNAMIC SERVER FILTER BY \"" - + JOIN_ITEM_INDEX_FULL_NAME + ".:item_id\" IN \\(\\$\\d+.\\$\\d+\\)\n" - + " AFTER-JOIN SERVER FILTER BY \\(\\$\\d+.\\$\\d+ IS NOT NULL OR \\$\\d+.\\$\\d+ IS NOT NULL\\)", - - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + "CLIENT MERGE SORT\n" + " PARALLEL ANTI-JOIN TABLE 0 (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]\n" - + " CLIENT MERGE SORT", - - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_CUSTOMER_INDEX_FULL_NAME + "\\(" - + JOIN_CUSTOMER_TABLE_FULL_NAME + "\\) \\[1\\]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT\n" - + " PARALLEL SEMI-JOIN TABLE 0 \\(SKIP MERGE\\)\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "\\(" - + JOIN_ITEM_TABLE_FULL_NAME + "\\) \\[1\\]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"O.customer_id\"\\]\n" - + " CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + "\n" + " PARALLEL LEFT-JOIN TABLE 1\\(DELAYED EVALUATION\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + "\n" + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" - + " DYNAMIC SERVER FILTER BY \"I.:item_id\" IN \\(\"O.item_id\"\\)\n" - + " AFTER-JOIN SERVER FILTER BY \\(\"I.0:NAME\" = 'T2' OR O.QUANTITY > \\$\\d+.\\$\\d+\\)\n" - + " DYNAMIC SERVER FILTER BY \"" + JOIN_CUSTOMER_INDEX_FULL_NAME - + ".:customer_id\" IN " + "\\(\\$\\d+.\\$\\d+\\)" } }); + "CREATE LOCAL INDEX " + JOIN_ITEM_INDEX + " ON " + JOIN_ITEM_TABLE_FULL_NAME + " " + + "(name) INCLUDE (price, discount1, discount2, \"supplier_id\", description)", + "CREATE LOCAL INDEX " + JOIN_SUPPLIER_INDEX + " ON " + JOIN_SUPPLIER_TABLE_FULL_NAME + + " (name)" } }); return testCases; } @@ -433,9 +248,10 @@ public void testInSubquery() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - String plan = QueryUtil.getExplainPlan(rs); - assertPlansMatch(plans[0], plan); + assertPlan(conn, query).subPlan(1).scanType("RANGE SCAN").table(tableName4) + .keyRanges(" ['000000000000001'] - [*]") + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT"); query = "SELECT i.\"item_id\", s.name FROM " + tableName2 + " s LEFT JOIN " + tableName1 + " i ON i.\"supplier_id\" = s.\"supplier_id\" WHERE i.\"item_id\" IN (SELECT \"item_id\" FROM " @@ -457,8 +273,9 @@ public void testInSubquery() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - assertPlansEqual(plans[1], QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).subPlan(1).scanType("FULL SCAN").table(tableName4) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT"); query = "SELECT * FROM " + tableName5 + " WHERE (item_id, item_name) IN (SELECT \"item_id\", name FROM " + tableName1 @@ -480,9 +297,7 @@ public void testInSubquery() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - plan = QueryUtil.getExplainPlan(rs); - assertPlansMatch(plans[2], plan); + assertCoitemDoubleSubqueryPlan(conn, query, tableName4, tableName5); } finally { conn.close(); } @@ -513,8 +328,9 @@ public void testExistsSubquery() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - assertPlansEqual(plans[3], QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).subPlan(0).scanType("FULL SCAN").table(tableName4) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT"); query = "SELECT * FROM " + tableName5 + " co WHERE EXISTS (SELECT 1 FROM " + tableName1 + " i WHERE NOT EXISTS (SELECT 1 FROM " + tableName4 @@ -537,9 +353,7 @@ public void testExistsSubquery() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - String plan = QueryUtil.getExplainPlan(rs); - assertPlansMatch(plans[2], plan); + assertCoitemDoubleSubqueryPlan(conn, query, tableName4, tableName5); // PHOENIX-3633 query = "SELECT * FROM " + tableName4 + " o WHERE NOT EXISTS (SELECT 1 FROM " + tableName1 @@ -627,9 +441,11 @@ public void testComparisonSubquery() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - String plan = QueryUtil.getExplainPlan(rs); - assertPlansMatch(plans[4], plan); + assertPlan(conn, query).subPlan(0) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"O.customer_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").subPlan(1).scanType("FULL SCAN").table(tableName4) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT"); query = "SELECT \"order_id\" FROM " + tableName4 + " o WHERE quantity = (SELECT quantity FROM " + tableName4 @@ -1121,4 +937,15 @@ public void testSubqueryReturnSingleAndCompare() throws Exception { assertEquals("C4", rs2.getString("NAME")); } } + + private void assertCoitemDoubleSubqueryPlan(Connection conn, String query, String orderTable, + String coitemTable) throws SQLException { + assertPlan(conn, query).scanType("FULL SCAN").table(coitemTable).iteratorType("PARALLEL") + .clientSortAlgo("CLIENT MERGE SORT").subPlanCount(2).subPlan(0).subPlan(0) + .scanType("FULL SCAN").table(orderTable) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end().end().subPlan(1).subPlan(0).scanType("FULL SCAN") + .table(orderTable).serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT"); + } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SubqueryUsingSortMergeJoinIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SubqueryUsingSortMergeJoinIT.java index 81f79d78cd8..74b95b80292 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SubqueryUsingSortMergeJoinIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SubqueryUsingSortMergeJoinIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end.join; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -39,7 +40,6 @@ import org.apache.phoenix.execute.TupleProjectionPlan; import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -52,173 +52,26 @@ @RunWith(Parameterized.class) public class SubqueryUsingSortMergeJoinIT extends BaseJoinIT { - public SubqueryUsingSortMergeJoinIT(String[] indexDDL, String[] plans) { - super(indexDDL, plans); + public SubqueryUsingSortMergeJoinIT(String[] indexDDL) { + super(indexDDL); } @Parameters public static synchronized Collection data() { List testCases = Lists.newArrayList(); - testCases.add(new String[][] { {}, { - "SORT-MERGE-JOIN (SEMI) TABLES\n" + " SORT-MERGE-JOIN (INNER) TABLES\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER SORTED BY [\"I.supplier_id\"]\n" + " CLIENT MERGE SORT\n" - + " AND\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SUPPLIER_TABLE_FULL_NAME + "\n" + " CLIENT SORTED BY [\"I.item_id\"]\n" - + "AND (SKIP MERGE)\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_ORDER_TABLE_FULL_NAME + " ['000000000000001'] - [*]\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]\n" - + " CLIENT MERGE SORT\n" + "CLIENT SORTED BY [I.NAME]", - - "SORT-MERGE-JOIN \\(LEFT\\) TABLES\n" + " SORT-MERGE-JOIN \\(LEFT\\) TABLES\n" - + " CLIENT PARALLEL 4-WAY FULL SCAN OVER " + JOIN_COITEM_TABLE_FULL_NAME + "\n" - + " CLIENT MERGE SORT\n" + " AND\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\".+.item_id\", .+.NAME\\]\n" - + " CLIENT MERGE SORT\n" - + " PARALLEL ANTI-JOIN TABLE 0 \\(SKIP MERGE\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + "\n" + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"]\\\n" - + " CLIENT MERGE SORT\n" - + " CLIENT SORTED BY \\[.*.CO_ITEM_ID, .*.CO_ITEM_NAME\\]\n" + "AND\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\".+.item_id\", .+.NAME\\]\n" - + " CLIENT MERGE SORT\n" + " SKIP-SCAN-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" + " DYNAMIC SERVER FILTER BY \"" - + JOIN_ITEM_TABLE_FULL_NAME + ".item_id\" IN \\(\\$\\d+.\\$\\d+\\)\n" - + "CLIENT FILTER BY \\(\\$\\d+.\\$\\d+ IS NOT NULL OR \\$\\d+.\\$\\d+ IS NOT NULL\\)", - - "SORT-MERGE-JOIN \\(SEMI\\) TABLES\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_CUSTOMER_TABLE_FULL_NAME + "\n" + "AND \\(SKIP MERGE\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"O.customer_id\"\\]\n" - + " CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " PARALLEL LEFT-JOIN TABLE 1\\(DELAYED EVALUATION\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" - + " DYNAMIC SERVER FILTER BY \"I.item_id\" IN \\(\"O.item_id\"\\)\n" - + " AFTER-JOIN SERVER FILTER BY \\(I.NAME = 'T2' OR O.QUANTITY > \\$\\d+.\\$\\d+\\)", } }); + testCases.add(new String[][] { {} }); testCases.add(new String[][] { { "CREATE INDEX \"idx_customer\" ON " + JOIN_CUSTOMER_TABLE_FULL_NAME + " (name)", "CREATE INDEX \"idx_item\" ON " + JOIN_ITEM_TABLE_FULL_NAME + " (name) INCLUDE (price, discount1, discount2, \"supplier_id\", description)", - "CREATE INDEX \"idx_supplier\" ON " + JOIN_SUPPLIER_TABLE_FULL_NAME + " (name)" }, - { "SORT-MERGE-JOIN (SEMI) TABLES\n" + " SORT-MERGE-JOIN (INNER) TABLES\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER SORTED BY [\"I.0:supplier_id\"]\n" + " CLIENT MERGE SORT\n" - + " AND\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA - + ".idx_supplier\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [\"S.:supplier_id\"]\n" + " CLIENT MERGE SORT\n" - + " CLIENT SORTED BY [\"I.:item_id\"]\n" + "AND (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + " ['000000000000001'] - [*]\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]\n" - + " CLIENT MERGE SORT\n" + "CLIENT SORTED BY [\"I.0:NAME\"]", - - "SORT-MERGE-JOIN \\(LEFT\\) TABLES\n" + " SORT-MERGE-JOIN \\(LEFT\\) TABLES\n" - + " CLIENT PARALLEL 4-WAY FULL SCAN OVER " + JOIN_COITEM_TABLE_FULL_NAME + "\n" - + " CLIENT MERGE SORT\n" + " AND\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY \\[\".+.0:NAME\", \".+.:item_id\"\\]\n" - + " CLIENT SORTED BY \\[\".+.:item_id\", \".+.0:NAME\"\\]\n" - + " PARALLEL ANTI-JOIN TABLE 0 \\(SKIP MERGE\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + "\n" + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" - + " CLIENT SORTED BY \\[.*.CO_ITEM_ID, .*.CO_ITEM_NAME\\]\n" + "AND\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY \\[\".+.0:NAME\", \".+.:item_id\"\\]\n" - + " CLIENT SORTED BY \\[\".+.:item_id\", \".+.0:NAME\"\\]\n" - + " PARALLEL SEMI-JOIN TABLE 0 \\(SKIP MERGE\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" - + "CLIENT FILTER BY \\(\\$\\d+.\\$\\d+ IS NOT NULL OR \\$\\d+.\\$\\d+ IS NOT NULL\\)", - - "SORT-MERGE-JOIN \\(SEMI\\) TABLES\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + JOIN_SCHEMA + ".idx_customer\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY \\[\"Join.idx_customer.:customer_id\"\\]\n" - + " CLIENT MERGE SORT\n" + "AND \\(SKIP MERGE\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"O.customer_id\"\\]\n" - + " CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " PARALLEL LEFT-JOIN TABLE 1\\(DELAYED EVALUATION\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" - + " AFTER-JOIN SERVER FILTER BY \\(\"I.0:NAME\" = 'T2' OR O.QUANTITY > \\$\\d+.\\$\\d+\\)", } }); - testCases.add(new String[][] { - { "CREATE LOCAL INDEX " + JOIN_CUSTOMER_INDEX + " ON " + JOIN_CUSTOMER_TABLE_FULL_NAME + "CREATE INDEX \"idx_supplier\" ON " + JOIN_SUPPLIER_TABLE_FULL_NAME + " (name)" } }); + testCases.add(new String[][] { { + "CREATE LOCAL INDEX " + JOIN_CUSTOMER_INDEX + " ON " + JOIN_CUSTOMER_TABLE_FULL_NAME + " (name)", - "CREATE LOCAL INDEX " + JOIN_ITEM_INDEX + " ON " + JOIN_ITEM_TABLE_FULL_NAME - + " (name) INCLUDE (price, discount1, discount2, \"supplier_id\", description)", - "CREATE LOCAL INDEX " + JOIN_SUPPLIER_INDEX + " ON " + JOIN_SUPPLIER_TABLE_FULL_NAME - + " (name)" }, - { "SORT-MERGE-JOIN (SEMI) TABLES\n" + " SORT-MERGE-JOIN (INNER) TABLES\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "(" - + JOIN_ITEM_TABLE_FULL_NAME + ") [1]\n" - + " SERVER SORTED BY [\"I.0:supplier_id\"]\n" + " CLIENT MERGE SORT\n" - + " AND\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_SUPPLIER_INDEX_FULL_NAME + "(" + JOIN_SUPPLIER_TABLE_FULL_NAME + ") [1]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER SORTED BY [\"S.:supplier_id\"]\n" + " CLIENT MERGE SORT\n" - + " CLIENT SORTED BY [\"I.:item_id\"]\n" + "AND (SKIP MERGE)\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + " ['000000000000001'] - [*]\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]\n" - + " CLIENT MERGE SORT\n" + "CLIENT SORTED BY [\"I.0:NAME\"]", - - "SORT-MERGE-JOIN \\(LEFT\\) TABLES\n" + " SORT-MERGE-JOIN \\(LEFT\\) TABLES\n" - + " CLIENT PARALLEL 4-WAY FULL SCAN OVER " + JOIN_COITEM_TABLE_FULL_NAME + "\n" - + " CLIENT MERGE SORT\n" + " AND\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "\\(" - + JOIN_ITEM_TABLE_FULL_NAME + "\\) \\[1\\]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY \\[\".+.0:NAME\", \".+.:item_id\"\\]\n" - + " CLIENT MERGE SORT\n" - + " CLIENT SORTED BY \\[\".+.:item_id\", \".+.0:NAME\"\\]\n" - + " PARALLEL ANTI-JOIN TABLE 0 \\(SKIP MERGE\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME - + "\n" + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" - + " CLIENT SORTED BY \\[.*.CO_ITEM_ID, .*.CO_ITEM_NAME\\]\n" + "AND\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_ITEM_INDEX_FULL_NAME + "\\(" - + JOIN_ITEM_TABLE_FULL_NAME + "\\) \\[1\\]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY \\[\".+.0:NAME\", \".+.:item_id\"\\]\n" - + " CLIENT MERGE SORT\n" - + " CLIENT SORTED BY \\[\".+.:item_id\", \".+.0:NAME\"\\]\n" - + " PARALLEL SEMI-JOIN TABLE 0 \\(SKIP MERGE\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" + " DYNAMIC SERVER FILTER BY \"" - + JOIN_ITEM_INDEX_FULL_NAME + ".:item_id\" IN \\(\\$\\d+" + ".\\$\\d+\\)\n" - + "CLIENT FILTER BY \\(\\$\\d+.\\$\\d+ IS NOT NULL OR \\$\\d+.\\$\\d+ IS NOT NULL\\)", - - "SORT-MERGE-JOIN \\(SEMI\\) TABLES\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_CUSTOMER_INDEX_FULL_NAME + "\\(" + JOIN_CUSTOMER_TABLE_FULL_NAME + "\\) \\[1\\]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER SORTED BY \\[\"" - + JOIN_CUSTOMER_INDEX_FULL_NAME + ".:customer_id\"\\]\n" + " CLIENT MERGE SORT\n" - + "AND \\(SKIP MERGE\\)\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + JOIN_ITEM_INDEX_FULL_NAME + "\\(" + JOIN_ITEM_TABLE_FULL_NAME + "\\) \\[1\\]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"O.customer_id\"\\]\n" - + " CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " PARALLEL LEFT-JOIN TABLE 1\\(DELAYED EVALUATION\\)\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_FULL_NAME + "\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY \\[\"item_id\"\\]\n" - + " CLIENT MERGE SORT\n" - + " DYNAMIC SERVER FILTER BY \"I.:item_id\" IN \\(\"O.item_id\"\\)\n" - + " AFTER-JOIN SERVER FILTER BY \\(\"I.0:NAME\" = 'T2' OR O.QUANTITY > \\$\\d+.\\$\\d+\\)", } }); + "CREATE LOCAL INDEX " + JOIN_ITEM_INDEX + " ON " + JOIN_ITEM_TABLE_FULL_NAME + + " (name) INCLUDE (price, discount1, discount2, \"supplier_id\", description)", + "CREATE LOCAL INDEX " + JOIN_SUPPLIER_INDEX + " ON " + JOIN_SUPPLIER_TABLE_FULL_NAME + + " (name)" } }); return testCases; } @@ -284,8 +137,10 @@ public void testInSubquery() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - assertPlansEqual(plans[0], QueryUtil.getExplainPlan(rs)); + assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (SEMI)").sortMergeSkipMerge(true) + .rhs().scanType("RANGE SCAN").table(tableName4).keyRanges(" ['000000000000001'] - [*]") + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT"); query = "SELECT /*+ USE_SORT_MERGE_JOIN*/ i.\"item_id\", s.name FROM " + tableName2 + " s LEFT JOIN " + tableName1 @@ -328,9 +183,11 @@ public void testInSubquery() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - String plan = QueryUtil.getExplainPlan(rs); - assertPlansMatch(plans[1], plan); + assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)") + .sortMergeSkipMerge(false).scanType("FULL SCAN").table(tableName5).iteratorType("PARALLEL") + .rhs().subPlan(0).scanType("FULL SCAN").table(tableName4) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT"); } finally { conn.close(); } @@ -383,9 +240,11 @@ public void testExistsSubquery() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - String plan = QueryUtil.getExplainPlan(rs); - assertPlansMatch(plans[1], plan); + assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)") + .sortMergeSkipMerge(false).scanType("FULL SCAN").table(tableName5).iteratorType("PARALLEL") + .rhs().subPlan(0).scanType("FULL SCAN").table(tableName4) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT"); } finally { conn.close(); } @@ -434,9 +293,10 @@ public void testComparisonSubquery() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - String plan = QueryUtil.getExplainPlan(rs); - assertPlansMatch(plans[2], plan); + assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (SEMI)").sortMergeSkipMerge(true) + .rhs().subPlan(1).scanType("FULL SCAN").table(tableName4) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT"); query = "SELECT /*+ USE_SORT_MERGE_JOIN*/ \"order_id\" FROM " + tableName4 + " o WHERE quantity = (SELECT quantity FROM " + tableName4 diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonFunctionsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonFunctionsIT.java index 5c79a6a0d37..79f7ad41710 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonFunctionsIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonFunctionsIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end.json; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -40,7 +41,6 @@ import java.util.Arrays; import java.util.Properties; import org.apache.commons.io.FileUtils; -import org.apache.phoenix.end2end.IndexToolIT; import org.apache.phoenix.end2end.ParallelStatsDisabledIT; import org.apache.phoenix.end2end.ParallelStatsDisabledTest; import org.apache.phoenix.exception.SQLExceptionCode; @@ -367,12 +367,10 @@ public void testJsonExpressionIndex() throws IOException { String selectSql = "SELECT JSON_VALUE(JSONCOL,'$.type'), " + "JSON_VALUE(JSONCOL,'$.info.address.town') FROM " + tableName + " WHERE JSON_VALUE(JSONCOL,'$.type') = 'Basic'"; - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql); - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - IndexToolIT.assertExplainPlan(false, actualExplainPlan, tableName, indexName); + assertPlan(conn, selectSql).scanType("RANGE SCAN").table(indexName); // Validate the total count of rows String countSql = "SELECT COUNT(1) FROM " + tableName; - rs = conn.createStatement().executeQuery(countSql); + ResultSet rs = conn.createStatement().executeQuery(countSql); assertTrue(rs.next()); assertEquals(5, rs.getInt(1)); // Delete the rows @@ -569,7 +567,7 @@ public void testArrayIndexAndJsonFunctionExpressions() throws Exception { ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + query); String explainPlan = QueryUtil.getExplainPlan(rs); assertFalse(explainPlan.contains(" SERVER JSON FUNCTION PROJECTION")); - assertFalse(explainPlan.contains(" SERVER ARRAY ELEMENT PROJECTION")); + assertPlan(conn, query).serverArrayElementProjection(false); rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals(conn.createArrayOf("INTEGER", new Integer[] { 1, 2 }), rs.getArray(1)); @@ -583,7 +581,7 @@ public void testArrayIndexAndJsonFunctionExpressions() throws Exception { rs = conn.createStatement().executeQuery("EXPLAIN " + query); explainPlan = QueryUtil.getExplainPlan(rs); assertTrue(explainPlan.contains(" SERVER JSON FUNCTION PROJECTION")); - assertTrue(explainPlan.contains(" SERVER ARRAY ELEMENT PROJECTION")); + assertPlan(conn, query).serverArrayElementProjection(true); // only Array optimization and not Json query = "SELECT arr[1], jsoncol, JSON_VALUE(jsoncol, '$.type')" + " FROM " + tableName @@ -591,7 +589,7 @@ public void testArrayIndexAndJsonFunctionExpressions() throws Exception { rs = conn.createStatement().executeQuery("EXPLAIN " + query); explainPlan = QueryUtil.getExplainPlan(rs); assertFalse(explainPlan.contains(" SERVER JSON FUNCTION PROJECTION")); - assertTrue(explainPlan.contains(" SERVER ARRAY ELEMENT PROJECTION")); + assertPlan(conn, query).serverArrayElementProjection(true); // only Json optimization and not Array Index query = "SELECT arr, arr[1], JSON_VALUE(jsoncol, '$.type')" + " FROM " + tableName @@ -599,7 +597,7 @@ public void testArrayIndexAndJsonFunctionExpressions() throws Exception { rs = conn.createStatement().executeQuery("EXPLAIN " + query); explainPlan = QueryUtil.getExplainPlan(rs); assertTrue(explainPlan.contains(" SERVER JSON FUNCTION PROJECTION")); - assertFalse(explainPlan.contains(" SERVER ARRAY ELEMENT PROJECTION")); + assertPlan(conn, query).serverArrayElementProjection(false); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/salted/BaseSaltedTableIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/salted/BaseSaltedTableIT.java index 24a46e02116..d75f443d355 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/salted/BaseSaltedTableIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/salted/BaseSaltedTableIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end.salted; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TABLE_WITH_SALTING; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; @@ -31,7 +32,6 @@ import org.apache.phoenix.end2end.ParallelStatsDisabledIT; import org.apache.phoenix.util.PropertiesUtil; import org.apache.phoenix.util.QueryBuilder; -import org.apache.phoenix.util.QueryUtil; import org.junit.Test; import org.apache.phoenix.thirdparty.com.google.common.collect.Lists; @@ -183,7 +183,6 @@ public void testSelectValueWithFullyQualifiedWhereClause() throws Exception { Connection conn = DriverManager.getConnection(getUrl(), props); try { String tableName = initTableValues(null); - PreparedStatement stmt; ResultSet rs; // Variable length slot with bounded ranges. @@ -418,10 +417,9 @@ public void testSelectWithOrderByRowKey() throws Exception { String query = "SELECT * FROM " + tableName + " ORDER BY a_integer, a_string, a_id"; PreparedStatement statement = conn.prepareStatement(query); - ResultSet explainPlan = statement.executeQuery("EXPLAIN " + query); // Confirm that ORDER BY in row key order will be optimized out for salted table - assertEquals("CLIENT PARALLEL 4-WAY FULL SCAN OVER " + tableName + "\n" + "CLIENT MERGE SORT", - QueryUtil.getExplainPlan(explainPlan)); + assertPlan(conn, query).iteratorType("PARALLEL").scanType("FULL SCAN").table(tableName) + .clientSortAlgo("CLIENT MERGE SORT"); ResultSet rs = statement.executeQuery(); assertTrue(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/salted/SaltedTableIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/salted/SaltedTableIT.java index 4794610b408..2c0760be947 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/salted/SaltedTableIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/salted/SaltedTableIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end.salted; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -31,7 +32,6 @@ import java.util.Properties; import org.apache.phoenix.end2end.ParallelStatsDisabledTest; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -80,9 +80,7 @@ public void testPointLookupOnSaltedTable() throws Exception { ResultSet rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals(1, rs.getInt("a_integer")); - query = "explain " + query; - rs = conn.createStatement().executeQuery(query); - assertTrue(QueryUtil.getExplainPlan(rs).contains("POINT LOOKUP ON 1 KEY")); + assertPlan(conn, query).scanType("POINT LOOKUP ON 1 KEY"); } } @@ -108,10 +106,8 @@ public void testPointLookupOnSaltedTable2() throws Exception { assertEquals(i, rs.getInt("A")); assertEquals(i + 10, rs.getInt("B")); assertFalse(rs.next()); - query = "explain " + query; - rs = conn.createStatement().executeQuery(query); - assertTrue(QueryUtil.getExplainPlan(rs) - .contains("CLIENT PARALLEL 1-WAY POINT LOOKUP ON 1 KEY OVER")); + assertPlan(conn, query).iteratorType("PARALLEL").scanType("POINT LOOKUP ON 1 KEY") + .table(tableName); } } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/rpc/PhoenixClientRpcIT.java b/phoenix-core/src/it/java/org/apache/phoenix/rpc/PhoenixClientRpcIT.java index ba4c06457b1..bbdd60a255d 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/rpc/PhoenixClientRpcIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/rpc/PhoenixClientRpcIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.rpc; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -32,9 +33,9 @@ import org.apache.hadoop.hbase.ipc.CallRunner; import org.apache.hadoop.hbase.regionserver.RSRpcServices; import org.apache.phoenix.end2end.NeedsOwnMiniClusterTest; +import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; import org.apache.phoenix.util.SchemaUtil; import org.junit.AfterClass; @@ -102,12 +103,11 @@ public void testIndexQos() throws Exception { stmt.setString(1, "v1"); // verify that the query does a range scan on the index table - ResultSet rs = stmt.executeQuery("EXPLAIN " + selectSql); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexFullName + " ['v1']", - QueryUtil.getExplainPlan(rs)); + assertPlan(stmt.unwrap(PhoenixPreparedStatement.class)).scanType("RANGE SCAN") + .tableContains(indexFullName).keyRanges(" ['v1']"); // verify that the correct results are returned - rs = stmt.executeQuery(); + ResultSet rs = stmt.executeQuery(); assertTrue(rs.next()); assertEquals("k1", rs.getString(1)); assertEquals("v2", rs.getString(2)); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/rpc/PhoenixServerRpcIT.java b/phoenix-core/src/it/java/org/apache/phoenix/rpc/PhoenixServerRpcIT.java index 3dc82388552..4fd69260f4e 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/rpc/PhoenixServerRpcIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/rpc/PhoenixServerRpcIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.rpc; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -45,10 +46,10 @@ import org.apache.hadoop.hbase.regionserver.RSRpcServices; import org.apache.hadoop.hbase.util.Bytes; import org.apache.phoenix.end2end.NeedsOwnMiniClusterTest; +import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; import org.apache.phoenix.util.SchemaUtil; import org.junit.After; @@ -115,12 +116,11 @@ public void testIndexQos() throws Exception { stmt.setString(1, "v1"); // verify that the query does a range scan on the index table - ResultSet rs = stmt.executeQuery("EXPLAIN " + selectSql); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexTableFullName + " ['v1']", - QueryUtil.getExplainPlan(rs)); + assertPlan(stmt.unwrap(PhoenixPreparedStatement.class)).scanType("RANGE SCAN") + .tableContains(indexTableFullName).keyRanges(" ['v1']"); // verify that the correct results are returned - rs = stmt.executeQuery(); + ResultSet rs = stmt.executeQuery(); assertTrue(rs.next()); assertEquals("k1", rs.getString(1)); assertEquals("v2", rs.getString(2)); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/schema/ConditionalTTLExpressionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/schema/ConditionalTTLExpressionIT.java index 3cf200e46e5..679cc2c92fe 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/schema/ConditionalTTLExpressionIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/schema/ConditionalTTLExpressionIT.java @@ -27,6 +27,7 @@ import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.BEFORE_REPAIR_EXTRA_VERIFIED_INDEX_ROW_COUNT; import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.REBUILT_INDEX_ROW_COUNT; import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.SCANNED_DATA_ROW_COUNT; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.schema.LiteralTTLExpression.TTL_EXPRESSION_FOREVER; import static org.apache.phoenix.util.TestUtil.retainSingleQuotes; import static org.junit.Assert.assertEquals; @@ -71,7 +72,6 @@ import org.apache.phoenix.hbase.index.IndexRegionObserver; import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr; import org.apache.phoenix.jdbc.PhoenixConnection; -import org.apache.phoenix.jdbc.PhoenixResultSet; import org.apache.phoenix.mapreduce.index.IndexTool; import org.apache.phoenix.query.PhoenixTestBuilder; import org.apache.phoenix.query.PhoenixTestBuilder.SchemaBuilder; @@ -83,7 +83,6 @@ import org.apache.phoenix.util.EnvironmentEdgeManager; import org.apache.phoenix.util.ManualEnvironmentEdge; import org.apache.phoenix.util.PhoenixRuntime; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; @@ -1037,9 +1036,7 @@ public void testUnverifiedRows() throws Exception { String dql = String.format("select VAL2, VAL5 from %s where VAL1='%s' AND ID2=0", fullDataTableName, val1_0); try (ResultSet rs1 = conn.createStatement().executeQuery(dql)) { - PhoenixResultSet prs = rs1.unwrap(PhoenixResultSet.class); - String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(explainPlan.contains(fullIndexName)); + assertPlan(conn, dql).tableContains(fullIndexName); assertTrue(rs1.next()); assertEquals(rs1.getInt("VAL2"), val2); assertFalse(rs1.getBoolean(ttlCol)); @@ -1048,9 +1045,7 @@ public void testUnverifiedRows() throws Exception { dql = String.format("select VAL2, VAL5 from %s where VAL1='%s' AND ID2=1", fullDataTableName, val1_1); try (ResultSet rs1 = conn.createStatement().executeQuery(dql)) { - PhoenixResultSet prs = rs1.unwrap(PhoenixResultSet.class); - String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(explainPlan.contains(fullIndexName)); + assertPlan(conn, dql).tableContains(fullIndexName); assertNotEquals(isStrictTTL, rs1.next()); } // run the reverse index verification tool diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/JoinQueryCompilerTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/JoinQueryCompilerTest.java index c6fe30a4463..8ee65259d45 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/compile/JoinQueryCompilerTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/JoinQueryCompilerTest.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.compile; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.JOIN_CUSTOMER_TABLE_DISPLAY_NAME; import static org.apache.phoenix.util.TestUtil.JOIN_CUSTOMER_TABLE_FULL_NAME; import static org.apache.phoenix.util.TestUtil.JOIN_ITEM_TABLE_DISPLAY_NAME; @@ -30,13 +31,11 @@ import java.sql.Connection; import java.sql.DriverManager; -import java.sql.ResultSet; import java.sql.SQLException; import org.apache.phoenix.compile.JoinCompiler.JoinTable; import org.apache.phoenix.jdbc.PhoenixConnection; import org.apache.phoenix.query.BaseConnectionlessQueryTest; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.TestUtil; import org.junit.BeforeClass; import org.junit.Test; @@ -75,22 +74,22 @@ public static synchronized void createJoinTables() throws SQLException { public void testExplainPlan() throws Exception { Connection conn = DriverManager.getConnection(getUrl()); String query = - "EXPLAIN SELECT s.\"supplier_id\", \"order_id\", c.name, i.name, quantity, o.\"date\" FROM " + "SELECT s.\"supplier_id\", \"order_id\", c.name, i.name, quantity, o.\"date\" FROM " + JOIN_ORDER_TABLE_FULL_NAME + " o LEFT JOIN " + JOIN_CUSTOMER_TABLE_FULL_NAME + " c ON o.\"customer_id\" = c.\"customer_id\" AND c.name LIKE 'C%' LEFT JOIN " + JOIN_ITEM_TABLE_FULL_NAME + " i ON o.\"item_id\" = i.\"item_id\" RIGHT JOIN " + JOIN_SUPPLIER_TABLE_FULL_NAME + " s ON s.\"supplier_id\" = i.\"supplier_id\" WHERE i.name LIKE 'T%'"; - ResultSet rs = conn.createStatement().executeQuery(query); - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_DISPLAY_NAME + "\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_DISPLAY_NAME + "\n" - + " PARALLEL LEFT-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_CUSTOMER_TABLE_DISPLAY_NAME - + "\n" + " SERVER FILTER BY NAME LIKE 'C%'\n" - + " PARALLEL LEFT-JOIN TABLE 1\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_DISPLAY_NAME - + "\n" + " AFTER-JOIN SERVER FILTER BY I.NAME LIKE 'T%'", QueryUtil.getExplainPlan(rs)); + // RIGHT JOIN drives the scan over SUPPLIER, with the rest of the join tree nested as sub-plans. + assertPlan(conn, query).scanType("FULL SCAN").table(JOIN_SUPPLIER_TABLE_DISPLAY_NAME) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .afterJoinFilter("AFTER-JOIN SERVER FILTER BY I.NAME LIKE 'T%'").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN") + .table(JOIN_ORDER_TABLE_DISPLAY_NAME).subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("FULL SCAN") + .table(JOIN_CUSTOMER_TABLE_DISPLAY_NAME).serverWhereFilter("SERVER FILTER BY NAME LIKE 'C%'") + .end().subPlan(1).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 1").scanType("FULL SCAN") + .table(JOIN_ITEM_TABLE_DISPLAY_NAME).end().end(); } @Test diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java index 59bdac5ff62..0016d9d4cc1 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java @@ -19,6 +19,7 @@ import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA; import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_STATS_TABLE; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.apache.phoenix.util.TestUtil.assertDegenerate; import static org.junit.Assert.assertArrayEquals; @@ -105,7 +106,6 @@ import org.apache.phoenix.util.EnvironmentEdgeManager; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ScanUtil; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; @@ -1348,13 +1348,6 @@ public void testDuplicateKVColumn() throws Exception { } } - private void assertImmutableRows(Connection conn, String fullTableName, boolean expectedValue) - throws SQLException { - PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class); - assertEquals(expectedValue, - pconn.getTable(new PTableKey(pconn.getTenantId(), fullTableName)).isImmutableRows()); - } - @Test public void testInvalidNegativeArrayIndex() throws Exception { String query = "SELECT a_double_array[-20] FROM table_with_array"; @@ -1597,7 +1590,6 @@ public void testGroupByLimitOptimization() throws Exception { Connection conn = DriverManager.getConnection(getUrl()); conn.createStatement().execute( "CREATE TABLE t (k1 varchar, k2 varchar, v varchar, constraint pk primary key(k1,k2))"); - ResultSet rs; String[] queries = { "SELECT DISTINCT v FROM T LIMIT 3", "SELECT v FROM T GROUP BY v,k1 LIMIT 3", "SELECT count(*) FROM T GROUP BY k1 LIMIT 3", "SELECT max(v) FROM T GROUP BY k1,k2 LIMIT 3", "SELECT k1,k2 FROM T GROUP BY k1,k2 LIMIT 3", @@ -1605,12 +1597,8 @@ public void testGroupByLimitOptimization() throws Exception { // of GROUP BY key not // important }; - String query; - for (int i = 0; i < queries.length; i++) { - query = queries[i]; - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - assertTrue("Expected to find GROUP BY limit optimization in: " + query, - QueryUtil.getExplainPlan(rs).contains(" LIMIT 3 GROUPS")); + for (String query : queries) { + assertPlan(conn, query).serverGroupByLimit(3); } } @@ -1619,7 +1607,6 @@ public void testNoGroupByLimitOptimization() throws Exception { Connection conn = DriverManager.getConnection(getUrl()); conn.createStatement().execute( "CREATE TABLE t (k1 varchar, k2 varchar, v varchar, constraint pk primary key(k1,k2))"); - ResultSet rs; String[] queries = { // "SELECT DISTINCT v FROM T ORDER BY v LIMIT 3", // "SELECT v FROM T GROUP BY v,k1 ORDER BY v LIMIT 3", @@ -1627,13 +1614,8 @@ public void testNoGroupByLimitOptimization() throws Exception { "SELECT count(1) FROM T GROUP BY v,k1 LIMIT 3", "SELECT max(v) FROM T GROUP BY k1,k2 HAVING count(k1) > 1 LIMIT 3", "SELECT count(v) FROM T GROUP BY to_date(k2),k1 LIMIT 3", }; - String query; - for (int i = 0; i < queries.length; i++) { - query = queries[i]; - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - String explainPlan = QueryUtil.getExplainPlan(rs); - assertFalse("Did not expected to find GROUP BY limit optimization in: " + query, - explainPlan.contains(" LIMIT 3 GROUPS")); + for (String query : queries) { + assertPlan(conn, query).serverGroupByLimit(null); } } @@ -2219,8 +2201,7 @@ public void testServerArrayElementProjection1() throws SQLException { Connection conn = DriverManager.getConnection(getUrl()); try { conn.createStatement().execute("CREATE TABLE t(a INTEGER PRIMARY KEY, arr INTEGER ARRAY)"); - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN SELECT arr[1] from t"); - assertTrue(QueryUtil.getExplainPlan(rs).contains(" SERVER ARRAY ELEMENT PROJECTION")); + assertPlan(conn, "SELECT arr[1] from t").serverArrayElementProjection(true); } finally { conn.createStatement().execute("DROP TABLE IF EXISTS t"); conn.close(); @@ -2232,8 +2213,7 @@ public void testServerArrayElementProjection2() throws SQLException { Connection conn = DriverManager.getConnection(getUrl()); try { conn.createStatement().execute("CREATE TABLE t(a INTEGER PRIMARY KEY, arr INTEGER ARRAY)"); - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN SELECT arr, arr[1] from t"); - assertFalse(QueryUtil.getExplainPlan(rs).contains(" SERVER ARRAY ELEMENT PROJECTION")); + assertPlan(conn, "SELECT arr, arr[1] from t").serverArrayElementProjection(false); } finally { conn.createStatement().execute("DROP TABLE IF EXISTS t"); conn.close(); @@ -2246,9 +2226,7 @@ public void testServerArrayElementProjection3() throws SQLException { try { conn.createStatement() .execute("CREATE TABLE t(a INTEGER PRIMARY KEY, arr INTEGER ARRAY, arr2 VARCHAR ARRAY)"); - ResultSet rs = - conn.createStatement().executeQuery("EXPLAIN SELECT arr, arr[1], arr2[1] from t"); - assertTrue(QueryUtil.getExplainPlan(rs).contains(" SERVER ARRAY ELEMENT PROJECTION")); + assertPlan(conn, "SELECT arr, arr[1], arr2[1] from t").serverArrayElementProjection(true); } finally { conn.createStatement().execute("DROP TABLE IF EXISTS t"); conn.close(); @@ -2261,9 +2239,9 @@ public void testServerArrayElementProjection4() throws SQLException { try { conn.createStatement() .execute("CREATE TABLE t (p INTEGER PRIMARY KEY, arr1 INTEGER ARRAY, arr2 INTEGER ARRAY)"); - ResultSet rs = conn.createStatement().executeQuery( - "EXPLAIN SELECT arr1, arr1[1], ARRAY_APPEND(ARRAY_APPEND(arr1, arr2[2]), arr2[1]), p from t"); - assertTrue(QueryUtil.getExplainPlan(rs).contains(" SERVER ARRAY ELEMENT PROJECTION")); + assertPlan(conn, + "SELECT arr1, arr1[1], ARRAY_APPEND(ARRAY_APPEND(arr1, arr2[2]), arr2[1]), p from t") + .serverArrayElementProjection(true); } finally { conn.createStatement().execute("DROP TABLE IF EXISTS t"); conn.close(); @@ -2321,9 +2299,9 @@ public void testServerArrayElementProjection5() throws SQLException { try { conn.createStatement() .execute("CREATE TABLE t (p INTEGER PRIMARY KEY, arr1 INTEGER ARRAY, arr2 INTEGER ARRAY)"); - ResultSet rs = conn.createStatement().executeQuery( - "EXPLAIN SELECT arr1, arr1[1], ARRAY_ELEM(ARRAY_APPEND(arr1, arr2[1]), 1), p, arr2[2] from t"); - assertTrue(QueryUtil.getExplainPlan(rs).contains(" SERVER ARRAY ELEMENT PROJECTION")); + assertPlan(conn, + "SELECT arr1, arr1[1], ARRAY_ELEM(ARRAY_APPEND(arr1, arr2[1]), 1), p, arr2[2] from t") + .serverArrayElementProjection(true); } finally { conn.createStatement().execute("DROP TABLE IF EXISTS t"); conn.close(); @@ -2335,8 +2313,7 @@ public void testServerArrayElementProjectionWithArrayPrimaryKey() throws SQLExce Connection conn = DriverManager.getConnection(getUrl()); try { conn.createStatement().execute("CREATE TABLE t(arr INTEGER ARRAY PRIMARY KEY)"); - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN SELECT arr[1] from t"); - assertFalse(QueryUtil.getExplainPlan(rs).contains(" SERVER ARRAY ELEMENT PROJECTION")); + assertPlan(conn, "SELECT arr[1] from t").serverArrayElementProjection(false); } finally { conn.createStatement().execute("DROP TABLE IF EXISTS t"); conn.close(); @@ -2569,27 +2546,22 @@ public void testFuncIndexUsage() throws SQLException { conn.createStatement() .execute("CREATE TABLE t3(j INTEGER PRIMARY KEY," + " col3 VARCHAR, col4 VARCHAR)"); conn.createStatement().execute("CREATE INDEX idx ON t1 (col1 || col2)"); - String query = "SELECT a.k from t1 a where a.col1 || a.col2 = 'foobar'"; - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + query); - String explainPlan = QueryUtil.getExplainPlan(rs); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER IDX ['foobar']\n" - + " SERVER FILTER BY FIRST KEY ONLY", explainPlan); - query = "SELECT k,j from t3 b join t1 a ON k = j where a.col1 || a.col2 = 'foobar'"; - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - explainPlan = QueryUtil.getExplainPlan(rs); - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER T3\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER IDX ['foobar']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " DYNAMIC SERVER FILTER BY B.J IN (\"A.:K\")", explainPlan); - query = "SELECT a.k,b.k from t2 b join t1 a ON a.k = b.k where a.col1 || a.col2 = 'foobar'"; - rs = conn.createStatement().executeQuery("EXPLAIN " + query); - explainPlan = QueryUtil.getExplainPlan(rs); - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER T2\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER IDX ['foobar']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " DYNAMIC SERVER FILTER BY B.K IN (\"A.:K\")", explainPlan); + assertPlan(conn, "SELECT a.k from t1 a where a.col1 || a.col2 = 'foobar'") + .scanType("RANGE SCAN").table("IDX").keyRanges(" ['foobar']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + assertPlan(conn, "SELECT k,j from t3 b join t1 a ON k = j where a.col1 || a.col2 = 'foobar'") + .scanType("FULL SCAN").table("T3").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY B.J IN (\"A.:K\")").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("RANGE SCAN") + .table("IDX").keyRanges(" ['foobar']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .end(); + assertPlan(conn, + "SELECT a.k,b.k from t2 b join t1 a ON a.k = b.k where a.col1 || a.col2 = 'foobar'") + .scanType("FULL SCAN").table("T2").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY B.K IN (\"A.:K\")").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("RANGE SCAN") + .table("IDX").keyRanges(" ['foobar']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); } finally { conn.close(); } @@ -6301,10 +6273,7 @@ private static void verifyQueryPlanForSortMergeBug4508(Connection conn, String p + "JOIN " + peopleTable + " ds ON ds.PERSON_ID = l.LOCALID"; for (String q : new String[] { query1, query2 }) { - ResultSet rs = conn.createStatement().executeQuery("explain " + q); - String plan = QueryUtil.getExplainPlan(rs); - assertFalse("Tables should not require sort over their PKs:\n" + plan, - plan.contains("SERVER SORTED BY")); + assertPlan(conn, q).serverSortedBy(null).rhs().serverSortedBy(null); } } @@ -7179,12 +7148,9 @@ public void testReverseIndexRangeBugPhoenix6916() throws Exception { String query = "select id, ts from " + tableName + " where ts >= TIMESTAMP '2023-02-23 13:30:00' and ts < TIMESTAMP '2023-02-23 13:40:00'"; - ResultSet rs = stmt.executeQuery("EXPLAIN " + query); - String explainPlan = QueryUtil.getExplainPlan(rs); - assertEquals( - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName - + " [~1,677,159,600,000] - [~1,677,159,000,000]\n SERVER FILTER BY FIRST KEY ONLY", - explainPlan); + assertPlan(conn, query).scanType("RANGE SCAN").table(indexName) + .keyRanges(" [~1,677,159,600,000] - [~1,677,159,000,000]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); } } @@ -7196,27 +7162,21 @@ public void testReverseVarLengthRange6916() throws Exception { stmt.execute("create table " + tableName + " (k varchar primary key desc)"); - // Explain doesn't display open/closed ranges - String explainExpected = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + tableName - + " [~'aaa'] - [~'a']\n SERVER FILTER BY FIRST KEY ONLY"; - String openQry = "select * from " + tableName + " where k > 'a' and k<'aaa'"; Scan openScan = getOptimizedQueryPlan(openQry, Collections.emptyList()).getContext().getScan(); assertEquals("\\x9E\\x9E\\x9F\\x00", Bytes.toStringBinary(openScan.getStartRow())); assertEquals("\\x9E\\xFF", Bytes.toStringBinary(openScan.getStopRow())); - ResultSet rs = stmt.executeQuery("EXPLAIN " + openQry); - String explainPlan = QueryUtil.getExplainPlan(rs); - assertEquals(explainExpected, explainPlan); + assertPlan(conn, openQry).scanType("RANGE SCAN").table(tableName) + .keyRanges(" [~'aaa'] - [~'a']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); String closedQry = "select * from " + tableName + " where k >= 'a' and k <= 'aaa'"; Scan closedScan = getOptimizedQueryPlan(closedQry, Collections.emptyList()).getContext().getScan(); assertEquals("\\x9E\\x9E\\x9E\\xFF", Bytes.toStringBinary(closedScan.getStartRow())); assertEquals("\\x9F\\x00", Bytes.toStringBinary(closedScan.getStopRow())); - rs = stmt.executeQuery("EXPLAIN " + closedQry); - explainPlan = QueryUtil.getExplainPlan(rs); - assertEquals(explainExpected, explainPlan); + assertPlan(conn, closedQry).scanType("RANGE SCAN").table(tableName) + .keyRanges(" [~'aaa'] - [~'a']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); } } @@ -7232,14 +7192,11 @@ public void testUncoveredPhoenix6969() throws Exception { stmt.execute("create index ii on dd (k4, k1, k2, k3)"); String query = "select /*+ index(dd ii) */ k1, k2, k3, k4, v1, v2, v3, v4 from dd" + " where k4=1 and k2=1 order by k1 asc, v1 asc limit 1"; - ResultSet rs = stmt.executeQuery("EXPLAIN " + query); - String explainPlan = QueryUtil.getExplainPlan(rs); - // We are more interested in the query compiling than the exact result - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER II [1]\n" - + " SERVER MERGE [0.V1, 0.V2, 0.V3, 0.V4]\n" - + " SERVER FILTER BY FIRST KEY ONLY AND \"K2\" = 1\n" - + " SERVER TOP 1 ROW SORTED BY [\"K1\", \"V1\"]\n" + "CLIENT MERGE SORT\n" - + "CLIENT LIMIT 1", explainPlan); + assertPlan(conn, query).scanType("RANGE SCAN").table("II").keyRanges(" [1]") + .serverMergeColumns("[0.V1, 0.V2, 0.V3, 0.V4]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND \"K2\" = 1") + .serverSortedBy("[\"K1\", \"V1\"]").serverRowLimit(1L).clientRowLimit(1) + .clientSortAlgo("CLIENT MERGE SORT"); } } @@ -7255,16 +7212,15 @@ public void testUncoveredPhoenix6984() throws Exception { String query = "SELECT /*+ INDEX(D I), NO_INDEX_SERVER_MERGE */ * " + "FROM D " + "WHERE K2 = 'XXX' AND " + "V2 >= TIMESTAMP '2023-05-31 23:59:59.000' AND " + "V1 <= TIMESTAMP '2023-04-01 00:00:00.000' " + "ORDER BY V2 asc"; - ResultSet rs = stmt.executeQuery("EXPLAIN " + query); - String explainPlan = QueryUtil.getExplainPlan(rs); - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER D\n" - + " SERVER FILTER BY (V2 >= TIMESTAMP '2023-05-31 23:59:59.000'" - + " AND V1 <= TIMESTAMP '2023-04-01 00:00:00.000')\n" + " SERVER SORTED BY [D.V2]\n" - + "CLIENT MERGE SORT\n" + " SKIP-SCAN-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER I ['XXX']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " DYNAMIC SERVER FILTER BY (\"D.K1\", \"D.K2\", \"D.K3\", \"D.K4\")" - + " IN (($2.$4, $2.$5, $2.$6, $2.$7))", explainPlan); + assertPlan(conn, query).scanType("FULL SCAN").table("D") + .serverWhereFilter("SERVER FILTER BY (V2 >= TIMESTAMP '2023-05-31 23:59:59.000'" + + " AND V1 <= TIMESTAMP '2023-04-01 00:00:00.000')") + .serverSortedBy("[D.V2]").clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY (\"D.K1\", \"D.K2\", \"D.K3\", \"D.K4\")" + + " IN (($2.$4, $2.$5, $2.$6, $2.$7))") + .subPlanCount(1).subPlan(0).abstractExplainPlan("SKIP-SCAN-JOIN TABLE 0") + .scanType("RANGE SCAN").table("I").keyRanges(" ['XXX']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); } } @@ -7284,17 +7240,16 @@ public void testUncoveredPhoenix6986() throws Exception { "SELECT /*+ INDEX(TAB_PHOENIX_6986 IDX_PHOENIX_6986) */ * " + "FROM TAB_PHOENIX_6986 " + "WHERE K2 = 'XXX' AND " + "V2 >= TIMESTAMP '2023-05-31 23:59:59.000' AND " + "V1 <= TIMESTAMP '2023-04-01 00:00:00.000' " + "ORDER BY V2 asc"; - ResultSet rs = stmt.executeQuery("EXPLAIN " + query); - String explainPlan = QueryUtil.getExplainPlan(rs); - assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER TAB_PHOENIX_6986\n" - + " SERVER FILTER BY (V2 >= TIMESTAMP '2023-05-31 23:59:59.000'" - + " AND V1 <= TIMESTAMP '2023-04-01 00:00:00.000')\n" - + " SERVER SORTED BY [TAB_PHOENIX_6986.V2]\n" + "CLIENT MERGE SORT\n" - + " SKIP-SCAN-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER IDX_PHOENIX_6986 ['XXX']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" - + " DYNAMIC SERVER FILTER BY (\"TAB_PHOENIX_6986.K1\", \"TAB_PHOENIX_6986.K2\", \"TAB_PHOENIX_6986.K3\", \"TAB_PHOENIX_6986.K4\")" - + " IN (($2.$4, $2.$5, $2.$6, $2.$7))", explainPlan); + assertPlan(conn, query).scanType("FULL SCAN").table("TAB_PHOENIX_6986") + .serverWhereFilter("SERVER FILTER BY (V2 >= TIMESTAMP '2023-05-31 23:59:59.000'" + + " AND V1 <= TIMESTAMP '2023-04-01 00:00:00.000')") + .serverSortedBy("[TAB_PHOENIX_6986.V2]").clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY (\"TAB_PHOENIX_6986.K1\"," + + " \"TAB_PHOENIX_6986.K2\", \"TAB_PHOENIX_6986.K3\", \"TAB_PHOENIX_6986.K4\")" + + " IN (($2.$4, $2.$5, $2.$6, $2.$7))") + .subPlanCount(1).subPlan(0).abstractExplainPlan("SKIP-SCAN-JOIN TABLE 0") + .scanType("RANGE SCAN").table("IDX_PHOENIX_6986").keyRanges(" ['XXX']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); } } @@ -7306,10 +7261,8 @@ public void testUncoveredPhoenix6961() throws Exception { "create table d (k integer primary key, v1 integer, v2 integer, v3 integer, v4 integer)"); stmt.execute("create index i on d(v2) include (v3)"); String query = "select /*+ index(d i) */ * from d where v2=1 and v3=1"; - ResultSet rs = stmt.executeQuery("EXPLAIN " + query); - String explainPlan = QueryUtil.getExplainPlan(rs); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER I [1]\n" - + " SERVER MERGE [0.V1, 0.V4]\n" + " SERVER FILTER BY \"V3\" = 1", explainPlan); + assertPlan(conn, query).scanType("RANGE SCAN").table("I").keyRanges(" [1]") + .serverMergeColumns("[0.V1, 0.V4]").serverWhereFilter("SERVER FILTER BY \"V3\" = 1"); } } diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java index 286ef8c29cc..bc793f0a55e 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java @@ -21,6 +21,7 @@ import static org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants.MIN_QUALIFIER; import static org.apache.phoenix.query.QueryConstants.ENCODED_CQ_COUNTER_INITIAL_VALUE; import static org.apache.phoenix.query.QueryConstants.ENCODED_EMPTY_COLUMN_NAME; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -53,7 +54,6 @@ import org.apache.phoenix.schema.PTableType; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; import org.junit.Ignore; @@ -502,11 +502,10 @@ public void testQueryOptimizerShouldSelectThePlanWithMoreNumberOfPKColumns() thr .execute("create index INDEX_TEST_TABLE_INDEX_D on INDEX_TEST_TABLE(A,D) include(B,C,E,F)"); conn1.createStatement() .execute("create index INDEX_TEST_TABLE_INDEX_F on INDEX_TEST_TABLE(A,F) include(B,C,D,E)"); - ResultSet rs = conn2.createStatement().executeQuery( - "explain select * from INDEX_TEST_TABLE where A in ('1','2','3','4','5') and F in ('1111','2222','3333')"); - assertEquals( - "CLIENT PARALLEL 1-WAY SKIP SCAN ON 15 KEYS OVER INDEX_TEST_TABLE_INDEX_F ['1','1111'] - ['5','3333']", - QueryUtil.getExplainPlan(rs)); + assertPlan(conn2, + "select * from INDEX_TEST_TABLE where A in ('1','2','3','4','5') and F in ('1111','2222','3333')") + .scanType("SKIP SCAN ON 15 KEYS").table("INDEX_TEST_TABLE_INDEX_F") + .keyRanges(" ['1','1111'] - ['5','3333']"); } @Test diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/StatementHintsCompilationTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/StatementHintsCompilationTest.java index dd30f563c1d..4cdd5dbf921 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/compile/StatementHintsCompilationTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/StatementHintsCompilationTest.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.compile; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -24,7 +25,6 @@ import java.sql.Connection; import java.sql.DriverManager; -import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collections; import java.util.List; @@ -36,7 +36,6 @@ import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.BaseConnectionlessQueryTest; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.TestUtil; import org.junit.Test; @@ -99,26 +98,27 @@ public void testSelectForceRangeScanForEH() throws Exception { Connection conn = DriverManager.getConnection(getUrl()); conn.createStatement().execute( "create table eh (organization_id char(15) not null,parent_id char(15) not null, created_date date not null, entity_history_id char(15) not null constraint pk primary key (organization_id, parent_id, created_date, entity_history_id))"); - ResultSet rs = conn.createStatement().executeQuery( - "explain select /*+ RANGE_SCAN */ ORGANIZATION_ID, PARENT_ID, CREATED_DATE, ENTITY_HISTORY_ID from eh where ORGANIZATION_ID='111111111111111' and SUBSTR(PARENT_ID, 1, 3) = 'foo' and CREATED_DATE >= TO_DATE ('2012-11-01 00:00:00') and CREATED_DATE < TO_DATE ('2012-11-30 00:00:00') order by ORGANIZATION_ID, PARENT_ID, CREATED_DATE DESC, ENTITY_HISTORY_ID limit 100"); - assertEquals( - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER EH ['111111111111111','foo ','2012-11-01 00:00:00.000'] - ['111111111111111','fop ','2012-11-30 00:00:00.000']\n" - + " SERVER FILTER BY FIRST KEY ONLY AND (CREATED_DATE >= DATE '2012-11-01 00:00:00.000' AND CREATED_DATE < DATE '2012-11-30 00:00:00.000')\n" - + " SERVER TOP 100 ROWS SORTED BY [ORGANIZATION_ID, PARENT_ID, CREATED_DATE DESC, ENTITY_HISTORY_ID]\n" - + "CLIENT MERGE SORT\nCLIENT LIMIT 100", - QueryUtil.getExplainPlan(rs)); + String query = + "select /*+ RANGE_SCAN */ ORGANIZATION_ID, PARENT_ID, CREATED_DATE, ENTITY_HISTORY_ID from eh where ORGANIZATION_ID='111111111111111' and SUBSTR(PARENT_ID, 1, 3) = 'foo' and CREATED_DATE >= TO_DATE ('2012-11-01 00:00:00') and CREATED_DATE < TO_DATE ('2012-11-30 00:00:00') order by ORGANIZATION_ID, PARENT_ID, CREATED_DATE DESC, ENTITY_HISTORY_ID limit 100"; + assertPlan(conn, query).scanType("RANGE SCAN").table("EH") + .keyRanges(" ['111111111111111','foo ','2012-11-01 00:00:00.000']" + + " - ['111111111111111','fop ','2012-11-30 00:00:00.000']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND (CREATED_DATE >= DATE" + + " '2012-11-01 00:00:00.000' AND CREATED_DATE < DATE '2012-11-30 00:00:00.000')") + .serverSortedBy("[ORGANIZATION_ID, PARENT_ID, CREATED_DATE DESC, ENTITY_HISTORY_ID]") + .serverRowLimit(100L).clientSortAlgo("CLIENT MERGE SORT").clientRowLimit(100); } @Test public void testSerialHint() throws Exception { // test AggregatePlan String query = "SELECT /*+ SERIAL */ COUNT(*) FROM atable"; - assertTrue("Expected a SERIAL query", - compileStatement(query).getExplainPlan().getPlanSteps().get(0).contains("SERIAL")); + assertPlan(compileStatement(query).getExplainPlan().getPlanStepsAsAttributes()) + .iteratorType("SERIAL"); // test ScanPlan query = "SELECT /*+ SERIAL */ * FROM atable limit 10"; - assertTrue("Expected a SERIAL query", compileStatement(query, Collections.emptyList(), 10) - .getExplainPlan().getPlanSteps().get(0).contains("SERIAL")); + assertPlan(compileStatement(query, Collections.emptyList(), 10).getExplainPlan() + .getPlanStepsAsAttributes()).iteratorType("SERIAL"); } } diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/ExplainPlanTextTest.java b/phoenix-core/src/test/java/org/apache/phoenix/query/ExplainPlanTextTest.java deleted file mode 100644 index a58db0889b1..00000000000 --- a/phoenix-core/src/test/java/org/apache/phoenix/query/ExplainPlanTextTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.phoenix.query; - -import static org.apache.phoenix.query.QueryServices.AUTO_COMMIT_ATTRIB; -import static org.apache.phoenix.util.TestUtil.ATABLE_NAME; -import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; -import static org.junit.Assert.assertEquals; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; -import org.apache.phoenix.util.PropertiesUtil; -import org.junit.Test; - -public class ExplainPlanTextTest extends BaseConnectionlessQueryTest { - - String defaultDeleteStatement = "DELETE FROM " + ATABLE_NAME + " WHERE entity_id='abc'"; - - @Test - public void explainDeleteClientTest() throws Exception { - Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); - List plan = getExplain(defaultDeleteStatement, props); - assertEquals("DELETE ROWS CLIENT SELECT", plan.get(0)); - } - - @Test - public void explainDeleteServerTest() throws Exception { - Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); - props.setProperty(AUTO_COMMIT_ATTRIB, "true"); // need autocommit for server today - List plan = getExplain(defaultDeleteStatement, props); - assertEquals("DELETE ROWS SERVER SELECT", plan.get(0)); - } - - private List getExplain(String query, Properties props) throws SQLException { - List explainPlan = new ArrayList<>(); - try (Connection conn = DriverManager.getConnection(getUrl(), props); - PreparedStatement statement = conn.prepareStatement("EXPLAIN " + query); - ResultSet rs = statement.executeQuery()) { - while (rs.next()) { - String plan = rs.getString(1); - explainPlan.add(plan); - } - } - return explainPlan; - } -} diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java b/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java index 29873ced09a..a783ccc0a15 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java @@ -17,162 +17,18 @@ */ package org.apache.phoenix.query; -import static org.junit.Assert.assertEquals; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import java.sql.Connection; import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.Statement; import java.util.Properties; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.Test; +/** Verifies query plan details via {@link org.apache.phoenix.compile.ExplainPlanAttributes}. */ public class QueryPlanTest extends BaseConnectionlessQueryTest { - @Test - public void testExplainPlan() throws Exception { - String[] queryPlans = new String[] { - - "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') ", - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER ATABLE ['000000000000001','000000000000003'] - ['000000000000001','000000000000005']", - - "SELECT host FROM PTSDB WHERE inst IS NULL AND host IS NOT NULL AND \"DATE\" >= to_date('2013-01-01')", - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER PTSDB [null,not null]\n" - + " SERVER FILTER BY FIRST KEY ONLY AND \"DATE\" >= DATE '2013-01-01 00:00:00.000'", - - "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') ", - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER ATABLE ['000000000000001','000000000000005'] - ['000000000000001','000000000000008']", - - "SELECT host FROM PTSDB3 WHERE host IN ('na1', 'na2','na3')", - "CLIENT PARALLEL 1-WAY SKIP SCAN ON 3 KEYS OVER PTSDB3 [~'na3'] - [~'na1']\n" - + " SERVER FILTER BY FIRST KEY ONLY", - - "SELECT /*+ SMALL*/ host FROM PTSDB3 WHERE host IN ('na1', 'na2','na3')", - "CLIENT PARALLEL 1-WAY SMALL SKIP SCAN ON 3 KEYS OVER PTSDB3 [~'na3'] - [~'na1']\n" - + " SERVER FILTER BY FIRST KEY ONLY", - - "SELECT inst,\"DATE\" FROM PTSDB2 WHERE inst = 'na1' ORDER BY inst DESC, \"DATE\" DESC", - "CLIENT PARALLEL 1-WAY REVERSE RANGE SCAN OVER PTSDB2 ['na1']\n" - + " SERVER FILTER BY FIRST KEY ONLY", - - // Since inst IS NOT NULL is unbounded, we won't continue optimizing - "SELECT host FROM PTSDB WHERE inst IS NOT NULL AND host IS NULL AND \"DATE\" >= to_date('2013-01-01')", - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER PTSDB [not null]\n" - + " SERVER FILTER BY FIRST KEY ONLY AND (HOST IS NULL AND \"DATE\" >= DATE '2013-01-01 00:00:00.000')", - - "SELECT a_string,b_string FROM atable WHERE organization_id = '000000000000001' AND entity_id = '000000000000002' AND x_integer = 2 AND a_integer < 5 ", - "CLIENT PARALLEL 1-WAY POINT LOOKUP ON 1 KEY OVER ATABLE\n" - + " SERVER FILTER BY (X_INTEGER = 2 AND A_INTEGER < 5)", - - "SELECT a_string,b_string FROM atable WHERE organization_id > '000000000000001' AND entity_id > '000000000000002' AND entity_id < '000000000000008' AND (organization_id,entity_id) >= ('000000000000003','000000000000005') ", - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER ATABLE ['000000000000003000000000000005'] - [*]\n" - + " SERVER FILTER BY (ENTITY_ID > '000000000000002' AND ENTITY_ID < '000000000000008')", - - "SELECT a_string,b_string FROM atable WHERE organization_id = '000000000000001' AND entity_id >= '000000000000002' AND entity_id < '000000000000008' AND (organization_id,entity_id) >= ('000000000000000','000000000000005') ", - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER ATABLE ['000000000000001','000000000000002'] - ['000000000000001','000000000000008']", - - "SELECT * FROM atable", "CLIENT PARALLEL 1-WAY FULL SCAN OVER ATABLE", - - "SELECT inst,host FROM PTSDB WHERE inst IN ('na1', 'na2','na3') AND host IN ('a','b') AND \"DATE\" >= to_date('2013-01-01') AND \"DATE\" < to_date('2013-01-02')", - "CLIENT PARALLEL 1-WAY SKIP SCAN ON 6 RANGES OVER PTSDB ['na1','a','2013-01-01'] - ['na3','b','2013-01-02']\n" - + " SERVER FILTER BY FIRST KEY ONLY", - - "SELECT inst,host FROM PTSDB WHERE inst LIKE 'na%' AND host IN ('a','b') AND \"DATE\" >= to_date('2013-01-01') AND \"DATE\" < to_date('2013-01-02')", - "CLIENT PARALLEL 1-WAY SKIP SCAN ON 2 RANGES OVER PTSDB ['na','a','2013-01-01'] - ['nb','b','2013-01-02']\n" - + " SERVER FILTER BY FIRST KEY ONLY", - - "SELECT count(*) FROM atable", - "CLIENT PARALLEL 1-WAY FULL SCAN OVER ATABLE\n" + " SERVER FILTER BY FIRST KEY ONLY\n" - + " SERVER AGGREGATE INTO SINGLE ROW", - - "SELECT count(*) FROM atable WHERE organization_id='000000000000001' AND SUBSTR(entity_id,1,3) > '002' AND SUBSTR(entity_id,1,3) <= '003'", - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER ATABLE ['000000000000001','003 '] - ['000000000000001','004 ']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER AGGREGATE INTO SINGLE ROW", - - "SELECT a_string FROM atable WHERE organization_id='000000000000001' AND SUBSTR(entity_id,1,3) > '002' AND SUBSTR(entity_id,1,3) <= '003'", - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER ATABLE ['000000000000001','003 '] - ['000000000000001','004 ']", - - "SELECT count(1) FROM atable GROUP BY a_string", - "CLIENT PARALLEL 1-WAY FULL SCAN OVER ATABLE\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]\n" + "CLIENT MERGE SORT", - - "SELECT count(1) FROM atable GROUP BY a_string LIMIT 5", - "CLIENT PARALLEL 1-WAY FULL SCAN OVER ATABLE\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]\n" + "CLIENT MERGE SORT\n" - + "CLIENT 5 ROW LIMIT", - - "SELECT a_string FROM atable ORDER BY a_string DESC LIMIT 3", - "CLIENT PARALLEL 1-WAY FULL SCAN OVER ATABLE\n" - + " SERVER TOP 3 ROWS SORTED BY [A_STRING DESC]\n" + "CLIENT MERGE SORT\n" - + "CLIENT LIMIT 3", - - "SELECT count(1) FROM atable GROUP BY a_string,b_string HAVING max(a_string) = 'a'", - "CLIENT PARALLEL 1-WAY FULL SCAN OVER ATABLE\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]\n" - + "CLIENT MERGE SORT\n" + "CLIENT FILTER BY MAX(A_STRING) = 'a'", - - "SELECT count(1) FROM atable WHERE a_integer = 1 GROUP BY ROUND(a_time,'HOUR',2),entity_id HAVING max(a_string) = 'a'", - "CLIENT PARALLEL 1-WAY FULL SCAN OVER ATABLE\n" + " SERVER FILTER BY A_INTEGER = 1\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [ENTITY_ID, ROUND(A_TIME)]\n" - + "CLIENT MERGE SORT\n" + "CLIENT FILTER BY MAX(A_STRING) = 'a'", - - "SELECT count(1) FROM atable WHERE a_integer = 1 GROUP BY a_string,b_string HAVING max(a_string) = 'a' ORDER BY b_string", - "CLIENT PARALLEL 1-WAY FULL SCAN OVER ATABLE\n" + " SERVER FILTER BY A_INTEGER = 1\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]\n" - + "CLIENT MERGE SORT\n" + "CLIENT FILTER BY MAX(A_STRING) = 'a'\n" - + "CLIENT SORTED BY [B_STRING]", - - "SELECT a_string,b_string FROM atable WHERE organization_id = '000000000000001' AND entity_id != '000000000000002' AND x_integer = 2 AND a_integer < 5 LIMIT 10", - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER ATABLE ['000000000000001']\n" - + " SERVER FILTER BY (ENTITY_ID != '000000000000002' AND X_INTEGER = 2 AND A_INTEGER < 5)\n" - + " SERVER 10 ROW LIMIT\n" + "CLIENT 10 ROW LIMIT", - - "SELECT a_string,b_string FROM atable WHERE organization_id = '000000000000001' ORDER BY a_string ASC NULLS FIRST LIMIT 10", - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER ATABLE ['000000000000001']\n" - + " SERVER TOP 10 ROWS SORTED BY [A_STRING]\n" + "CLIENT MERGE SORT\n" - + "CLIENT LIMIT 10", - - "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", - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER ATABLE ['000000000000001']\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [ORGANIZATION_ID, ENTITY_ID, ROUND(A_DATE)]\n" - + "CLIENT MERGE SORT\n" + "CLIENT 10 ROW LIMIT", - - "SELECT a_string,b_string FROM atable WHERE organization_id = '000000000000001' ORDER BY a_string DESC NULLS LAST LIMIT 10", - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER ATABLE ['000000000000001']\n" - + " SERVER TOP 10 ROWS SORTED BY [A_STRING DESC NULLS LAST]\n" + "CLIENT MERGE SORT\n" - + "CLIENT LIMIT 10", - - "SELECT a_string,b_string FROM atable WHERE organization_id IN ('000000000000001', '000000000000005')", - "CLIENT PARALLEL 1-WAY SKIP SCAN ON 2 KEYS OVER ATABLE ['000000000000001'] - ['000000000000005']", - - "SELECT a_string,b_string FROM atable WHERE organization_id IN ('00D000000000001', '00D000000000005') AND entity_id IN('00E00000000000X','00E00000000000Z')", - "CLIENT PARALLEL 1-WAY POINT LOOKUP ON 4 KEYS OVER ATABLE", - - "SELECT inst,host FROM PTSDB WHERE REGEXP_SUBSTR(INST, '[^-]+', 1) IN ('na1', 'na2','na3')", - "CLIENT PARALLEL 1-WAY SKIP SCAN ON 3 RANGES OVER PTSDB ['na1'] - ['na4']\n" - + " SERVER FILTER BY FIRST KEY ONLY AND REGEXP_SUBSTR(INST, '[^-]+', 1) IN ('na1','na2','na3')", - - }; - for (int i = 0; i < queryPlans.length; i += 2) { - String query = queryPlans[i]; - String plan = queryPlans[i + 1]; - Properties props = new Properties(); - // Override date format so we don't have a bunch of zeros - props.setProperty(QueryServices.DATE_FORMAT_ATTRIB, "yyyy-MM-dd"); - Connection conn = DriverManager.getConnection(getUrl(), props); - try { - Statement statement = conn.createStatement(); - ResultSet rs = statement.executeQuery("EXPLAIN " + query); - // TODO: figure out a way of verifying that query isn't run during explain execution - assertEquals((i / 2 + 1) + ") " + query, plan, QueryUtil.getExplainPlan(rs)); - } finally { - conn.close(); - } - } - } - @Test public void testTenantSpecificConnWithLimit() throws Exception { String baseTableDDL = @@ -190,27 +46,24 @@ public void testTenantSpecificConnWithLimit() throws Exception { conn = DriverManager.getConnection(getUrl(), tenantProps); conn.createStatement().execute(tenantViewDDL); - String query = "EXPLAIN SELECT * FROM TENANT_VIEW LIMIT 1"; - ResultSet rs = conn.createStatement().executeQuery(query); - assertEquals("CLIENT SERIAL 1-WAY RANGE SCAN OVER BASE_MULTI_TENANT_TABLE ['tenantId']\n" - + " SERVER 1 ROW LIMIT\n" + "CLIENT 1 ROW LIMIT", QueryUtil.getExplainPlan(rs)); - query = "EXPLAIN SELECT * FROM TENANT_VIEW LIMIT " + Integer.MAX_VALUE; - rs = conn.createStatement().executeQuery(query); - assertEquals( - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER BASE_MULTI_TENANT_TABLE ['tenantId']\n" + " SERVER " - + Integer.MAX_VALUE + " ROW LIMIT\n" + "CLIENT " + Integer.MAX_VALUE + " ROW LIMIT", - QueryUtil.getExplainPlan(rs)); - query = "EXPLAIN SELECT * FROM TENANT_VIEW WHERE username = 'Joe' LIMIT 1"; - rs = conn.createStatement().executeQuery(query); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER BASE_MULTI_TENANT_TABLE ['tenantId']\n" - + " SERVER FILTER BY USERNAME = 'Joe'\n" + " SERVER 1 ROW LIMIT\n" - + "CLIENT 1 ROW LIMIT", QueryUtil.getExplainPlan(rs)); - query = "EXPLAIN SELECT * FROM TENANT_VIEW WHERE col = 'Joe' LIMIT 1"; - rs = conn.createStatement().executeQuery(query); - assertEquals( - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER BASE_MULTI_TENANT_TABLE ['tenantId']\n" - + " SERVER FILTER BY COL = 'Joe'\n" + " SERVER 1 ROW LIMIT\n" + "CLIENT 1 ROW LIMIT", - QueryUtil.getExplainPlan(rs)); + // LIMIT 1 uses a SERIAL iterator and pushes the limit to both server and client. + assertPlan(conn, "SELECT * FROM TENANT_VIEW LIMIT 1").iteratorType("SERIAL") + .scanType("RANGE SCAN").table("BASE_MULTI_TENANT_TABLE").keyRanges(" ['tenantId']") + .serverRowLimit(1L).clientRowLimit(1); + + // A very large limit falls back to a PARALLEL iterator. + assertPlan(conn, "SELECT * FROM TENANT_VIEW LIMIT " + Integer.MAX_VALUE) + .iteratorType("PARALLEL").scanType("RANGE SCAN").table("BASE_MULTI_TENANT_TABLE") + .keyRanges(" ['tenantId']").serverRowLimit((long) Integer.MAX_VALUE) + .clientRowLimit(Integer.MAX_VALUE); + + assertPlan(conn, "SELECT * FROM TENANT_VIEW WHERE username = 'Joe' LIMIT 1") + .scanType("RANGE SCAN").table("BASE_MULTI_TENANT_TABLE").keyRanges(" ['tenantId']") + .serverWhereFilter("SERVER FILTER BY USERNAME = 'Joe'").serverRowLimit(1L).clientRowLimit(1); + + assertPlan(conn, "SELECT * FROM TENANT_VIEW WHERE col = 'Joe' LIMIT 1").scanType("RANGE SCAN") + .table("BASE_MULTI_TENANT_TABLE").keyRanges(" ['tenantId']") + .serverWhereFilter("SERVER FILTER BY COL = 'Joe'").serverRowLimit(1L).clientRowLimit(1); } @Test @@ -223,18 +76,13 @@ public void testDescTimestampAtBoundary() throws Exception { + " b TIMESTAMP NOT NULL,\n" + " c VARCHAR,\n" + " CONSTRAINT pk PRIMARY KEY (a, b DESC, c)\n" + " ) IMMUTABLE_ROWS=true\n" + " ,SALT_BUCKETS=20"); - String query = - "select * from foo where a = 'a' and b >= timestamp '2016-01-28 00:00:00' and b < timestamp '2016-01-29 00:00:00'"; - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + query); - String queryPlan = QueryUtil.getExplainPlan(rs); - // For real connection CQSI, the result is supposed to be 20-WAY RANGE SCAN, however - // for connection-less impl, since we retrieve region locations for 20 splits and each - // time we get all region locations due to connection-less specific impl, we get - // 20*20 = 400-WAY RANGE SCAN. - assertEquals( - "CLIENT PARALLEL 400-WAY RANGE SCAN OVER FOO [X'00','a',~'2016-01-28 23:59:59.999'] - [X'13','a',~'2016-01-28 00:00:00.000']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + "CLIENT MERGE SORT", - queryPlan); + String query = "select * from foo where a = 'a' and b >= timestamp '2016-01-28 00:00:00'" + + " and b < timestamp '2016-01-29 00:00:00'"; + // The salient detail is the DESC-timestamp key range. + assertPlan(conn, query).scanType("RANGE SCAN").table("FOO") + .keyRanges( + " [X'00','a',~'2016-01-28 23:59:59.999'] -" + " [X'13','a',~'2016-01-28 00:00:00.000']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); } finally { conn.close(); } @@ -252,17 +100,14 @@ public void testUseOfRoundRobinIteratorSurfaced() throws Exception { + " b TIMESTAMP NOT NULL,\n" + " c VARCHAR,\n" + " CONSTRAINT pk PRIMARY KEY (a, b DESC, c)\n" + " ) IMMUTABLE_ROWS=true\n" + " ,SALT_BUCKETS=20"); - String query = "select * from " + tableName - + " where a = 'a' and b >= timestamp '2016-01-28 00:00:00' and b < timestamp '2016-01-29 00:00:00'"; - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + query); - String queryPlan = QueryUtil.getExplainPlan(rs); - // For real connection CQSI, the result is supposed to be 20-WAY RANGE SCAN, however - // for connection-less impl, since we retrieve region locations for 20 splits and each - // time we get all region locations due to connection-less specific impl, we get - // 20*20 = 400-WAY RANGE SCAN. - assertEquals("CLIENT PARALLEL 400-WAY ROUND ROBIN RANGE SCAN OVER " + tableName - + " [X'00','a',~'2016-01-28 23:59:59.999'] - [X'13','a',~'2016-01-28 00:00:00.000']\n" - + " SERVER FILTER BY FIRST KEY ONLY", queryPlan); + String query = + "select * from " + tableName + " where a = 'a' and b >= timestamp '2016-01-28 00:00:00'" + + " and b < timestamp '2016-01-29 00:00:00'"; + // The round-robin iterator is surfaced as a dedicated attribute. + assertPlan(conn, query).useRoundRobinIterator(true).scanType("RANGE SCAN").table(tableName) + .keyRanges( + " [X'00','a',~'2016-01-28 23:59:59.999'] -" + " [X'13','a',~'2016-01-28 00:00:00.000']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); } finally { conn.close(); } @@ -270,7 +115,6 @@ public void testUseOfRoundRobinIteratorSurfaced() throws Exception { @Test public void testSerialHintIgnoredForNonRowkeyOrderBy() throws Exception { - Properties props = PropertiesUtil.deepCopy(new Properties()); Connection conn = DriverManager.getConnection(getUrl(), props); try { @@ -279,15 +123,13 @@ public void testSerialHintIgnoredForNonRowkeyOrderBy() throws Exception { + " b TIMESTAMP NOT NULL,\n" + " c VARCHAR,\n" + " CONSTRAINT pk PRIMARY KEY (a, b DESC, c)\n" + " )"); String query = "select /*+ SERIAL*/ * from foo where a = 'a' ORDER BY b, c"; - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + query); - String queryPlan = QueryUtil.getExplainPlan(rs); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER FOO ['a']\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER SORTED BY [B, C]\n" - + "CLIENT MERGE SORT", queryPlan); + // The SERIAL hint is ignored for a non-rowkey ORDER BY, so the iterator stays PARALLEL and a + // server sort + client merge sort are planned. + assertPlan(conn, query).iteratorType("PARALLEL").scanType("RANGE SCAN").table("FOO") + .keyRanges(" ['a']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverSortedBy("[B, C]").clientSortAlgo("CLIENT MERGE SORT"); } finally { conn.close(); } - } - } 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 51144afd20b..eccde4303ac 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 @@ -18,8 +18,10 @@ package org.apache.phoenix.query.explain; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.regex.Matcher; import java.util.regex.Pattern; /** @@ -30,6 +32,11 @@ public final class ExplainJsonNormalizer { private static final Pattern WAY_COUNT = Pattern.compile("\\b\\d+-WAY\\b"); + // Dynamic filter bind aliases vary with compilation and test-execution order. + private static final Pattern DYNAMIC_FILTER_ALIAS = Pattern.compile("\\$\\d+\\.\\$\\d+"); + private static final String DYNAMIC_FILTER_ALIAS_PLACEHOLDER = + Matcher.quoteReplacement("$.$"); + /** * Recursively normalize the given attributes-shaped JSON node. * @return the same node, for fluent chaining. @@ -61,11 +68,25 @@ public JsonNode normalize(JsonNode node) { obj.put("iteratorTypeAndScanSize", WAY_COUNT.matcher(iter.asText()).replaceAll("-WAY")); } + JsonNode dynamicServerFilter = obj.get("dynamicServerFilter"); + if (dynamicServerFilter != null && dynamicServerFilter.isTextual()) { + obj.put("dynamicServerFilter", DYNAMIC_FILTER_ALIAS.matcher(dynamicServerFilter.asText()) + .replaceAll(DYNAMIC_FILTER_ALIAS_PLACEHOLDER)); + } + JsonNode rhs = obj.get("rhsJoinQueryExplainPlan"); if (rhs != null && rhs.isObject()) { normalize(rhs); } + JsonNode subPlans = obj.get("subPlans"); + if (subPlans != null && subPlans.isArray()) { + ArrayNode subPlansArray = (ArrayNode) subPlans; + for (JsonNode subPlan : subPlansArray) { + normalize(subPlan); + } + } + return obj; } } diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainCompatibilityTest.java b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTest.java similarity index 74% rename from phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainCompatibilityTest.java rename to phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTest.java index cd171762865..df26951ff37 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainCompatibilityTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTest.java @@ -25,6 +25,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -45,7 +46,6 @@ import org.apache.phoenix.compile.ExplainPlan; import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.compile.ExplainPlanAttributes.ExplainPlanAttributesBuilder; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.BaseConnectionlessQueryTest; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.schema.PColumn; @@ -66,7 +66,7 @@ * to the {@link ExplainOracle} for a tolerant comparison. The corpus covers every EXPLAIN grammar * branch reachable without a connection. */ -public class ExplainCompatibilityTest extends BaseConnectionlessQueryTest { +public class ExplainPlanTest extends BaseConnectionlessQueryTest { private static final String SALTED = "EO_SALTED"; private static final String SEQ = "EO_SEQ"; @@ -266,6 +266,170 @@ public void testArrayElementProjection() throws Exception { scanAttrs("FULL SCAN ", "TABLE_WITH_ARRAY", "").put("serverArrayElementProjection", true)); } + @Test + public void testRangeScanCompositeRvcUpperBound() throws Exception { + verifyQuery("rangeScanCompositeRvcUpper", + "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 -WAY RANGE SCAN OVER ATABLE" + + " ['000000000000001','000000000000003'] - ['000000000000001','000000000000005']"), + scanAttrs("RANGE SCAN ", "ATABLE", + " ['000000000000001','000000000000003'] - ['000000000000001','000000000000005']")); + } + + @Test + public void testRangeScanCompositeRvcOpenUpperBound() throws Exception { + verifyQuery("rangeScanCompositeRvcOpen", + "SELECT a_string,b_string FROM atable WHERE organization_id > '000000000000001'" + + " AND entity_id > '000000000000002' AND entity_id < '000000000000008'" + + " AND (organization_id,entity_id) >= ('000000000000003','000000000000005')", + text( + "CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE" + + " ['000000000000003000000000000005'] - [*]", + " SERVER FILTER BY (ENTITY_ID > '000000000000002' AND ENTITY_ID < '000000000000008')"), + scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000003000000000000005'] - [*]").put( + "serverWhereFilter", + "SERVER FILTER BY (ENTITY_ID > '000000000000002' AND ENTITY_ID < '000000000000008')")); + } + + @Test + public void testRangeScanNullNotNull() throws Exception { + 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 -WAY RANGE SCAN OVER PTSDB [null,not null]", + " 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'")); + } + + @Test + public void testRangeScanNotNull() throws Exception { + 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 -WAY RANGE SCAN OVER PTSDB [not null]", + " 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" + + " AND \"DATE\" >= DATE '2013-01-01 00:00:00.000')")); + } + + @Test + public void testSkipScanLikeRanges() throws Exception { + verifyQuery("skipScanLikeRanges", + "SELECT inst,host FROM PTSDB WHERE inst LIKE 'na%' AND host IN ('a','b')" + + " AND \"DATE\" >= to_date('2013-01-01') AND \"DATE\" < to_date('2013-01-02')", + text( + "CLIENT PARALLEL -WAY SKIP SCAN ON 2 RANGES OVER PTSDB" + + " ['na','a','2013-01-01'] - ['nb','b','2013-01-02']", + " 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")); + } + + @Test + public void testSkipScanRegexpRanges() throws Exception { + verifyQuery("skipScanRegexpRanges", + "SELECT inst,host FROM PTSDB WHERE REGEXP_SUBSTR(INST, '[^-]+', 1) IN ('na1', 'na2','na3')", + text("CLIENT PARALLEL -WAY SKIP SCAN ON 3 RANGES OVER PTSDB ['na1'] - ['na4']", + " 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')")); + } + + @Test + public void testRangeScanSubstrBounds() throws Exception { + 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 -WAY RANGE SCAN OVER ATABLE" + + " ['000000000000001','003 '] - ['000000000000001','004 ']"), + scanAttrs("RANGE SCAN ", "ATABLE", + " ['000000000000001','003 '] - ['000000000000001','004 ']")); + } + + @Test + public void testSkipScanTwoKeys() throws Exception { + verifyQuery("skipScanTwoKeys", + "SELECT a_string,b_string FROM atable" + + " WHERE organization_id IN ('000000000000001', '000000000000005')", + text("CLIENT PARALLEL -WAY SKIP SCAN ON 2 KEYS OVER ATABLE" + + " ['000000000000001'] - ['000000000000005']"), + 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 -WAY FULL SCAN OVER ATABLE", + " 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")); + } + + @Test + public void testTopNAscNullsFirstLimit() throws Exception { + 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 -WAY RANGE SCAN OVER ATABLE ['000000000000001']", + " 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")); + } + + @Test + public void testTopNDescNullsLastLimit() throws Exception { + 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 -WAY RANGE SCAN OVER ATABLE ['000000000000001']", + " 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")); + } + + @Test + public void testAggregateOrderByClientLimit() throws Exception { + verifyQuery("aggregateOrderByClientLimit", + "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 -WAY RANGE SCAN OVER ATABLE ['000000000000001']", + " 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']") + .put("serverAggregate", + "SERVER AGGREGATE INTO DISTINCT ROWS BY [ORGANIZATION_ID, ENTITY_ID, ROUND(A_DATE)]") + .put("clientRowLimit", 10).put("clientSortAlgo", "CLIENT MERGE SORT")); + } + + @Test + public void testClientSortedByHaving() throws Exception { + 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 -WAY FULL SCAN OVER ATABLE", " 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", "") + .put("serverWhereFilter", "SERVER FILTER BY A_INTEGER = 1") + .put("serverAggregate", "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]") + .put("clientFilterBy", "MAX(A_STRING) = 'a'").put("clientSortedBy", "[B_STRING]") + .put("clientOffset", 0).put("clientSortAlgo", "CLIENT MERGE SORT")); + } + @Test public void testSortMergeJoin() throws Exception { ObjectNode rhs = scanAttrs("FULL SCAN ", "ATABLE", ""); @@ -282,6 +446,14 @@ public void testSortMergeJoin() throws Exception { @Test public void testHashJoinInner() throws Exception { + // HashJoinPlan root attributes come from the delegate scan. Each hash/skip-scan child + // is recorded under subPlans with its join header on abstractExplainPlan. + ObjectNode child = scanAttrs("FULL SCAN ", "ATABLE", "").put("abstractExplainPlan", + "PARALLEL INNER-JOIN TABLE 0"); + ObjectNode expected = scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']"); + expected.set("subPlans", mapper.createArrayNode().add(child)); + expected.put("dynamicServerFilter", + "DYNAMIC SERVER FILTER BY A.ORGANIZATION_ID IN (B.ORGANIZATION_ID)"); verifyQuery("hashJoinInner", "SELECT a.a_string, b.a_string FROM atable a" + " JOIN atable b ON a.organization_id = b.organization_id" @@ -289,13 +461,20 @@ public void testHashJoinInner() throws Exception { text("CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']", " PARALLEL INNER-JOIN TABLE 0", " CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " DYNAMIC SERVER FILTER BY A.ORGANIZATION_ID IN (B.ORGANIZATION_ID)"), - // HashJoinPlan uses the List-only ExplainPlan constructor, which installs the - // default attributes (all-null/empty). Freeze that baseline. - defaultAttrs()); + expected); } @Test public void testHashJoinSemiInSubquery() throws Exception { + ObjectNode child = + scanAttrs("FULL SCAN ", "ATABLE", "").put("abstractExplainPlan", "SKIP-SCAN-JOIN TABLE 0") + .put("serverWhereFilter", "SERVER FILTER BY A_INTEGER = 1") + .put("serverAggregate", "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [ORGANIZATION_ID]"); + ObjectNode expected = scanAttrs("FULL SCAN ", "ATABLE", ""); + expected.set("subPlans", mapper.createArrayNode().add(child)); + // Dynamic filter bind aliases ($N.$N) are normalized to $.$. + expected.put("dynamicServerFilter", + "DYNAMIC SERVER FILTER BY ATABLE.ORGANIZATION_ID IN ($.$)"); verifyQuery("hashJoinSemiInSubquery", "SELECT a_string FROM atable" + " WHERE organization_id IN (SELECT organization_id FROM atable WHERE a_integer = 1)", @@ -303,8 +482,8 @@ public void testHashJoinSemiInSubquery() throws Exception { " CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " SERVER FILTER BY A_INTEGER = 1", " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [ORGANIZATION_ID]", - " DYNAMIC SERVER FILTER BY ATABLE.ORGANIZATION_ID IN ($1.$3)"), - defaultAttrs()); + " DYNAMIC SERVER FILTER BY ATABLE.ORGANIZATION_ID IN ($.$)"), + expected); } @Test @@ -355,8 +534,11 @@ public void testUpsertSelectServer() throws Exception { @Test public void testDeleteSingleRow() throws Exception { - verifyMutation("deleteSingleRow", "DELETE FROM atable WHERE organization_id = '00D000000000001'" - + " AND entity_id = '00E00000000001'", true, text("DELETE SINGLE ROW"), defaultAttrs()); + verifyMutation("deleteSingleRow", + "DELETE FROM atable WHERE organization_id = '00D000000000001'" + + " AND entity_id = '00E00000000001'", + true, text("DELETE SINGLE ROW"), + defaultAttrs().put("abstractExplainPlan", "DELETE SINGLE ROW")); } @Test @@ -496,21 +678,20 @@ public void testJsonNormalizerRecursesIntoRhsJoinQueryExplainPlan() { @Test public void testJacksonFieldOrderMatchesPropertyOrderAnnotation() throws Exception { - ExplainPlanAttributes a = new ExplainPlanAttributesBuilder() - .setIteratorTypeAndScanSize("PARALLEL 1-WAY").setTableName("T").build(); - String json = mapper.writeValueAsString(a); - int iAbstract = json.indexOf("\"abstractExplainPlan\""); - int iIter = json.indexOf("\"iteratorTypeAndScanSize\""); - int iTable = json.indexOf("\"tableName\""); - int iRhs = json.indexOf("\"rhsJoinQueryExplainPlan\""); - int iMerge = json.indexOf("\"serverMergeColumns\""); - int iRegions = json.indexOf("\"regionLocations\""); - int iLookups = json.indexOf("\"numRegionLocationLookups\""); - assertTrue("abstractExplainPlan first", iAbstract >= 0 && iAbstract < iIter); - assertTrue("iteratorTypeAndScanSize before tableName", iIter < iTable); - assertTrue("rhsJoinQueryExplainPlan before serverMergeColumns", iRhs < iMerge); - assertTrue("serverMergeColumns before regionLocations", iMerge < iRegions); - assertTrue("regionLocations before numRegionLocationLookups", iRegions < iLookups); + // The serialized field order must exactly follow the @JsonPropertyOrder declaration. Deriving + // the expected order from the annotation keeps this test correct across future reorderings. + String[] expectedOrder = + ExplainPlanAttributes.class.getAnnotation(JsonPropertyOrder.class).value(); + String json = mapper.writeValueAsString(new ExplainPlanAttributesBuilder().build()); + int prevIdx = -1; + String prevName = null; + for (String name : expectedOrder) { + int idx = json.indexOf("\"" + name + "\""); + assertTrue(name + " present in serialized JSON", idx >= 0); + assertTrue(name + " must serialize after " + prevName, idx > prevIdx); + prevIdx = idx; + prevName = name; + } } @Test @@ -575,9 +756,8 @@ public void testChangeRuleRewritesText() throws Exception { // Sanity: with no rules, today's plan compares against today's expected. new ExplainOracle().verify("today", todayPlan, todayExpectedText, todayExpectedJson); - // Tomorrow: design renames the marker. Future plan emits the new form; the embedded baseline - // stays unchanged ("today's" expected); a rule transforms the baseline into the new shape and - // the comparison passes. + // Future plan emits the new form. The embedded baseline stays unchanged and a rule transforms + // the baseline into the new shape so it passes the comparison. ExplainPlanAttributes futureAttrs = new ExplainPlanAttributesBuilder() .setIteratorTypeAndScanSize("PARALLEL 1-WAY").setExplainScanType("FULL SCAN ") .setTableName("T").setServerWhereFilter("SERVER PROJECTION FILTER BY FIRST KEY ONLY").build(); @@ -644,8 +824,7 @@ private void verifyQuery(String caseId, String query, List expectedText, private void verifyQuery(String caseId, String query, Properties props, List expectedText, JsonNode expectedJson) throws Exception { try (Connection conn = DriverManager.getConnection(getUrl(), props)) { - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); + ExplainPlan plan = ExplainPlanTestUtil.getExplainPlan(conn, query); oracle.verify(caseId, plan, expectedText, expectedJson); } } @@ -663,9 +842,7 @@ private void verifyMutation(String caseId, String query, boolean autoCommit, } private ExplainPlan compileMutation(Connection conn, String query) throws SQLException { - PhoenixPreparedStatement ps = - conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class); - return ps.compileMutation().getExplainPlan(); + return ExplainPlanTestUtil.getMutationExplainPlan(conn, query); } private static Properties defaultProps() { @@ -678,13 +855,6 @@ private static List text(String... lines) { return Arrays.asList(lines); } - /** - * Returns a fresh {@link ObjectNode} populated with the JSON shape that - * {@link ExplainPlanAttributes#getDefaultExplainPlan()} serializes to (after normalization): all - * nullable fields are null, both booleans are false, and {@code numRegionLocationLookups} is 0. - * Each test case starts from this baseline and overrides only the fields it asserts on. Field - * order is irrelevant — {@link JsonNode#equals(Object)} compares maps, not order. - */ private static ObjectNode defaultAttrs() { ObjectNode n = mapper.createObjectNode(); n.putNull("abstractExplainPlan"); @@ -723,13 +893,17 @@ private static ObjectNode defaultAttrs() { n.putNull("serverMergeColumns"); n.putNull("regionLocations"); n.put("numRegionLocationLookups", 0); + n.putNull("subPlans"); + n.putNull("serverGroupByLimit"); + n.putNull("dynamicServerFilter"); + n.putNull("afterJoinFilter"); + n.putNull("joinScannerLimit"); + n.put("sortMergeSkipMerge", false); return n; } /** - * Convenience wrapper that builds {@link #defaultAttrs()} and sets the five fields every - * connection-backed scan emits via {@code ExplainTable.explain}: {@code iteratorTypeAndScanSize}, - * {@code consistency}, {@code explainScanType}, {@code tableName}, and {@code keyRanges}. + * Convenience method that builds {@link #defaultAttrs()}. * @param scanType the {@code explainScanType} string (with its trailing space, e.g. * {@code "FULL SCAN "}) * @param table the {@code tableName} value 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 new file mode 100644 index 00000000000..6a969d87f67 --- /dev/null +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTestUtil.java @@ -0,0 +1,361 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.phoenix.query.explain; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; +import org.apache.phoenix.compile.ExplainPlan; +import org.apache.phoenix.compile.ExplainPlanAttributes; +import org.apache.phoenix.jdbc.PhoenixPreparedStatement; + +/** + * Test helpers for retrieving the {@link ExplainPlan} and its structured + * {@link ExplainPlanAttributes} for a query or mutation (without going through the textual + * {@code EXPLAIN ...} ResultSet path), plus a fluent {@link ExplainPlanAssert} API for asserting on + * the attribute values. + */ +public final class ExplainPlanTestUtil { + + private ExplainPlanTestUtil() { + } + + /** Optimize {@code query} and return its {@link ExplainPlan}. */ + public static ExplainPlan getExplainPlan(Connection conn, String query) throws SQLException { + try (PhoenixPreparedStatement statement = + conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class)) { + return statement.optimizeQuery().getExplainPlan(); + } + } + + /** Optimize {@code query} and return its structured {@link ExplainPlanAttributes}. */ + public static ExplainPlanAttributes getExplainAttributes(Connection conn, String query) + throws SQLException { + return getExplainPlan(conn, query).getPlanStepsAsAttributes(); + } + + /** Optimize {@code query} and return its plan-steps text. */ + public static List getPlanSteps(Connection conn, String query) throws SQLException { + return getExplainPlan(conn, query).getPlanSteps(); + } + + /** Compile a mutation (UPSERT/DELETE) and return its {@link ExplainPlan}. */ + public static ExplainPlan getMutationExplainPlan(Connection conn, String query) + throws SQLException { + try (PhoenixPreparedStatement statement = + conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class)) { + return statement.compileMutation().getExplainPlan(); + } + } + + /** Compile a mutation (UPSERT/DELETE) and return its structured {@link ExplainPlanAttributes}. */ + public static ExplainPlanAttributes getMutationExplainAttributes(Connection conn, String query) + throws SQLException { + return getMutationExplainPlan(conn, query).getPlanStepsAsAttributes(); + } + + /** Begin assertions on the given attributes. */ + public static ExplainPlanAssert assertPlan(ExplainPlanAttributes attributes) { + assertNotNull("ExplainPlanAttributes must not be null", attributes); + return new ExplainPlanAssert(attributes, null, "plan"); + } + + /** Optimize {@code query} on {@code conn} and begin assertions on its plan attributes. */ + public static ExplainPlanAssert assertPlan(Connection conn, String query) throws SQLException { + return assertPlan(getExplainAttributes(conn, query)); + } + + /** + * Optimize an already-prepared and, if needed, parameter-bound {@link PhoenixPreparedStatement} + * and begin assertions on its plan attributes. + */ + public static ExplainPlanAssert assertPlan(PhoenixPreparedStatement statement) + throws SQLException { + return assertPlan(statement.optimizeQuery().getExplainPlan().getPlanStepsAsAttributes()); + } + + /** Compile the mutation {@code query} on {@code conn} and begin assertions on its attributes. */ + public static ExplainPlanAssert assertMutationPlan(Connection conn, String query) + throws SQLException { + return assertPlan(getMutationExplainAttributes(conn, query)); + } + + /** + * Compile an already-prepared and, if needed, parameter-bound mutation + * {@link PhoenixPreparedStatement} and begin assertions on its attributes. + */ + public static ExplainPlanAssert assertMutationPlan(PhoenixPreparedStatement statement) + throws SQLException { + return assertPlan(statement.compileMutation().getExplainPlan().getPlanStepsAsAttributes()); + } + + /** Fluent assertions over {@link ExplainPlanAttributes}. */ + public static final class ExplainPlanAssert { + + private final ExplainPlanAttributes attributes; + private final ExplainPlanAssert parent; + private final String context; + + private ExplainPlanAssert(ExplainPlanAttributes attributes, ExplainPlanAssert parent, + String context) { + this.attributes = attributes; + this.parent = parent; + this.context = context; + } + + public ExplainPlanAttributes attributes() { + return attributes; + } + + /** + * Assert the scan type, e.g. {@code "FULL SCAN"}, {@code "RANGE SCAN"}, + * {@code "POINT LOOKUP ON 1 KEY"}, {@code "SKIP SCAN ON 3 KEYS"}. + */ + public ExplainPlanAssert scanType(String expected) { + assertEquals(at("explainScanType"), trim(expected), trim(attributes.getExplainScanType())); + return this; + } + + /** Assert the scanned table (or index) name. */ + public ExplainPlanAssert table(String expected) { + assertEquals(at("tableName"), expected, attributes.getTableName()); + return this; + } + + /** + * Assert the scanned table or index name contains {@code expected}. Useful for "index used" + * checks where the exact name carries a suffix. + */ + public ExplainPlanAssert tableContains(String expected) { + String actual = attributes.getTableName(); + assertNotNull(at("tableName") + " must not be null", actual); + assertTrue( + at("tableName") + " expected to contain '" + expected + "' but was '" + actual + "'", + actual.contains(expected)); + return this; + } + + /** Assert the hex-string row-value-constructor offset marker. */ + public ExplainPlanAssert hexStringRVCOffset(String expected) { + assertEquals(at("hexStringRVCOffset"), expected, attributes.getHexStringRVCOffset()); + return this; + } + + /** Assert the key ranges string (exact; leading space is significant). */ + public ExplainPlanAssert keyRanges(String expected) { + assertEquals(at("keyRanges"), expected, attributes.getKeyRanges()); + return this; + } + + public ExplainPlanAssert abstractExplainPlan(String expected) { + assertEquals(at("abstractExplainPlan"), expected, attributes.getAbstractExplainPlan()); + return this; + } + + /** Assert the read consistency level. */ + public ExplainPlanAssert consistency(String expected) { + assertEquals(at("consistency"), expected, + attributes.getConsistency() == null ? null : attributes.getConsistency().name()); + return this; + } + + public ExplainPlanAssert serverWhereFilter(String expected) { + assertEquals(at("serverWhereFilter"), expected, attributes.getServerWhereFilter()); + return this; + } + + public ExplainPlanAssert serverAggregate(String expected) { + assertEquals(at("serverAggregate"), expected, attributes.getServerAggregate()); + return this; + } + + public ExplainPlanAssert serverSortedBy(String expected) { + assertEquals(at("serverSortedBy"), expected, attributes.getServerSortedBy()); + return this; + } + + public ExplainPlanAssert serverDistinctFilter(String expected) { + assertEquals(at("serverDistinctFilter"), expected, attributes.getServerDistinctFilter()); + return this; + } + + public ExplainPlanAssert serverMergeColumns(String expected) { + assertEquals(at("serverMergeColumns"), expected, + attributes.getServerMergeColumns() == null + ? null + : attributes.getServerMergeColumns().toString()); + return this; + } + + public ExplainPlanAssert clientFilterBy(String expected) { + assertEquals(at("clientFilterBy"), expected, attributes.getClientFilterBy()); + return this; + } + + public ExplainPlanAssert clientAggregate(String expected) { + assertEquals(at("clientAggregate"), expected, attributes.getClientAggregate()); + return this; + } + + public ExplainPlanAssert clientSortedBy(String expected) { + assertEquals(at("clientSortedBy"), expected, attributes.getClientSortedBy()); + return this; + } + + public ExplainPlanAssert clientAfterAggregate(String expected) { + assertEquals(at("clientAfterAggregate"), expected, attributes.getClientAfterAggregate()); + return this; + } + + public ExplainPlanAssert clientDistinctFilter(String expected) { + assertEquals(at("clientDistinctFilter"), expected, attributes.getClientDistinctFilter()); + return this; + } + + public ExplainPlanAssert clientSortAlgo(String expected) { + assertEquals(at("clientSortAlgo"), expected, attributes.getClientSortAlgo()); + return this; + } + + public ExplainPlanAssert serverRowLimit(Long expected) { + assertEquals(at("serverRowLimit"), expected, attributes.getServerRowLimit()); + return this; + } + + public ExplainPlanAssert serverGroupByLimit(Integer expected) { + assertEquals(at("serverGroupByLimit"), expected, attributes.getServerGroupByLimit()); + return this; + } + + public ExplainPlanAssert clientRowLimit(Integer expected) { + assertEquals(at("clientRowLimit"), expected, attributes.getClientRowLimit()); + return this; + } + + public ExplainPlanAssert serverOffset(Integer expected) { + assertEquals(at("serverOffset"), expected, attributes.getServerOffset()); + return this; + } + + public ExplainPlanAssert clientOffset(Integer expected) { + assertEquals(at("clientOffset"), expected, attributes.getClientOffset()); + return this; + } + + public ExplainPlanAssert clientSequenceCount(Integer expected) { + assertEquals(at("clientSequenceCount"), expected, attributes.getClientSequenceCount()); + return this; + } + + public ExplainPlanAssert hint(String expected) { + assertEquals(at("hint"), expected, + attributes.getHint() == null ? null : attributes.getHint().toString()); + return this; + } + + public ExplainPlanAssert samplingRate(Double expected) { + assertEquals(at("samplingRate"), expected, attributes.getSamplingRate()); + return this; + } + + public ExplainPlanAssert serverArrayElementProjection(boolean expected) { + assertEquals(at("serverArrayElementProjection"), expected, + attributes.isServerArrayElementProjection()); + return this; + } + + public ExplainPlanAssert useRoundRobinIterator(boolean expected) { + assertEquals(at("useRoundRobinIterator"), expected, attributes.isUseRoundRobinIterator()); + return this; + } + + public ExplainPlanAssert dynamicServerFilter(String expected) { + assertEquals(at("dynamicServerFilter"), expected, attributes.getDynamicServerFilter()); + return this; + } + + public ExplainPlanAssert afterJoinFilter(String expected) { + assertEquals(at("afterJoinFilter"), expected, attributes.getAfterJoinFilter()); + return this; + } + + public ExplainPlanAssert joinScannerLimit(Long expected) { + assertEquals(at("joinScannerLimit"), expected, attributes.getJoinScannerLimit()); + return this; + } + + /** Assert the sort-merge-join "(SKIP MERGE)" marker on this (left) side of the join. */ + public ExplainPlanAssert sortMergeSkipMerge(boolean expected) { + assertEquals(at("sortMergeSkipMerge"), expected, attributes.isSortMergeSkipMerge()); + return this; + } + + /** Assert the (normalized) parallelism width, e.g. {@code "PARALLEL"} or {@code "SERIAL"}. */ + public ExplainPlanAssert iteratorType(String expectedPrefix) { + String iter = attributes.getIteratorTypeAndScanSize(); + assertNotNull(at("iteratorTypeAndScanSize"), iter); + assertTrue(at("iteratorTypeAndScanSize") + " expected to start with '" + expectedPrefix + + "' but was '" + iter + "'", iter.startsWith(expectedPrefix)); + return this; + } + + /** Navigate to the right-hand side plan (sort-merge join / union all). */ + public ExplainPlanAssert rhs() { + ExplainPlanAttributes rhs = attributes.getRhsJoinQueryExplainPlan(); + assertNotNull(at("rhsJoinQueryExplainPlan") + " must not be null", rhs); + return new ExplainPlanAssert(rhs, this, context + ".rhs"); + } + + /** Assert the number of hash-join sub-plans (children). */ + public ExplainPlanAssert subPlanCount(int expected) { + List subPlans = attributes.getSubPlans(); + int actual = subPlans == null ? 0 : subPlans.size(); + assertEquals(at("subPlans.size"), expected, actual); + return this; + } + + /** Navigate to the i-th hash-join sub-plan (child). */ + public ExplainPlanAssert subPlan(int i) { + List subPlans = attributes.getSubPlans(); + assertNotNull(at("subPlans") + " must not be null", subPlans); + assertTrue(at("subPlans") + " has no index " + i + " (size=" + subPlans.size() + ")", + i >= 0 && i < subPlans.size()); + return new ExplainPlanAssert(subPlans.get(i), this, context + ".subPlan[" + i + "]"); + } + + /** + * Return to the parent assertion after navigating into {@link #rhs()} or {@link #subPlan(int)}. + */ + public ExplainPlanAssert end() { + assertNotNull("end() called on a root ExplainPlanAssert", parent); + return parent; + } + + private String at(String field) { + return context + "." + field; + } + + private static String trim(String s) { + return s == null ? null : s.trim(); + } + } +} 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 62b5c6854d7..446e1fe2fda 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 @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; import java.util.regex.Pattern; /** @@ -37,6 +38,12 @@ public final class ExplainTextNormalizer { // 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*"); + // The dynamic filter bind aliases are assigned from a per-connection counter and so vary with + // compilation and test-execution order. + private static final Pattern DYNAMIC_FILTER_ALIAS = Pattern.compile("\\$\\d+\\.\\$\\d+"); + private static final String DYNAMIC_FILTER_ALIAS_PLACEHOLDER = + Matcher.quoteReplacement("$.$"); + // " (region locations = [...]) " emitted via planSteps.add(regionLocationPlan); the line always // begins with the leading-space form of ExplainTable.REGION_LOCATIONS. private static final String REGION_LOCATIONS_PREFIX = " (region locations = "; @@ -61,6 +68,8 @@ public List normalize(List raw) { normalized = CHUNK_COUNT.matcher(normalized).replaceAll("-CHUNK"); normalized = WAY_COUNT.matcher(normalized).replaceAll("-WAY"); normalized = ROWS_BYTES.matcher(normalized).replaceAll(""); + normalized = + DYNAMIC_FILTER_ALIAS.matcher(normalized).replaceAll(DYNAMIC_FILTER_ALIAS_PLACEHOLDER); out.add(normalized); } return out;