From ae7574dfe109338188a3625b5e38b88cd9040cb6 Mon Sep 17 00:00:00 2001 From: Andrew Purtell Date: Thu, 4 Jun 2026 16:55:52 -0700 Subject: [PATCH 01/27] PHOENIX-7879 Tests for EXPLAIN text and ExplainPlanAttributes serialization compatibility (#2495) Co-authored-by: Claude Opus 4.8[1m] --- .../compile/ExplainPlanAttributes.java | 13 + .../RegionLocationsListSerializer.java | 61 ++ .../compile/ServerMergeColumnsSerializer.java | 59 ++ .../apache/phoenix/schema/MetaDataClient.java | 6 +- .../explain/ExplainCompatibilityTest.java | 750 ++++++++++++++++++ .../query/explain/ExplainJsonNormalizer.java | 101 +++ .../phoenix/query/explain/ExplainOracle.java | 177 +++++ .../query/explain/ExplainTextNormalizer.java | 72 ++ .../query/explain/TempAliasRenumberer.java | 70 ++ 9 files changed, 1306 insertions(+), 3 deletions(-) create mode 100644 phoenix-core-client/src/main/java/org/apache/phoenix/compile/RegionLocationsListSerializer.java create mode 100644 phoenix-core-client/src/main/java/org/apache/phoenix/compile/ServerMergeColumnsSerializer.java create mode 100644 phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainCompatibilityTest.java create mode 100644 phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainJsonNormalizer.java create mode 100644 phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainOracle.java create mode 100644 phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainTextNormalizer.java create mode 100644 phoenix-core/src/test/java/org/apache/phoenix/query/explain/TempAliasRenumberer.java 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 07f00f2b672..f4ed63f2454 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 @@ -17,6 +17,8 @@ */ package org.apache.phoenix.compile; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.util.List; import java.util.Set; import org.apache.hadoop.hbase.HRegionLocation; @@ -30,6 +32,15 @@ * 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", + "regionLocations", "numRegionLocationLookups" }) public class ExplainPlanAttributes { private final String abstractExplainPlan; @@ -299,10 +310,12 @@ public ExplainPlanAttributes getRhsJoinQueryExplainPlan() { return rhsJoinQueryExplainPlan; } + @JsonSerialize(using = ServerMergeColumnsSerializer.class) public Set getServerMergeColumns() { return serverMergeColumns; } + @JsonSerialize(using = RegionLocationsListSerializer.class) public List getRegionLocations() { return regionLocations; } diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/RegionLocationsListSerializer.java b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/RegionLocationsListSerializer.java new file mode 100644 index 00000000000..1ae1b77a534 --- /dev/null +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/RegionLocationsListSerializer.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.phoenix.compile; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; +import java.util.List; +import org.apache.hadoop.hbase.HRegionLocation; +import org.apache.hadoop.hbase.ServerName; +import org.apache.hadoop.hbase.client.RegionInfo; +import org.apache.hadoop.hbase.util.Bytes; + +/** + * Jackson serializer for {@code List} as it appears on + * {@link ExplainPlanAttributes#getRegionLocations()}. The HBase {@code HRegionLocation} bean is not + * cleanly serializable by Jackson's default introspection. + */ +public class RegionLocationsListSerializer extends StdSerializer> { + + private static final long serialVersionUID = 1L; + + @SuppressWarnings("unchecked") + public RegionLocationsListSerializer() { + super((Class>) (Class) List.class); + } + + @Override + public void serialize(List value, JsonGenerator gen, SerializerProvider provider) + throws IOException { + gen.writeStartArray(); + for (HRegionLocation loc : value) { + gen.writeStartObject(); + RegionInfo region = loc == null ? null : loc.getRegion(); + gen.writeStringField("startKey", + region == null ? null : Bytes.toStringBinary(region.getStartKey())); + gen.writeStringField("endKey", + region == null ? null : Bytes.toStringBinary(region.getEndKey())); + ServerName sn = loc == null ? null : loc.getServerName(); + gen.writeStringField("server", sn == null ? null : sn.toString()); + gen.writeEndObject(); + } + gen.writeEndArray(); + } +} diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ServerMergeColumnsSerializer.java b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ServerMergeColumnsSerializer.java new file mode 100644 index 00000000000..988c319bb85 --- /dev/null +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ServerMergeColumnsSerializer.java @@ -0,0 +1,59 @@ +/* + * 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.compile; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import org.apache.phoenix.schema.PColumn; + +/** + * Jackson serializer for {@code Set} as it appears on + * {@link ExplainPlanAttributes#getServerMergeColumns()}. {@code PColumn} is an interface backed by + * implementations that are not Jackson-friendly. + */ +public class ServerMergeColumnsSerializer extends StdSerializer> { + + private static final long serialVersionUID = 1L; + + @SuppressWarnings("unchecked") + public ServerMergeColumnsSerializer() { + super((Class>) (Class) Set.class); + } + + @Override + public void serialize(Set value, JsonGenerator gen, SerializerProvider provider) + throws IOException { + List names = new ArrayList<>(value.size()); + for (PColumn column : value) { + names.add(column == null ? null : column.toString()); + } + Collections.sort(names, Comparator.nullsFirst(Comparator.naturalOrder())); + gen.writeStartArray(); + for (String name : names) { + gen.writeString(name); + } + gen.writeEndArray(); + } +} diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/MetaDataClient.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/MetaDataClient.java index 22833e24945..e70aba774ff 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/MetaDataClient.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/MetaDataClient.java @@ -4923,9 +4923,9 @@ public MutationState addColumn(PTable table, List origColumnDefs, /** * To check if TTL is defined at any of the child below we are checking it at * {@link org.apache.phoenix.coprocessor.MetaDataEndpointImpl#mutateColumn(List, ColumnMutator, int, PTable, PTable, boolean)} - * level where in function - * {@link org.apache.phoenix.coprocessor.MetaDataEndpointImpl# validateIfMutationAllowedOnParent(PTable, List, PTableType, long, byte[], byte[], byte[], List, int)} - * we are already traversing through allDescendantViews. + * level where in function {@link org.apache.phoenix.coprocessor.MetaDataEndpointImpl# + * validateIfMutationAllowedOnParent(PTable, List, PTableType, long, byte[], byte[], + * byte[], List, int)} we are already traversing through allDescendantViews. */ } 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/ExplainCompatibilityTest.java new file mode 100644 index 00000000000..e7905df91b0 --- /dev/null +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainCompatibilityTest.java @@ -0,0 +1,750 @@ +/* + * 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.apache.phoenix.query.QueryServices.AUTO_COMMIT_ATTRIB; +import static org.apache.phoenix.query.QueryServices.DATE_FORMAT_ATTRIB; +import static org.apache.phoenix.util.PhoenixRuntime.TENANT_ID_ATTRIB; +import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import org.apache.hadoop.hbase.HRegionLocation; +import org.apache.hadoop.hbase.ServerName; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.RegionInfoBuilder; +import org.apache.phoenix.compile.ExplainPlan; +import org.apache.phoenix.compile.ExplainPlanAttributes; +import org.apache.phoenix.compile.ExplainPlanAttributes.ExplainPlanAttributesBuilder; +import org.apache.phoenix.jdbc.PhoenixPreparedStatement; +import org.apache.phoenix.query.BaseConnectionlessQueryTest; +import org.apache.phoenix.query.QueryServices; +import org.apache.phoenix.schema.PColumn; +import org.apache.phoenix.schema.PColumnImpl; +import org.apache.phoenix.schema.PName; +import org.apache.phoenix.schema.PNameFactory; +import org.apache.phoenix.schema.SortOrder; +import org.apache.phoenix.schema.types.PInteger; +import org.apache.phoenix.util.PropertiesUtil; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Backward compatibility tests for Phoenix EXPLAIN output. + *

+ * Each corpus {@code @Test} method compiles a representative query against a connectionless Phoenix + * driver, builds the expected normalized plan-steps text and JSON attributes inline, and hands both + * to the {@link ExplainOracle} for a tolerant comparison. The corpus covers every EXPLAIN grammar + * branch reachable without a connection. + */ +public class ExplainCompatibilityTest extends BaseConnectionlessQueryTest { + + private static final String SALTED = "EO_SALTED"; + private static final String SEQ = "EO_SEQ"; + private static final String MT_BASE = "EO_MT_BASE"; + private static final String MT_VIEW = "EO_MT_VIEW"; + private static final String TENANT_ID = "tenant42"; + + private static ExplainOracle oracle; + private static ObjectMapper mapper; + + @BeforeClass + public static synchronized void setUp() throws Exception { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + props.setProperty(DATE_FORMAT_ATTRIB, "yyyy-MM-dd"); + props.setProperty(QueryServices.FORCE_ROW_KEY_ORDER_ATTRIB, Boolean.toString(false)); + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + conn.createStatement().execute("CREATE TABLE IF NOT EXISTS " + SALTED + + " (k VARCHAR NOT NULL PRIMARY KEY, v INTEGER) SALT_BUCKETS=4"); + conn.createStatement().execute("CREATE SEQUENCE IF NOT EXISTS " + SEQ); + conn.createStatement() + .execute("CREATE TABLE IF NOT EXISTS " + MT_BASE + " (" + " tenant_id VARCHAR(8) NOT NULL," + + " userid INTEGER NOT NULL," + " username VARCHAR NOT NULL," + " col VARCHAR" + + " CONSTRAINT pk PRIMARY KEY (tenant_id, userid, username)) MULTI_TENANT=true"); + } + Properties tenantProps = PropertiesUtil.deepCopy(TEST_PROPERTIES); + tenantProps.setProperty(DATE_FORMAT_ATTRIB, "yyyy-MM-dd"); + tenantProps.setProperty(TENANT_ID_ATTRIB, TENANT_ID); + try (Connection conn = DriverManager.getConnection(getUrl(), tenantProps)) { + conn.createStatement() + .execute("CREATE VIEW IF NOT EXISTS " + MT_VIEW + " AS SELECT * FROM " + MT_BASE); + } + + oracle = new ExplainOracle(); + mapper = oracle.mapper(); + } + + @Test + public void testPointLookup() throws Exception { + verifyQuery("pointLookup", + "SELECT a_string, b_string FROM atable" + + " WHERE organization_id = '00D000000000001' AND entity_id = '00E00000000001'" + + " AND x_integer = 2 AND a_integer < 5", + text("CLIENT PARALLEL -WAY POINT LOOKUP ON 1 KEY OVER ATABLE", + " SERVER FILTER BY (X_INTEGER = 2 AND A_INTEGER < 5)"), + scanAttrs("POINT LOOKUP ON 1 KEY ", "ATABLE", null).put("serverWhereFilter", + "SERVER FILTER BY (X_INTEGER = 2 AND A_INTEGER < 5)")); + } + + @Test + public void testPointLookupMultiKey() throws Exception { + verifyQuery("pointLookupMultiKey", + "SELECT a_string, b_string FROM atable" + + " WHERE organization_id IN ('00D000000000001', '00D000000000005')" + + " AND entity_id IN ('00E00000000000X','00E00000000000Z')", + text("CLIENT PARALLEL -WAY POINT LOOKUP ON 4 KEYS OVER ATABLE"), + scanAttrs("POINT LOOKUP ON 4 KEYS ", "ATABLE", null)); + } + + @Test + public void testRangeScan() throws Exception { + verifyQuery("rangeScan", + "SELECT a_string FROM atable WHERE organization_id = '00D000000000001'" + + " AND entity_id > '00E00000000002' AND entity_id < '00E00000000008'", + text("CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE" + + " ['00D000000000001','00E00000000002!'] - ['00D000000000001','00E00000000008 ']"), + scanAttrs("RANGE SCAN ", "ATABLE", + " ['00D000000000001','00E00000000002!'] - ['00D000000000001','00E00000000008 ']")); + } + + @Test + public void testSkipScanKeys() throws Exception { + verifyQuery("skipScanKeys", "SELECT host FROM ptsdb3 WHERE host IN ('na1','na2','na3')", + text("CLIENT PARALLEL -WAY SKIP SCAN ON 3 KEYS OVER PTSDB3 [~'na3'] - [~'na1']", + " SERVER FILTER BY FIRST KEY ONLY"), + scanAttrs("SKIP SCAN ON 3 KEYS ", "PTSDB3", " [~'na3'] - [~'na1']").put("serverWhereFilter", + "SERVER FILTER BY FIRST KEY ONLY")); + } + + @Test + public void testSkipScanRanges() throws Exception { + verifyQuery("skipScanRanges", + "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')", + text( + "CLIENT PARALLEL -WAY SKIP SCAN ON 6 RANGES OVER PTSDB" + + " ['na1','a','2013-01-01'] - ['na3','b','2013-01-02']", + " SERVER FILTER BY FIRST KEY ONLY"), + scanAttrs("SKIP SCAN ON 6 RANGES ", "PTSDB", + " ['na1','a','2013-01-01'] - ['na3','b','2013-01-02']").put("serverWhereFilter", + "SERVER FILTER BY FIRST KEY ONLY")); + } + + @Test + public void testFullScan() throws Exception { + verifyQuery("fullScan", "SELECT * FROM atable", + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE"), scanAttrs("FULL SCAN ", "ATABLE", "")); + } + + @Test + public void testReverseScan() throws Exception { + verifyQuery("reverseScan", + "SELECT inst,\"DATE\" FROM ptsdb2 WHERE inst = 'na1' ORDER BY inst DESC, \"DATE\" DESC", + text("CLIENT PARALLEL -WAY REVERSE RANGE SCAN OVER PTSDB2 ['na1']", + " SERVER FILTER BY FIRST KEY ONLY"), + scanAttrs("RANGE SCAN ", "PTSDB2", " ['na1']") + .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY") + .put("clientSortedBy", "REVERSE")); + } + + @Test + public void testSmallHint() throws Exception { + verifyQuery("smallHint", + "SELECT /*+ SMALL */ host FROM ptsdb3 WHERE host IN ('na1','na2','na3')", + text("CLIENT PARALLEL -WAY SMALL SKIP SCAN ON 3 KEYS OVER PTSDB3 [~'na3'] - [~'na1']", + " SERVER FILTER BY FIRST KEY ONLY"), + scanAttrs("SKIP SCAN ON 3 KEYS ", "PTSDB3", " [~'na3'] - [~'na1']").put("hint", "SMALL") + .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY")); + } + + @Test + public void testAggregateSingleRow() throws Exception { + verifyQuery("aggregateSingleRow", "SELECT count(*) FROM atable", + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " SERVER FILTER BY FIRST KEY ONLY", + " SERVER AGGREGATE INTO SINGLE ROW"), + scanAttrs("FULL SCAN ", "ATABLE", "") + .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY") + .put("serverAggregate", "SERVER AGGREGATE INTO SINGLE ROW")); + } + + @Test + public void testAggregateOrderedDistinct() throws Exception { + verifyQuery("aggregateOrderedDistinct", "SELECT count(1) FROM atable GROUP BY a_string", + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", + " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]", "CLIENT MERGE SORT"), + scanAttrs("FULL SCAN ", "ATABLE", "") + .put("serverAggregate", "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]") + .put("clientSortAlgo", "CLIENT MERGE SORT")); + } + + @Test + public void testAggregateHashDistinct() throws Exception { + verifyQuery("aggregateHashDistinct", + "SELECT count(1) FROM atable WHERE a_integer = 1" + + " GROUP BY ROUND(a_time,'HOUR',2), entity_id", + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " SERVER FILTER BY A_INTEGER = 1", + " SERVER AGGREGATE INTO DISTINCT ROWS BY [ENTITY_ID, ROUND(A_TIME)]", + "CLIENT MERGE SORT"), + scanAttrs("FULL SCAN ", "ATABLE", "") + .put("serverWhereFilter", "SERVER FILTER BY A_INTEGER = 1") + .put("serverAggregate", "SERVER AGGREGATE INTO DISTINCT ROWS BY [ENTITY_ID, ROUND(A_TIME)]") + .put("clientSortAlgo", "CLIENT MERGE SORT")); + } + + @Test + public void testTopNSortedBy() throws Exception { + verifyQuery("topNSortedBy", "SELECT a_string FROM atable ORDER BY a_string DESC LIMIT 3", + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", + " SERVER TOP 3 ROWS SORTED BY [A_STRING DESC]", "CLIENT MERGE SORT", "CLIENT LIMIT 3"), + scanAttrs("FULL SCAN ", "ATABLE", "").put("serverSortedBy", "[A_STRING DESC]") + .put("serverRowLimit", 3).put("clientRowLimit", 3) + .put("clientSortAlgo", "CLIENT MERGE SORT")); + } + + @Test + public void testClientFilterByMax() throws Exception { + verifyQuery("clientFilterByMax", + "SELECT count(1) FROM atable GROUP BY a_string, b_string HAVING max(a_string) = 'a'", + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", + " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]", "CLIENT MERGE SORT", + "CLIENT FILTER BY MAX(A_STRING) = 'a'"), + scanAttrs("FULL SCAN ", "ATABLE", "") + .put("serverAggregate", "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]") + .put("clientFilterBy", "MAX(A_STRING) = 'a'").put("clientSortAlgo", "CLIENT MERGE SORT")); + } + + @Test + public void testClientLimit() throws Exception { + verifyQuery("clientLimit", + "SELECT a_string, b_string FROM atable" + + " WHERE organization_id = '00D000000000001' AND entity_id != '00E00000000002'" + + " AND x_integer = 2 AND a_integer < 5 LIMIT 10", + text("CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']", + " SERVER FILTER BY (ENTITY_ID != '00E00000000002' AND X_INTEGER = 2 AND A_INTEGER < 5)", + " SERVER 10 ROW LIMIT", "CLIENT 10 ROW LIMIT"), + scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']") + .put("serverWhereFilter", + "SERVER FILTER BY (ENTITY_ID != '00E00000000002' AND X_INTEGER = 2 AND A_INTEGER < 5)") + .put("serverRowLimit", 10).put("clientRowLimit", 10)); + } + + @Test + public void testArrayElementProjection() throws Exception { + verifyQuery("arrayElementProjection", "SELECT a_string_array[1] FROM table_with_array", + text("CLIENT PARALLEL -WAY FULL SCAN OVER TABLE_WITH_ARRAY", + " SERVER ARRAY ELEMENT PROJECTION"), + scanAttrs("FULL SCAN ", "TABLE_WITH_ARRAY", "").put("serverArrayElementProjection", true)); + } + + @Test + public void testSortMergeJoin() throws Exception { + ObjectNode rhs = scanAttrs("FULL SCAN ", "ATABLE", ""); + verifyQuery("sortMergeJoin", + "SELECT /*+ USE_SORT_MERGE_JOIN */ a.a_string, b.a_string FROM atable a" + + " JOIN atable b ON a.organization_id = b.organization_id" + + " WHERE a.organization_id = '00D000000000001'", + text("SORT-MERGE-JOIN (INNER) TABLES", + " CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']", "AND", + " CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE"), + scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']") + .put("abstractExplainPlan", "SORT-MERGE-JOIN (INNER)").set("rhsJoinQueryExplainPlan", rhs)); + } + + @Test + public void testHashJoinInner() throws Exception { + verifyQuery("hashJoinInner", + "SELECT a.a_string, b.a_string FROM atable a" + + " JOIN atable b ON a.organization_id = b.organization_id" + + " WHERE a.organization_id = '00D000000000001'", + text("CLIENT PARALLEL -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()); + } + + @Test + public void testHashJoinSemiInSubquery() throws Exception { + // Phoenix temp aliases are renamed by first appearance so this case asserts on the canonical + // "$1.$2" form. + verifyQuery("hashJoinSemiInSubquery", + "SELECT a_string FROM atable" + + " WHERE organization_id IN (SELECT organization_id FROM atable WHERE a_integer = 1)", + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " SKIP-SCAN-JOIN TABLE 0", + " 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.$2)"), + defaultAttrs()); + } + + @Test + public void testUnionAll() throws Exception { + ObjectNode rhs = scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000002']"); + verifyQuery("unionAll", + "SELECT a_string FROM atable WHERE organization_id = '00D000000000001'" + " UNION ALL" + + " SELECT a_string FROM atable WHERE organization_id = '00D000000000002'", + text("UNION ALL OVER 2 QUERIES", + " CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']", + " CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000002']"), + scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']") + .put("abstractExplainPlan", "UNION ALL OVER 2 QUERIES") + .set("rhsJoinQueryExplainPlan", rhs)); + } + + @Test + public void testPutSingleRow() throws Exception { + verifyMutation("putSingleRow", + "UPSERT INTO atable (organization_id, entity_id, a_string)" + + " VALUES ('00D000000000001','00E00000000001','x')", + false, text("PUT SINGLE ROW"), defaultAttrs()); + } + + @Test + public void testUpsertSelectClient() throws Exception { + verifyMutation("upsertSelectClient", + "UPSERT INTO atable (organization_id, entity_id, a_string)" + + " SELECT organization_id, entity_id, a_string FROM atable" + + " WHERE organization_id = '00D000000000001'", + false, + text("UPSERT SELECT", "CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']"), + scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']").put("abstractExplainPlan", + "UPSERT SELECT")); + } + + @Test + public void testUpsertSelectServer() throws Exception { + verifyMutation("upsertSelectServer", + "UPSERT INTO atable (organization_id, entity_id, a_string)" + + " SELECT organization_id, entity_id, a_string FROM atable" + + " WHERE organization_id = '00D000000000001'", + true, + text("UPSERT ROWS", "CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']"), + scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']").put("abstractExplainPlan", + "UPSERT ROWS")); + } + + @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()); + } + + @Test + public void testDeleteServer() throws Exception { + verifyMutation("deleteServer", "DELETE FROM atable WHERE entity_id = 'abc'", true, + text("DELETE ROWS SERVER SELECT", "CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", + " SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID = 'abc'"), + scanAttrs("FULL SCAN ", "ATABLE", "").put("abstractExplainPlan", "DELETE ROWS SERVER SELECT") + .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID = 'abc'")); + } + + @Test + public void testDeleteClient() throws Exception { + verifyMutation("deleteClient", "DELETE FROM atable WHERE entity_id = 'abc'", false, + text("DELETE ROWS CLIENT SELECT", "CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", + " SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID = 'abc'"), + scanAttrs("FULL SCAN ", "ATABLE", "").put("abstractExplainPlan", "DELETE ROWS CLIENT SELECT") + .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID = 'abc'")); + } + + @Test + public void testSequenceNextValue() throws Exception { + verifyQuery("sequenceNextValue", "SELECT NEXT VALUE FOR " + SEQ + " FROM atable", + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " SERVER FILTER BY FIRST KEY ONLY", + "CLIENT RESERVE VALUES FROM 1 SEQUENCE"), + scanAttrs("FULL SCAN ", "ATABLE", "") + .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY").put("clientSequenceCount", 1)); + } + + @Test + public void testSaltedTableScan() throws Exception { + verifyQuery("saltedTableScan", "SELECT * FROM " + SALTED + " WHERE v = 7", + text("CLIENT PARALLEL -WAY FULL SCAN OVER EO_SALTED", " SERVER FILTER BY V = 7", + "CLIENT MERGE SORT"), + scanAttrs("FULL SCAN ", "EO_SALTED", "").put("serverWhereFilter", "SERVER FILTER BY V = 7") + .put("clientSortAlgo", "CLIENT MERGE SORT")); + } + + @Test + public void testMultiTenantView() throws Exception { + Properties tenantProps = PropertiesUtil.deepCopy(TEST_PROPERTIES); + tenantProps.setProperty(DATE_FORMAT_ATTRIB, "yyyy-MM-dd"); + tenantProps.setProperty(TENANT_ID_ATTRIB, TENANT_ID); + verifyQuery("multiTenantView", "SELECT * FROM " + MT_VIEW + " LIMIT 1", tenantProps, + text("CLIENT SERIAL -WAY RANGE SCAN OVER EO_MT_BASE ['tenant42']", + " SERVER 1 ROW LIMIT", "CLIENT 1 ROW LIMIT"), + attrs().put("iteratorTypeAndScanSize", "SERIAL -WAY").put("consistency", "STRONG") + .put("explainScanType", "RANGE SCAN ").put("tableName", "EO_MT_BASE") + .put("keyRanges", " ['tenant42']").put("serverRowLimit", 1).put("clientRowLimit", 1)); + } + + @Test + public void testTextNormalizerCollapsesWayCount() { + assertEquals(Collections.singletonList("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE"), + new ExplainTextNormalizer() + .normalize(Arrays.asList("CLIENT PARALLEL 400-WAY FULL SCAN OVER ATABLE"))); + } + + @Test + public void testTextNormalizerCollapsesChunkCount() { + assertEquals( + Collections.singletonList("CLIENT -CHUNK PARALLEL -WAY FULL SCAN OVER ATABLE"), + new ExplainTextNormalizer() + .normalize(Arrays.asList("CLIENT 5-CHUNK PARALLEL 16-WAY FULL SCAN OVER ATABLE"))); + } + + @Test + public void testTextNormalizerStripsRowsBytes() { + assertEquals( + Collections.singletonList("CLIENT -CHUNK PARALLEL -WAY FULL SCAN OVER ATABLE"), + new ExplainTextNormalizer().normalize( + Arrays.asList("CLIENT 1-CHUNK 100 ROWS 2048 BYTES PARALLEL 1-WAY FULL SCAN OVER ATABLE"))); + } + + @Test + public void testTextNormalizerDropsRegionLocationsLine() { + assertEquals( + Arrays.asList("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", + " SERVER FILTER BY FIRST KEY ONLY"), + new ExplainTextNormalizer().normalize(Arrays.asList( + "CLIENT PARALLEL 1-WAY FULL SCAN OVER ATABLE", " SERVER FILTER BY FIRST KEY ONLY", + " (region locations = [{startKey=\\x00, endKey=, server=foo,1234}])"))); + } + + @Test + public void testTextNormalizerRenumbersTempAliasesByFirstAppearance() { + // Two distinct aliases ($7, $9) with $7 appearing first → $1, $9 → $2. + assertEquals( + Collections.singletonList(" DYNAMIC SERVER FILTER BY ATABLE.ORGANIZATION_ID IN ($1.$2)"), + new ExplainTextNormalizer() + .normalize(Collections.singletonList( + " DYNAMIC SERVER FILTER BY ATABLE.ORGANIZATION_ID IN ($7.$9)"))); + } + + @Test + public void testTextNormalizerSharesAliasStateAcrossLines() { + // The same alias ($5) appearing in two different lines gets the same renumbered token. A + // distinct alias ($8) on the second line gets the next number. + List in = Arrays.asList( + "CLIENT PARALLEL 1-WAY FULL SCAN OVER ($5)", + " DYNAMIC SERVER FILTER BY T.X IN ($5.$8)"); + assertEquals(Arrays.asList( + "CLIENT PARALLEL -WAY FULL SCAN OVER ($1)", + " DYNAMIC SERVER FILTER BY T.X IN ($1.$2)"), + new ExplainTextNormalizer().normalize(in)); + } + + @Test + public void testTextNormalizerLeavesNonAliasDollarTokensAlone() { + // The "$" form below is the canonical renumbered form, not a temp alias, so the token + // pattern only matches "$". A literal dollar followed by a non-digit is preserved. + List in = Collections.singletonList("CLIENT FILTER BY price > $5.50 AND tag = '$abc'"); + assertEquals( + Collections.singletonList("CLIENT FILTER BY price > $1.50 AND tag = '$abc'"), + new ExplainTextNormalizer().normalize(in)); + } + + @Test + public void testJsonNormalizerRenumbersTempAliasesAcrossFields() { + ObjectNode root = mapper.createObjectNode(); + // First-appearance order is field-insertion order: $7 → $1, $9 → $2, $7 reused → $1. + root.put("serverWhereFilter", "SERVER FILTER BY ATABLE.ORGANIZATION_ID IN ($7.$9)"); + root.put("clientFilterBy", "X = $7"); + new ExplainJsonNormalizer().normalize(root); + assertEquals("SERVER FILTER BY ATABLE.ORGANIZATION_ID IN ($1.$2)", + root.get("serverWhereFilter").asText()); + assertEquals("X = $1", root.get("clientFilterBy").asText()); + } + + @Test + public void testJsonNormalizerSharesAliasStateWithRhsRecursion() { + ObjectNode root = mapper.createObjectNode(); + root.put("serverWhereFilter", "X IN ($3)"); + ObjectNode rhs = mapper.createObjectNode(); + rhs.put("serverWhereFilter", "Y = $3 AND Z = $5"); + root.set("rhsJoinQueryExplainPlan", rhs); + new ExplainJsonNormalizer().normalize(root); + assertEquals("X IN ($1)", root.get("serverWhereFilter").asText()); + assertEquals("Y = $1 AND Z = $2", + root.get("rhsJoinQueryExplainPlan").get("serverWhereFilter").asText()); + } + + @Test + public void testTextNormalizerPreservesAllGrammar() { + List in = Arrays.asList("CLIENT PARALLEL 1-WAY RANGE SCAN OVER ATABLE ['a','b']", + " SERVER FILTER BY (X = 1 AND Y = 'z')", + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [Y]", "CLIENT MERGE SORT", + "CLIENT 3 ROW LIMIT"); + assertEquals(Arrays.asList("CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['a','b']", + " SERVER FILTER BY (X = 1 AND Y = 'z')", + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [Y]", "CLIENT MERGE SORT", + "CLIENT 3 ROW LIMIT"), new ExplainTextNormalizer().normalize(in)); + } + + @Test + public void testJsonNormalizerErasesClusterFields() { + ObjectNode root = mapper.createObjectNode(); + root.put("iteratorTypeAndScanSize", "PARALLEL 16-WAY"); + root.put("splitsChunk", 4); + root.put("estimatedRows", 1234L); + root.put("estimatedSizeInBytes", 9876L); + root.put("numRegionLocationLookups", 7); + root.set("regionLocations", mapper.createArrayNode().add("anything")); + new ExplainJsonNormalizer().normalize(root); + assertEquals("PARALLEL -WAY", root.get("iteratorTypeAndScanSize").asText()); + assertTrue(root.get("splitsChunk").isNull()); + assertTrue(root.get("estimatedRows").isNull()); + assertTrue(root.get("estimatedSizeInBytes").isNull()); + assertTrue(root.get("regionLocations").isNull()); + assertEquals(0, root.get("numRegionLocationLookups").asInt()); + } + + @Test + public void testJsonNormalizerRecursesIntoRhsJoinQueryExplainPlan() { + ObjectNode root = mapper.createObjectNode(); + root.put("iteratorTypeAndScanSize", "PARALLEL 5-WAY"); + root.put("numRegionLocationLookups", 1); + ObjectNode rhs = mapper.createObjectNode(); + rhs.put("iteratorTypeAndScanSize", "PARALLEL 12-WAY"); + rhs.put("numRegionLocationLookups", 99); + rhs.set("regionLocations", mapper.createArrayNode().add(1)); + root.set("rhsJoinQueryExplainPlan", rhs); + new ExplainJsonNormalizer().normalize(root); + assertEquals("PARALLEL -WAY", root.get("iteratorTypeAndScanSize").asText()); + assertEquals(0, root.get("numRegionLocationLookups").asInt()); + JsonNode nestedRhs = root.get("rhsJoinQueryExplainPlan"); + assertEquals("PARALLEL -WAY", nestedRhs.get("iteratorTypeAndScanSize").asText()); + assertEquals(0, nestedRhs.get("numRegionLocationLookups").asInt()); + assertTrue(nestedRhs.get("regionLocations").isNull()); + } + + @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); + } + + @Test + public void testRegionLocationsSerializerRendersTriple() throws Exception { + HRegionLocation loc = new HRegionLocation( + RegionInfoBuilder.newBuilder(TableName.valueOf("FOO")).setStartKey(new byte[] { 0x01, 0x02 }) + .setEndKey(new byte[] { 0x03, 0x04 }).build(), + ServerName.valueOf("rs.example.com", 16020, 1234567890L)); + ExplainPlanAttributes a = new ExplainPlanAttributesBuilder() + .setRegionLocations(Collections.singletonList(loc)).setNumRegionLocationLookups(1).build(); + JsonNode tree = mapper.readTree(mapper.writeValueAsString(a)); + JsonNode entry = tree.get("regionLocations").get(0); + assertEquals("\\x01\\x02", entry.get("startKey").asText()); + assertEquals("\\x03\\x04", entry.get("endKey").asText()); + assertTrue(entry.get("server").asText().contains("rs.example.com")); + } + + @Test + public void testServerMergeColumnsSerializerEmitsSortedNames() throws Exception { + Set cols = new HashSet<>( + Arrays.asList(column("CF", "B_COL"), column("CF", "A_COL"), column("CF", "C_COL"))); + ExplainPlanAttributes a = + new ExplainPlanAttributesBuilder().setServerMergeColumns(cols).build(); + JsonNode tree = mapper.readTree(mapper.writeValueAsString(a)); + JsonNode array = tree.get("serverMergeColumns"); + assertEquals(3, array.size()); + // PColumn.toString() uses QueryConstants.NAME_SEPARATOR (".") between family and name. + assertEquals("CF.A_COL", array.get(0).asText()); + assertEquals("CF.B_COL", array.get(1).asText()); + assertEquals("CF.C_COL", array.get(2).asText()); + } + + @Test + public void testDiffMessageShowsExpectedAndActualForTextMismatch() { + ExplainPlan plan = samplePlan("PARALLEL 1-WAY", "FULL SCAN "); + // Caller's "expected" disagrees with what the plan actually emits. + List divergentExpectedText = + text("CLIENT PARALLEL -WAY FULL SCAN OVER T", " SERVER FILTER BY (X = 9)"); + ObjectNode divergentExpectedJson = defaultAttrs() + .put("iteratorTypeAndScanSize", "PARALLEL -WAY").put("explainScanType", "FULL SCAN ") + .put("tableName", "T").put("serverWhereFilter", "SERVER FILTER BY (X = 9)"); + try { + new ExplainOracle().verify("x", plan, divergentExpectedText, divergentExpectedJson); + fail("Expected AssertionError for diverged plan"); + } catch (AssertionError expected) { + String msg = expected.getMessage(); + assertTrue(msg.contains("Text mismatch for case 'x'")); + assertTrue(msg.contains("SERVER FILTER BY FIRST KEY ONLY")); + assertTrue(msg.contains("SERVER FILTER BY (X = 9)")); + } catch (Exception e) { + fail("Unexpected exception type: " + e); + } + } + + private void verifyQuery(String caseId, String query, List expectedText, + JsonNode expectedJson) throws Exception { + verifyQuery(caseId, query, defaultProps(), expectedText, expectedJson); + } + + 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(); + oracle.verify(caseId, plan, expectedText, expectedJson); + } + } + + private void verifyMutation(String caseId, String query, boolean autoCommit, + List expectedText, JsonNode expectedJson) throws Exception { + Properties props = defaultProps(); + if (autoCommit) { + props.setProperty(AUTO_COMMIT_ATTRIB, "true"); + } + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + ExplainPlan plan = compileMutation(conn, query); + oracle.verify(caseId, plan, expectedText, expectedJson); + } + } + + private ExplainPlan compileMutation(Connection conn, String query) throws SQLException { + PhoenixPreparedStatement ps = + conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class); + return ps.compileMutation().getExplainPlan(); + } + + private static Properties defaultProps() { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + props.setProperty(DATE_FORMAT_ATTRIB, "yyyy-MM-dd"); + return props; + } + + 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. + */ + private static ObjectNode defaultAttrs() { + ObjectNode n = mapper.createObjectNode(); + n.putNull("abstractExplainPlan"); + n.putNull("splitsChunk"); + n.putNull("estimatedRows"); + n.putNull("estimatedSizeInBytes"); + n.putNull("iteratorTypeAndScanSize"); + n.putNull("samplingRate"); + n.put("useRoundRobinIterator", false); + n.putNull("hexStringRVCOffset"); + n.putNull("consistency"); + n.putNull("hint"); + n.putNull("serverSortedBy"); + n.putNull("explainScanType"); + n.putNull("tableName"); + n.putNull("keyRanges"); + n.putNull("scanTimeRangeMin"); + n.putNull("scanTimeRangeMax"); + n.putNull("serverWhereFilter"); + n.putNull("serverDistinctFilter"); + n.putNull("serverOffset"); + n.putNull("serverRowLimit"); + n.put("serverArrayElementProjection", false); + n.putNull("serverAggregate"); + n.putNull("clientFilterBy"); + n.putNull("clientAggregate"); + n.putNull("clientSortedBy"); + n.putNull("clientAfterAggregate"); + n.putNull("clientDistinctFilter"); + n.putNull("clientOffset"); + n.putNull("clientRowLimit"); + n.putNull("clientSequenceCount"); + n.putNull("clientCursorName"); + n.putNull("clientSortAlgo"); + n.putNull("rhsJoinQueryExplainPlan"); + n.putNull("serverMergeColumns"); + n.putNull("regionLocations"); + n.put("numRegionLocationLookups", 0); + 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}. + * @param scanType the {@code explainScanType} string (with its trailing space, e.g. + * {@code "FULL SCAN "}) + * @param table the {@code tableName} value + * @param keys the {@code keyRanges} string (may be {@code null} or empty) + */ + private static ObjectNode scanAttrs(String scanType, String table, String keys) { + ObjectNode n = defaultAttrs(); + n.put("iteratorTypeAndScanSize", "PARALLEL -WAY"); + n.put("consistency", "STRONG"); + n.put("explainScanType", scanType); + n.put("tableName", table); + if (keys != null) { + n.put("keyRanges", keys); + } + return n; + } + + /** A plain {@link ObjectNode} alias for clarity in tests that don't use {@link #scanAttrs}. */ + private static ObjectNode attrs() { + return defaultAttrs(); + } + + private static ExplainPlan samplePlan(String way, String scanType) { + ExplainPlanAttributes a = new ExplainPlanAttributesBuilder().setIteratorTypeAndScanSize(way) + .setExplainScanType(scanType).setTableName("T") + .setServerWhereFilter("SERVER FILTER BY FIRST KEY ONLY").build(); + return new ExplainPlan(Arrays.asList("CLIENT " + way + " " + scanType.trim() + " OVER T", + " SERVER FILTER BY FIRST KEY ONLY"), a); + } + + private static PColumn column(String family, String name) { + PName fName = PNameFactory.newName(family); + PName cName = PNameFactory.newName(name); + return new PColumnImpl(cName, fName, PInteger.INSTANCE, null, null, false, 0, SortOrder.ASC, 0, + null, false, "expression", false, false, name.getBytes(), 0L); + } +} 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 new file mode 100644 index 00000000000..f3ea93f12cd --- /dev/null +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainJsonNormalizer.java @@ -0,0 +1,101 @@ +/* + * 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 com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Elides cluster- and connection-specific fields from the JSON view of + * {@code ExplainPlanAttributes} so the comparison is invariant under environment differences. + */ +public final class ExplainJsonNormalizer { + + private static final Pattern WAY_COUNT = Pattern.compile("\\b\\d+-WAY\\b"); + + /** + * Recursively normalize the given attributes-shaped JSON node. + * @return the same node, for fluent chaining. + */ + public JsonNode normalize(JsonNode node) { + // Temp-alias state is shared across the entire tree (top-level + recursive + // rhsJoinQueryExplainPlan) so that an alias appearing in multiple string fields renumbers + // consistently. + return normalize(node, new TempAliasRenumberer()); + } + + private JsonNode normalize(JsonNode node, TempAliasRenumberer aliases) { + if (node == null || node.isNull() || !node.isObject()) { + return node; + } + ObjectNode obj = (ObjectNode) node; + + if (obj.has("regionLocations")) { + obj.set("regionLocations", NullNode.getInstance()); + } + if (obj.has("numRegionLocationLookups")) { + obj.put("numRegionLocationLookups", 0); + } + if (obj.has("splitsChunk")) { + obj.set("splitsChunk", NullNode.getInstance()); + } + if (obj.has("estimatedRows")) { + obj.set("estimatedRows", NullNode.getInstance()); + } + if (obj.has("estimatedSizeInBytes")) { + obj.set("estimatedSizeInBytes", NullNode.getInstance()); + } + + JsonNode iter = obj.get("iteratorTypeAndScanSize"); + if (iter != null && iter.isTextual()) { + obj.put("iteratorTypeAndScanSize", WAY_COUNT.matcher(iter.asText()).replaceAll("-WAY")); + } + + // Rewrite temp aliases in every textual field at this level. Walk the entries in + // insertion order so the first-appearance numbering is deterministic across runs. Collect + // updates first to avoid concurrent modification of the field map. + LinkedHashMap updates = new LinkedHashMap<>(); + Iterator> it = obj.fields(); + while (it.hasNext()) { + Map.Entry e = it.next(); + JsonNode v = e.getValue(); + if (v != null && v.isTextual()) { + String original = v.asText(); + String rewritten = aliases.rewrite(original); + if (!rewritten.equals(original)) { + updates.put(e.getKey(), rewritten); + } + } + } + for (Map.Entry u : updates.entrySet()) { + obj.put(u.getKey(), u.getValue()); + } + + JsonNode rhs = obj.get("rhsJoinQueryExplainPlan"); + if (rhs != null && rhs.isObject()) { + normalize(rhs, aliases); + } + + return obj; + } +} diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainOracle.java b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainOracle.java new file mode 100644 index 00000000000..9b57cc18eac --- /dev/null +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainOracle.java @@ -0,0 +1,177 @@ +/* + * 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.fail; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.SerializationFeature; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.apache.phoenix.compile.ExplainPlan; +import org.apache.phoenix.compile.ExplainPlanAttributes; + +/** Backward compatibility test for Phoenix EXPLAIN output. */ +public final class ExplainOracle { + + private final ExplainTextNormalizer textNormalizer; + private final ExplainJsonNormalizer jsonNormalizer; + private final ObjectMapper mapper; + private final ObjectWriter prettyWriter; + + public ExplainOracle() { + this.textNormalizer = new ExplainTextNormalizer(); + this.jsonNormalizer = new ExplainJsonNormalizer(); + this.mapper = new ObjectMapper(); + this.mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); + this.mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + this.mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + this.prettyWriter = mapper.writerWithDefaultPrettyPrinter(); + } + + /** Test-side {@link ObjectMapper} for building expected JSON */ + public ObjectMapper mapper() { + return mapper; + } + + /** + * Verify the given plan against the embedded expected baseline for {@code caseId}. + * @param caseId the corpus case identifier (used in diff messages) + * @param plan the plan under test + * @param expectedText the embedded expected, post-normalization plan-steps text + * @param expectedJson the embedded expected, post-normalization JSON attributes tree + */ + public void verify(String caseId, ExplainPlan plan, List expectedText, + JsonNode expectedJson) throws IOException { + List textCurrent = textNormalizer.normalize(plan.getPlanSteps()); + JsonNode jsonCurrent = serializeNormalized(plan.getPlanStepsAsAttributes()); + List textExpected = new ArrayList<>(expectedText); + JsonNode jsonExpected = expectedJson == null ? null : expectedJson.deepCopy(); + if (!textExpected.equals(textCurrent)) { + fail(textDiffMessage(caseId, textExpected, textCurrent)); + } + if (jsonExpected != null && !jsonExpected.equals(jsonCurrent)) { + fail(jsonDiffMessage(caseId, jsonExpected, jsonCurrent)); + } + } + + /** + * Serialize the given attributes to JSON and apply the cluster/connection normalizer in one step. + * Exposed so the test can render and inspect the normalized JSON. + */ + public JsonNode serializeNormalized(ExplainPlanAttributes attributes) throws IOException { + String raw = mapper.writeValueAsString(attributes); + JsonNode node = mapper.readTree(raw); + jsonNormalizer.normalize(node); + return node; + } + + /** Normalize plan-steps text. Exposed so the test can render and inspect normalized output. */ + public List normalizeText(List raw) { + return textNormalizer.normalize(raw); + } + + /** Pretty-print a JSON node using the oracle's mapper config. */ + public String prettyJson(JsonNode node) throws JsonProcessingException { + return prettyWriter.writeValueAsString(node); + } + + private static String textDiffMessage(String caseId, List expected, List actual) { + StringBuilder sb = new StringBuilder(); + sb.append("Text mismatch for case '").append(caseId).append("'.\n"); + sb.append("--- expected (").append(expected.size()).append(" lines)\n"); + for (String l : expected) { + sb.append(" ").append(l).append('\n'); + } + sb.append("--- actual (").append(actual.size()).append(" lines)\n"); + for (String l : actual) { + sb.append(" ").append(l).append('\n'); + } + sb.append("--- line-by-line diff\n"); + int n = Math.max(expected.size(), actual.size()); + for (int i = 0; i < n; i++) { + String e = i < expected.size() ? expected.get(i) : ""; + String a = i < actual.size() ? actual.get(i) : ""; + if (!e.equals(a)) { + sb.append(" @").append(i).append(":\n"); + sb.append(" - expected: ").append(e).append('\n'); + sb.append(" - actual: ").append(a).append('\n'); + } + } + return sb.toString(); + } + + private String jsonDiffMessage(String caseId, JsonNode expected, JsonNode actual) { + StringBuilder sb = new StringBuilder(); + sb.append("JSON mismatch for case '").append(caseId).append("'.\n"); + try { + sb.append("--- expected\n").append(prettyJson(expected)).append('\n'); + sb.append("--- actual\n").append(prettyJson(actual)).append('\n'); + } catch (JsonProcessingException e) { + sb.append("(failed to pretty-print: ").append(e.getMessage()).append(")\n"); + } + sb.append("--- pointer diff\n"); + appendPointerDiff(sb, "", expected, actual); + return sb.toString(); + } + + private static void appendPointerDiff(StringBuilder sb, String pointer, JsonNode expected, + JsonNode actual) { + if (expected == null && actual == null) { + return; + } + if (expected == null || actual == null) { + sb.append(" ").append(pointer.isEmpty() ? "/" : pointer).append(" expected=") + .append(expected).append(" actual=").append(actual).append('\n'); + return; + } + if (expected.equals(actual)) { + return; + } + if (expected.isObject() && actual.isObject()) { + List fields = new ArrayList<>(); + expected.fieldNames().forEachRemaining(fields::add); + actual.fieldNames().forEachRemaining(f -> { + if (!fields.contains(f)) { + fields.add(f); + } + }); + Collections.sort(fields); + for (String f : fields) { + appendPointerDiff(sb, pointer + "/" + f, expected.get(f), actual.get(f)); + } + return; + } + if (expected.isArray() && actual.isArray()) { + int n = Math.max(expected.size(), actual.size()); + for (int i = 0; i < n; i++) { + appendPointerDiff(sb, pointer + "/" + i, i < expected.size() ? expected.get(i) : null, + i < actual.size() ? actual.get(i) : null); + } + return; + } + sb.append(" ").append(pointer.isEmpty() ? "/" : pointer).append(" expected=").append(expected) + .append(" actual=").append(actual).append('\n'); + } +} 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 new file mode 100644 index 00000000000..56c2424bd98 --- /dev/null +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainTextNormalizer.java @@ -0,0 +1,72 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +/** + * Elides cluster- and connection-specific details from the {@code List} returned by + * {@code ExplainPlan.getPlanSteps()} so the EXPLAIN text can be compared across environments. + */ +public final class ExplainTextNormalizer { + + // CLIENT 5-CHUNK -> CLIENT -CHUNK ; matches any non-negative integer immediately before + // -CHUNK. + private static final Pattern CHUNK_COUNT = Pattern.compile("\\b\\d+-CHUNK\\b"); + + // PARALLEL 400-WAY -> PARALLEL -WAY ; matches the iterator parallelism count. + private static final Pattern WAY_COUNT = Pattern.compile("\\b\\d+-WAY\\b"); + + // 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*"); + + // " (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 = "; + + /** + * @param raw the result of {@code ExplainPlan.getPlanSteps()} + * @return a new list with cluster/connection-specific detail elided. The original list is not + * mutated. + */ + public List normalize(List raw) { + // Temp alias state is shared across every line of a single plan so that an alias appearing in + // both a sub-plan body and a parent level dynamic filter renumbers consistently. + TempAliasRenumberer aliases = new TempAliasRenumberer(); + List out = new ArrayList<>(raw.size()); + for (String line : raw) { + if (line == null) { + out.add(null); + continue; + } + // Drop region-location lines outright + if (line.contains(REGION_LOCATIONS_PREFIX)) { + continue; + } + String normalized = line; + normalized = CHUNK_COUNT.matcher(normalized).replaceAll("-CHUNK"); + normalized = WAY_COUNT.matcher(normalized).replaceAll("-WAY"); + normalized = ROWS_BYTES.matcher(normalized).replaceAll(""); + normalized = aliases.rewrite(normalized); + out.add(normalized); + } + return out; + } +} diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/TempAliasRenumberer.java b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/TempAliasRenumberer.java new file mode 100644 index 00000000000..464b986d914 --- /dev/null +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/TempAliasRenumberer.java @@ -0,0 +1,70 @@ +/* + * 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 java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Renumbers Phoenix temp-alias tokens of the form {@code $}. + *

+ * Temp aliases are produced by {@code ParseNodeFactory.createTempAlias()} from a JVM-global + * {@code AtomicInteger}, so the literal numbers in an EXPLAIN line depend on what was compiled + * earlier in the same JVM. + *

+ * The first distinct alias encountered becomes {@code $1}, the second {@code $2}, and so on, so + * that structural relationships are preserved within one plan. + */ +final class TempAliasRenumberer { + + private static final Pattern TEMP_ALIAS = Pattern.compile("\\$\\d+"); + + private final Map mapping = new HashMap<>(); + private int next = 0; + + /** + * @param s arbitrary text + * @return {@code s} with each {@code $} token replaced by its renumbered alias, or + * {@code s} unchanged when no token appears. {@code null} input returns {@code null}. + */ + String rewrite(String s) { + if (s == null) { + return null; + } + Matcher m = TEMP_ALIAS.matcher(s); + if (!m.find()) { + return s; + } + m.reset(); + StringBuffer sb = new StringBuffer(); + while (m.find()) { + String tok = m.group(); + String repl = mapping.get(tok); + if (repl == null) { + next++; + repl = "$" + next; + mapping.put(tok, repl); + } + m.appendReplacement(sb, Matcher.quoteReplacement(repl)); + } + m.appendTail(sb); + return sb.toString(); + } +} From 607181e69d46fb6932083aeb4c8b736d773e2b27 Mon Sep 17 00:00:00 2001 From: Andrew Purtell Date: Mon, 8 Jun 2026 23:24:58 -0700 Subject: [PATCH 02/27] PHOENIX-7881 Refactor UTs and ITs to assert on ExplainPlanAttributes (#2503) Co-authored-by: Claude Opus 4.8[1m] --- .../phoenix/compile/DeleteCompiler.java | 4 +- .../compile/ExplainPlanAttributes.java | 564 ++++++++----- .../phoenix/compile/GroupByCompiler.java | 6 + .../phoenix/execute/ClientAggregatePlan.java | 77 +- .../phoenix/execute/ClientScanPlan.java | 30 +- .../apache/phoenix/execute/HashJoinPlan.java | 82 +- .../phoenix/execute/SortMergeJoinPlan.java | 19 +- .../phoenix/execute/TupleProjectionPlan.java | 4 +- .../phoenix/iterate/CursorResultIterator.java | 4 +- .../DistinctAggregatingResultIterator.java | 4 +- .../apache/phoenix/iterate/ExplainTable.java | 60 +- .../FilterAggregatingResultIterator.java | 4 +- .../phoenix/iterate/FilterResultIterator.java | 4 +- .../iterate/LimitingResultIterator.java | 4 +- .../MergeSortRowKeyResultIterator.java | 1 + .../iterate/MergeSortTopNResultIterator.java | 9 +- .../phoenix/iterate/OffsetResultIterator.java | 4 +- .../iterate/OrderedResultIterator.java | 6 +- .../iterate/SegmentResultIterator.java | 1 + .../iterate/SequenceResultIterator.java | 6 +- .../phoenix/end2end/AlterSessionIT.java | 17 +- .../phoenix/end2end/BaseAggregateIT.java | 76 +- .../BaseAggregateWithRegionMoves2IT.java | 76 +- .../BaseAggregateWithRegionMovesIT.java | 28 +- .../apache/phoenix/end2end/BaseOrderByIT.java | 21 +- .../end2end/BaseOrderByWithRegionMovesIT.java | 21 +- .../phoenix/end2end/BasePermissionsIT.java | 13 +- .../BaseTenantSpecificViewIndexIT.java | 63 +- .../apache/phoenix/end2end/BaseViewIT.java | 29 +- .../org/apache/phoenix/end2end/Bson4IT.java | 16 +- .../org/apache/phoenix/end2end/Bson5IT.java | 13 +- .../apache/phoenix/end2end/CDCQueryIT.java | 15 +- .../phoenix/end2end/CastAndCoerceIT.java | 19 +- .../end2end/ClientHashAggregateIT.java | 21 +- .../phoenix/end2end/CostBasedDecisionIT.java | 294 +++---- ...CountDistinctApproximateHyperLogLogIT.java | 15 +- .../apache/phoenix/end2end/CreateTableIT.java | 14 +- .../phoenix/end2end/CsvBulkLoadToolIT.java | 8 +- .../org/apache/phoenix/end2end/DeleteIT.java | 64 +- .../phoenix/end2end/DerivedTableIT.java | 96 +-- .../end2end/DistinctPrefixFilterIT.java | 13 +- .../apache/phoenix/end2end/EmptyColumnIT.java | 14 +- .../ExplainPlanWithStatsDisabledIT.java | 110 +-- .../phoenix/end2end/FlappingLocalIndexIT.java | 83 +- .../org/apache/phoenix/end2end/InListIT.java | 136 +--- .../end2end/IndexBuildTimestampIT.java | 15 +- .../phoenix/end2end/IndexExtendedIT.java | 29 +- .../end2end/IndexToolForPartialBuildIT.java | 25 +- .../apache/phoenix/end2end/IndexToolIT.java | 110 +-- .../org/apache/phoenix/end2end/KeyOnlyIT.java | 15 +- .../end2end/LocalIndexSplitMergeIT.java | 64 +- .../end2end/LogicalTableNameExtendedIT.java | 16 +- .../phoenix/end2end/LogicalTableNameIT.java | 15 +- .../end2end/MaxLookbackExtendedIT.java | 8 +- .../apache/phoenix/end2end/MaxLookbackIT.java | 10 +- .../phoenix/end2end/MultiCfQueryExecIT.java | 7 +- .../phoenix/end2end/OnDuplicateKey2IT.java | 11 +- .../phoenix/end2end/OnDuplicateKeyIT.java | 14 +- .../end2end/ParallelStatsDisabledIT.java | 12 - ...arallelStatsDisabledWithRegionMovesIT.java | 11 - .../ProjectArrayElemAfterHashJoinIT.java | 20 +- .../apache/phoenix/end2end/QueryLoggerIT.java | 4 + .../phoenix/end2end/QueryWithLimitIT.java | 15 +- .../phoenix/end2end/QueryWithOffsetIT.java | 36 +- .../end2end/QueryWithTableSampleIT.java | 55 +- .../phoenix/end2end/RTrimFunctionIT.java | 6 +- .../apache/phoenix/end2end/ReverseScanIT.java | 30 +- .../end2end/RowValueConstructorIT.java | 14 +- .../end2end/RowValueConstructorOffsetIT.java | 22 +- .../end2end/SequenceBulkAllocationIT.java | 14 +- .../apache/phoenix/end2end/SequenceIT.java | 24 +- .../phoenix/end2end/ServerPagingIT.java | 22 +- .../phoenix/end2end/SortMergeJoinMoreIT.java | 70 +- .../phoenix/end2end/SpillableGroupByIT.java | 13 +- .../phoenix/end2end/SubBinaryFunctionIT.java | 12 +- .../apache/phoenix/end2end/TableTTLIT.java | 14 +- .../end2end/TenantSpecificTablesDDLIT.java | 15 +- .../end2end/TenantSpecificTablesDMLIT.java | 11 +- .../end2end/TenantSpecificViewIndexIT.java | 34 +- .../end2end/UCFWithDisabledIndexIT.java | 9 +- .../apache/phoenix/end2end/UnionAllIT.java | 39 +- .../phoenix/end2end/UpsertSelectIT.java | 11 +- .../UpsertSelectWithRegionMovesIT.java | 11 +- .../end2end/UserDefinedFunctionsIT.java | 25 +- .../phoenix/end2end/VarBinaryEncoded2IT.java | 11 +- .../org/apache/phoenix/end2end/ViewIT.java | 119 +-- .../phoenix/end2end/ViewMetadataIT.java | 29 +- .../end2end/WhereOptimizerForArrayAnyIT.java | 8 +- .../WhereOptimizerForArrayAnyITBase.java | 29 +- ...WhereOptimizerForArrayAnyNullablePKIT.java | 12 +- .../end2end/index/BaseImmutableIndexIT.java | 9 +- .../phoenix/end2end/index/BaseIndexIT.java | 217 ++--- .../index/BaseIndexWithRegionMovesIT.java | 200 ++--- .../index/ChildViewsUseParentViewIndexIT.java | 46 +- .../end2end/index/GlobalIndexCheckerIT.java | 119 ++- .../GlobalIndexCheckerWithRegionMovesIT.java | 74 +- .../index/GlobalIndexOptimizationIT.java | 155 ++-- .../index/ImmutableIndexWithStatsIT.java | 12 +- .../end2end/index/IndexMaintenanceIT.java | 39 +- .../phoenix/end2end/index/IndexUsageIT.java | 249 ++---- .../phoenix/end2end/index/LocalIndexIT.java | 282 +++---- .../end2end/index/MutableIndexFailureIT.java | 58 +- .../phoenix/end2end/index/MutableIndexIT.java | 86 +- .../phoenix/end2end/index/PartialIndexIT.java | 83 +- .../index/PartialSystemCatalogIndexIT.java | 50 +- .../phoenix/end2end/index/SaltedIndexIT.java | 89 +-- .../end2end/index/SingleCellIndexIT.java | 36 +- .../UncoveredGlobalIndexRegionScanner2IT.java | 11 +- .../UncoveredGlobalIndexRegionScannerIT.java | 25 +- .../phoenix/end2end/index/ViewIndexIT.java | 26 +- .../end2end/index/txn/TxWriteFailureIT.java | 15 +- .../phoenix/end2end/join/BaseJoinIT.java | 36 +- .../end2end/join/HashJoinGlobalIndexIT.java | 574 +++++++------- .../phoenix/end2end/join/HashJoinIT.java | 391 +++++++-- .../end2end/join/HashJoinLocalIndexIT.java | 747 +++++++++--------- .../phoenix/end2end/join/HashJoinMoreIT.java | 117 +-- .../end2end/join/HashJoinNoIndexIT.java | 564 ++++++------- .../join/SortMergeJoinGlobalIndexIT.java | 86 +- .../phoenix/end2end/join/SortMergeJoinIT.java | 63 +- .../join/SortMergeJoinLocalIndexIT.java | 106 ++- .../end2end/join/SortMergeJoinNoIndexIT.java | 68 +- .../join/SortMergeJoinNoSpoolingIT.java | 4 +- .../phoenix/end2end/join/SubqueryIT.java | 251 +----- .../join/SubqueryUsingSortMergeJoinIT.java | 198 +---- .../phoenix/end2end/json/JsonFunctionsIT.java | 18 +- .../end2end/salted/BaseSaltedTableIT.java | 8 +- .../phoenix/end2end/salted/SaltedTableIT.java | 12 +- .../monitoring/HBaseScanMetricsIT.java | 17 +- .../phoenix/rpc/PhoenixClientRpcIT.java | 10 +- .../phoenix/rpc/PhoenixServerRpcIT.java | 10 +- .../schema/ConditionalTTLExpressionIT.java | 11 +- .../schema/stats/BaseStatsCollectorIT.java | 103 +-- .../compile/JoinQueryCompilerTest.java | 25 +- .../phoenix/compile/QueryCompilerTest.java | 177 ++--- .../phoenix/compile/QueryOptimizerTest.java | 11 +- .../StatementHintsCompilationTest.java | 28 +- .../TenantSpecificViewIndexCompileTest.java | 127 ++- .../phoenix/query/ExplainPlanTextTest.java | 67 -- .../apache/phoenix/query/QueryPlanTest.java | 238 +----- .../query/explain/ExplainJsonNormalizer.java | 21 +- ...tibilityTest.java => ExplainPlanTest.java} | 342 ++++++-- .../query/explain/ExplainPlanTestUtil.java | 475 +++++++++++ .../query/explain/ExplainTextNormalizer.java | 10 +- 143 files changed, 4819 insertions(+), 5241 deletions(-) delete mode 100644 phoenix-core/src/test/java/org/apache/phoenix/query/ExplainPlanTextTest.java rename phoenix-core/src/test/java/org/apache/phoenix/query/explain/{ExplainCompatibilityTest.java => ExplainPlanTest.java} (67%) create mode 100644 phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTestUtil.java 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..74cd9833324 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 @@ -19,6 +19,8 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Set; import org.apache.hadoop.hbase.HRegionLocation; @@ -32,149 +34,187 @@ * 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", - "regionLocations", "numRegionLocationLookups" }) +@JsonPropertyOrder({ "abstractExplainPlan", "hint", "explainScanType", "consistency", "tableName", + "keyRanges", "scanTimeRangeMin", "scanTimeRangeMax", "splitsChunk", "useRoundRobinIterator", + "samplingRate", "hexStringRVCOffset", "iteratorTypeAndScanSize", "estimatedRows", + "estimatedSizeInBytes", "serverWhereFilter", "serverDistinctFilter", "serverMergeColumns", + "serverArrayElementProjection", "serverAggregate", "serverGroupByLimit", "serverSortedBy", + "serverOffset", "serverRowLimit", "clientFilterBy", "clientAggregate", "clientDistinctFilter", + "clientAfterAggregate", "clientSortAlgo", "clientSortedBy", "clientOffset", "clientRowLimit", + "clientSequenceCount", "clientCursorName", "clientSteps", "lhsJoinQueryExplainPlan", + "rhsJoinQueryExplainPlan", "subPlans", "dynamicServerFilter", "afterJoinFilter", + "joinScannerLimit", "sortMergeSkipMerge", "regionLocations", "regionLocationsTotalSize", + "numRegionLocationLookups" }) 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 + // Ordered client-side pipeline (CLIENT* lines in emission order). + private final List clientSteps; + + // Join / sub-plan + private final ExplainPlanAttributes lhsJoinQueryExplainPlan; 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; + // Total number of distinct region locations before any trimming. Null when not populated. + private final Integer regionLocationsTotalSize; private final int numRegionLocationLookups; private static final ExplainPlanAttributes EXPLAIN_PLAN_INSTANCE = new 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.clientSteps = null; + this.lhsJoinQueryExplainPlan = 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.regionLocationsTotalSize = 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, + List clientSteps, ExplainPlanAttributes lhsJoinQueryExplainPlan, + ExplainPlanAttributes rhsJoinQueryExplainPlan, List subPlans, + String dynamicServerFilter, String afterJoinFilter, Long joinScannerLimit, + boolean sortMergeSkipMerge, List regionLocations, + Integer regionLocationsTotalSize, 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.clientSteps = (clientSteps == null || clientSteps.isEmpty()) + ? null + : Collections.unmodifiableList(new ArrayList<>(clientSteps)); + this.lhsJoinQueryExplainPlan = lhsJoinQueryExplainPlan; 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.regionLocationsTotalSize = regionLocationsTotalSize; this.numRegionLocationLookups = numRegionLocationLookups; } @@ -182,64 +222,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 +286,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 +299,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 +323,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 +355,36 @@ public String getClientCursorName() { return clientCursorName; } - public String getClientSortAlgo() { - return clientSortAlgo; + public List getClientSteps() { + return clientSteps; + } + + public ExplainPlanAttributes getLhsJoinQueryExplainPlan() { + return lhsJoinQueryExplainPlan; } 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) @@ -320,6 +392,10 @@ public List getRegionLocations() { return regionLocations; } + public Integer getRegionLocationsTotalSize() { + return regionLocationsTotalSize; + } + public int getNumRegionLocationLookups() { return numRegionLocationLookups; } @@ -330,40 +406,49 @@ 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 List clientSteps; + private ExplainPlanAttributes lhsJoinQueryExplainPlan; 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 Integer regionLocationsTotalSize; private int numRegionLocationLookups; public ExplainPlanAttributesBuilder() { @@ -372,40 +457,50 @@ 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(); + List srcClientSteps = explainPlanAttributes.getClientSteps(); + this.clientSteps = srcClientSteps == null ? null : new ArrayList<>(srcClientSteps); + this.lhsJoinQueryExplainPlan = explainPlanAttributes.getLhsJoinQueryExplainPlan(); 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.regionLocationsTotalSize = explainPlanAttributes.getRegionLocationsTotalSize(); this.numRegionLocationLookups = explainPlanAttributes.getNumRegionLocationLookups(); } @@ -414,78 +509,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 +589,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 +605,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 +635,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 +645,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,8 +675,22 @@ public ExplainPlanAttributesBuilder setClientCursorName(String clientCursorName) return this; } - public ExplainPlanAttributesBuilder setClientSortAlgo(String clientSortAlgo) { - this.clientSortAlgo = clientSortAlgo; + public ExplainPlanAttributesBuilder setClientSteps(List clientSteps) { + this.clientSteps = clientSteps == null ? null : new ArrayList<>(clientSteps); + return this; + } + + public ExplainPlanAttributesBuilder addClientStep(String step) { + if (this.clientSteps == null) { + this.clientSteps = new ArrayList<>(); + } + this.clientSteps.add(step); + return this; + } + + public ExplainPlanAttributesBuilder + setLhsJoinQueryExplainPlan(ExplainPlanAttributes lhsJoinQueryExplainPlan) { + this.lhsJoinQueryExplainPlan = lhsJoinQueryExplainPlan; return this; } @@ -576,8 +700,28 @@ public ExplainPlanAttributesBuilder setClientSortAlgo(String clientSortAlgo) { 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; } @@ -586,20 +730,28 @@ public ExplainPlanAttributesBuilder setRegionLocations(List reg return this; } + public ExplainPlanAttributesBuilder + setRegionLocationsTotalSize(Integer regionLocationsTotalSize) { + this.regionLocationsTotalSize = regionLocationsTotalSize; + return this; + } + public ExplainPlanAttributesBuilder setNumRegionLocationLookups(int numRegionLocationLookups) { this.numRegionLocationLookups = numRegionLocationLookups; return this; } 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, clientSteps, lhsJoinQueryExplainPlan, + rhsJoinQueryExplainPlan, subPlans, dynamicServerFilter, afterJoinFilter, joinScannerLimit, + sortMergeSkipMerge, regionLocations, regionLocationsTotalSize, 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/ClientAggregatePlan.java b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/ClientAggregatePlan.java index e89bf515ccd..4a3d1b5d28a 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/ClientAggregatePlan.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/ClientAggregatePlan.java @@ -226,63 +226,86 @@ public ExplainPlan getExplainPlan() throws SQLException { ExplainPlanAttributesBuilder newBuilder = new ExplainPlanAttributesBuilder(explainPlanAttributes); if (where != null) { - planSteps.add("CLIENT FILTER BY " + where.toString()); + String step = "CLIENT FILTER BY " + where.toString(); + planSteps.add(step); newBuilder.setClientFilterBy(where.toString()); + newBuilder.addClientStep(step); } if (groupBy.isEmpty()) { - planSteps.add("CLIENT AGGREGATE INTO SINGLE ROW"); - newBuilder.setClientAggregate("CLIENT AGGREGATE INTO SINGLE ROW"); + String step = "CLIENT AGGREGATE INTO SINGLE ROW"; + planSteps.add(step); + newBuilder.setClientAggregate(step); + newBuilder.addClientStep(step); } else if (groupBy.isOrderPreserving()) { - planSteps.add( - "CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY " + groupBy.getExpressions().toString()); - newBuilder.setClientAggregate( - "CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY " + groupBy.getExpressions().toString()); + String step = + "CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY " + groupBy.getExpressions().toString(); + planSteps.add(step); + newBuilder.setClientAggregate(step); + newBuilder.addClientStep(step); } else if (useHashAgg) { - planSteps - .add("CLIENT HASH AGGREGATE INTO DISTINCT ROWS BY " + groupBy.getExpressions().toString()); - newBuilder.setClientAggregate( - "CLIENT HASH AGGREGATE INTO DISTINCT ROWS BY " + groupBy.getExpressions().toString()); + String step = + "CLIENT HASH AGGREGATE INTO DISTINCT ROWS BY " + groupBy.getExpressions().toString(); + planSteps.add(step); + newBuilder.setClientAggregate(step); + newBuilder.addClientStep(step); if (orderBy == OrderBy.FWD_ROW_KEY_ORDER_BY || orderBy == OrderBy.REV_ROW_KEY_ORDER_BY) { - planSteps.add("CLIENT SORTED BY " + groupBy.getKeyExpressions().toString()); + String sortStep = "CLIENT SORTED BY " + groupBy.getKeyExpressions().toString(); + planSteps.add(sortStep); newBuilder.setClientSortedBy(groupBy.getKeyExpressions().toString()); + newBuilder.addClientStep(sortStep); } } else { - planSteps.add("CLIENT SORTED BY " + groupBy.getKeyExpressions().toString()); - planSteps - .add("CLIENT AGGREGATE INTO DISTINCT ROWS BY " + groupBy.getExpressions().toString()); + String sortStep = "CLIENT SORTED BY " + groupBy.getKeyExpressions().toString(); + String aggStep = + "CLIENT AGGREGATE INTO DISTINCT ROWS BY " + groupBy.getExpressions().toString(); + planSteps.add(sortStep); + planSteps.add(aggStep); newBuilder.setClientSortedBy(groupBy.getKeyExpressions().toString()); - newBuilder.setClientAggregate( - "CLIENT AGGREGATE INTO DISTINCT ROWS BY " + groupBy.getExpressions().toString()); + newBuilder.setClientAggregate(aggStep); + newBuilder.addClientStep(sortStep); + newBuilder.addClientStep(aggStep); } if (having != null) { - planSteps.add("CLIENT AFTER-AGGREGATION FILTER BY " + having.toString()); - newBuilder.setClientAfterAggregate("CLIENT AFTER-AGGREGATION FILTER BY " + having.toString()); + String step = "CLIENT AFTER-AGGREGATION FILTER BY " + having.toString(); + planSteps.add(step); + newBuilder.setClientAfterAggregate(step); + newBuilder.addClientStep(step); } if (statement.isDistinct() && statement.isAggregate()) { - planSteps.add("CLIENT DISTINCT ON " + projector.toString()); + String step = "CLIENT DISTINCT ON " + projector.toString(); + planSteps.add(step); newBuilder.setClientDistinctFilter(projector.toString()); + newBuilder.addClientStep(step); } if (offset != null) { - planSteps.add("CLIENT OFFSET " + offset); + String step = "CLIENT OFFSET " + offset; + planSteps.add(step); newBuilder.setClientOffset(offset); + newBuilder.addClientStep(step); } if (orderBy.getOrderByExpressions().isEmpty()) { if (limit != null) { - planSteps.add("CLIENT " + limit + " ROW LIMIT"); + String step = "CLIENT " + limit + " ROW LIMIT"; + planSteps.add(step); newBuilder.setClientRowLimit(limit); + newBuilder.addClientStep(step); } } else { - planSteps - .add("CLIENT" + (limit == null ? "" : " TOP " + limit + " ROW" + (limit == 1 ? "" : "S")) - + " SORTED BY " + orderBy.getOrderByExpressions().toString()); + String step = + "CLIENT" + (limit == null ? "" : " TOP " + limit + " ROW" + (limit == 1 ? "" : "S")) + + " SORTED BY " + orderBy.getOrderByExpressions().toString(); + planSteps.add(step); newBuilder.setClientRowLimit(limit); newBuilder.setClientSortedBy(orderBy.getOrderByExpressions().toString()); + newBuilder.addClientStep(step); } if (context.getSequenceManager().getSequenceCount() > 0) { int nSequences = context.getSequenceManager().getSequenceCount(); - planSteps.add( - "CLIENT RESERVE VALUES FROM " + nSequences + " SEQUENCE" + (nSequences == 1 ? "" : "S")); + String step = + "CLIENT RESERVE VALUES FROM " + nSequences + " SEQUENCE" + (nSequences == 1 ? "" : "S"); + planSteps.add(step); newBuilder.setClientSequenceCount(nSequences); + newBuilder.addClientStep(step); } return new ExplainPlan(planSteps, newBuilder.build()); diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/ClientScanPlan.java b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/ClientScanPlan.java index 8bc71989604..9807ba8ef92 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/ClientScanPlan.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/ClientScanPlan.java @@ -123,34 +123,46 @@ public ExplainPlan getExplainPlan() throws SQLException { ExplainPlanAttributesBuilder newBuilder = new ExplainPlanAttributesBuilder(explainPlanAttributes); if (where != null) { - planSteps.add("CLIENT FILTER BY " + where.toString()); + String step = "CLIENT FILTER BY " + where.toString(); + planSteps.add(step); newBuilder.setClientFilterBy(where.toString()); + newBuilder.addClientStep(step); } if (!orderBy.getOrderByExpressions().isEmpty()) { if (offset != null) { - planSteps.add("CLIENT OFFSET " + offset); + String step = "CLIENT OFFSET " + offset; + planSteps.add(step); newBuilder.setClientOffset(offset); + newBuilder.addClientStep(step); } - planSteps - .add("CLIENT" + (limit == null ? "" : " TOP " + limit + " ROW" + (limit == 1 ? "" : "S")) - + " SORTED BY " + orderBy.getOrderByExpressions().toString()); + String step = + "CLIENT" + (limit == null ? "" : " TOP " + limit + " ROW" + (limit == 1 ? "" : "S")) + + " SORTED BY " + orderBy.getOrderByExpressions().toString(); + planSteps.add(step); newBuilder.setClientRowLimit(limit); newBuilder.setClientSortedBy(orderBy.getOrderByExpressions().toString()); + newBuilder.addClientStep(step); } else { if (offset != null) { - planSteps.add("CLIENT OFFSET " + offset); + String step = "CLIENT OFFSET " + offset; + planSteps.add(step); newBuilder.setClientOffset(offset); + newBuilder.addClientStep(step); } if (limit != null) { - planSteps.add("CLIENT " + limit + " ROW LIMIT"); + String step = "CLIENT " + limit + " ROW LIMIT"; + planSteps.add(step); newBuilder.setClientRowLimit(limit); + newBuilder.addClientStep(step); } } if (context.getSequenceManager().getSequenceCount() > 0) { int nSequences = context.getSequenceManager().getSequenceCount(); - planSteps.add( - "CLIENT RESERVE VALUES FROM " + nSequences + " SEQUENCE" + (nSequences == 1 ? "" : "S")); + String step = + "CLIENT RESERVE VALUES FROM " + nSequences + " SEQUENCE" + (nSequences == 1 ? "" : "S"); + planSteps.add(step); newBuilder.setClientSequenceCount(nSequences); + newBuilder.addClientStep(step); } return new ExplainPlan(planSteps, newBuilder.build()); diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java index 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..9e1cd2290ba 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 @@ -192,10 +192,6 @@ public ExplainPlan getExplainPlan() throws SQLException { ExplainPlan lhsExplainPlan = lhsPlan.getExplainPlan(); List lhsPlanSteps = lhsExplainPlan.getPlanSteps(); ExplainPlanAttributes lhsPlanAttributes = lhsExplainPlan.getPlanStepsAsAttributes(); - ExplainPlanAttributesBuilder lhsPlanBuilder = - new ExplainPlanAttributesBuilder(lhsPlanAttributes); - lhsPlanBuilder - .setAbstractExplainPlan("SORT-MERGE-JOIN (" + joinType.toString().toUpperCase() + ")"); for (String step : lhsPlanSteps) { steps.add(" " + step); @@ -205,15 +201,20 @@ public ExplainPlan getExplainPlan() throws SQLException { ExplainPlan rhsExplainPlan = rhsPlan.getExplainPlan(); List rhsPlanSteps = rhsExplainPlan.getPlanSteps(); ExplainPlanAttributes rhsPlanAttributes = rhsExplainPlan.getPlanStepsAsAttributes(); - ExplainPlanAttributesBuilder rhsPlanBuilder = - new ExplainPlanAttributesBuilder(rhsPlanAttributes); - - lhsPlanBuilder.setRhsJoinQueryExplainPlan(rhsPlanBuilder.build()); for (String step : rhsPlanSteps) { steps.add(" " + step); } - return new ExplainPlan(steps, lhsPlanBuilder.build()); + + // Build a synthetic root that holds the join operator and its two operands as separate + // child plans so nested sort-merge-joins can be represented. + ExplainPlanAttributesBuilder rootBuilder = new ExplainPlanAttributesBuilder(); + rootBuilder + .setAbstractExplainPlan("SORT-MERGE-JOIN (" + joinType.toString().toUpperCase() + ")"); + rootBuilder.setSortMergeSkipMerge(rhsSchema.getFieldCount() == 0); + rootBuilder.setLhsJoinQueryExplainPlan(lhsPlanAttributes); + rootBuilder.setRhsJoinQueryExplainPlan(rhsPlanAttributes); + return new ExplainPlan(steps, rootBuilder.build()); } @Override diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/TupleProjectionPlan.java b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/TupleProjectionPlan.java index ac38657f181..413f046d221 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/TupleProjectionPlan.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/TupleProjectionPlan.java @@ -147,10 +147,12 @@ public ExplainPlan getExplainPlan() throws SQLException { List planSteps = Lists.newArrayList(explainPlan.getPlanSteps()); ExplainPlanAttributes explainPlanAttributes = explainPlan.getPlanStepsAsAttributes(); if (postFilter != null) { - planSteps.add("CLIENT FILTER BY " + postFilter.toString()); + String step = "CLIENT FILTER BY " + postFilter.toString(); + planSteps.add(step); ExplainPlanAttributesBuilder newBuilder = new ExplainPlanAttributesBuilder(explainPlanAttributes); newBuilder.setClientFilterBy(postFilter.toString()); + newBuilder.addClientStep(step); explainPlanAttributes = newBuilder.build(); } diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/CursorResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/CursorResultIterator.java index 9c44579366b..113c6fad459 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/CursorResultIterator.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/CursorResultIterator.java @@ -60,7 +60,9 @@ public void explain(List planSteps, ExplainPlanAttributesBuilder explainPlanAttributesBuilder) { delegate.explain(planSteps, explainPlanAttributesBuilder); explainPlanAttributesBuilder.setClientCursorName(cursorName); - planSteps.add("CLIENT CURSOR " + cursorName); + String step = "CLIENT CURSOR " + cursorName; + planSteps.add(step); + explainPlanAttributesBuilder.addClientStep(step); } @Override diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/DistinctAggregatingResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/DistinctAggregatingResultIterator.java index 10f081c8a48..6dfdfb8857e 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/DistinctAggregatingResultIterator.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/DistinctAggregatingResultIterator.java @@ -134,7 +134,9 @@ public void explain(List planSteps, ExplainPlanAttributesBuilder explainPlanAttributesBuilder) { targetAggregatingResultIterator.explain(planSteps, explainPlanAttributesBuilder); explainPlanAttributesBuilder.setClientDistinctFilter(rowProjector.toString()); - planSteps.add("CLIENT DISTINCT ON " + rowProjector.toString()); + String step = "CLIENT DISTINCT ON " + rowProjector.toString(); + planSteps.add(step); + explainPlanAttributesBuilder.addClientStep(step); } @Override diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java index 221a69c8dd8..83821005614 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java @@ -24,7 +24,6 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.NoSuchElementException; import java.util.Set; import org.apache.hadoop.hbase.HRegionLocation; import org.apache.hadoop.hbase.client.Consistency; @@ -53,7 +52,6 @@ import org.apache.phoenix.query.QueryServicesOptions; import org.apache.phoenix.schema.PColumn; import org.apache.phoenix.schema.PTable; -import org.apache.phoenix.schema.RowKeySchema; import org.apache.phoenix.schema.SortOrder; import org.apache.phoenix.schema.TableRef; import org.apache.phoenix.schema.types.PDataType; @@ -288,8 +286,11 @@ protected void explain(String prefix, List planSteps, } if (explainPlanAttributesBuilder != null) { explainPlanAttributesBuilder.setServerOffset(offset); - if (pageFilter != null) { - explainPlanAttributesBuilder.setServerRowLimit(pageFilter.getPageSize()); + // Populate the attribute whenever a "SERVER n ROW LIMIT" step is emitted, including the + // uncovered-index/server-merge path where the limit originates from the INDEX_LIMIT scan + // attribute rather than a PageFilter. + if (limit != null) { + explainPlanAttributesBuilder.setServerRowLimit(limit); } } } @@ -365,7 +366,8 @@ private String getRegionLocationsForExplainPlan( regionLocations.subList(0, maxLimitRegionLoc); if (explainPlanAttributesBuilder != null) { explainPlanAttributesBuilder - .setRegionLocations(Collections.unmodifiableList(trimmedRegionLocations)); + .setRegionLocations(Collections.unmodifiableList(trimmedRegionLocations)) + .setRegionLocationsTotalSize(originalSize); } buf.append(trimmedRegionLocations); buf.append("...total size = "); @@ -374,7 +376,8 @@ private String getRegionLocationsForExplainPlan( buf.append(regionLocations); if (explainPlanAttributesBuilder != null) { explainPlanAttributesBuilder - .setRegionLocations(Collections.unmodifiableList(regionLocations)); + .setRegionLocations(Collections.unmodifiableList(regionLocations)) + .setRegionLocationsTotalSize(regionLocations.size()); } } buf.append(") "); @@ -418,6 +421,7 @@ public int hashCode() { } } + @SuppressWarnings("rawtypes") private void appendPKColumnValue(StringBuilder buf, byte[] range, Boolean isNull, int slotIndex, boolean changeViewIndexId) { if (Boolean.TRUE.equals(isNull)) { @@ -449,55 +453,13 @@ private void appendPKColumnValue(StringBuilder buf, byte[] range, Boolean isNull } } + @SuppressWarnings("rawtypes") private Long getViewIndexValue(PDataType type, byte[] range) { boolean useLongViewIndex = MetaDataUtil.getViewIndexIdDataType().equals(type); Object s = type.toObject(range); return (useLongViewIndex ? (Long) s : (Short) s) + Short.MAX_VALUE + 2; } - private static class RowKeyValueIterator implements Iterator { - private final RowKeySchema schema; - private ImmutableBytesWritable ptr = new ImmutableBytesWritable(); - private int position = 0; - private final int maxOffset; - private byte[] nextValue; - - public RowKeyValueIterator(RowKeySchema schema, byte[] rowKey) { - this.schema = schema; - this.maxOffset = schema.iterator(rowKey, ptr); - iterate(); - } - - private void iterate() { - if (schema.next(ptr, position++, maxOffset) == null) { - nextValue = null; - } else { - nextValue = ptr.copyBytes(); - } - } - - @Override - public boolean hasNext() { - return nextValue != null; - } - - @Override - public byte[] next() { - if (nextValue == null) { - throw new NoSuchElementException(); - } - byte[] value = nextValue; - iterate(); - return value; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - } - private void appendScanRow(StringBuilder buf, Bound bound) { ScanRanges scanRanges = context.getScanRanges(); Iterator minMaxIterator = Collections.emptyIterator(); diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/FilterAggregatingResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/FilterAggregatingResultIterator.java index fb3609066cc..82d6ba68248 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/FilterAggregatingResultIterator.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/FilterAggregatingResultIterator.java @@ -82,7 +82,9 @@ public void explain(List planSteps, ExplainPlanAttributesBuilder explainPlanAttributesBuilder) { delegate.explain(planSteps, explainPlanAttributesBuilder); explainPlanAttributesBuilder.setClientFilterBy(expression.toString()); - planSteps.add("CLIENT FILTER BY " + expression.toString()); + String step = "CLIENT FILTER BY " + expression.toString(); + planSteps.add(step); + explainPlanAttributesBuilder.addClientStep(step); } @Override diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/FilterResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/FilterResultIterator.java index f6631dee434..9435f20464e 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/FilterResultIterator.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/FilterResultIterator.java @@ -80,7 +80,9 @@ public void explain(List planSteps, ExplainPlanAttributesBuilder explainPlanAttributesBuilder) { delegate.explain(planSteps, explainPlanAttributesBuilder); explainPlanAttributesBuilder.setClientFilterBy(expression.toString()); - planSteps.add("CLIENT FILTER BY " + expression.toString()); + String step = "CLIENT FILTER BY " + expression.toString(); + planSteps.add(step); + explainPlanAttributesBuilder.addClientStep(step); } @Override diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/LimitingResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/LimitingResultIterator.java index 0d60c3b04dc..1a90c87d3b8 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/LimitingResultIterator.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/LimitingResultIterator.java @@ -55,7 +55,9 @@ public void explain(List planSteps, ExplainPlanAttributesBuilder explainPlanAttributesBuilder) { super.explain(planSteps, explainPlanAttributesBuilder); explainPlanAttributesBuilder.setClientRowLimit(limit); - planSteps.add("CLIENT " + limit + " ROW LIMIT"); + String step = "CLIENT " + limit + " ROW LIMIT"; + planSteps.add(step); + explainPlanAttributesBuilder.addClientStep(step); } @Override diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/MergeSortRowKeyResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/MergeSortRowKeyResultIterator.java index 2412c2a092a..7691504d936 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/MergeSortRowKeyResultIterator.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/MergeSortRowKeyResultIterator.java @@ -59,6 +59,7 @@ public void explain(List planSteps, resultIterators.explain(planSteps, explainPlanAttributesBuilder); explainPlanAttributesBuilder.setClientSortAlgo("CLIENT MERGE SORT"); planSteps.add("CLIENT MERGE SORT"); + explainPlanAttributesBuilder.addClientStep("CLIENT MERGE SORT"); } @Override diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java index 2c72682e19c..767656a9502 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java @@ -116,13 +116,18 @@ public void explain(List planSteps, resultIterators.explain(planSteps, explainPlanAttributesBuilder); explainPlanAttributesBuilder.setClientSortAlgo("CLIENT MERGE SORT"); planSteps.add("CLIENT MERGE SORT"); + explainPlanAttributesBuilder.addClientStep("CLIENT MERGE SORT"); if (offset > 0) { explainPlanAttributesBuilder.setClientOffset(offset); - planSteps.add("CLIENT OFFSET " + offset); + String step = "CLIENT OFFSET " + offset; + planSteps.add(step); + explainPlanAttributesBuilder.addClientStep(step); } if (limit > 0) { explainPlanAttributesBuilder.setClientRowLimit(limit); - planSteps.add("CLIENT LIMIT " + limit); + String step = "CLIENT LIMIT " + limit; + planSteps.add(step); + explainPlanAttributesBuilder.addClientStep(step); } } diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/OffsetResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/OffsetResultIterator.java index bc092a0a2e5..fc369eed25f 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/OffsetResultIterator.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/OffsetResultIterator.java @@ -92,7 +92,9 @@ public void explain(List planSteps, ExplainPlanAttributesBuilder explainPlanAttributesBuilder) { super.explain(planSteps, explainPlanAttributesBuilder); explainPlanAttributesBuilder.setClientOffset(offset); - planSteps.add("CLIENT OFFSET " + offset); + String step = "CLIENT OFFSET " + offset; + planSteps.add(step); + explainPlanAttributesBuilder.addClientStep(step); } @Override diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/OrderedResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/OrderedResultIterator.java index 2cc3ccb3f6f..1662b5b4655 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/OrderedResultIterator.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/OrderedResultIterator.java @@ -514,9 +514,11 @@ public void explain(List planSteps, explainPlanAttributesBuilder.setClientOffset(offset); explainPlanAttributesBuilder.setClientRowLimit(limit); explainPlanAttributesBuilder.setClientSortedBy(orderByExpressions.toString()); - planSteps.add("CLIENT" + (offset == null || offset == 0 ? "" : " OFFSET " + offset) + String step = "CLIENT" + (offset == null || offset == 0 ? "" : " OFFSET " + offset) + (limit == null ? "" : " TOP " + limit + " ROW" + (limit == 1 ? "" : "S")) + " SORTED BY " - + orderByExpressions.toString()); + + orderByExpressions.toString(); + planSteps.add(step); + explainPlanAttributesBuilder.addClientStep(step); } @Override diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/SegmentResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/SegmentResultIterator.java index 1bed34afbd1..752600a2969 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/SegmentResultIterator.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/SegmentResultIterator.java @@ -120,5 +120,6 @@ public void explain(List planSteps) { public void explain(List planSteps, ExplainPlanAttributesBuilder explainPlanAttributesBuilder) { planSteps.add("CLIENT SEGMENT SCAN"); + explainPlanAttributesBuilder.addClientStep("CLIENT SEGMENT SCAN"); } } diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/SequenceResultIterator.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/SequenceResultIterator.java index b58ba586104..ae98654cfa0 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/SequenceResultIterator.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/SequenceResultIterator.java @@ -60,8 +60,10 @@ public void explain(List planSteps, super.explain(planSteps, explainPlanAttributesBuilder); int nSequences = sequenceManager.getSequenceCount(); explainPlanAttributesBuilder.setClientSequenceCount(nSequences); - planSteps - .add("CLIENT RESERVE VALUES FROM " + nSequences + " SEQUENCE" + (nSequences == 1 ? "" : "S")); + String step = + "CLIENT RESERVE VALUES FROM " + nSequences + " SEQUENCE" + (nSequences == 1 ? "" : "S"); + planSteps.add(step); + explainPlanAttributesBuilder.addClientStep(step); } @Override 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/BaseAggregateIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateIT.java index fde20a61309..2be8e8f7721 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateIT.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; @@ -39,10 +40,7 @@ import org.apache.hadoop.hbase.client.ResultScanner; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.client.Table; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.jdbc.PhoenixConnection; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.ConnectionQueryServices; import org.apache.phoenix.query.KeyRange; import org.apache.phoenix.query.QueryServices; @@ -513,20 +511,14 @@ public void testGroupByOrderPreserving() throws Exception { assertEquals("abc", rs.getString(2)); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(queryBuilder.build()) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals( - " ['000001111122222','333334444455555',0,*] - ['000001111122222','333334444455555',0,1]", - explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals( - "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [MATCH_STATUS, EXTERNAL_DATASOURCE_KEY]", - explainPlanAttributes.getServerAggregate()); - assertEquals("COUNT(1) > 1", explainPlanAttributes.getClientFilterBy()); + assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(tableName) + .keyRanges( + " ['000001111122222','333334444455555',0,*] - ['000001111122222','333334444455555',0,1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate( + "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [MATCH_STATUS, EXTERNAL_DATASOURCE_KEY]") + .clientFilterBy("COUNT(1) > 1"); } @Test @@ -564,18 +556,10 @@ public void testGroupByOrderPreservingDescSort() throws Exception { assertEquals("a", rs.getString(1)); assertEquals(4, rs.getLong(2)); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(queryBuilder.build()) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("REVERSE", explainPlanAttributes.getClientSortedBy()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]", - explainPlanAttributes.getServerAggregate()); - assertFalse("Explain plan regionLocation attribute should not be empty", - explainPlanAttributes.getRegionLocations().isEmpty()); + assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").clientSortedBy("REVERSE") + .scanType("FULL SCAN").table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]") + .regionLocationsNotEmpty(); } @Test @@ -621,18 +605,10 @@ public void testSumGroupByOrderPreservingDesc() throws Exception { assertEquals("a", rs.getString(1)); assertEquals(10, rs.getLong(2)); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(queryBuilder.build()) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("REVERSE", explainPlanAttributes.getClientSortedBy()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]", - explainPlanAttributes.getServerAggregate()); - assertFalse("Explain plan regionLocation attribute should not be empty", - explainPlanAttributes.getRegionLocations().isEmpty()); + assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").clientSortedBy("REVERSE") + .scanType("FULL SCAN").table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]") + .regionLocationsNotEmpty(); } @Test @@ -688,21 +664,14 @@ protected void testAvgGroupByOrderPreserving(Connection conn, String tableName, assertEquals("n", rs.getString(1)); assertEquals(2, rs.getDouble(2), 1e-6); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(queryBuilder.build()) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]", - explainPlanAttributes.getServerAggregate()); + assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]") + .regionLocationsNotEmpty(); TestUtil.analyzeTable(conn, tableName); List splits = TestUtil.getAllSplits(conn, tableName); // nGuideposts when stats are enabled, 4 when disabled assertEquals(4, splits.size()); - assertFalse("Explain plan regionLocation attribute should not be empty", - explainPlanAttributes.getRegionLocations().isEmpty()); } @Test @@ -833,9 +802,6 @@ public void testGroupByOrderByDescBug3451() throws Exception { assertEquals("entityId3", rs.getString(1)); assertEquals(1.4, rs.getDouble(2), 0.001); assertFalse(rs.next()); - - String expectedPhoenixPlan = ""; - validateQueryPlan(conn, queryBuilder, expectedPhoenixPlan, null); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateWithRegionMoves2IT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateWithRegionMoves2IT.java index 6ae7869cfe5..7d908eecc19 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateWithRegionMoves2IT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateWithRegionMoves2IT.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; @@ -31,9 +32,6 @@ import java.sql.SQLException; import java.util.List; import java.util.Properties; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.KeyRange; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.schema.types.PChar; @@ -137,20 +135,14 @@ public void testGroupByOrderPreserving() throws Exception { assertEquals("abc", rs.getString(2)); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(queryBuilder.build()) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals( - " ['000001111122222','333334444455555',0,*] - ['000001111122222','333334444455555',0,1]", - explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals( - "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [MATCH_STATUS, EXTERNAL_DATASOURCE_KEY]", - explainPlanAttributes.getServerAggregate()); - assertEquals("COUNT(1) > 1", explainPlanAttributes.getClientFilterBy()); + assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(tableName) + .keyRanges( + " ['000001111122222','333334444455555',0,*] - ['000001111122222','333334444455555',0,1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate( + "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [MATCH_STATUS, EXTERNAL_DATASOURCE_KEY]") + .clientFilterBy("COUNT(1) > 1"); } @Test @@ -190,16 +182,9 @@ public void testGroupByOrderPreservingDescSort() throws Exception { assertEquals("a", rs.getString(1)); assertEquals(4, rs.getLong(2)); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(queryBuilder.build()) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("REVERSE", explainPlanAttributes.getClientSortedBy()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]", - explainPlanAttributes.getServerAggregate()); + assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").clientSortedBy("REVERSE") + .scanType("FULL SCAN").table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]"); } @Test @@ -249,16 +234,9 @@ public void testSumGroupByOrderPreservingDesc() throws Exception { assertEquals("a", rs.getString(1)); assertEquals(10, rs.getLong(2)); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(queryBuilder.build()) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("REVERSE", explainPlanAttributes.getClientSortedBy()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]", - explainPlanAttributes.getServerAggregate()); + assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").clientSortedBy("REVERSE") + .scanType("FULL SCAN").table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]"); } @Test @@ -304,13 +282,8 @@ public void testSumGroupByOrderPreservingDescWithoutSplit() throws Exception { assertEquals("a", rs.getString(1)); assertEquals(10, rs.getLong(2)); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(queryBuilder.build()) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("REVERSE", explainPlanAttributes.getClientSortedBy()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); + assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").clientSortedBy("REVERSE") + .scanType("RANGE SCAN").table(tableName); } @Test @@ -463,15 +436,9 @@ protected void testAvgGroupByOrderPreserving(Connection conn, String tableName, assertEquals("n", rs.getString(1)); assertEquals(2, rs.getDouble(2), 1e-6); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(queryBuilder.build()) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]", - explainPlanAttributes.getServerAggregate()); + assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]"); TestUtil.analyzeTable(conn, tableName); List splits = TestUtil.getAllSplits(conn, tableName); // nGuideposts when stats are enabled, 4 when disabled @@ -527,9 +494,6 @@ public void testGroupByOrderByDescBug3451() throws Exception { assertEquals("entityId3", rs.getString(1)); assertEquals(1.4, rs.getDouble(2), 0.001); assertFalse(rs.next()); - - String expectedPhoenixPlan = ""; - validateQueryPlan(conn, queryBuilder, expectedPhoenixPlan, null); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateWithRegionMovesIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateWithRegionMovesIT.java index 621a3d33805..e363b0bf29b 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateWithRegionMovesIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateWithRegionMovesIT.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; @@ -32,9 +33,6 @@ import java.sql.Statement; import java.util.List; import java.util.Properties; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.KeyRange; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.schema.types.PChar; @@ -365,15 +363,9 @@ protected void testAvgGroupByOrderPreserving(Connection conn, String tableName, assertEquals("n", rs.getString(1)); assertEquals(2, rs.getDouble(2), 1e-6); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(queryBuilder.build()) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]", - explainPlanAttributes.getServerAggregate()); + assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]"); TestUtil.analyzeTable(conn, tableName); List splits = TestUtil.getAllSplits(conn, tableName); // nGuideposts when stats are enabled, 4 when disabled @@ -402,15 +394,9 @@ protected void testAvgGroupByDescOrderPreserving(Connection conn, String tableNa assertEquals("a", rs.getString(1)); assertEquals(3, rs.getDouble(2), 1e-6); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(queryBuilder.build()) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]", - explainPlanAttributes.getServerAggregate()); + assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]"); TestUtil.analyzeTable(conn, tableName); List splits = TestUtil.getAllSplits(conn, tableName); // nGuideposts when stats are enabled, 4 when disabled diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseOrderByIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseOrderByIT.java index 62e7a9271e1..d02a8a7058e 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseOrderByIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseOrderByIT.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.ROW1; import static org.apache.phoenix.util.TestUtil.ROW2; import static org.apache.phoenix.util.TestUtil.ROW3; @@ -30,7 +31,6 @@ import static org.apache.phoenix.util.TestUtil.assertResultSet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.sql.Connection; @@ -40,9 +40,6 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.PropertiesUtil; import org.apache.phoenix.util.QueryBuilder; @@ -290,18 +287,10 @@ public void testAggregateOptimizedOutOrderBy() throws Exception { .setSelectExpression("DISTINCT(K2)").setWhereClause("K2 = 'ABC'"); // verify that the phoenix query plan doesn't contain an order by - ExplainPlan plan = conn.prepareStatement(queryBuilder.build()) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY K2 = 'ABC'", explainPlanAttributes.getServerWhereFilter()); - assertEquals("SERVER AGGREGATE INTO DISTINCT ROWS BY [K2, VAL1, VAL2]", - explainPlanAttributes.getServerAggregate()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - assertNull(explainPlanAttributes.getClientSortedBy()); - assertNull(explainPlanAttributes.getServerSortedBy()); + assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(tableName).serverWhereFilter("SERVER FILTER BY K2 = 'ABC'") + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [K2, VAL1, VAL2]") + .clientSortAlgo("CLIENT MERGE SORT").clientSortedBy(null).serverSortedBy(null); ResultSet rs = executeQuery(conn, queryBuilder); assertTrue(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseOrderByWithRegionMovesIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseOrderByWithRegionMovesIT.java index bef4c8aaceb..2fb0d59022b 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseOrderByWithRegionMovesIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseOrderByWithRegionMovesIT.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.ROW1; import static org.apache.phoenix.util.TestUtil.ROW2; import static org.apache.phoenix.util.TestUtil.ROW3; @@ -30,7 +31,6 @@ import static org.apache.phoenix.util.TestUtil.assertResultSet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.sql.Connection; @@ -40,9 +40,6 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.util.PropertiesUtil; import org.apache.phoenix.util.QueryBuilder; import org.apache.phoenix.util.SchemaUtil; @@ -362,18 +359,10 @@ public void testAggregateOptimizedOutOrderBy() throws Exception { .setSelectExpression("DISTINCT(K2)").setWhereClause("K2 = 'ABC'"); // verify that the phoenix query plan doesn't contain an order by - ExplainPlan plan = conn.prepareStatement(queryBuilder.build()) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY K2 = 'ABC'", explainPlanAttributes.getServerWhereFilter()); - assertEquals("SERVER AGGREGATE INTO DISTINCT ROWS BY [K2, VAL1, VAL2]", - explainPlanAttributes.getServerAggregate()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - assertNull(explainPlanAttributes.getClientSortedBy()); - assertNull(explainPlanAttributes.getServerSortedBy()); + assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(tableName).serverWhereFilter("SERVER FILTER BY K2 = 'ABC'") + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [K2, VAL1, VAL2]") + .clientSortAlgo("CLIENT MERGE SORT").clientSortedBy(null).serverSortedBy(null); ResultSet rs = executeQuery(conn, queryBuilder); assertTrue(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BasePermissionsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BasePermissionsIT.java index 4f8622aacc9..e3fc2ac9ee0 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BasePermissionsIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BasePermissionsIT.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.assertNotEquals; @@ -85,7 +86,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.phoenix.thirdparty.com.google.common.base.Joiner; import org.apache.phoenix.thirdparty.com.google.common.base.Throwables; import org.apache.phoenix.thirdparty.com.google.common.collect.Lists; import org.apache.phoenix.thirdparty.com.google.common.collect.Maps; @@ -754,12 +754,13 @@ public Object run() throws Exception { ResultSet rs = stmt.executeQuery(readTableSQL); assertNotNull(rs); int i = 0; - String explainPlan = Joiner.on(" ") - .join(((PhoenixStatement) stmt).getQueryPlan().getExplainPlan().getPlanSteps()); + String scannedTable = ((PhoenixStatement) stmt).getQueryPlan().getExplainPlan() + .getPlanStepsAsAttributes().getTableName(); rs = stmt.executeQuery(readTableSQL); if (tenantId != null) { rs.next(); - assertFalse(explainPlan.contains("_IDX_")); + assertFalse("expected scanned table <" + scannedTable + "> to not be a view index", + scannedTable != null && scannedTable.contains("_IDX_")); assertEquals(((PhoenixConnection) conn).getTenantId().toString(), tenantId); // For tenant ID "o3", the value in table will be 3 assertEquals(Character.toString(tenantId.charAt(1)), rs.getString(1)); @@ -796,9 +797,7 @@ public Object run() throws Exception { ResultSet rs = stmt.executeQuery(readTableSQL); assertNotNull(rs); int i = 0; - String explainPlan = Joiner.on(" ") - .join(((PhoenixStatement) stmt).getQueryPlan().getExplainPlan().getPlanSteps()); - assertTrue(explainPlan.contains("_IDX_")); + assertPlan(((PhoenixStatement) stmt).getQueryPlan()).tableContains("_IDX_"); rs = stmt.executeQuery(readTableSQL); if (tenantId != null) { rs.next(); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificViewIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificViewIndexIT.java index 539d1316edd..1fab8de99b8 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificViewIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificViewIndexIT.java @@ -17,8 +17,8 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.thirdparty.com.google.common.collect.Sets.newHashSet; -import static org.junit.Assert.assertEquals; import java.sql.Connection; import java.sql.DriverManager; @@ -29,9 +29,6 @@ import java.util.Properties; import java.util.Set; import org.apache.hadoop.hbase.util.Pair; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.schema.types.PVarbinary; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.SchemaUtil; @@ -159,22 +156,10 @@ private void createAndVerifyIndex(Connection conn, String viewName, String table } else { conn.createStatement().execute("CREATE INDEX " + indexName + " ON " + viewName + "(v2)"); } + // sanity check that we can upsert after index is there conn.createStatement() - .execute("UPSERT INTO " + viewName + "(k2,v1,v2) VALUES (-1, 'blah', 'superblah')"); // sanity - // check - // that - // we can - // upsert - // after - // index - // is - // there + .execute("UPSERT INTO " + viewName + "(k2,v1,v2) VALUES (-1, 'blah', 'superblah')"); conn.commit(); - ExplainPlan plan = conn - .prepareStatement( - "SELECT k1, k2, v2 FROM " + viewName + " WHERE v2='" + valuePrefix + "v2-1'") - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); final String iteratorTypeAndScanSize; final String clientSortAlgo; final String expectedTableName; @@ -209,42 +194,27 @@ private void createAndVerifyIndex(Connection conn, String viewName, String table } expectedTableName = "_IDX_" + tableName; } - assertEquals(iteratorTypeAndScanSize, explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(clientSortAlgo, explainPlanAttributes.getClientSortAlgo()); - assertEquals(expectedTableName, explainPlanAttributes.getTableName()); - assertEquals(keyRanges, explainPlanAttributes.getKeyRanges()); + assertPlan(conn, "SELECT k1, k2, v2 FROM " + viewName + " WHERE v2='" + valuePrefix + "v2-1'") + .iteratorType(iteratorTypeAndScanSize).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .scanType("RANGE SCAN").clientSortAlgo(clientSortAlgo).table(expectedTableName) + .keyRanges(keyRanges); } private void createAndVerifyIndexNonStringTenantId(Connection conn, String viewName, String tableName, String tenantId, String valuePrefix) throws SQLException { String indexName = generateUniqueName(); conn.createStatement().execute("CREATE LOCAL INDEX " + indexName + " ON " + viewName + "(v2)"); + // sanity check that we can upsert after index is there conn.createStatement() - .execute("UPSERT INTO " + viewName + "(k2,v1,v2) VALUES (-1, 'blah', 'superblah')"); // sanity - // check - // that - // we can - // upsert - // after - // index - // is - // there + .execute("UPSERT INTO " + viewName + "(k2,v1,v2) VALUES (-1, 'blah', 'superblah')"); conn.commit(); - ExplainPlan plan = conn - .prepareStatement( - "SELECT k1, k2, v2 FROM " + viewName + " WHERE v2='" + valuePrefix + "v2-1'") - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(SchemaUtil.getTableName(SchemaUtil.getSchemaNameFromFullName(viewName), indexName) - + "(" + tableName + ")", explainPlanAttributes.getTableName()); - assertEquals(" [1," + tenantId + ",'" + valuePrefix + "v2-1']", - explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + assertPlan(conn, "SELECT k1, k2, v2 FROM " + viewName + " WHERE v2='" + valuePrefix + "v2-1'") + .iteratorType("PARALLEL 1-WAY").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .scanType("RANGE SCAN") + .table(SchemaUtil.getTableName(SchemaUtil.getSchemaNameFromFullName(viewName), indexName) + + "(" + tableName + ")") + .keyRanges(" [1," + tenantId + ",'" + valuePrefix + "v2-1']") + .clientSortAlgo("CLIENT MERGE SORT"); } private Connection createTenantConnection(String tenantId) throws SQLException { @@ -253,7 +223,6 @@ private Connection createTenantConnection(String tenantId) throws SQLException { return DriverManager.getConnection(getUrl(), props); } - @SuppressWarnings("unchecked") private void verifyViewData(Connection conn, String viewName, String valuePrefix) throws SQLException { String query = "SELECT k1, k2, v2 FROM " + viewName + " WHERE v2='" + valuePrefix + "v2-1'"; diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java index 2ca630f1132..3b3f6f81d42 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.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.analyzeTable; import static org.apache.phoenix.util.TestUtil.getAllSplits; import static org.junit.Assert.assertEquals; @@ -38,10 +39,7 @@ import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Pair; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.jdbc.PhoenixConnection; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.jdbc.PhoenixStatement; import org.apache.phoenix.query.KeyRange; import org.apache.phoenix.util.MetaDataUtil; @@ -200,9 +198,6 @@ protected Pair testUpdatableViewIndex(Integer saltBuckets, boolean assertEquals("bar", rs.getString(4)); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); String iteratorTypeAndScanSize; String clientSortAlgo; String expectedTableName; @@ -226,12 +221,9 @@ protected Pair testUpdatableViewIndex(Integer saltBuckets, boolean clientSortAlgo = saltBuckets == null ? null : "CLIENT MERGE SORT"; serverFilterBy = null; } - assertEquals(iteratorTypeAndScanSize, explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(expectedTableName, explainPlanAttributes.getTableName()); - assertEquals(clientSortAlgo, explainPlanAttributes.getClientSortAlgo()); - assertEquals(keyRanges, explainPlanAttributes.getKeyRanges()); - assertEquals(serverFilterBy, explainPlanAttributes.getServerWhereFilter()); + assertPlan(conn, query).iteratorType(iteratorTypeAndScanSize).scanType("RANGE SCAN") + .table(expectedTableName).clientSortAlgo(clientSortAlgo).keyRanges(keyRanges) + .serverWhereFilter(serverFilterBy); String viewIndexName2 = "I_" + generateUniqueName(); if (localIndex) { @@ -260,10 +252,6 @@ protected Pair testUpdatableViewIndex(Integer saltBuckets, boolean assertEquals("foo", rs.getString(3)); assertFalse(rs.next()); - plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - String physicalTableName; if (localIndex) { physicalTableName = fullTableName; @@ -280,12 +268,9 @@ protected Pair testUpdatableViewIndex(Integer saltBuckets, boolean + (Short.MIN_VALUE + 1) + ",'foo']"; clientSortAlgo = saltBuckets == null ? null : "CLIENT MERGE SORT"; } - assertEquals(physicalTableName, explainPlanAttributes.getTableName()); - assertEquals(iteratorTypeAndScanSize, explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(keyRanges, explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals(clientSortAlgo, explainPlanAttributes.getClientSortAlgo()); + assertPlan(conn, query).table(physicalTableName).iteratorType(iteratorTypeAndScanSize) + .scanType("RANGE SCAN").keyRanges(keyRanges) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo(clientSortAlgo); conn.close(); return new Pair<>(physicalTableName, scan); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java index 315932bdd09..7c2b93f4d37 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.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; @@ -43,7 +44,6 @@ import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Pair; import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.exception.SQLExceptionCode; import org.apache.phoenix.hbase.index.metrics.GlobalIndexCheckerSource; import org.apache.phoenix.hbase.index.metrics.GlobalIndexCheckerSourceImpl; @@ -1188,21 +1188,15 @@ private static void assertReturnedOldRowResult(PreparedStatement stmt, String js private static void validateExplainPlan(PreparedStatement ps, String tableName, String scanType) throws SQLException { - ExplainPlan plan = ps.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - validatePlan(tableName, scanType, plan); + assertPlan(ps.unwrap(PhoenixPreparedStatement.class)).table(tableName) + .iteratorType("PARALLEL 1-WAY").scanType(scanType); } private static void validateExplainPlan(Statement stmt, String query, String tableName, String scanType) throws SQLException { ExplainPlan plan = stmt.unwrap(PhoenixStatement.class).optimizeQuery(query).getExplainPlan(); - validatePlan(tableName, scanType, plan); - } - - private static void validatePlan(String tableName, String scanType, ExplainPlan plan) { - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals(scanType, explainPlanAttributes.getExplainScanType()); + assertPlan(plan.getPlanStepsAsAttributes()).table(tableName).iteratorType("PARALLEL 1-WAY") + .scanType(scanType); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson5IT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson5IT.java index eea0ca8efe7..0b7d4be1404 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson5IT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson5IT.java @@ -20,6 +20,7 @@ import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_RETRY_PAUSE_MS; import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS; import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_MUTATION_SERIALIZE; +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,8 +44,6 @@ import java.util.Properties; import org.apache.commons.io.FileUtils; import org.apache.hadoop.hbase.util.Bytes; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants; import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.QueryConstants; @@ -600,6 +599,7 @@ public void testBsonOpsWithSqlConditionsUpdateFailure() throws Exception { } } + @SuppressWarnings("unchecked") private static void testCDCAfterFirstUpsert(Connection conn, String cdcName, Timestamp ts1, Timestamp ts2, BsonDocument bsonDocument1, BsonDocument bsonDocument2, BsonDocument bsonDocument3) throws SQLException, JsonProcessingException { @@ -650,6 +650,7 @@ private static void testCDCAfterFirstUpsert(Connection conn, String cdcName, Tim } } + @SuppressWarnings("unchecked") private static void testCDCPostUpdate(Connection conn, String cdcName, Timestamp ts1, Timestamp ts2, BsonDocument bsonDocument1, BsonDocument bsonDocument2, BsonDocument bsonDocument3) throws SQLException, IOException { @@ -716,6 +717,7 @@ private static void testCDCPostUpdate(Connection conn, String cdcName, Timestamp } } + @SuppressWarnings("unchecked") private static void testCDCUpdateOneRowChange(Connection conn, String cdcName, Timestamp ts1, Timestamp ts2, BsonDocument bsonDocument1) throws SQLException, IOException { try ( @@ -747,11 +749,8 @@ private static void testCDCUpdateOneRowChange(Connection conn, String cdcName, T private static void validateExplainPlan(PreparedStatement ps, String tableName, String scanType) throws SQLException { - ExplainPlan plan = ps.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals(scanType, explainPlanAttributes.getExplainScanType()); + assertPlan(ps.unwrap(PhoenixPreparedStatement.class)).table(tableName) + .iteratorType("PARALLEL 1-WAY").scanType(scanType); } } 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 385e4401ec2..6bf2f39f741 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/CastAndCoerceIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CastAndCoerceIT.java index 6f80b4b6fb9..1d1203c17b6 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CastAndCoerceIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CastAndCoerceIT.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.ROW1; import static org.apache.phoenix.util.TestUtil.ROW7; import static org.apache.phoenix.util.TestUtil.ROW9; @@ -32,8 +33,6 @@ import java.sql.ResultSet; import java.util.Collection; import java.util.Properties; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.util.PropertiesUtil; import org.junit.Test; @@ -164,12 +163,8 @@ public void testCoerceTinyIntToSmallInt() throws Exception { assertEquals(ROW9, rs.getString(1)); assertFalse(rs.next()); - ExplainPlan plan = - statement.unwrap(PhoenixPreparedStatement.class).optimizeQuery(query).getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + assertPlan(statement.unwrap(PhoenixPreparedStatement.class)).table(tableName) + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); } } @@ -187,12 +182,8 @@ public void testCoerceWithRangeScan() throws Exception { assertEquals(ROW9, rs.getString(1)); assertFalse(rs.next()); - ExplainPlan plan = - statement.unwrap(PhoenixPreparedStatement.class).optimizeQuery(query).getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + assertPlan(statement.unwrap(PhoenixPreparedStatement.class)).table(tableName) + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); } } 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/CostBasedDecisionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CostBasedDecisionIT.java index 2acfd94ca7d..e2bd9a66ede 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CostBasedDecisionIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CostBasedDecisionIT.java @@ -17,14 +17,14 @@ */ 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.assertTrue; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.util.Map; import java.util.Properties; import org.apache.phoenix.compile.ExplainPlan; @@ -33,7 +33,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.junit.BeforeClass; import org.junit.Test; @@ -79,7 +78,7 @@ public void testCostOverridesStaticPlanOrdering1() throws Exception { String query = "SELECT rowkey, c1, c2 FROM " + tableName + " where c1 LIKE 'X0%' ORDER BY rowkey"; // Use the data table plan that opts out order-by when stats are not available. - verifyQueryPlan(query, "FULL SCAN"); + assertPlan(conn, query).scanType("FULL SCAN"); PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2) VALUES (?, ?, ?)"); @@ -94,7 +93,7 @@ public void testCostOverridesStaticPlanOrdering1() throws Exception { conn.createStatement().execute("UPDATE STATISTICS " + tableName); // Use the index table plan that has a lower cost when stats become available. - verifyQueryPlan(query, "RANGE SCAN"); + assertPlan(conn, query).scanType("RANGE SCAN"); } finally { conn.close(); } @@ -237,11 +236,12 @@ public void testCostOverridesStaticPlanOrderingInUpsertQuery() throws Exception String query = "UPSERT INTO " + tableName + " SELECT * FROM " + tableName + " where c1 BETWEEN 10 AND 20 AND c2 < 9000 AND C3 < 5000"; // Use the idx2 plan with a wider PK slot span when stats are not available. - verifyQueryPlan(query, - "UPSERT SELECT\n" + "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName2 + "(" + tableName - + ")" + " [2,*] - [2,9,000]\n" - + " SERVER FILTER BY ((\"C1\" >= 10 AND \"C1\" <= 20) AND TO_INTEGER(\"C3\") < 5000)\n" - + "CLIENT MERGE SORT"); + assertMutationPlan(conn, query).abstractExplainPlan("UPSERT SELECT").iteratorType("PARALLEL") + .scanType("RANGE SCAN").table(indexName2 + "(" + tableName + ")") + .keyRanges(" [2,*] - [2,9,000]") + .serverWhereFilter( + "SERVER FILTER BY ((\"C1\" >= 10 AND \"C1\" <= 20) AND TO_INTEGER(\"C3\") < 5000)") + .clientSortAlgo("CLIENT MERGE SORT"); PreparedStatement stmt = conn .prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2, c3) VALUES (?, ?, ?, ?)"); @@ -256,10 +256,11 @@ public void testCostOverridesStaticPlanOrderingInUpsertQuery() throws Exception conn.createStatement().execute("UPDATE STATISTICS " + tableName); // Use the idx2 plan that scans less data when stats become available. - verifyQueryPlan(query, - "UPSERT SELECT\n" + "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName1 + "(" + tableName - + ")" + " [1,10] - [1,20]\n" + " SERVER FILTER BY (\"C2\" < 9000 AND \"C3\" < 5000)\n" - + "CLIENT MERGE SORT"); + assertMutationPlan(conn, query).abstractExplainPlan("UPSERT SELECT").iteratorType("PARALLEL") + .scanType("RANGE SCAN").table(indexName1 + "(" + tableName + ")") + .keyRanges(" [1,10] - [1,20]") + .serverWhereFilter("SERVER FILTER BY (\"C2\" < 9000 AND \"C3\" < 5000)") + .clientSortAlgo("CLIENT MERGE SORT"); } finally { conn.close(); } @@ -284,11 +285,12 @@ public void testCostOverridesStaticPlanOrderingInDeleteQuery() throws Exception String query = "DELETE FROM " + tableName + " where c1 BETWEEN 10 AND 20 AND c2 < 9000 AND C3 < 5000"; // Use the idx2 plan with a wider PK slot span when stats are not available. - verifyQueryPlan(query, - "DELETE ROWS CLIENT SELECT\n" + "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName2 + "(" - + tableName + ")" + " [2,*] - [2,9,000]\n" - + " SERVER FILTER BY ((\"C1\" >= 10 AND \"C1\" <= 20) AND TO_INTEGER(\"C3\") < 5000)\n" - + "CLIENT MERGE SORT"); + assertMutationPlan(conn, query).abstractExplainPlan("DELETE ROWS CLIENT SELECT") + .iteratorType("PARALLEL").scanType("RANGE SCAN").table(indexName2 + "(" + tableName + ")") + .keyRanges(" [2,*] - [2,9,000]") + .serverWhereFilter( + "SERVER FILTER BY ((\"C1\" >= 10 AND \"C1\" <= 20) AND TO_INTEGER(\"C3\") < 5000)") + .clientSortAlgo("CLIENT MERGE SORT"); PreparedStatement stmt = conn .prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2, c3) VALUES (?, ?, ?, ?)"); @@ -303,10 +305,11 @@ public void testCostOverridesStaticPlanOrderingInDeleteQuery() throws Exception conn.createStatement().execute("UPDATE STATISTICS " + tableName); // Use the idx2 plan that scans less data when stats become available. - verifyQueryPlan(query, - "DELETE ROWS CLIENT SELECT\n" + "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName1 + "(" - + tableName + ")" + " [1,10] - [1,20]\n" - + " SERVER FILTER BY (\"C2\" < 9000 AND \"C3\" < 5000)\n" + "CLIENT MERGE SORT"); + assertMutationPlan(conn, query).abstractExplainPlan("DELETE ROWS CLIENT SELECT") + .iteratorType("PARALLEL").scanType("RANGE SCAN").table(indexName1 + "(" + tableName + ")") + .keyRanges(" [1,10] - [1,20]") + .serverWhereFilter("SERVER FILTER BY (\"C2\" < 9000 AND \"C3\" < 5000)") + .clientSortAlgo("CLIENT MERGE SORT"); } finally { conn.close(); } @@ -329,12 +332,13 @@ public void testCostOverridesStaticPlanOrderingInUnionQuery() throws Exception { + " where rowkey <= 'z' GROUP BY c1 " + "UNION ALL SELECT c1, max(rowkey), max(c2) FROM " + tableName + " where rowkey >= 'a' GROUP BY c1"; // Use the default plan when stats are not available. - verifyQueryPlan(query, - "UNION ALL OVER 2 QUERIES\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + tableName - + " [*] - ['z']\n" + " SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]\n" - + " CLIENT MERGE SORT\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + tableName - + " ['a'] - [*]\n" + " SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]\n" - + " CLIENT MERGE SORT"); + assertPlan(conn, query).abstractExplainPlan("UNION ALL OVER 2 QUERIES") + .iteratorType("PARALLEL").scanType("RANGE SCAN").table(tableName).keyRanges(" [*] - ['z']") + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]") + .clientSortAlgo("CLIENT MERGE SORT").rhs().iteratorType("PARALLEL").scanType("RANGE SCAN") + .table(tableName).keyRanges(" ['a'] - [*]") + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]") + .clientSortAlgo("CLIENT MERGE SORT"); PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2) VALUES (?, ?, ?)"); @@ -349,16 +353,16 @@ public void testCostOverridesStaticPlanOrderingInUnionQuery() throws Exception { conn.createStatement().execute("UPDATE STATISTICS " + tableName); // Use the optimal plan based on cost when stats become available. - verifyQueryPlan(query, - "UNION ALL OVER 2 QUERIES\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName - + "(" + tableName + ") [1]\n" + " SERVER MERGE [0.C2]\n" - + " SERVER FILTER BY FIRST KEY ONLY AND \"ROWKEY\" <= 'z'\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]\n" - + " CLIENT MERGE SORT\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName - + "(" + tableName + ") [1]\n" + " SERVER MERGE [0.C2]\n" - + " SERVER FILTER BY FIRST KEY ONLY AND \"ROWKEY\" >= 'a'\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]\n" - + " CLIENT MERGE SORT"); + assertPlan(conn, query).abstractExplainPlan("UNION ALL OVER 2 QUERIES") + .iteratorType("PARALLEL").scanType("RANGE SCAN").table(indexName + "(" + tableName + ")") + .keyRanges(" [1]").serverMergeColumns("[0.C2]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND \"ROWKEY\" <= 'z'") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]") + .clientSortAlgo("CLIENT MERGE SORT").rhs().iteratorType("PARALLEL").scanType("RANGE SCAN") + .table(indexName + "(" + tableName + ")").keyRanges(" [1]").serverMergeColumns("[0.C2]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND \"ROWKEY\" >= 'a'") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]") + .clientSortAlgo("CLIENT MERGE SORT"); } finally { conn.close(); } @@ -382,12 +386,13 @@ public void testCostOverridesStaticPlanOrderingInJoinQuery() throws Exception { + " where rowkey <= 'z' GROUP BY c1) t2 " + "ON t1.rowkey = t2.mrk WHERE t1.c1 LIKE 'X0%' ORDER BY t1.rowkey"; // Use the default plan when stats are not available. - verifyQueryPlan(query, - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + tableName + "\n" - + " SERVER FILTER BY C1 LIKE 'X0%'\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + tableName + " [*] - ['z']\n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]\n" - + " CLIENT MERGE SORT\n" + " DYNAMIC SERVER FILTER BY T1.ROWKEY IN (T2.MRK)"); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(tableName) + .serverWhereFilter("SERVER FILTER BY C1 LIKE 'X0%'") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY T1.ROWKEY IN (T2.MRK)").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(tableName) + .keyRanges(" [*] - ['z']").serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]") + .clientSortAlgo("CLIENT MERGE SORT"); PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2) VALUES (?, ?, ?)"); @@ -402,17 +407,17 @@ public void testCostOverridesStaticPlanOrderingInJoinQuery() throws Exception { conn.createStatement().execute("UPDATE STATISTICS " + tableName); // Use the optimal plan based on cost when stats become available. - verifyQueryPlan(query, - "CLIENT PARALLEL 626-WAY RANGE SCAN OVER " + indexName + "(" + tableName - + ") [1,'X0'] - [1,'X1']\n" + " SERVER MERGE [0.C2]\n" - + " SERVER FILTER BY FIRST KEY ONLY\n" + " SERVER SORTED BY [\"T1.:ROWKEY\"]\n" - + "CLIENT MERGE SORT\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexName + "(" + tableName - + ") [1]\n" + " SERVER MERGE [0.C2]\n" - + " SERVER FILTER BY FIRST KEY ONLY AND \"ROWKEY\" <= 'z'\n" - + " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]\n" - + " CLIENT MERGE SORT\n" - + " DYNAMIC SERVER FILTER BY \"T1.:ROWKEY\" IN (T2.MRK)"); + assertPlan(conn, query).iteratorType("PARALLEL 626-WAY").scanType("RANGE SCAN") + .table(indexName + "(" + tableName + ")").keyRanges(" [1,'X0'] - [1,'X1']") + .serverMergeColumns("[0.C2]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverSortedBy("[\"T1.:ROWKEY\"]").clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"T1.:ROWKEY\" IN (T2.MRK)").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(indexName + "(" + tableName + ")").keyRanges(" [1]").serverMergeColumns("[0.C2]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND \"ROWKEY\" <= 'z'") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]") + .clientSortAlgo("CLIENT MERGE SORT"); } finally { conn.close(); } @@ -479,17 +484,9 @@ public void testJoinStrategy() throws Exception { + "ON t1.ID = t2.ID"; Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); try (Connection conn = DriverManager.getConnection(getUrl(), props)) { - ExplainPlan plan = conn.prepareStatement(q).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("SORT-MERGE-JOIN (INNER)", explainPlanAttributes.getAbstractExplainPlan()); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(testTable500, explainPlanAttributes.getTableName()); - ExplainPlanAttributes rhsTable = explainPlanAttributes.getRhsJoinQueryExplainPlan(); - assertEquals("PARALLEL 1-WAY", rhsTable.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", rhsTable.getExplainScanType()); - assertEquals(testTable1000, rhsTable.getTableName()); + assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").lhs() + .iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable500).end().rhs() + .iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable1000); } } @@ -502,20 +499,11 @@ public void testJoinStrategy2() throws Exception { + "ON t1.ID = t2.ID\n" + "WHERE t1.COL1 < 200"; Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); try (Connection conn = DriverManager.getConnection(getUrl(), props)) { - ExplainPlan plan = conn.prepareStatement(q).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("SORT-MERGE-JOIN (INNER)", explainPlanAttributes.getAbstractExplainPlan()); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals("SERVER FILTER BY COL1 < 200", explainPlanAttributes.getServerWhereFilter()); - assertEquals(testTable500, explainPlanAttributes.getTableName()); - assertEquals("CLIENT AGGREGATE INTO SINGLE ROW", explainPlanAttributes.getClientAggregate()); - ExplainPlanAttributes rhsTable = explainPlanAttributes.getRhsJoinQueryExplainPlan(); - assertEquals("PARALLEL 1-WAY", rhsTable.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", rhsTable.getExplainScanType()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", rhsTable.getServerWhereFilter()); - assertEquals(testTable1000, rhsTable.getTableName()); + assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (INNER)") + .clientAggregate("CLIENT AGGREGATE INTO SINGLE ROW").lhs().iteratorType("PARALLEL 1-WAY") + .scanType("FULL SCAN").table(testTable500).serverWhereFilter("SERVER FILTER BY COL1 < 200") + .end().rhs().iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable1000) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); } } @@ -524,10 +512,14 @@ public void testJoinStrategy2() throws Exception { public void testJoinStrategy3() throws Exception { String q = "SELECT *\n" + "FROM " + testTable500 + " t1 JOIN " + testTable1000 + " t2\n" + "ON t1.COL1 = t2.ID\n" + "WHERE t1.ID > 200"; - String expected = "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + testTable1000 + "\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + testTable500 + " [201] - [*]\n" + " DYNAMIC SERVER FILTER BY T2.ID IN (T1.COL1)"; - verifyQueryPlan(q, expected); + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + assertPlan(conn, q).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable1000) + .dynamicServerFilter("DYNAMIC SERVER FILTER BY T2.ID IN (T1.COL1)").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(testTable500) + .keyRanges(" [201] - [*]"); + } } /** @@ -538,10 +530,13 @@ public void testJoinStrategy3() throws Exception { public void testJoinStrategy4() throws Exception { String q = "SELECT *\n" + "FROM " + testTable990 + " t1 JOIN " + testTable1000 + " t2\n" + "ON t1.ID = t2.COL1"; - String expected = "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + testTable990 + "\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + testTable1000 + "\n" + " DYNAMIC SERVER FILTER BY T1.ID IN (T2.COL1)"; - verifyQueryPlan(q, expected); + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + assertPlan(conn, q).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable990) + .dynamicServerFilter("DYNAMIC SERVER FILTER BY T1.ID IN (T2.COL1)").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable1000); + } } /** Hash-join wins over sort-merge-join w/ smaller side ordered. */ @@ -549,10 +544,13 @@ public void testJoinStrategy4() throws Exception { public void testJoinStrategy5() throws Exception { String q = "SELECT *\n" + "FROM " + testTable500 + " t1 JOIN " + testTable1000 + " t2\n" + "ON t1.ID = t2.COL1\n" + "WHERE t1.ID > 200"; - String expected = "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + testTable1000 + "\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + testTable500 + " [201] - [*]"; - verifyQueryPlan(q, expected); + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + assertPlan(conn, q).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable1000) + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(testTable500) + .keyRanges(" [201] - [*]"); + } } /** Hash-join wins over sort-merge-join w/o any side ordered. */ @@ -560,10 +558,13 @@ public void testJoinStrategy5() throws Exception { public void testJoinStrategy6() throws Exception { String q = "SELECT *\n" + "FROM " + testTable500 + " t1 JOIN " + testTable1000 + " t2\n" + "ON t1.COL1 = t2.COL1\n" + "WHERE t1.ID > 200"; - String expected = "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + testTable1000 + "\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + testTable500 + " [201] - [*]"; - verifyQueryPlan(q, expected); + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + assertPlan(conn, q).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable1000) + .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(testTable500) + .keyRanges(" [201] - [*]"); + } } /** @@ -575,11 +576,14 @@ public void testJoinStrategy6() throws Exception { public void testJoinStrategy7() throws Exception { String q = "SELECT *\n" + "FROM " + testTable500 + " t1 JOIN " + testTable1000 + " t2\n" + "ON t1.ID = t2.ID\n" + "ORDER BY t1.COL1"; - String expected = "CLIENT PARALLEL 1001-WAY FULL SCAN OVER " + testTable1000 + "\n" - + " SERVER SORTED BY [T1.COL1]\n" + "CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + testTable500 + "\n" + " DYNAMIC SERVER FILTER BY T2.ID IN (T1.ID)"; - verifyQueryPlan(q, expected); + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + assertPlan(conn, q).iteratorType("PARALLEL 1001-WAY").scanType("FULL SCAN") + .table(testTable1000).serverSortedBy("[T1.COL1]").clientSortAlgo("CLIENT MERGE SORT") + .dynamicServerFilter("DYNAMIC SERVER FILTER BY T2.ID IN (T1.ID)").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").iteratorType("PARALLEL 1-WAY") + .scanType("FULL SCAN").table(testTable500); + } } /** @@ -593,19 +597,10 @@ public void testJoinStrategy8() throws Exception { + "ON t1.ID = t2.ID\n" + "ORDER BY t1.COL1 LIMIT 5"; Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); try (Connection conn = DriverManager.getConnection(getUrl(), props)) { - ExplainPlan plan = conn.prepareStatement(q).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("SORT-MERGE-JOIN (INNER)", explainPlanAttributes.getAbstractExplainPlan()); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(testTable500, explainPlanAttributes.getTableName()); - assertEquals("[T1.COL1]", explainPlanAttributes.getClientSortedBy()); - assertEquals(new Integer(5), explainPlanAttributes.getClientRowLimit()); - ExplainPlanAttributes rhsTable = explainPlanAttributes.getRhsJoinQueryExplainPlan(); - assertEquals("PARALLEL 1-WAY", rhsTable.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", rhsTable.getExplainScanType()); - assertEquals(testTable1000, rhsTable.getTableName()); + assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").clientSortedBy("[T1.COL1]") + .clientRowLimit(5).lhs().iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(testTable500).end().rhs().iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(testTable1000); } } @@ -617,11 +612,15 @@ public void testJoinStrategy9() throws Exception { String q = "SELECT *\n" + "FROM " + testTable1000 + " t1 LEFT JOIN " + testTable500 + " t2\n" + "ON t1.ID = t2.ID AND t2.ID > 200\n" + "LEFT JOIN " + testTable990 + " t3\n" + "ON t1.ID = t3.ID AND t3.ID < 100"; - String expected = "SORT-MERGE-JOIN (LEFT) TABLES\n" + " SORT-MERGE-JOIN (LEFT) TABLES\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + testTable1000 + "\n" + " AND\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + testTable500 + " [201] - [*]\n" + "AND\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + testTable990 + " [*] - [100]"; - verifyQueryPlan(q, expected); + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)").lhs() + .abstractExplainPlan("SORT-MERGE-JOIN (LEFT)").lhs().iteratorType("PARALLEL 1-WAY") + .scanType("FULL SCAN").table(testTable1000).end().rhs().iteratorType("PARALLEL 1-WAY") + .scanType("RANGE SCAN").table(testTable500).keyRanges(" [201] - [*]").end().end().rhs() + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(testTable990) + .keyRanges(" [*] - [100]"); + } } /** @@ -632,13 +631,16 @@ public void testJoinStrategy10() throws Exception { String q = "SELECT *\n" + "FROM " + testTable1000 + " t1 JOIN " + testTable500 + " t2\n" + "ON t1.ID = t2.COL1 AND t2.ID > 200\n" + "JOIN " + testTable990 + " t3\n" + "ON t1.ID = t3.ID AND t3.ID < 100"; - String expected = - "SORT-MERGE-JOIN (INNER) TABLES\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " - + testTable1000 + "\n" + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + testTable500 + " [201] - [*]\n" - + " DYNAMIC SERVER FILTER BY T1.ID IN (T2.COL1)\n" + "AND\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + testTable990 + " [*] - [100]"; - verifyQueryPlan(q, expected); + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").lhs() + .iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable1000) + .dynamicServerFilter("DYNAMIC SERVER FILTER BY T1.ID IN (T2.COL1)").subPlanCount(1) + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(testTable500) + .keyRanges(" [201] - [*]").end().end().rhs().iteratorType("PARALLEL 1-WAY") + .scanType("RANGE SCAN").table(testTable990).keyRanges(" [*] - [100]"); + } } /** @@ -650,11 +652,15 @@ public void testJoinStrategy11() throws Exception { String q = "SELECT *\n" + "FROM " + testTable1000 + " t1 JOIN " + testTable500 + " t2\n" + "ON t1.COL2 = t2.COL1 AND t2.ID > 200\n" + "JOIN " + testTable990 + " t3\n" + "ON t1.COL1 = t3.COL2 AND t3.ID < 100"; - String expected = "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + testTable1000 + "\n" - + " PARALLEL INNER-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " - + testTable500 + " [201] - [*]\n" + " PARALLEL INNER-JOIN TABLE 1\n" - + " CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + testTable990 + " [*] - [100]"; - verifyQueryPlan(q, expected); + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + assertPlan(conn, q).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable1000) + .subPlanCount(2).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(testTable500) + .keyRanges(" [201] - [*]").end().subPlan(1) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1").iteratorType("PARALLEL 1-WAY") + .scanType("RANGE SCAN").table(testTable990).keyRanges(" [*] - [100]"); + } } /** @@ -665,22 +671,16 @@ public void testJoinStrategy11() throws Exception { public void testJoinStrategy12() throws Exception { String q = "SELECT *\n" + "FROM " + testTable1000 + " t1 JOIN " + testTable990 + " t2\n" + "ON t1.COL2 = t2.COL1\n" + "JOIN " + testTable990 + " t3\n" + "ON t1.COL1 = t3.COL2"; - String expected = - "SORT-MERGE-JOIN (INNER) TABLES\n" + " CLIENT PARALLEL 1001-WAY FULL SCAN OVER " - + testTable1000 + "\n" + " SERVER SORTED BY [T1.COL1]\n" + " CLIENT MERGE SORT\n" - + " PARALLEL INNER-JOIN TABLE 0\n" - + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + testTable990 + "\n" + "AND\n" - + " CLIENT PARALLEL 991-WAY FULL SCAN OVER " + testTable990 + "\n" - + " SERVER SORTED BY [T3.COL2]\n" + " CLIENT MERGE SORT"; - verifyQueryPlan(q, expected); - } - - private static void verifyQueryPlan(String query, String expected) throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); - Connection conn = DriverManager.getConnection(getUrl(), props); - ResultSet rs = conn.createStatement().executeQuery("explain " + query); - String plan = QueryUtil.getExplainPlan(rs); - assertTrue("Expected '" + expected + "' in the plan:\n" + plan + ".", plan.contains(expected)); + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").lhs() + .iteratorType("PARALLEL 1001-WAY").scanType("FULL SCAN").table(testTable1000) + .serverSortedBy("[T1.COL1]").clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").iteratorType("PARALLEL 1-WAY") + .scanType("FULL SCAN").table(testTable990).end().end().rhs() + .iteratorType("PARALLEL 991-WAY").scanType("FULL SCAN").table(testTable990) + .serverSortedBy("[T3.COL2]").clientSortAlgo("CLIENT MERGE SORT"); + } } private static String initTestTableValues(int rows) throws Exception { diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CountDistinctApproximateHyperLogLogIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CountDistinctApproximateHyperLogLogIT.java index 193e0bf0fcc..0d30793c23b 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CountDistinctApproximateHyperLogLogIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CountDistinctApproximateHyperLogLogIT.java @@ -17,14 +17,12 @@ */ 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.*; import java.sql.*; import java.util.Properties; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.schema.ColumnNotFoundException; import org.apache.phoenix.util.PropertiesUtil; import org.junit.Before; @@ -124,14 +122,9 @@ public void testDistinctCountPlanExplain() throws Exception { String query = "SELECT APPROX_COUNT_DISTINCT(i1||i2) FROM " + tableName; try (Connection conn = DriverManager.getConnection(getUrl(), props)) { prepareTableWithValues(conn, 100); - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("SERVER AGGREGATE INTO SINGLE ROW", explainPlanAttributes.getServerAggregate()); + assertPlan(conn, query).table(tableName).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW"); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CreateTableIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CreateTableIT.java index f4f57bafc8a..c196118b78f 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CreateTableIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CreateTableIT.java @@ -19,6 +19,7 @@ import static org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants.PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY; import static org.apache.phoenix.mapreduce.index.IndexUpgradeTool.ROLLBACK_OP; +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; @@ -83,7 +84,6 @@ import org.apache.phoenix.util.EnvironmentEdgeManager; import org.apache.phoenix.util.IndexUtil; 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; @@ -1072,9 +1072,7 @@ public void testCreateTableWithNamespaceMappingEnabled() throws Exception { conn.createStatement() .execute("CREATE TABLE " + table + " (PK VARCHAR PRIMARY KEY, " + CF + ".COL VARCHAR)"); - assertTrue(QueryUtil - .getExplainPlan(conn.createStatement().executeQuery("explain select * from " + table)) - .contains(NS + ":" + TBL)); + assertPlan(conn, "select * from " + table).tableContains(NS + ":" + TBL); conn.createStatement().execute("DROP TABLE " + table); } @@ -1085,9 +1083,7 @@ public void testCreateTableWithNamespaceMappingEnabled() throws Exception { conn.createStatement() .execute("CREATE TABLE " + table + " (PK VARCHAR PRIMARY KEY, " + CF + ".COL VARCHAR)"); - assertTrue(QueryUtil - .getExplainPlan(conn.createStatement().executeQuery("explain select * from " + table)) - .contains(NS + "." + TBL)); + assertPlan(conn, "select * from " + table).tableContains(NS + "." + TBL); conn.createStatement().execute("DROP TABLE " + table); } @@ -1098,9 +1094,7 @@ public void testCreateTableWithNamespaceMappingEnabled() throws Exception { conn.createStatement() .execute("CREATE TABLE " + table + " (PK VARCHAR PRIMARY KEY, " + CF + ".COL VARCHAR)"); - assertTrue(QueryUtil - .getExplainPlan(conn.createStatement().executeQuery("explain select * from " + table)) - .contains(NS + ":" + NS + "." + TBL)); + assertPlan(conn, "select * from " + table).tableContains(NS + ":" + NS + "." + TBL); conn.createStatement().execute("DROP TABLE " + table); } 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..4a652ab3fff 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,27 @@ 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); - // 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 + 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. 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 if (expectedToBeUsed) { + assertPlan(attributes).table(indexName); } else { - assertEquals(expectedToBeUsed, explainPlan.contains(" SCAN OVER " + indexName)); + assertNotEquals(indexName, attributes.getTableName()); } } @@ -499,16 +509,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 +939,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/DerivedTableIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DerivedTableIT.java index d6cde17cf02..85ac718ac61 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/DerivedTableIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/DerivedTableIT.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.A_VALUE; import static org.apache.phoenix.util.TestUtil.B_VALUE; import static org.apache.phoenix.util.TestUtil.C_VALUE; @@ -40,11 +41,11 @@ import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; import java.util.Collection; import java.util.List; import java.util.Properties; import org.apache.phoenix.util.PropertiesUtil; -import org.apache.phoenix.util.QueryUtil; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -54,8 +55,6 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.phoenix.thirdparty.com.google.common.collect.Lists; @@ -68,12 +67,10 @@ public class DerivedTableIT extends ParallelStatsDisabledIT { public TestName name = new TestName(); private String[] indexDDL; - private String[] plans; + private PlanSpec[] plans; private String tableName; - private static final Logger LOGGER = LoggerFactory.getLogger(DerivedTableIT.class); - - public DerivedTableIT(String[] indexDDL, String[] plans) { + public DerivedTableIT(String[] indexDDL, PlanSpec[] plans) { this.indexDDL = indexDDL; this.plans = plans; } @@ -92,13 +89,6 @@ public void initTable() throws Exception { conn.createStatement().execute(ddl); } } - String[] newplan = new String[plans.length]; - if (plans != null && plans.length > 0) { - for (int i = 0; i < plans.length; i++) { - newplan[i] = plans[i].replace(dynamicTableName, tableName); - } - plans = newplan; - } } @After @@ -108,31 +98,53 @@ public void cleanUp() throws Exception { assertFalse("refCount leaked", refCountLeaked); } + /** Structured EXPLAIN plan expectations for a derived-table query */ + private static final class PlanSpec { + final String iteratorType; + final String tableSuffix; + final String serverAggregate; + final String[] clientSteps; + + PlanSpec(String iteratorType, String tableSuffix, String serverAggregate, + String... clientSteps) { + this.iteratorType = iteratorType; + this.tableSuffix = tableSuffix; + this.serverAggregate = serverAggregate; + this.clientSteps = clientSteps; + } + } + + private void verifyPlan(Connection conn, String query, PlanSpec spec) throws SQLException { + assertPlan(conn, query).iteratorType(spec.iteratorType).scanType("FULL SCAN") + .table(tableName + spec.tableSuffix).serverAggregate(spec.serverAggregate) + .clientSteps(spec.clientSteps); + } + @Parameters(name = "DerivedTableIT_{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 INDEX " + dynamicTableName + "_DERIVED_IDX ON " + dynamicTableName + testCases.add(new Object[] { + new String[] { "CREATE INDEX " + dynamicTableName + "_DERIVED_IDX ON " + dynamicTableName + " (a_byte) INCLUDE (A_STRING, B_STRING)" }, - { "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + dynamicTableName + "_DERIVED_IDX \n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"A_STRING\", \"B_STRING\"]\n" - + "CLIENT MERGE SORT\n" + "CLIENT SORTED BY [\"B_STRING\"]\n" + "CLIENT SORTED BY [A]\n" - + "CLIENT AGGREGATE INTO DISTINCT ROWS BY [A]\n" + "CLIENT SORTED BY [A DESC]", - - "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + dynamicTableName + "_DERIVED_IDX \n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [\"A_STRING\", \"B_STRING\"]\n" - + "CLIENT MERGE SORT\n" + "CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY [A]\n" - + "CLIENT DISTINCT ON [COLLECTDISTINCT(B)]\n" + "CLIENT SORTED BY [A DESC]" } }); - testCases.add(new String[][] { {}, - { "CLIENT PARALLEL 4-WAY FULL SCAN OVER " + dynamicTableName + " \n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]\n" - + "CLIENT MERGE SORT\n" + "CLIENT SORTED BY [B_STRING]\n" + "CLIENT SORTED BY [A]\n" - + "CLIENT AGGREGATE INTO DISTINCT ROWS BY [A]\n" + "CLIENT SORTED BY [A DESC]", - - "CLIENT PARALLEL 4-WAY FULL SCAN OVER " + dynamicTableName + " \n" - + " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]\n" - + "CLIENT MERGE SORT\n" + "CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY [A]\n" - + "CLIENT DISTINCT ON [COLLECTDISTINCT(B)]\n" + "CLIENT SORTED BY [A DESC]" } }); + new PlanSpec[] { + new PlanSpec("PARALLEL 1-WAY", "_DERIVED_IDX", + "SERVER AGGREGATE INTO DISTINCT ROWS BY [\"A_STRING\", \"B_STRING\"]", + "CLIENT MERGE SORT", "CLIENT SORTED BY [\"B_STRING\"]", "CLIENT SORTED BY [A]", + "CLIENT AGGREGATE INTO DISTINCT ROWS BY [A]", "CLIENT SORTED BY [A DESC]"), + new PlanSpec("PARALLEL 1-WAY", "_DERIVED_IDX", + "SERVER AGGREGATE INTO DISTINCT ROWS BY [\"A_STRING\", \"B_STRING\"]", + "CLIENT MERGE SORT", "CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY [A]", + "CLIENT DISTINCT ON [COLLECTDISTINCT(B)]", "CLIENT SORTED BY [A DESC]") } }); + testCases.add(new Object[] { new String[] {}, + new PlanSpec[] { + new PlanSpec("PARALLEL 4-WAY", "", + "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]", "CLIENT MERGE SORT", + "CLIENT SORTED BY [B_STRING]", "CLIENT SORTED BY [A]", + "CLIENT AGGREGATE INTO DISTINCT ROWS BY [A]", "CLIENT SORTED BY [A DESC]"), + new PlanSpec("PARALLEL 4-WAY", "", + "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]", "CLIENT MERGE SORT", + "CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY [A]", + "CLIENT DISTINCT ON [COLLECTDISTINCT(B)]", "CLIENT SORTED BY [A DESC]") } }); return testCases; } @@ -384,12 +396,7 @@ public void testDerivedTableWithGroupBy() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN WITH REGIONS " + query); - String explainPlanOutput = QueryUtil.getExplainPlan(rs); - LOGGER.info("Explain plan output: {}", explainPlanOutput); - String[] splitExplainPlan = explainPlanOutput.split("\\n \\(region locations = \\[region="); - String[] secondSplitExplainPlan = splitExplainPlan[1].split("]\\)"); - assertEquals(plans[0], splitExplainPlan[0] + secondSplitExplainPlan[1]); + verifyPlan(conn, query, plans[0]); // distinct b (groupby a, b) groupby a orderby a query = "SELECT DISTINCT COLLECTDISTINCT(t.b) FROM (SELECT b_string b, a_string a FROM " @@ -411,12 +418,7 @@ public void testDerivedTableWithGroupBy() throws Exception { assertFalse(rs.next()); - rs = conn.createStatement().executeQuery("EXPLAIN WITH REGIONS " + query); - explainPlanOutput = QueryUtil.getExplainPlan(rs); - LOGGER.info("Explain plan output: {}", explainPlanOutput); - splitExplainPlan = explainPlanOutput.split("\\n \\(region locations = \\[region="); - secondSplitExplainPlan = splitExplainPlan[1].split("]\\)"); - assertEquals(plans[1], splitExplainPlan[0] + secondSplitExplainPlan[1]); + verifyPlan(conn, query, plans[1]); // (orderby) groupby query = "SELECT t.a_string, count(*) FROM (SELECT * FROM " + tableName 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..7606aec6fd8 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; @@ -27,14 +28,10 @@ import java.sql.ResultSet; import java.util.List; import java.util.Properties; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.end2end.ExplainPlanWithStatsEnabledIT.Estimate; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; 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 +253,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 +273,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,61 +340,33 @@ 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(); - assertEquals(2, planAttributes.getNumRegionLocationLookups()); + assertPlan(conn, query).scanType("RANGE SCAN").table(tableName).keyRanges(" [*] - ['b']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW").numRegionLocationLookups(2); 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(); - assertEquals(3, planAttributes.getNumRegionLocationLookups()); + assertPlan(conn, query).scanType("RANGE SCAN").table(tableName).keyRanges(" [*] - ['cd']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW").numRegionLocationLookups(3); 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(); - assertEquals(1, planAttributes.getNumRegionLocationLookups()); + assertPlan(conn, query).scanType("RANGE SCAN").table(tableName).keyRanges(" ['ef'] - ['eg']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW").numRegionLocationLookups(1); 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(); - assertEquals(1, planAttributes.getNumRegionLocationLookups()); + assertPlan(conn, query).scanType("RANGE SCAN").table(tableName).keyRanges(" ['de'] - [*]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW").numRegionLocationLookups(1); } } @@ -494,29 +460,15 @@ 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(); - assertEquals(1, planAttributes.getNumRegionLocationLookups()); + assertPlan(tenantConn, query).scanType("RANGE SCAN").table(tableName).keyRanges(" ['ab12']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW").numRegionLocationLookups(1); } 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(); - assertEquals(1, planAttributes.getNumRegionLocationLookups()); + assertPlan(tenantConn, query).scanType("RANGE SCAN").table(tableName).keyRanges(" ['cd12']") + .serverSortedBy("[COL2]").clientSortAlgo("CLIENT MERGE SORT").numRegionLocationLookups(1); } try (Connection tenantConn = getTenantConnection("de12")) { @@ -528,14 +480,8 @@ 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(); - assertEquals(1, planAttributes.getNumRegionLocationLookups()); + assertPlan(tenantConn, query).scanType("RANGE SCAN").table(tableName).keyRanges(" ['de12']") + .serverWhereFilter("SERVER FILTER BY COL1 = 'col101'").numRegionLocationLookups(1); } } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/FlappingLocalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/FlappingLocalIndexIT.java index d84c3f6bb61..9fa2e49bd50 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/FlappingLocalIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/FlappingLocalIndexIT.java @@ -17,9 +17,9 @@ */ 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.assertNull; import static org.junit.Assert.assertTrue; import java.io.IOException; @@ -42,24 +42,16 @@ import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; import org.apache.hadoop.hbase.coprocessor.RegionObserver; import org.apache.hadoop.hbase.util.Pair; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.end2end.index.BaseLocalIndexIT; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.QueryConstants; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.query.QueryServicesOptions; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class FlappingLocalIndexIT extends BaseLocalIndexIT { - private static final Logger LOGGER = LoggerFactory.getLogger(FlappingLocalIndexIT.class); - public FlappingLocalIndexIT(boolean isNamespaceMapped) { super(isNamespaceMapped); } @@ -173,25 +165,13 @@ public void testLocalIndexScan() throws Exception { String query = "SELECT * FROM " + tableName + " where v1 like 'a%'"; - String explainPlanOutput = QueryUtil - .getExplainPlan(conn1.createStatement().executeQuery("EXPLAIN WITH REGIONS " + query)); - LOGGER.info("Explain plan output: {}", explainPlanOutput); - // MAX_REGION_LOCATIONS_SIZE_EXPLAIN_PLAN is set as 2 - assertTrue("Expected total " + numRegions + " regions", - explainPlanOutput.contains("...total size = " + numRegions)); - - ExplainPlan plan = conn1.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL " + numRegions + "-WAY", - explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(indexTableName + "(" + indexPhysicalTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1,'a'] - [1,'b']", explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - assertEquals(trimmedRegionLocations, explainPlanAttributes.getRegionLocations().size()); + // MAX_REGION_LOCATIONS_SIZE_EXPLAIN_PLAN is set as 2 so getRegionLocations() is trimmed. The + // full number of regions is reported via regionLocationsTotalSize. + assertPlan(conn1, query).iteratorType("PARALLEL " + numRegions + "-WAY") + .scanType("RANGE SCAN").table(indexTableName + "(" + indexPhysicalTableName + ")") + .keyRanges(" [1,'a'] - [1,'b']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .clientSortAlgo("CLIENT MERGE SORT").regionLocationCount(trimmedRegionLocations) + .regionLocationsTotalSize(numRegions); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -209,17 +189,10 @@ public void testLocalIndexScan() throws Exception { assertFalse(rs.next()); query = "SELECT t_id, k1, k2,V1 FROM " + tableName + " where v1='a'"; - plan = conn1.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL " + numRegions + "-WAY", - explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(indexTableName + "(" + indexPhysicalTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1,'a']", explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + assertPlan(conn1, query).iteratorType("PARALLEL " + numRegions + "-WAY") + .scanType("RANGE SCAN").table(indexTableName + "(" + indexPhysicalTableName + ")") + .keyRanges(" [1,'a']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .clientSortAlgo("CLIENT MERGE SORT"); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -233,18 +206,10 @@ public void testLocalIndexScan() throws Exception { assertFalse(rs.next()); query = "SELECT t_id, k1, k2,V1, k3 FROM " + tableName + " where v1<='z' order by k3"; - plan = conn1.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL " + numRegions + "-WAY", - explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(indexTableName + "(" + indexPhysicalTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1,*] - [1,'z']", explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - assertEquals("[\"K3\"]", explainPlanAttributes.getServerSortedBy()); + assertPlan(conn1, query).iteratorType("PARALLEL " + numRegions + "-WAY") + .scanType("RANGE SCAN").table(indexTableName + "(" + indexPhysicalTableName + ")") + .keyRanges(" [1,*] - [1,'z']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .clientSortAlgo("CLIENT MERGE SORT").serverSortedBy("[\"K3\"]"); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -263,18 +228,10 @@ public void testLocalIndexScan() throws Exception { query = "SELECT t_id, k1, k2,v1 from " + tableName + " order by V1,t_id"; - plan = conn1.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL " + numRegions + "-WAY", - explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(indexTableName + "(" + indexPhysicalTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - assertNull(explainPlanAttributes.getServerSortedBy()); + assertPlan(conn1, query).iteratorType("PARALLEL " + numRegions + "-WAY") + .scanType("RANGE SCAN").table(indexTableName + "(" + indexPhysicalTableName + ")") + .keyRanges(" [1]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .clientSortAlgo("CLIENT MERGE SORT").serverSortedBy(null); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/InListIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/InListIT.java index 8d16b3a1c2f..ccbd52caf0d 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/InListIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/InListIT.java @@ -19,6 +19,7 @@ import static java.util.Collections.singletonList; 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.PhoenixRuntime.TENANT_ID_ATTRIB; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; @@ -46,8 +47,6 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.commons.lang3.RandomStringUtils; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.compile.ExpressionCompiler; import org.apache.phoenix.compile.QueryPlan; import org.apache.phoenix.compile.StatementContext; @@ -1024,20 +1023,14 @@ public void testPkDescOrderedTenantViewOnGlobalViewWithRightQueryPlan() throws E try (PreparedStatement preparedStmt = viewConn.prepareStatement("SELECT * FROM " + tenantView + " WHERE (ID1, ID2) " + "IN (('005xx000001Sv6o', '000000000000500'))")) { - QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - ExplainPlan plan = queryPlan.getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertTrue(explainPlanAttributes.getExplainScanType() - .startsWith(ExplainTable.POINT_LOOKUP_ON_STRING)); + assertPlan(PhoenixRuntime.getOptimizedQueryPlan(preparedStmt)) + .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING); } try (PreparedStatement preparedStmt = viewConn.prepareStatement("SELECT * FROM " + tenantView + " WHERE (ID2, ID1) " + "IN (('000000000000500', '005xx000001Sv6o'))")) { - QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - ExplainPlan plan = queryPlan.getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertTrue(explainPlanAttributes.getExplainScanType() - .startsWith(ExplainTable.POINT_LOOKUP_ON_STRING)); + assertPlan(PhoenixRuntime.getOptimizedQueryPlan(preparedStmt)) + .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING); } stmt.execute( @@ -1071,21 +1064,15 @@ public void testColumnDescOrderedTenantViewOnGlobalViewWithStringValue() throws try (PreparedStatement preparedStmt = viewConn.prepareStatement("SELECT * FROM " + tenantView + " WHERE (ID1, ID2) " + "IN (('005xx000001Sv6o', '000000000000500')," + "('bar', '000000000000400')," + "('foo', '000000000000300'))")) { - QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - ExplainPlan plan = queryPlan.getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertTrue(explainPlanAttributes.getExplainScanType() - .startsWith(ExplainTable.POINT_LOOKUP_ON_STRING)); + assertPlan(PhoenixRuntime.getOptimizedQueryPlan(preparedStmt)) + .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING); } try (PreparedStatement preparedStmt = viewConn.prepareStatement("SELECT * FROM " + tenantView + " WHERE (ID2, ID1) IN " + "(('bar', '005xx000001Sv6o')," + "('foo', '005xx000001Sv6o'))")) { - QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - ExplainPlan plan = queryPlan.getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertTrue(explainPlanAttributes.getExplainScanType() - .startsWith(ExplainTable.POINT_LOOKUP_ON_STRING)); + assertPlan(PhoenixRuntime.getOptimizedQueryPlan(preparedStmt)) + .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING); } stmt.execute("DELETE FROM " + tenantView + " WHERE (ID2, ID1) IN " @@ -1123,21 +1110,15 @@ public void testInListExpressionWithRightQueryPlanForTenantViewOnGlobalView() th viewConn.prepareStatement("SELECT * FROM " + tenantView + " WHERE (ID1, ID2) IN " + "(('005xx000001Sv6o', '000000000000500')," + "('005xx000001Sv6o', '000000000000400')," + "('005xx000001Sv6o', '000000000000300'))")) { - QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - ExplainPlan plan = queryPlan.getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertTrue(explainPlanAttributes.getExplainScanType() - .startsWith(ExplainTable.POINT_LOOKUP_ON_STRING)); + assertPlan(PhoenixRuntime.getOptimizedQueryPlan(preparedStmt)) + .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING); } try (PreparedStatement preparedStmt = viewConn.prepareStatement("SELECT * FROM " + tenantView + " WHERE (ID2, ID1) IN " + "(('000000000000400', '005xx000001Sv6o')," + "('000000000000300', '005xx000001Sv6o'))")) { - QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - ExplainPlan plan = queryPlan.getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertTrue(explainPlanAttributes.getExplainScanType() - .startsWith(ExplainTable.POINT_LOOKUP_ON_STRING)); + assertPlan(PhoenixRuntime.getOptimizedQueryPlan(preparedStmt)) + .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING); } ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM " + tenantView + " WHERE (ID2, ID1) IN " + "(('000000000000400', '005xx000001Sv6o')," @@ -1178,19 +1159,13 @@ private void testFullPkListPlan(String tenantView) throws Exception { + "(('005xx000001Sv6o', '000000000000500')," + "('005xx000001Sv6o', '000000000000400'))"); QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); assertEquals(numberOfRowsToScan, queryPlan.getEstimatedRowsToScan()); - ExplainPlan plan = queryPlan.getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertTrue( - explainPlanAttributes.getExplainScanType().startsWith(ExplainTable.POINT_LOOKUP_ON_STRING)); + assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING); viewConn.prepareStatement("DELETE FROM " + tenantView + " WHERE (ID1, ID2) IN " + "(('005xx000001Sv6o', '000000000000500')," + "('005xx000001Sv6o', '000000000000400'))"); queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); assertEquals(numberOfRowsToScan, queryPlan.getEstimatedRowsToScan()); - plan = queryPlan.getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertTrue( - explainPlanAttributes.getExplainScanType().startsWith(ExplainTable.POINT_LOOKUP_ON_STRING)); + assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING); } } @@ -1199,30 +1174,22 @@ private void testPartialPkListPlan(String tenantView) throws Exception { PreparedStatement preparedStmt = viewConn.prepareStatement("SELECT * FROM " + tenantView + " WHERE (ID1) IN " + "(('005xx000001Sv6o')," + "('005xx000001Sv6o'))"); QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - ExplainPlan plan = queryPlan.getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); viewConn.prepareStatement("DELETE FROM " + tenantView + " WHERE (ID1) IN " + "(('005xx000001Sv6o')," + "('005xx000001Sv6o'))"); queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - assertTrue( - queryPlan.getExplainPlan().toString().contains("CLIENT PARALLEL 1-WAY RANGE SCAN OVER")); + assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); preparedStmt = viewConn.prepareStatement("SELECT * FROM " + tenantView + " WHERE (ID2) IN " + "(('000000000000500')," + "('000000000000400'))"); queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - plan = queryPlan.getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); viewConn.prepareStatement("DELETE FROM " + tenantView + " WHERE (ID2) IN " + "(('000000000000500')," + "('000000000000400'))"); queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - assertTrue( - queryPlan.getExplainPlan().toString().contains("CLIENT PARALLEL 1-WAY RANGE SCAN OVER")); + assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); } } @@ -1231,30 +1198,22 @@ private void testPartialPkPlusNonPkListPlan(String tenantView) throws Exception PreparedStatement preparedStmt = viewConn.prepareStatement("SELECT * FROM " + tenantView + " WHERE (ID1, ID3) IN " + "(('005xx000001Sv6o', 1)," + "('005xx000001Sv6o', 2))"); QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - ExplainPlan plan = queryPlan.getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); viewConn.prepareStatement("DELETE FROM " + tenantView + " WHERE (ID1, ID3) IN " + "(('005xx000001Sv6o', 1)," + "('005xx000001Sv6o', 2))"); queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - assertTrue( - queryPlan.getExplainPlan().toString().contains("CLIENT PARALLEL 1-WAY RANGE SCAN OVER")); + assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); preparedStmt = viewConn.prepareStatement("SELECT * FROM " + tenantView + " WHERE (ID2, ID3) IN " + "(('000000000000500', 1)," + "('000000000000400', 2))"); queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - plan = queryPlan.getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); viewConn.prepareStatement("DELETE FROM " + tenantView + " WHERE (ID2, ID3) IN " + "(('000000000000500', 1)," + "('000000000000400', 2))"); queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - assertTrue( - queryPlan.getExplainPlan().toString().contains("CLIENT PARALLEL 1-WAY RANGE SCAN OVER")); + assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); } } @@ -1264,16 +1223,12 @@ private void testNonPkListPlan(String tenantView) throws Exception { PreparedStatement preparedStmt = viewConn.prepareStatement( "SELECT * FROM " + tenantView + " WHERE (ID3, ID4) IN " + "((1, 1)," + "(2, 2))"); QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - ExplainPlan plan = queryPlan.getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); viewConn.prepareStatement( "DELETE FROM " + tenantView + " WHERE (ID3, ID4) IN " + "((1, 1)," + "(2, 2))"); queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - assertTrue( - queryPlan.getExplainPlan().toString().contains("CLIENT PARALLEL 1-WAY RANGE SCAN OVER")); + assertPlan(queryPlan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); } } @@ -1775,28 +1730,19 @@ public void testBaseTableAndIndexTableHaveReversePKOrder() throws Exception { "SELECT * FROM " + view + " WHERE (ID1, ID2) IN " + "((1, 1)," + "(2, 2))"); QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); assertEquals(new Long(2), queryPlan.getEstimatedRowsToScan()); - ExplainPlan plan = queryPlan.getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertTrue(explainPlanAttributes.getExplainScanType() - .startsWith(ExplainTable.POINT_LOOKUP_ON_STRING)); + assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING); preparedStmt = conn.prepareStatement("SELECT * FROM " + view + " WHERE (ID2, ID1) IN ((1, 1),(2, 2))"); queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); assertEquals(new Long(2), queryPlan.getEstimatedRowsToScan()); - plan = queryPlan.getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertTrue(explainPlanAttributes.getExplainScanType() - .startsWith(ExplainTable.POINT_LOOKUP_ON_STRING)); + assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING); preparedStmt = conn.prepareStatement("SELECT * FROM " + view + " WHERE (ID2, ID1) IN ((1, 1),(2, 2))"); queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); assertEquals(new Long(2), queryPlan.getEstimatedRowsToScan()); - plan = queryPlan.getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertTrue(explainPlanAttributes.getExplainScanType() - .startsWith(ExplainTable.POINT_LOOKUP_ON_STRING)); + assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING); ResultSet rs = stmt.executeQuery("SELECT ID1, ID2, ID5, ID4 FROM " + view + " WHERE (POWER(ID2, 2), ID1) IN " + "((4.0, 9)," + "(10, 12))"); @@ -1842,27 +1788,19 @@ public void testDeletionFromTenantViewAndViewIndex() throws Exception { PreparedStatement preparedStmt = conn.prepareStatement( "SELECT * FROM " + view + " WHERE (ID4, ID2) IN " + "((1, 1)," + "(2, 2))"); QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); - ExplainPlan plan = queryPlan.getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + assertPlan(queryPlan).scanType("RANGE SCAN"); preparedStmt = conn.prepareStatement( "SELECT ID1,ID5 FROM " + view + " WHERE (ID1, ID2) IN " + "((1, 1),(2, 2))"); queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); assertEquals(new Long(2), queryPlan.getEstimatedRowsToScan()); - plan = queryPlan.getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertTrue(explainPlanAttributes.getExplainScanType() - .startsWith(ExplainTable.POINT_LOOKUP_ON_STRING)); + assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING); preparedStmt = conn.prepareStatement("SELECT * FROM " + view + " WHERE (ID2, ID1) IN ((1, 1),(2, 2))"); queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); assertEquals(new Long(2), queryPlan.getEstimatedRowsToScan()); - plan = queryPlan.getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertTrue(explainPlanAttributes.getExplainScanType() - .startsWith(ExplainTable.POINT_LOOKUP_ON_STRING)); + assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING); ResultSet rs = stmt.executeQuery("SELECT ID1, ID2, ID5, ID4 FROM " + view + " WHERE (POWER(ID2, 2), ID1) IN " + "((4.0, 9)," + "(10, 12))"); @@ -1901,10 +1839,7 @@ public void testBaseTableAndIndexTableHaveRightScan() throws Exception { "SELECT VAL2 FROM " + fullTableName + " WHERE (ID2, ID1) IN ((1, 1),(2, 2))"); QueryPlan queryPlan = PhoenixRuntime.getOptimizedQueryPlan(preparedStmt); queryPlan.getTableRef().getTable().getType(); - ExplainPlan plan = queryPlan.getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertTrue( - explainPlanAttributes.getExplainScanType().startsWith(ExplainTable.POINT_LOOKUP_ON_STRING)); + assertPlan(queryPlan).scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING); } } @@ -2247,11 +2182,10 @@ private void assertExpectedWithMaxInList(int tenantId, String testType, PDataTyp setBindVariables(stmt, lastBoundCol, numInLists, testPKTypes); QueryPlan plan = stmt.compileQuery(query.toString()); if (expectSkipScan) { - assertTrue( - plan.getExplainPlan().toString().contains("CLIENT PARALLEL 1-WAY POINT LOOKUP ON")); + assertPlan(plan).iteratorType("PARALLEL 1-WAY") + .scanTypeStartsWith(ExplainTable.POINT_LOOKUP_ON_STRING); } else { - assertTrue( - plan.getExplainPlan().toString().contains("CLIENT PARALLEL 1-WAY RANGE SCAN OVER")); + assertPlan(plan).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); } ResultSet rs = stmt.executeQuery(query.toString()); 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/IndexExtendedIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexExtendedIT.java index d633c064e9e..eda03aaa9c1 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexExtendedIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexExtendedIT.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.checkIndexState; import static org.apache.phoenix.util.TestUtil.getRowCount; @@ -35,13 +36,10 @@ import java.util.Properties; import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.util.Bytes; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.coprocessor.BaseScannerRegionObserver; import org.apache.phoenix.coprocessor.IndexRebuildRegionScanner; import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants; import org.apache.phoenix.jdbc.PhoenixConnection; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.mapreduce.index.IndexTool; import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.QueryServices; @@ -177,15 +175,10 @@ public void testMutableIndexWithUpdates() throws Exception { String selectSql = String.format("SELECT ID FROM %s WHERE UPPER(NAME, 'en_US') ='UNAME2'", dataTableFullName); - ExplainPlan plan = conn.prepareStatement(selectSql).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); // assert we are pulling from data table. - assertEquals(dataTableFullName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY UPPER(NAME, 'en_US') = 'UNAME2'", - explainPlanAttributes.getServerWhereFilter()); + assertPlan(conn, selectSql).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(dataTableFullName) + .serverWhereFilter("SERVER FILTER BY UPPER(NAME, 'en_US') = 'UNAME2'"); ResultSet rs = stmt1.executeQuery(selectSql); assertTrue(rs.next()); @@ -195,13 +188,10 @@ public void testMutableIndexWithUpdates() throws Exception { // run the index MR job. IndexToolIT.runIndexTool(useSnapshot, schemaName, dataTableName, indexTableName); - plan = conn.prepareStatement(selectSql).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); // assert we are pulling from index table. String expectedTableName = localIndex ? indexTableFullName + "(" + dataTableFullName + ")" : indexTableFullName; - assertEquals(expectedTableName, explainPlanAttributes.getTableName()); + assertPlan(conn, selectSql).table(expectedTableName); rs = stmt.executeQuery(selectSql); assertTrue(rs.next()); @@ -259,13 +249,8 @@ public void testDeleteFromImmutable() throws Exception { // deleted String query = "SELECT pk3 from " + dataTableFullName + " ORDER BY pk3"; - 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(indexTableFullName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(indexTableFullName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); ResultSet rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolForPartialBuildIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolForPartialBuildIT.java index c4f3be5c383..c9f5c84bbed 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolForPartialBuildIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolForPartialBuildIT.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; @@ -39,10 +40,7 @@ import org.apache.hadoop.hbase.client.ResultScanner; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.client.Table; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.jdbc.PhoenixConnection; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.mapreduce.index.IndexTool; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.query.QueryServicesOptions; @@ -165,15 +163,10 @@ public void testSecondaryIndex() throws Exception { String selectSql = String.format("SELECT LPAD(UPPER(NAME),11,'x')||'_xyz',ID FROM %s", fullTableName); - ExplainPlan plan = conn.prepareStatement(selectSql).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); // assert we are pulling from data table. - assertEquals(SchemaUtil - .getPhysicalHBaseTableName(schemaName, dataTableName, isNamespaceEnabled).toString(), - explainPlanAttributes.getTableName()); + assertPlan(conn, selectSql).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(SchemaUtil.getPhysicalHBaseTableName(schemaName, dataTableName, isNamespaceEnabled) + .toString()); rs = stmt1.executeQuery(selectSql); for (int i = 1; i <= 7; i++) { @@ -208,15 +201,9 @@ public void testSecondaryIndex() throws Exception { upsertRow(stmt1, 9000); conn.commit(); - plan = conn.prepareStatement(selectSql).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); // assert we are pulling from index table. - assertEquals( - SchemaUtil.getPhysicalHBaseTableName(schemaName, indxTable, isNamespaceEnabled).toString(), - explainPlanAttributes.getTableName()); + assertPlan(conn, selectSql).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table( + SchemaUtil.getPhysicalHBaseTableName(schemaName, indxTable, isNamespaceEnabled).toString()); rs = stmt.executeQuery(selectSql); 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..8f7c732c408 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; @@ -82,11 +84,9 @@ import org.apache.hadoop.mapreduce.CounterGroup; import org.apache.hadoop.mapreduce.Counters; import org.apache.hadoop.mapreduce.Job; -import org.apache.phoenix.compile.ExplainPlan; import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.end2end.index.IndexTestUtil; import org.apache.phoenix.jdbc.PhoenixConnection; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.mapreduce.index.IndexTool; import org.apache.phoenix.mapreduce.index.IndexVerificationOutputRepository; import org.apache.phoenix.mapreduce.index.IndexVerificationResultRepository; @@ -104,7 +104,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; @@ -312,11 +311,8 @@ public void testSecondaryIndex() throws Exception { dataTableFullName); // assert we are pulling from data table. - ExplainPlan plan = conn.prepareStatement(selectSql).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); + ExplainPlanAttributes explainPlanAttributes = assertPlan(conn, selectSql) + .iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").attributes(); assertEquals(dataTableFullName, explainPlanAttributes.getTableName().replaceAll(":", ".")); assertEquals( "SERVER FILTER BY (LPAD(UPPER(NAME, 'en_US'), 8, 'x') || '_xyz') = 'xxUNAME2_xyz'", @@ -345,10 +341,7 @@ public void testSecondaryIndex() throws Exception { QueryConstants.VERIFIED_BYTES); } // assert we are pulling from index table. - plan = conn.prepareStatement(selectSql).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + explainPlanAttributes = assertPlan(conn, selectSql).scanType("RANGE SCAN").attributes(); final String expectedTableName; if (localIndex) { expectedTableName = indexTableFullName + "(" + dataTableFullName + ")"; @@ -542,12 +535,8 @@ public void testIndexToolWithTenantId() throws Exception { runIndexTool(false, "", viewTenantName, indexNameTenant, tenantId, 0, new String[0]); String selectSql = String.format("SELECT ID FROM %s WHERE NAME='x'", viewTenantName); - ExplainPlan plan = connTenant.prepareStatement(selectSql) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(viewIndexTableName, explainPlanAttributes.getTableName()); + assertPlan(connTenant, selectSql).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(viewIndexTableName); ResultSet rs = connTenant.createStatement().executeQuery(selectSql); assertTrue(rs.next()); @@ -779,16 +768,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 +794,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()); @@ -899,28 +897,40 @@ private void testIndexToDataVerificationHelper(boolean caseSensitive) throws Exc } } - public static void assertExplainPlan(boolean localIndex, String actualExplainPlan, - String dataTableFullName, String indexTableFullName) { - assertExplainPlan(localIndex, actualExplainPlan, dataTableFullName, indexTableFullName, true); + /** + * Verify the optimizer chose to scan {@code indexTableFullName}. For a local index the scanned + * "table" is reported as {@code IDX(DATA)} and the key range starts with {@code [1,}. + */ + public static void assertExplainPlan(Connection conn, boolean localIndex, String selectSql, + String dataTableFullName, String indexTableFullName) throws SQLException { + assertExplainPlan(conn, localIndex, selectSql, dataTableFullName, indexTableFullName, true); } - public static void assertExplainPlan(boolean localIndex, String actualExplainPlan, - String dataTableFullName, String indexTableFullName, boolean normalizeTableNames) { - String expectedExplainPlan; + public static void assertExplainPlan(Connection conn, boolean localIndex, String selectSql, + String dataTableFullName, String indexTableFullName, boolean normalizeTableNames) + throws SQLException { + String expectedTable; if (localIndex) { - expectedExplainPlan = String.format(" RANGE SCAN OVER %s [1,", - normalizeTableNames - ? SchemaUtil.normalizeIdentifier(indexTableFullName) + "(" - + SchemaUtil.normalizeIdentifier(dataTableFullName) + ")" - : indexTableFullName + "(" + dataTableFullName + ")"); + expectedTable = normalizeTableNames + ? SchemaUtil.normalizeIdentifier(indexTableFullName) + "(" + + SchemaUtil.normalizeIdentifier(dataTableFullName) + ")" + : indexTableFullName + "(" + dataTableFullName + ")"; } else { - expectedExplainPlan = String.format(" RANGE SCAN OVER %s", - normalizeTableNames - ? SchemaUtil.normalizeIdentifier(indexTableFullName) - : indexTableFullName); + expectedTable = normalizeTableNames + ? SchemaUtil.normalizeIdentifier(indexTableFullName) + : indexTableFullName; + } + ExplainPlanAttributes attributes = getExplainAttributes(conn, selectSql); + assertPlan(attributes).scanType("RANGE SCAN"); + String actualTable = + attributes.getTableName() == null ? null : attributes.getTableName().replaceAll(":", "."); + assertTrue("expected scanned table <" + actualTable + "> to contain <" + expectedTable + ">", + actualTable != null && actualTable.contains(expectedTable)); + if (localIndex) { + String keyRanges = attributes.getKeyRanges(); + assertTrue("expected local-index key ranges <" + keyRanges + "> to start with '[1,'", + keyRanges != null && keyRanges.trim().startsWith("[1,")); } - assertTrue(actualExplainPlan + "\n expected to contain \n" + expectedExplainPlan, - actualExplainPlan.replaceAll(":", ".").contains(expectedExplainPlan)); } public static CounterGroup getMRJobCounters(IndexTool indexTool) throws IOException { diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/KeyOnlyIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/KeyOnlyIT.java index e500cb40210..dffcea8379a 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/KeyOnlyIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/KeyOnlyIT.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; @@ -32,9 +33,6 @@ import java.sql.Statement; import java.util.List; import java.util.Properties; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.KeyRange; import org.apache.phoenix.util.PropertiesUtil; import org.apache.phoenix.util.TestUtil; @@ -145,15 +143,8 @@ public void testQueryWithLimitAndStats() throws Exception { assertEquals(0, rs.getInt(1)); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("SERIAL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals(1, explainPlanAttributes.getServerRowLimit().intValue()); - assertEquals(1, explainPlanAttributes.getClientRowLimit().intValue()); + assertPlan(conn, query).iteratorType("SERIAL 1-WAY").scanType("FULL SCAN").table(tableName) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverRowLimit(1L).clientRowLimit(1); } @Test diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/LocalIndexSplitMergeIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/LocalIndexSplitMergeIT.java index e654fe0c19c..eebcfaf6fdc 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/LocalIndexSplitMergeIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/LocalIndexSplitMergeIT.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.assertTrue; @@ -33,10 +34,7 @@ import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.RegionInfo; import org.apache.hadoop.hbase.util.Bytes; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.jdbc.PhoenixConnection; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.query.QueryServicesOptions; @@ -143,33 +141,17 @@ public void testLocalIndexScanAfterRegionSplit() throws Exception { assertEquals(m, k1ColumnValue[m]); } - ExplainPlan plan = conn1.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL " + (4 + i) + "-WAY", - explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName + "(" + indexPhysicalTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", - explainPlanAttributes.getServerWhereFilter()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + assertPlan(conn1, query).iteratorType("PARALLEL " + (4 + i) + "-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); query = "SELECT t_id,k1,k3 FROM " + tableName; - plan = conn1.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals( - "PARALLEL " + ((strings[3 * i].compareTo("j") < 0) ? (4 + i) : (4 + i - 1)) + "-WAY", - explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName + "_2(" + indexPhysicalTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [2]", explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", - explainPlanAttributes.getServerWhereFilter()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + assertPlan(conn1, query) + .iteratorType( + "PARALLEL " + ((strings[3 * i].compareTo("j") < 0) ? (4 + i) : (4 + i - 1)) + "-WAY") + .scanType("RANGE SCAN").table(fullIndexName + "_2(" + indexPhysicalTableName + ")") + .keyRanges(" [2]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .clientSortAlgo("CLIENT MERGE SORT"); rs = conn1.createStatement().executeQuery(query); Thread.sleep(1000); @@ -245,28 +227,14 @@ public void testLocalIndexScanAfterRegionsMerge() throws Exception { assertEquals(strings[j], rs.getString("V1")); } - ExplainPlan plan = conn1.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 3-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName + "(" + indexPhysicalTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + assertPlan(conn1, query).iteratorType("PARALLEL 3-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); query = "SELECT t_id,k1,k3 FROM " + tableName; - plan = conn1.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 3-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName + "_2(" + indexPhysicalTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [2]", explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + assertPlan(conn1, query).iteratorType("PARALLEL 3-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "_2(" + indexPhysicalTableName + ")").keyRanges(" [2]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); rs = conn1.createStatement().executeQuery(query); Thread.sleep(1000); 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..9de4fd9f350 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; @@ -36,7 +37,6 @@ import org.apache.phoenix.schema.types.PInteger; import org.apache.phoenix.util.ByteUtil; 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.Test; @@ -216,13 +216,12 @@ public void testHint() throws Exception { populateTable(conn, tableName, 1, 2); String tableSelect = "SELECT V1,V2,V3 FROM " + tableName; - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + tableSelect); - String plan = QueryUtil.getExplainPlan(rs); + String scannedTable = assertPlan(conn, tableSelect).attributes().getTableName(); // plan should use one of the indexes - assertEquals(true, plan.contains(indexName) || plan.contains(indexName2)); + assertEquals(true, scannedTable != null + && (scannedTable.contains(indexName) || scannedTable.contains(indexName2))); // Test hint for the other index - String hintedIndex = - QueryUtil.getExplainPlan(rs).contains(indexName) ? indexName2 : indexName; + String hintedIndex = scannedTable.contains(indexName) ? indexName2 : indexName; try (Admin admin = conn.unwrap(PhoenixConnection.class).getQueryServices().getAdmin()) { String snapshotName = new StringBuilder(hintedIndex).append("-Snapshot").toString(); admin.snapshot(snapshotName, TableName.valueOf(hintedIndex)); @@ -232,9 +231,8 @@ 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)); - rs = conn.createStatement().executeQuery(indexSelect); + assertPlan(conn, indexSelect).tableContains(hintedIndex); + ResultSet 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/MultiCfQueryExecIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MultiCfQueryExecIT.java index 6b9a15b024c..a5e36123977 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/MultiCfQueryExecIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MultiCfQueryExecIT.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; @@ -36,7 +37,6 @@ import java.util.Properties; import org.apache.phoenix.query.KeyRange; 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.Before; @@ -536,10 +536,7 @@ public void testBug4658() throws Exception { // Tests for FORWARD_SCAN hint String query = "SELECT /*+ FORWARD_SCAN */ * FROM " + tableName + " WHERE COL2 = 'AAA' ORDER BY COL1 DESC"; - try (ResultSet rs = stmt.executeQuery("EXPLAIN " + query)) { - String explainPlan = QueryUtil.getExplainPlan(rs); - assertFalse(explainPlan.contains("REVERSE")); - } + assertPlan(conn, query).clientSortedBy(null); try (ResultSet rs = stmt.executeQuery(query)) { assertTrue(rs.next()); assertEquals(rs.getString("COL1"), "222"); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/OnDuplicateKey2IT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/OnDuplicateKey2IT.java index b5a7b055f97..52d0e4ba4a3 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/OnDuplicateKey2IT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/OnDuplicateKey2IT.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; @@ -47,8 +48,6 @@ import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.hbase.util.VersionInfo; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.jdbc.PhoenixConnection; import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.jdbc.PhoenixStatement; @@ -381,14 +380,12 @@ private void verifyIndexRow(Connection conn, String tableName, boolean deleted) } assertFalse(resultSet.next()); - ExplainPlan plan = - preparedStatement.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(indexDDL.contains("index") + String expectedTable = indexDDL.contains("index") ? (indexDDL.contains("local index") ? tableName + "_IDX(" + tableName + ")" : tableName + "_IDX") - : tableName, explainPlanAttributes.getTableName()); + : tableName; + assertPlan(preparedStatement.unwrap(PhoenixPreparedStatement.class)).table(expectedTable); } private static void validateMultiRowDelete(String tableName, Connection conn, 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/ParallelStatsDisabledIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ParallelStatsDisabledIT.java index 8a1e55ad3ab..378498e6d29 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ParallelStatsDisabledIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ParallelStatsDisabledIT.java @@ -17,20 +17,16 @@ */ package org.apache.phoenix.end2end; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.sql.Connection; import java.sql.ResultSet; -import java.sql.SQLException; import java.util.Map; -import org.apache.commons.lang3.StringUtils; import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants; import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.QueryBuilder; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -72,12 +68,4 @@ protected ResultSet executeQueryThrowsException(Connection conn, QueryBuilder qu } return rs; } - - public static void validateQueryPlan(Connection conn, QueryBuilder queryBuilder, - String expectedPhoenixPlan, String expectedSparkPlan) throws SQLException { - if (StringUtils.isNotBlank(expectedPhoenixPlan)) { - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + queryBuilder.build()); - assertEquals(expectedPhoenixPlan, QueryUtil.getExplainPlan(rs)); - } - } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ParallelStatsDisabledWithRegionMovesIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ParallelStatsDisabledWithRegionMovesIT.java index 5de095496fe..e556f808d7c 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ParallelStatsDisabledWithRegionMovesIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ParallelStatsDisabledWithRegionMovesIT.java @@ -27,7 +27,6 @@ import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; -import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -37,7 +36,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.TableName; @@ -56,7 +54,6 @@ import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.ByteUtil; import org.apache.phoenix.util.QueryBuilder; -import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ReadOnlyProps; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -253,14 +250,6 @@ protected ResultSet executeQueryThrowsException(Connection conn, QueryBuilder qu return rs; } - public static void validateQueryPlan(Connection conn, QueryBuilder queryBuilder, - String expectedPhoenixPlan, String expectedSparkPlan) throws SQLException { - if (StringUtils.isNotBlank(expectedPhoenixPlan)) { - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + queryBuilder.build()); - assertEquals(expectedPhoenixPlan, QueryUtil.getExplainPlan(rs)); - } - } - protected static void assertResultSetWithRegionMoves(ResultSet rs, Object[][] rows, String tableName) throws Exception { for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) { 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/QueryLoggerIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryLoggerIT.java index 6504c92bcaf..cdfe3ab2f13 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryLoggerIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryLoggerIT.java @@ -127,6 +127,8 @@ public void testDebugLogs() throws Exception { assertEquals(rs.getString(BIND_PARAMETERS), null); assertEquals(rs.getString(USER), System.getProperty("user.name")); assertEquals(rs.getString(CLIENT_IP), InetAddress.getLocalHost().getHostAddress()); + // The EXPLAIN_PLAN column logged by the query logger must be equal to a freshly + // computed plan. assertEquals(rs.getString(EXPLAIN_PLAN), QueryUtil.getExplainPlan(explainRS)); assertEquals(rs.getString(GLOBAL_SCAN_DETAILS), context.getScan().toJSON()); assertEquals(rs.getLong(NO_OF_RESULTS_ITERATED), 10); @@ -311,6 +313,8 @@ private void testPreparedStatement(LogLevel loglevel) throws Exception { loglevel == LogLevel.TRACE ? "value5" : null); assertEquals(rs.getString(USER), System.getProperty("user.name")); assertEquals(rs.getString(CLIENT_IP), InetAddress.getLocalHost().getHostAddress()); + // The EXPLAIN_PLAN column logged by the query logger must be equal to a freshly + // computed plan. assertEquals(rs.getString(EXPLAIN_PLAN), QueryUtil.getExplainPlan(explainRS)); assertEquals(rs.getString(GLOBAL_SCAN_DETAILS), context.getScan().toJSON()); assertEquals(rs.getLong(NO_OF_RESULTS_ITERATED), 1); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithLimitIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithLimitIT.java index 2278f7341c8..e7666f9a68f 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithLimitIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithLimitIT.java @@ -18,6 +18,7 @@ package org.apache.phoenix.end2end; import static org.apache.phoenix.query.QueryServicesOptions.UNLIMITED_QUEUE_SIZE; +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; @@ -31,9 +32,6 @@ import java.util.Map; import java.util.Properties; import java.util.concurrent.RejectedExecutionException; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.PropertiesUtil; @@ -91,15 +89,8 @@ public void testQueryWithLimitAndStats() throws Exception { assertEquals(0, rs.getInt(1)); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("SERIAL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals(1, explainPlanAttributes.getServerRowLimit().intValue()); - assertEquals(1, explainPlanAttributes.getClientRowLimit().intValue()); + assertPlan(conn, query).iteratorType("SERIAL 1-WAY").scanType("FULL SCAN").table(tableName) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverRowLimit(1L).clientRowLimit(1); } finally { conn.close(); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithOffsetIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithOffsetIT.java index 65ec34ff08c..6cda98ab04a 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithOffsetIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithOffsetIT.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; @@ -33,9 +34,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Properties; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.PropertiesUtil; import org.junit.Before; @@ -129,20 +127,14 @@ public void testOffsetSerialQueryExecutedOnServer() throws SQLException { initTableValues(conn); updateStatistics(conn); String query = "SELECT t_id from " + tableName + " offset " + offset; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY EMPTY COLUMN ONLY", - explainPlanAttributes.getServerWhereFilter()); if (!isSalted) { - assertEquals("SERIAL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals(offset, explainPlanAttributes.getServerOffset().intValue()); + assertPlan(conn, query).scanType("FULL SCAN").table(tableName) + .serverWhereFilter("SERVER FILTER BY EMPTY COLUMN ONLY").iteratorType("SERIAL 1-WAY") + .serverOffset(offset); } else { - assertEquals("PARALLEL 10-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - assertEquals(offset, explainPlanAttributes.getClientOffset().intValue()); + assertPlan(conn, query).scanType("FULL SCAN").table(tableName) + .serverWhereFilter("SERVER FILTER BY EMPTY COLUMN ONLY").iteratorType("PARALLEL 10-WAY") + .clientSortAlgo("CLIENT MERGE SORT").clientOffset(offset); } ResultSet rs = conn.createStatement().executeQuery(query); @@ -152,22 +144,16 @@ public void testOffsetSerialQueryExecutedOnServer() throws SQLException { assertEquals(STRINGS[offset + i - 1], rs.getString(1)); } query = "SELECT t_id from " + tableName + " ORDER BY v1 offset " + offset; - plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("[C2.V1]", explainPlanAttributes.getServerSortedBy()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - assertEquals(offset, explainPlanAttributes.getClientOffset().intValue()); if (!isSalted) { // When Parallel stats is actually disabled, it is PARALLEL 4-WAY // CLIENT PARALLEL 4-WAY FULL SCAN OVER T_N000001 // SERVER SORTED BY [C2.V1] CLIENT MERGE SORT CLIENT OFFSET 10 // When enabled, it is 5-WAY - assertEquals("PARALLEL 4-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); + assertPlan(conn, query).scanType("FULL SCAN").table(tableName).serverSortedBy("[C2.V1]") + .clientSortAlgo("CLIENT MERGE SORT").clientOffset(offset).iteratorType("PARALLEL 4-WAY"); } else { - assertEquals("PARALLEL 10-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); + assertPlan(conn, query).scanType("FULL SCAN").table(tableName).serverSortedBy("[C2.V1]") + .clientSortAlgo("CLIENT MERGE SORT").clientOffset(offset).iteratorType("PARALLEL 10-WAY"); } conn.close(); } 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/ReverseScanIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ReverseScanIT.java index 2fcf0c6a3ee..d3eb6177869 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ReverseScanIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ReverseScanIT.java @@ -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.ROW2; import static org.apache.phoenix.util.TestUtil.ROW3; import static org.apache.phoenix.util.TestUtil.ROW4; @@ -37,9 +38,6 @@ import java.sql.Statement; import java.util.Properties; import org.apache.hadoop.hbase.util.Bytes; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.util.PropertiesUtil; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -81,15 +79,9 @@ public void testReverseRangeScan() throws Exception { assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("REVERSE", explainPlanAttributes.getClientSortedBy()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID >= '00A323122312312'", - explainPlanAttributes.getServerWhereFilter()); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").clientSortedBy("REVERSE") + .scanType("FULL SCAN").table(tableName) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID >= '00A323122312312'"); PreparedStatement statement = conn.prepareStatement("SELECT entity_id FROM " + tableName + " WHERE organization_id = ? AND entity_id >= ? ORDER BY organization_id DESC, entity_id DESC"); @@ -185,17 +177,9 @@ public void testReverseScanIndex() throws Exception { assertEquals(1, rs.getInt(1)); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("SERIAL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("REVERSE", explainPlanAttributes.getClientSortedBy()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(indexName, explainPlanAttributes.getTableName()); - assertEquals(" [not null]", explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals(1, explainPlanAttributes.getServerRowLimit().intValue()); - assertEquals(1, explainPlanAttributes.getClientRowLimit().intValue()); + assertPlan(conn, query).iteratorType("SERIAL 1-WAY").clientSortedBy("REVERSE") + .scanType("RANGE SCAN").table(indexName).keyRanges(" [not null]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverRowLimit(1L).clientRowLimit(1); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java index a10cc8745d3..2757800b6d1 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java @@ -18,6 +18,7 @@ package org.apache.phoenix.end2end; 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.ENTITYHISTID1; import static org.apache.phoenix.util.TestUtil.ENTITYHISTID3; import static org.apache.phoenix.util.TestUtil.ENTITYHISTID7; @@ -54,8 +55,6 @@ import java.util.Properties; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.util.Bytes; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.jdbc.PhoenixStatement; import org.apache.phoenix.util.DateUtil; @@ -1349,14 +1348,9 @@ public void testForceSkipScan() throws Exception { assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 4-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("SKIP SCAN ON 12 KEYS ", explainPlanAttributes.getExplainScanType()); - assertEquals(tempTableWithCompositePK, explainPlanAttributes.getTableName()); - assertEquals(" [X'00',2] - [X'03',4]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + assertPlan(conn, query).iteratorType("PARALLEL 4-WAY").scanType("SKIP SCAN ON 12 KEYS") + .table(tempTableWithCompositePK).keyRanges(" [X'00',2] - [X'03',4]") + .clientSortAlgo("CLIENT MERGE SORT"); } finally { 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/SequenceBulkAllocationIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SequenceBulkAllocationIT.java index 85133397d4e..697b1aad8b1 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SequenceBulkAllocationIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SequenceBulkAllocationIT.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.PhoenixRuntime.TENANT_ID_ATTRIB; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.junit.Assert.assertEquals; @@ -37,10 +38,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.exception.SQLExceptionCode; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.util.PropertiesUtil; import org.apache.phoenix.util.SchemaUtil; import org.junit.After; @@ -825,14 +823,8 @@ public void testExplainPlanForNextValuesFor() throws Exception { String query = "SELECT NEXT 1000 VALUES FOR " + sequenceName + " FROM " + tableName; // Assert output for Explain Plain result is as expected - 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(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals(1, explainPlanAttributes.getClientSequenceCount().intValue()); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(tableName) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSequenceCount(1); } /** diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SequenceIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SequenceIT.java index 3cc3a0510df..0ff1bb53db7 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SequenceIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SequenceIT.java @@ -20,6 +20,7 @@ import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA; import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TYPE_SEQUENCE; import static org.apache.phoenix.query.QueryServicesTestImpl.DEFAULT_SEQUENCE_CACHE_SIZE; +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,12 +36,9 @@ import java.sql.Statement; import java.util.List; import java.util.Properties; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.exception.SQLExceptionCode; import org.apache.phoenix.jdbc.PhoenixConnection; import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.jdbc.PhoenixStatement; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.query.QueryServicesTestImpl; @@ -824,14 +822,8 @@ public void testExplainPlanValidatesSequences() throws Exception { Connection conn2 = DriverManager.getConnection(getUrl(), PropertiesUtil.deepCopy(TEST_PROPERTIES)); String query = "SELECT NEXT VALUE FOR " + sequenceName + " FROM " + tableName; - 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(tableName, explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals(1, explainPlanAttributes.getClientSequenceCount().intValue()); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(tableName) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSequenceCount(1); ResultSet rs = conn.createStatement().executeQuery( "SELECT sequence_name, current_value FROM \"SYSTEM\".\"SEQUENCE\" WHERE sequence_name='" @@ -1484,20 +1476,14 @@ public void testNoFromClause() throws Exception { .execute("CREATE SEQUENCE " + secondSeqName + " START WITH 2 INCREMENT BY 3"); String query = "SELECT NEXT VALUE FOR " + seqName; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(new Integer(1), explainPlanAttributes.getClientSequenceCount()); + assertPlan(conn, query).clientSequenceCount(1); rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals(1, rs.getInt(1)); query = "SELECT CURRENT VALUE FOR " + seqName; - plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(new Integer(1), explainPlanAttributes.getClientSequenceCount()); + assertPlan(conn, query).clientSequenceCount(1); rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); 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..175ea4cd73e 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; @@ -46,7 +46,6 @@ import org.apache.phoenix.coprocessor.PagingRegionScanner; import org.apache.phoenix.hbase.index.IndexRegionObserver; import org.apache.phoenix.jdbc.PhoenixConnection; -import org.apache.phoenix.jdbc.PhoenixResultSet; import org.apache.phoenix.jdbc.PhoenixStatement; import org.apache.phoenix.monitoring.MetricType; import org.apache.phoenix.query.QueryServices; @@ -55,7 +54,6 @@ import org.apache.phoenix.util.DateUtil; 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.Assert; import org.junit.BeforeClass; @@ -146,11 +144,9 @@ public void testScanWithLimit() throws Exception { conn.createStatement().execute(ddl); conn.commit(); + assertPlan(conn, "select * from " + tablename + " limit " + limit).tableContains(indexName); stmt = conn.prepareStatement("select * from " + tablename + " limit " + limit); try (ResultSet rs = stmt.executeQuery()) { - PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class); - String explainPlan = QueryUtil.getExplainPlan(prs.getUnderlyingIterator()); - assertTrue(explainPlan.contains(indexName)); int expectedRowCount = 0; int expectedId1 = 4; int expectedId2 = 0; @@ -434,10 +430,8 @@ public void testPagingWithUnverifiedIndexRows() throws Exception { } conn.commit(); String dql = String.format("SELECT count(*) from %s where k3 = 5", tablename); + assertPlan(conn, dql).tableContains(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(totalRows / 10, rs.getInt(1)); assertFalse(rs.next()); @@ -462,10 +456,8 @@ public void testPagingWithUnverifiedIndexRows() throws Exception { } finally { IndexRegionObserver.setFailDataTableUpdatesForTesting(false); } + assertPlan(conn, dql).tableContains(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(totalRows / 10, rs.getInt(1)); assertFalse(rs.next()); @@ -658,7 +650,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 +660,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 +668,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..1a57ae39e0d 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,32 @@ 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) + .clientAggregate("CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY [E.BUCKET, E.TIMESTAMP]") + .lhs().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]") + .end().rhs().iteratorType("PARALLEL").scanType("SKIP SCAN ON 2 RANGES").table(t[i]) + .keyRanges(rhsKeyRanges) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND SRC_LOCATION = DST_LOCATION") + .serverDistinctFilter( + "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 +713,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).lhs().serverSortedBy(null).end().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/SpillableGroupByIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SpillableGroupByIT.java index ab65682080f..3b14fe72efd 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SpillableGroupByIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SpillableGroupByIT.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.createGroupByTestTable; import static org.junit.Assert.assertEquals; @@ -32,10 +33,7 @@ import java.sql.Statement; import java.util.Map; import java.util.Properties; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.exception.SQLExceptionCode; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.PropertiesUtil; import org.apache.phoenix.util.ReadOnlyProps; @@ -191,13 +189,8 @@ public void testStatisticsAreNotWritten() throws SQLException { rs.close(); stmt.close(); String query = "SELECT * FROM " + tableName; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(new Integer(1), explainPlanAttributes.getSplitsChunk()); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName, explainPlanAttributes.getTableName()); + assertPlan(conn, query).splitsChunk(1).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(tableName); conn.close(); } 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/TenantSpecificViewIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificViewIndexIT.java index 35614d565d8..0223b8bca54 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificViewIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificViewIndexIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end; +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.PhoenixRuntime.TENANT_ID_ATTRIB; @@ -41,7 +42,6 @@ import org.apache.phoenix.schema.PNameFactory; import org.apache.phoenix.util.MetaDataUtil; 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; @@ -358,27 +358,27 @@ public void testOverlappingDatesFilter() throws Exception { viewConn.createStatement() .execute("CREATE VIEW IF NOT EXISTS " + viewName + " AS SELECT * FROM " + tableName); - String query = "EXPLAIN SELECT PARENT_ID FROM " + viewName + " WHERE PARENT_TYPE='001' " + String expectedIndexName = SchemaUtil.getTableName(SCHEMA1, "IDX"); + String query1 = "SELECT PARENT_ID FROM " + viewName + " WHERE PARENT_TYPE='001' " + "AND (CREATED_DATE > to_date('2011-01-01') AND CREATED_DATE < to_date('2016-10-31'))" + "ORDER BY PARENT_TYPE,CREATED_DATE LIMIT 501"; - - ResultSet rs = viewConn.createStatement().executeQuery(query); - String exptectedIndexName = SchemaUtil.getTableName(SCHEMA1, "IDX"); - String expectedPlanFormat = "CLIENT SERIAL 1-WAY RANGE SCAN OVER " + exptectedIndexName - + " ['tenant1 ','001','%s 00:00:00.001'] - ['tenant1 ','001','%s 00:00:00.000']" - + "\n" + " SERVER FILTER BY FIRST KEY ONLY" + "\n" + " SERVER 501 ROW LIMIT" + "\n" - + "CLIENT 501 ROW LIMIT"; - assertEquals(String.format(expectedPlanFormat, "2011-01-01", "2016-10-31"), - QueryUtil.getExplainPlan(rs)); - - query = "EXPLAIN SELECT PARENT_ID FROM " + viewName + " WHERE PARENT_TYPE='001' " + assertPlan(viewConn, query1).iteratorType("SERIAL").scanType("RANGE SCAN") + .table(expectedIndexName) + .keyRanges(" ['tenant1 ','001','2011-01-01 00:00:00.001']" + + " - ['tenant1 ','001','2016-10-31 00:00:00.000']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverRowLimit(501L) + .clientRowLimit(501).clientSteps("CLIENT 501 ROW LIMIT"); + + String query2 = "SELECT PARENT_ID FROM " + viewName + " WHERE PARENT_TYPE='001' " + " AND (CREATED_DATE >= to_date('2011-01-01') AND CREATED_DATE <= to_date('2016-01-01'))" + " AND (CREATED_DATE > to_date('2012-10-21') AND CREATED_DATE < to_date('2016-10-31')) " + "ORDER BY PARENT_TYPE,CREATED_DATE LIMIT 501"; - - rs = viewConn.createStatement().executeQuery(query); - assertEquals(String.format(expectedPlanFormat, "2012-10-21", "2016-01-01"), - QueryUtil.getExplainPlan(rs)); + assertPlan(viewConn, query2).iteratorType("SERIAL").scanType("RANGE SCAN") + .table(expectedIndexName) + .keyRanges(" ['tenant1 ','001','2012-10-21 00:00:00.001']" + + " - ['tenant1 ','001','2016-01-01 00:00:00.000']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverRowLimit(501L) + .clientRowLimit(501).clientSteps("CLIENT 501 ROW LIMIT"); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithDisabledIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithDisabledIndexIT.java index a1372be5496..33deda98211 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithDisabledIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithDisabledIndexIT.java @@ -19,6 +19,7 @@ import static org.apache.phoenix.query.QueryServices.DISABLE_VIEW_SUBTREE_VALIDATION; import static org.apache.phoenix.query.QueryServices.INDEX_USE_SERVER_METADATA_ATTRIB; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import com.google.protobuf.RpcCallback; import com.google.protobuf.RpcController; @@ -31,15 +32,12 @@ import java.util.Properties; import org.apache.hadoop.hbase.DoNotRetryIOException; import org.apache.hadoop.hbase.util.Bytes; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.coprocessor.MetaDataEndpointImpl; import org.apache.phoenix.coprocessor.generated.MetaDataProtos; import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants; import org.apache.phoenix.exception.PhoenixIOException; import org.apache.phoenix.jdbc.PhoenixConnection; import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.protobuf.ProtobufUtil; import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.QueryServices; @@ -172,10 +170,7 @@ public void testUcfWithNoGetTableCalls() throws Throwable { Assert.assertTrue(rs.next()); Assert.assertFalse(rs.next()); - ExplainPlan explainPlan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = explainPlan.getPlanStepsAsAttributes(); - Assert.assertEquals(indexName, explainPlanAttributes.getTableName()); + assertPlan(conn, query).table(indexName); TestUtil.removeCoprocessor(conn, "SYSTEM.CATALOG", TestMetaDataEndpointImpl.class); TestUtil.addCoprocessor(conn, "SYSTEM.CATALOG", MetaDataEndpointImpl.class); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UnionAllIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UnionAllIT.java index 208e18f88e2..e63972ba38e 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UnionAllIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UnionAllIT.java @@ -17,10 +17,10 @@ */ 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; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -30,7 +30,6 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Statement; import java.util.Properties; import org.apache.phoenix.compile.ExplainPlan; import org.apache.phoenix.compile.ExplainPlanAttributes; @@ -38,7 +37,6 @@ import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.QueryServices; 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; @@ -643,35 +641,16 @@ public void testExplainUnionAll() throws Exception { assertEquals(1, rhsPlanAttributes.getClientRowLimit().intValue()); assertEquals("CLIENT MERGE SORT", rhsPlanAttributes.getClientSortAlgo()); - String limitPlan = "UNION ALL OVER 2 QUERIES\n" + " CLIENT SERIAL 1-WAY FULL SCAN OVER " - + tableName1 + "\n" + " SERVER 2 ROW LIMIT\n" + " CLIENT 2 ROW LIMIT\n" - + " CLIENT SERIAL 1-WAY FULL SCAN OVER " + tableName2 + "\n" - + " SERVER 2 ROW LIMIT\n" + " CLIENT 2 ROW LIMIT\n" + "CLIENT 2 ROW LIMIT"; - + // Each branch emits a SERIAL 1-WAY FULL SCAN with SERVER 2 ROW LIMIT and an inner CLIENT 2 + // ROW LIMIT, and the union root carries an outer CLIENT 2 ROW LIMIT. The outer wrap shows + // up as a second "CLIENT 2 ROW LIMIT" entry in the root's clientSteps. ddl = "select a_string, col1 from " + tableName1 + " union all select a_string, col1 from " + tableName2 + " limit 2"; - plan = conn.prepareStatement(ddl).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("UNION ALL OVER 2 QUERIES", explainPlanAttributes.getAbstractExplainPlan()); - assertEquals("SERIAL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName1, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getServerSortedBy()); - assertEquals(2L, explainPlanAttributes.getServerRowLimit().longValue()); - assertEquals(2, explainPlanAttributes.getClientRowLimit().intValue()); - rhsPlanAttributes = explainPlanAttributes.getRhsJoinQueryExplainPlan(); - assertEquals("SERIAL 1-WAY", rhsPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", rhsPlanAttributes.getExplainScanType()); - assertEquals(tableName2, rhsPlanAttributes.getTableName()); - assertNull(rhsPlanAttributes.getServerSortedBy()); - assertEquals(2L, rhsPlanAttributes.getServerRowLimit().longValue()); - assertEquals(2, rhsPlanAttributes.getClientRowLimit().intValue()); - - Statement stmt = conn.createStatement(); - stmt.setMaxRows(2); - ResultSet rs = stmt.executeQuery("explain " + ddl); - assertEquals(limitPlan, QueryUtil.getExplainPlan(rs)); + assertPlan(conn, ddl).abstractExplainPlan("UNION ALL OVER 2 QUERIES").iteratorType("SERIAL") + .scanType("FULL SCAN").table(tableName1).serverSortedBy(null).serverRowLimit(2L) + .clientRowLimit(2).clientSteps("CLIENT 2 ROW LIMIT", "CLIENT 2 ROW LIMIT").rhs() + .iteratorType("SERIAL").scanType("FULL SCAN").table(tableName2).serverSortedBy(null) + .serverRowLimit(2L).clientRowLimit(2).clientSteps("CLIENT 2 ROW LIMIT"); ddl = "select a_string, col1 from " + tableName1 + " union all select a_string, col1 from " + tableName2; 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/UserDefinedFunctionsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UserDefinedFunctionsIT.java index 4f174bb2b2f..231d877ecdb 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UserDefinedFunctionsIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UserDefinedFunctionsIT.java @@ -20,6 +20,7 @@ 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.QueryServices.DYNAMIC_JARS_DIR_KEY; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.PhoenixRuntime.JDBC_PROTOCOL_SEPARATOR; import static org.apache.phoenix.util.PhoenixRuntime.JDBC_PROTOCOL_TERMINATOR; import static org.apache.phoenix.util.PhoenixRuntime.JDBC_PROTOCOL_ZK; @@ -55,10 +56,7 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.HBaseTestingUtility; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.expression.function.UDFExpression; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.jdbc.PhoenixTestDriver; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.schema.FunctionAlreadyExistsException; @@ -842,13 +840,8 @@ public void testFunctionalIndexesWithUDFFunction() throws Exception { stmt.execute("create index idx on t5(myreverse5(lastname_reverse))"); String query = "select myreverse5(lastname_reverse) from t5"; - 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("IDX", explainPlanAttributes.getTableName()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table("IDX") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); ResultSet rs = stmt.executeQuery(query); assertTrue(rs.next()); @@ -858,15 +851,9 @@ public void testFunctionalIndexesWithUDFFunction() throws Exception { query = "select k,k1,myreverse5(lastname_reverse) from t5 where myreverse5(lastname_reverse)='kcoj'"; - plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals("IDX2(T5)", explainPlanAttributes.getTableName()); - assertEquals(" [1,'kcoj']", explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table("IDX2(T5)") + .keyRanges(" [1,'kcoj']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .clientSortAlgo("CLIENT MERGE SORT"); rs = stmt.executeQuery(query); assertTrue(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncoded2IT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncoded2IT.java index 6b05b7dd0a5..205801164a9 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncoded2IT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncoded2IT.java @@ -20,6 +20,7 @@ import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_RETRY_PAUSE_MS; import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS; import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_MUTATION_SERIALIZE; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import java.sql.Connection; @@ -33,8 +34,6 @@ import java.util.Map; import java.util.Properties; import org.apache.hadoop.hbase.util.Bytes; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants; import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.QueryServices; @@ -1275,12 +1274,8 @@ public void testVarBinaryPkSchema4() throws Exception { private static void assertIndexUsed(PreparedStatement preparedStatement, String indexName, String scanType) throws SQLException { - ExplainPlan plan = - preparedStatement.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); - - Assert.assertEquals(indexName, planAttributes.getTableName()); - Assert.assertEquals(scanType, planAttributes.getExplainScanType()); + assertPlan(preparedStatement.unwrap(PhoenixPreparedStatement.class)).table(indexName) + .scanType(scanType); } private static void upsertRow(PreparedStatement preparedStatement, byte[] b10, byte[] b20, diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java index c58946caae5..e3bf620dc62 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java @@ -18,13 +18,13 @@ package org.apache.phoenix.end2end; import static org.apache.phoenix.coprocessor.PhoenixMetaDataCoprocessorHost.PHOENIX_META_DATA_COPROCESSOR_CONF_KEY; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.apache.phoenix.thirdparty.com.google.common.collect.Lists.newArrayListWithExpectedSize; import static org.apache.phoenix.util.TestUtil.analyzeTable; import static org.apache.phoenix.util.TestUtil.getAllSplits; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -49,12 +49,9 @@ import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 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.exception.SQLExceptionCode; import org.apache.phoenix.jdbc.PhoenixConnection; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.jdbc.PhoenixStatement; import org.apache.phoenix.query.KeyRange; import org.apache.phoenix.query.QueryServices; @@ -523,26 +520,13 @@ private void testViewUsesTableIndex(boolean localIndex) throws Exception { assertEquals(100, rs.getInt(1)); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("SKIP SCAN ON 4 KEYS ", explainPlanAttributes.getExplainScanType()); - assertEquals("SERVER FILTER BY (\"S2\" = 'bas' AND \"S1\" = 'foo')", - explainPlanAttributes.getServerWhereFilter()); - - // Assert that in either case (local & global) that index from - // physical table used for query on view. - if (localIndex) { - assertEquals(fullIndexName1 + "(" + fullTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1,1,100] - [1,2,109]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - } else { - assertEquals(fullIndexName1, explainPlanAttributes.getTableName()); - assertEquals(" [1,100] - [2,109]", explainPlanAttributes.getKeyRanges()); - assertNull(explainPlanAttributes.getClientSortAlgo()); - } + String expectedTable = + localIndex ? fullIndexName1 + "(" + fullTableName + ")" : fullIndexName1; + String expectedKeyRanges = localIndex ? " [1,1,100] - [1,2,109]" : " [1,100] - [2,109]"; + String expectedClientSortAlgo = localIndex ? "CLIENT MERGE SORT" : null; + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("SKIP SCAN ON 4 KEYS") + .serverWhereFilter("SERVER FILTER BY (\"S2\" = 'bas' AND \"S1\" = 'foo')") + .table(expectedTable).keyRanges(expectedKeyRanges).clientSortAlgo(expectedClientSortAlgo); } } @@ -979,36 +963,24 @@ public static Pair testUpdatableViewIndex(String fullTableName, In assertEquals("bar", rs.getString(4)); assertFalse(rs.next()); - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - if (localIndex) { - assertEquals("PARALLEL " + (saltBuckets == null ? 1 : saltBuckets) + "-WAY", - explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals(viewIndexFullName1 + "(" + fullTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1,51]", explainPlanAttributes.getKeyRanges()); - assertTrue( - explainPlanAttributes.getServerWhereFilter().equals("SERVER FILTER BY FIRST KEY ONLY") - || explainPlanAttributes.getServerWhereFilter() - .equals("SERVER FILTER BY EMPTY COLUMN ONLY")); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + assertPlan(conn, query).scanType("RANGE SCAN") + .iteratorType("PARALLEL " + (saltBuckets == null ? 1 : saltBuckets) + "-WAY") + .table(viewIndexFullName1 + "(" + fullTableName + ")").keyRanges(" [1,51]") + .serverWhereFilterAnyOf("SERVER FILTER BY FIRST KEY ONLY", + "SERVER FILTER BY EMPTY COLUMN ONLY") + .clientSortAlgo("CLIENT MERGE SORT"); + } else if (saltBuckets == null) { + assertPlan(conn, query).scanType("RANGE SCAN").iteratorType("PARALLEL 1-WAY") + .table(viewIndexPhysicalName).keyRanges(" [" + Short.MIN_VALUE + ",51]") + .clientSortAlgo(null); } else { - assertEquals(viewIndexPhysicalName, explainPlanAttributes.getTableName()); - if (saltBuckets == null) { - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals(" [" + Short.MIN_VALUE + ",51]", explainPlanAttributes.getKeyRanges()); - assertNull(explainPlanAttributes.getClientSortAlgo()); - } else { - assertEquals("PARALLEL " + saltBuckets + "-WAY", - explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals(" [X'00'," + Short.MIN_VALUE + ",51] - [" + assertPlan(conn, query).scanType("RANGE SCAN") + .iteratorType("PARALLEL " + saltBuckets + "-WAY").table(viewIndexPhysicalName) + .keyRanges(" [X'00'," + Short.MIN_VALUE + ",51] - [" + PVarbinary.INSTANCE.toStringLiteral(new byte[] { (byte) (saltBuckets - 1) }) + "," - + Short.MIN_VALUE + ",51]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - } + + Short.MIN_VALUE + ",51]") + .clientSortAlgo("CLIENT MERGE SORT"); } String viewIndexName2 = "I_" + generateUniqueName(); @@ -1038,37 +1010,30 @@ public static Pair testUpdatableViewIndex(String fullTableName, In assertFalse(rs.next()); String physicalTableName; - plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertTrue( - explainPlanAttributes.getServerWhereFilter().equals("SERVER FILTER BY FIRST KEY ONLY") - || explainPlanAttributes.getServerWhereFilter() - .equals("SERVER FILTER BY EMPTY COLUMN ONLY")); if (localIndex) { physicalTableName = fullTableName; - assertEquals("PARALLEL " + (saltBuckets == null ? 1 : saltBuckets) + "-WAY", - explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals(viewIndexFullName2 + "(" + fullTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [" + (2) + ",'foo']", explainPlanAttributes.getKeyRanges()); + assertPlan(conn, query).scanType("RANGE SCAN") + .serverWhereFilterAnyOf("SERVER FILTER BY FIRST KEY ONLY", + "SERVER FILTER BY EMPTY COLUMN ONLY") + .iteratorType("PARALLEL " + (saltBuckets == null ? 1 : saltBuckets) + "-WAY") + .table(viewIndexFullName2 + "(" + fullTableName + ")").keyRanges(" [" + (2) + ",'foo']"); + } else if (saltBuckets == null) { + physicalTableName = viewIndexPhysicalName; + assertPlan(conn, query).scanType("RANGE SCAN") + .serverWhereFilterAnyOf("SERVER FILTER BY FIRST KEY ONLY", + "SERVER FILTER BY EMPTY COLUMN ONLY") + .iteratorType("PARALLEL 1-WAY").table(viewIndexPhysicalName) + .keyRanges(" [" + (Short.MIN_VALUE + 1) + ",'foo']").clientSortAlgo(null); } else { physicalTableName = viewIndexPhysicalName; - assertEquals(viewIndexPhysicalName, explainPlanAttributes.getTableName()); - if (saltBuckets == null) { - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals(" [" + (Short.MIN_VALUE + 1) + ",'foo']", - explainPlanAttributes.getKeyRanges()); - assertNull(explainPlanAttributes.getClientSortAlgo()); - } else { - assertEquals("PARALLEL " + saltBuckets + "-WAY", - explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals(" [X'00'," + (Short.MIN_VALUE + 1) + ",'foo'] - [" + assertPlan(conn, query).scanType("RANGE SCAN") + .serverWhereFilterAnyOf("SERVER FILTER BY FIRST KEY ONLY", + "SERVER FILTER BY EMPTY COLUMN ONLY") + .iteratorType("PARALLEL " + saltBuckets + "-WAY").table(viewIndexPhysicalName) + .keyRanges(" [X'00'," + (Short.MIN_VALUE + 1) + ",'foo'] - [" + PVarbinary.INSTANCE.toStringLiteral(new byte[] { (byte) (saltBuckets - 1) }) + "," - + (Short.MIN_VALUE + 1) + ",'foo']", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - } + + (Short.MIN_VALUE + 1) + ",'foo']") + .clientSortAlgo("CLIENT MERGE SORT"); } return new Pair<>(physicalTableName, scan); } 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..aa306208e66 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,8 @@ 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.assertPlan; +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; @@ -80,7 +82,6 @@ import org.apache.phoenix.schema.TableAlreadyExistsException; import org.apache.phoenix.schema.TableNotFoundException; 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.ViewUtil; @@ -235,9 +236,7 @@ public void testCreateViewMappedToExistingHbaseTableWithNSMappingEnabled() throw conn.createStatement() .execute("CREATE VIEW " + view1 + " (PK VARCHAR PRIMARY KEY, " + CF + ".COL VARCHAR)"); - assertTrue(QueryUtil - .getExplainPlan(conn.createStatement().executeQuery("explain select * from " + view1)) - .contains(NS + ":" + TBL)); + assertPlan(conn, "select * from " + view1).tableContains(NS + ":" + TBL); conn.createStatement().execute("DROP VIEW " + view1); admin.disableTable(tableName); @@ -256,9 +255,7 @@ public void testCreateViewMappedToExistingHbaseTableWithNSMappingEnabled() throw conn.createStatement() .execute("CREATE VIEW " + view2 + " (PK VARCHAR PRIMARY KEY, " + CF + ".COL VARCHAR)"); - assertTrue(QueryUtil - .getExplainPlan(conn.createStatement().executeQuery("explain select * from " + view2)) - .contains(NS + "." + TBL)); + assertPlan(conn, "select * from " + view2).tableContains(NS + "." + TBL); conn.createStatement().execute("DROP VIEW " + view2); admin.disableTable(tableName); @@ -277,9 +274,7 @@ public void testCreateViewMappedToExistingHbaseTableWithNSMappingEnabled() throw conn.createStatement() .execute("CREATE VIEW " + view3 + " (PK VARCHAR PRIMARY KEY, " + CF + ".COL VARCHAR)"); - assertTrue(QueryUtil - .getExplainPlan(conn.createStatement().executeQuery("explain select * from " + view3)) - .contains(NS + ":" + NS + "." + TBL)); + assertPlan(conn, "select * from " + view3).tableContains(NS + ":" + NS + "." + TBL); conn.createStatement().execute("DROP VIEW " + view3); admin.disableTable(tableName); @@ -947,13 +942,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/WhereOptimizerForArrayAnyIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/WhereOptimizerForArrayAnyIT.java index c9651f2113a..d7012009fe4 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/WhereOptimizerForArrayAnyIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/WhereOptimizerForArrayAnyIT.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; @@ -34,8 +35,6 @@ import java.sql.Timestamp; import java.sql.Types; import org.apache.hadoop.hbase.TableName; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.compile.QueryPlan; import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.jdbc.PhoenixStatement; @@ -770,10 +769,7 @@ private void assertBinaryValue(byte[] expected, byte[] actual) { } private void assertPointLookupsAreNotGenerated(PreparedStatement stmt) throws SQLException { - ExplainPlan explain = - stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes planAttributes = explain.getPlanStepsAsAttributes(); - assertEquals("FULL SCAN ", planAttributes.getExplainScanType()); + assertPlan(stmt.unwrap(PhoenixPreparedStatement.class)).scanType("FULL SCAN"); } private void assertPointLookupsAreGenerated(Statement stmt, String selectSql, diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/WhereOptimizerForArrayAnyITBase.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/WhereOptimizerForArrayAnyITBase.java index b33362e48df..40e2b6ec6b3 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/WhereOptimizerForArrayAnyITBase.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/WhereOptimizerForArrayAnyITBase.java @@ -17,14 +17,11 @@ */ package org.apache.phoenix.end2end; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.HashMap; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.compile.QueryPlan; import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.BaseTest; @@ -49,35 +46,23 @@ protected void assertPointLookupsAreGenerated(PreparedStatement stmt, int noOfPo protected void assertPointLookupsAreGenerated(QueryPlan queryPlan, int noOfPointLookups) throws SQLException { - ExplainPlan explain = queryPlan.getExplainPlan(); - ExplainPlanAttributes planAttributes = explain.getPlanStepsAsAttributes(); String expectedScanType = - "POINT LOOKUP ON " + noOfPointLookups + " KEY" + (noOfPointLookups > 1 ? "S " : " "); - assertEquals(expectedScanType, planAttributes.getExplainScanType()); + "POINT LOOKUP ON " + noOfPointLookups + " KEY" + (noOfPointLookups > 1 ? "S" : ""); + assertPlan(queryPlan.getExplainPlan().getPlanStepsAsAttributes()).scanType(expectedScanType); } protected void assertSkipScanIsGenerated(PreparedStatement stmt, int skipListSize) throws SQLException { - QueryPlan queryPlan = stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery(); - ExplainPlan explain = queryPlan.getExplainPlan(); - ExplainPlanAttributes planAttributes = explain.getPlanStepsAsAttributes(); String expectedScanType = - "SKIP SCAN ON " + skipListSize + " KEY" + (skipListSize > 1 ? "S " : " "); - assertEquals(expectedScanType, planAttributes.getExplainScanType()); + "SKIP SCAN ON " + skipListSize + " KEY" + (skipListSize > 1 ? "S" : ""); + assertPlan(stmt.unwrap(PhoenixPreparedStatement.class)).scanType(expectedScanType); } protected void assertRangeScanIsGenerated(PreparedStatement stmt) throws SQLException { - QueryPlan queryPlan = stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery(); - ExplainPlan explain = queryPlan.getExplainPlan(); - ExplainPlanAttributes planAttributes = explain.getPlanStepsAsAttributes(); - String expectedScanType = "RANGE SCAN "; - assertEquals(expectedScanType, planAttributes.getExplainScanType()); + assertPlan(stmt.unwrap(PhoenixPreparedStatement.class)).scanType("RANGE SCAN"); } protected void assertDegenerateScanIsGenerated(PreparedStatement stmt) throws SQLException { - QueryPlan queryPlan = stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery(); - ExplainPlan explain = queryPlan.getExplainPlan(); - ExplainPlanAttributes planAttributes = explain.getPlanStepsAsAttributes(); - assertNull(planAttributes.getExplainScanType()); + assertPlan(stmt.unwrap(PhoenixPreparedStatement.class)).scanType(null); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/WhereOptimizerForArrayAnyNullablePKIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/WhereOptimizerForArrayAnyNullablePKIT.java index c96a985cec1..c30542b2519 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/WhereOptimizerForArrayAnyNullablePKIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/WhereOptimizerForArrayAnyNullablePKIT.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.assertNotEquals; @@ -33,9 +34,6 @@ import java.sql.Types; import java.util.Arrays; import java.util.Collection; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; -import org.apache.phoenix.compile.QueryPlan; import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.junit.Assume; import org.junit.Test; @@ -233,13 +231,7 @@ private String createTableAndInsertTestDataForNullablePKTests() throws Exception } private void assertQueryUsesIndex(PreparedStatement stmt, String indexName) throws SQLException { - QueryPlan queryPlan = stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery(); - ExplainPlan explain = queryPlan.getExplainPlan(); - ExplainPlanAttributes planAttributes = explain.getPlanStepsAsAttributes(); - String tableName = planAttributes.getTableName(); - System.out.println("Explain plan: " + explain.toString()); - assertTrue("Expected query to use index " + indexName + " but used table " + tableName, - tableName != null && tableName.contains(indexName)); + assertPlan(stmt.unwrap(PhoenixPreparedStatement.class)).tableContains(indexName); } /** 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..3a1ffdcc9e8 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; @@ -53,15 +54,12 @@ import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Pair; import org.apache.phoenix.compile.ColumnResolver; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.compile.FromCompiler; import org.apache.phoenix.end2end.CreateTableIT; import org.apache.phoenix.end2end.IndexToolIT; import org.apache.phoenix.end2end.ParallelStatsDisabledIT; import org.apache.phoenix.exception.SQLExceptionCode; 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.parse.NamedTableNode; @@ -69,6 +67,7 @@ import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.QueryConstants; import org.apache.phoenix.query.QueryServices; +import org.apache.phoenix.query.explain.ExplainPlanTestUtil; import org.apache.phoenix.schema.PIndexState; import org.apache.phoenix.schema.PTable; import org.apache.phoenix.schema.PTableImpl; @@ -80,7 +79,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; @@ -152,30 +150,20 @@ public void testIndexWithNullableFixedWithCols() throws Exception { String query = "SELECT d.char_col1, int_col1 from " + fullTableName + " as d"; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY"); if (!uncovered) { // Optimizer would not select the uncovered index for this query - assertEquals( - columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY", - explainPlanAttributes.getServerWhereFilter()); + basePlan.serverWhereFilter( + columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY"); } - if (localIndex) { - assertEquals(fullIndexName + "(" + fullTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + basePlan.table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1]") + .clientSortAlgo("CLIENT MERGE SORT").scanType("RANGE SCAN"); } else if (!uncovered) { - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); + basePlan.table(fullIndexName).clientSortAlgo(null).scanType("FULL SCAN"); } - assertFalse("Explain plan regionLocation attribute should not be empty", - explainPlanAttributes.getRegionLocations().isEmpty()); + basePlan.regionLocationsNotEmpty(); ResultSet rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -584,26 +572,16 @@ public void testIndexWithNullableDateCol() throws Exception { String query = "SELECT" + (uncovered ? " /*+ INDEX(" + fullTableName + " " + indexName + ")*/ " : " ") + "int_pk from " + fullTableName; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals( - columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY", - explainPlanAttributes.getServerWhereFilter()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").serverWhereFilter( + columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY"); if (localIndex) { - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName + "(" + fullTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); + basePlan.scanType("RANGE SCAN").table(fullIndexName + "(" + fullTableName + ")") + .clientSortAlgo("CLIENT MERGE SORT").keyRanges(" [1]"); } else { - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("FULL SCAN").table(fullIndexName).clientSortAlgo(null); } - assertFalse("Explain plan regionLocation attribute should not be empty", - explainPlanAttributes.getRegionLocations().isEmpty()); + basePlan.regionLocationsNotEmpty(); ResultSet rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -615,23 +593,13 @@ public void testIndexWithNullableDateCol() throws Exception { assertFalse(rs.next()); query = "SELECT date_col from " + fullTableName + " order by date_col"; - plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals( - columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY", - explainPlanAttributes.getServerWhereFilter()); + basePlan = assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").serverWhereFilter( + columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY"); if (localIndex) { - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName + "(" + fullTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); + basePlan.scanType("RANGE SCAN").table(fullIndexName + "(" + fullTableName + ")") + .clientSortAlgo("CLIENT MERGE SORT").keyRanges(" [1]"); } else { - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("FULL SCAN").table(fullIndexName).clientSortAlgo(null); } rs = conn.createStatement().executeQuery(query); @@ -686,23 +654,15 @@ public void testSelectAllAndAliasWithIndex() throws Exception { query = "SELECT" + (uncovered ? " /*+ INDEX(" + fullTableName + " " + indexName + ")*/" : "") + " * FROM " + fullTableName; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY"); if (localIndex) { - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName + "(" + fullTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); + basePlan.scanType("RANGE SCAN").table(fullIndexName + "(" + fullTableName + ")") + .clientSortAlgo("CLIENT MERGE SORT").keyRanges(" [1]"); } else { - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("FULL SCAN").table(fullIndexName).clientSortAlgo(null); } - assertFalse("Explain plan regionLocation attribute should not be empty", - explainPlanAttributes.getRegionLocations().isEmpty()); + basePlan.regionLocationsNotEmpty(); rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -722,19 +682,9 @@ public void testSelectAllAndAliasWithIndex() throws Exception { assertFalse(rs.next()); query = "SELECT v1 as foo FROM " + fullTableName + " WHERE v2 = '1' ORDER BY foo"; - plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals("[\"V1\"]", explainPlanAttributes.getServerSortedBy()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - if (localIndex) { - assertEquals(fullIndexName + "(" + fullTableName + ")", - explainPlanAttributes.getTableName()); - } else { - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - } + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .serverSortedBy("[\"V1\"]").clientSortAlgo("CLIENT MERGE SORT") + .table(localIndex ? fullIndexName + "(" + fullTableName + ")" : fullIndexName); rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -784,29 +734,19 @@ public void testSelectCF() throws Exception { conn.commit(); query = "SELECT * FROM " + fullTableName; - 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(fullTableName, explainPlanAttributes.getTableName()); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(fullTableName); query = "SELECT" + (uncovered ? " /*+ INDEX(" + fullTableName + " " + indexName + ")*/ " : " ") + "a.* FROM " + fullTableName; - plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY"); if (localIndex) { - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName + "(" + fullTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("RANGE SCAN").table(fullIndexName + "(" + fullTableName + ")") + .keyRanges(" [1]").clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); + basePlan.scanType("FULL SCAN").table(fullIndexName); } rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -970,23 +910,15 @@ public void testMultipleUpdatesAcrossRegions() throws Exception { // make sure the index is working as expected query = "SELECT" + (uncovered ? " /*+ INDEX(" + testTable + " " + indexName + ")*/ " : " ") + "* FROM " + testTable; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals( - columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY", - explainPlanAttributes.getServerWhereFilter()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, query).serverWhereFilter( + columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY"); if (localIndex) { - assertEquals("PARALLEL 2-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName + "(" + testTable + ")", explainPlanAttributes.getTableName()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.iteratorType("PARALLEL 2-WAY").scanType("RANGE SCAN") + .table(fullIndexName + "(" + testTable + ")").keyRanges(" [1]") + .clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(fullIndexName) + .clientSortAlgo(null); } // check that the data table matches as expected @@ -1048,18 +980,12 @@ public void testIndexWithCaseSensitiveCols() throws Exception { } query = "SELECT * FROM " + fullTableName + " WHERE \"v2\" = '1'"; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); if (localIndex) { - assertEquals(fullIndexName + "(" + fullTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1,'1']", explainPlanAttributes.getKeyRanges()); + basePlan.table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1,'1']"); } else { - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - assertEquals(" ['1']", explainPlanAttributes.getKeyRanges()); + basePlan.table(fullIndexName).keyRanges(" ['1']"); } rs = conn.createStatement().executeQuery(query); @@ -1086,20 +1012,12 @@ public void testIndexWithCaseSensitiveCols() throws Exception { query = "SELECT \"V1\", \"V1\" as foo1, \"v2\" as foo, \"v2\" as \"Foo1\", \"v2\" FROM " + fullTableName + " ORDER BY foo"; - plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); + basePlan = assertPlan(conn, query).iteratorType("PARALLEL 1-WAY"); if (localIndex) { - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName + "(" + fullTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("RANGE SCAN").table(fullIndexName + "(" + fullTableName + ")") + .keyRanges(" [1]").clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("FULL SCAN").table(fullIndexName).clientSortAlgo(null); } rs = conn.createStatement().executeQuery(query); @@ -1220,20 +1138,13 @@ private void testIndexWithDecimalCol(boolean enableServerSideUpsert) throws Exce query = "SELECT" + (uncovered ? " /*+ INDEX(" + fullTableName + " " + indexName + ")*/ " : " ") + "decimal_pk, decimal_col1, decimal_col2 from " + fullTableName; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY"); if (localIndex) { - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName + "(" + fullTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("RANGE SCAN").table(fullIndexName + "(" + fullTableName + ")") + .keyRanges(" [1]").clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("FULL SCAN").table(fullIndexName).clientSortAlgo(null); } rs = conn.createStatement().executeQuery(query); @@ -1547,12 +1458,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..256e4ef8d24 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; @@ -53,15 +54,12 @@ import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Pair; import org.apache.phoenix.compile.ColumnResolver; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.compile.FromCompiler; import org.apache.phoenix.end2end.CreateTableIT; import org.apache.phoenix.end2end.IndexToolIT; import org.apache.phoenix.end2end.ParallelStatsDisabledWithRegionMovesIT; import org.apache.phoenix.exception.SQLExceptionCode; 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.parse.NamedTableNode; @@ -69,6 +67,7 @@ import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.QueryConstants; import org.apache.phoenix.query.QueryServices; +import org.apache.phoenix.query.explain.ExplainPlanTestUtil; import org.apache.phoenix.schema.PIndexState; import org.apache.phoenix.schema.PTable; import org.apache.phoenix.schema.PTableImpl; @@ -80,7 +79,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; @@ -168,26 +166,18 @@ public void testIndexWithNullableFixedWithCols() throws Exception { String query = "SELECT d.char_col1, int_col1 from " + fullTableName + " as d"; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY"); if (!uncovered) { // Optimizer would not select the uncovered index for this query - assertEquals( - columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY", - explainPlanAttributes.getServerWhereFilter()); + basePlan.serverWhereFilter( + columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY"); } - if (localIndex) { - assertEquals(fullTableName, explainPlanAttributes.getTableName()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + basePlan.table(fullTableName).keyRanges(" [1]").clientSortAlgo("CLIENT MERGE SORT") + .scanType("RANGE SCAN"); } else if (!uncovered) { - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); + basePlan.table(fullIndexName).clientSortAlgo(null).scanType("FULL SCAN"); } ResultSet rs = conn.createStatement().executeQuery(query); @@ -642,22 +632,14 @@ public void testIndexWithNullableDateCol() throws Exception { String query = "SELECT" + (uncovered ? " /*+ INDEX(" + fullTableName + " " + indexName + ")*/ " : " ") + "int_pk from " + fullTableName; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals( - columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY", - explainPlanAttributes.getServerWhereFilter()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").serverWhereFilter( + columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY"); if (localIndex) { - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullTableName, explainPlanAttributes.getTableName()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); + basePlan.scanType("RANGE SCAN").table(fullTableName).clientSortAlgo("CLIENT MERGE SORT") + .keyRanges(" [1]"); } else { - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("FULL SCAN").table(fullIndexName).clientSortAlgo(null); } ResultSet rs = conn.createStatement().executeQuery(query); @@ -674,22 +656,13 @@ public void testIndexWithNullableDateCol() throws Exception { assertFalse(rs.next()); query = "SELECT date_col from " + fullTableName + " order by date_col"; - plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals( - columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY", - explainPlanAttributes.getServerWhereFilter()); + basePlan = assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").serverWhereFilter( + columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY"); if (localIndex) { - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullTableName, explainPlanAttributes.getTableName()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); + basePlan.scanType("RANGE SCAN").table(fullTableName).clientSortAlgo("CLIENT MERGE SORT") + .keyRanges(" [1]"); } else { - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("FULL SCAN").table(fullIndexName).clientSortAlgo(null); } rs = conn.createStatement().executeQuery(query); @@ -750,19 +723,13 @@ public void testSelectAllAndAliasWithIndex() throws Exception { query = "SELECT" + (uncovered ? " /*+ INDEX(" + fullTableName + " " + indexName + ")*/" : "") + " * FROM " + fullTableName; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY"); if (localIndex) { - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullTableName, explainPlanAttributes.getTableName()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); + basePlan.scanType("RANGE SCAN").table(fullTableName).clientSortAlgo("CLIENT MERGE SORT") + .keyRanges(" [1]"); } else { - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("FULL SCAN").table(fullIndexName).clientSortAlgo(null); } rs = conn.createStatement().executeQuery(query); @@ -787,18 +754,9 @@ public void testSelectAllAndAliasWithIndex() throws Exception { assertFalse(rs.next()); query = "SELECT v1 as foo FROM " + fullTableName + " WHERE v2 = '1' ORDER BY foo"; - plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals("[\"V1\"]", explainPlanAttributes.getServerSortedBy()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - if (localIndex) { - assertEquals(fullTableName, explainPlanAttributes.getTableName()); - } else { - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - } + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .serverSortedBy("[\"V1\"]").clientSortAlgo("CLIENT MERGE SORT") + .table(localIndex ? fullTableName : fullIndexName); rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -852,28 +810,19 @@ public void testSelectCF() throws Exception { conn.commit(); query = "SELECT * FROM " + fullTableName; - 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(fullTableName, explainPlanAttributes.getTableName()); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(fullTableName); query = "SELECT" + (uncovered ? " /*+ INDEX(" + fullTableName + " " + indexName + ")*/ " : " ") + "a.* FROM " + fullTableName; - plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY"); if (localIndex) { - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullTableName, explainPlanAttributes.getTableName()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("RANGE SCAN").table(fullTableName).keyRanges(" [1]") + .clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); + basePlan.scanType("FULL SCAN").table(fullIndexName); } rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -1055,23 +1004,14 @@ public void testMultipleUpdatesAcrossRegions() throws Exception { // make sure the index is working as expected query = "SELECT" + (uncovered ? " /*+ INDEX(" + testTable + " " + indexName + ")*/ " : " ") + "* FROM " + testTable; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals( - columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY", - explainPlanAttributes.getServerWhereFilter()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, query).serverWhereFilter( + columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY"); if (localIndex) { - assertEquals("PARALLEL 2-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(testTable, explainPlanAttributes.getTableName()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.iteratorType("PARALLEL 2-WAY").scanType("RANGE SCAN").table(testTable) + .keyRanges(" [1]").clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(fullIndexName) + .clientSortAlgo(null); } // check that the data table matches as expected @@ -1139,17 +1079,12 @@ public void testIndexWithCaseSensitiveCols() throws Exception { } query = "SELECT * FROM " + fullTableName + " WHERE \"v2\" = '1'"; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); if (localIndex) { - assertEquals(fullTableName, explainPlanAttributes.getTableName()); - assertEquals(" [1,'1']", explainPlanAttributes.getKeyRanges()); + basePlan.table(fullTableName).keyRanges(" [1,'1']"); } else { - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - assertEquals(" ['1']", explainPlanAttributes.getKeyRanges()); + basePlan.table(fullIndexName).keyRanges(" ['1']"); } rs = conn.createStatement().executeQuery(query); @@ -1178,19 +1113,12 @@ public void testIndexWithCaseSensitiveCols() throws Exception { query = "SELECT \"V1\", \"V1\" as foo1, \"v2\" as foo, \"v2\" as \"Foo1\", \"v2\" FROM " + fullTableName + " ORDER BY foo"; - plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); + basePlan = assertPlan(conn, query).iteratorType("PARALLEL 1-WAY"); if (localIndex) { - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullTableName, explainPlanAttributes.getTableName()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("RANGE SCAN").table(fullTableName).keyRanges(" [1]") + .clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("FULL SCAN").table(fullIndexName).clientSortAlgo(null); } rs = conn.createStatement().executeQuery(query); @@ -1320,19 +1248,13 @@ private void testIndexWithDecimalCol(boolean enableServerSideUpsert) throws Exce query = "SELECT" + (uncovered ? " /*+ INDEX(" + fullTableName + " " + indexName + ")*/ " : " ") + "decimal_pk, decimal_col1, decimal_col2 from " + fullTableName; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY"); if (localIndex) { - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullTableName, explainPlanAttributes.getTableName()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("RANGE SCAN").table(fullTableName).keyRanges(" [1]") + .clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullIndexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("FULL SCAN").table(fullIndexName).clientSortAlgo(null); } rs = conn.createStatement().executeQuery(query); @@ -1673,14 +1595,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/ChildViewsUseParentViewIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ChildViewsUseParentViewIndexIT.java index 9b84aad8f79..5a8c41b2668 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ChildViewsUseParentViewIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ChildViewsUseParentViewIndexIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end.index; +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,12 +29,9 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.end2end.ParallelStatsDisabledIT; import org.apache.phoenix.end2end.ParallelStatsDisabledTest; import org.apache.phoenix.jdbc.PhoenixConnection; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.schema.PTable; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -165,16 +163,11 @@ private void assertQueryUsesIndex(final String baseTableName, final String viewN Connection conn, boolean isChildView) throws SQLException { String sql = "SELECT A0, A1, A2, A4 FROM " + viewName + " WHERE A4 IN ('1', '2', '3') ORDER BY A4, A2"; - ExplainPlan plan = conn.prepareStatement(sql).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("SKIP SCAN ON 3 KEYS ", explainPlanAttributes.getExplainScanType()); - assertEquals("_IDX_" + baseTableName, explainPlanAttributes.getTableName()); String childViewScanKey = isChildView ? ",'Y'" : ""; - assertEquals(" [" + Short.MIN_VALUE + ",'1'" + childViewScanKey + "] - [" + Short.MIN_VALUE - + ",'3'" + childViewScanKey + "]", explainPlanAttributes.getKeyRanges()); + assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").scanType("SKIP SCAN ON 3 KEYS") + .table("_IDX_" + baseTableName).keyRanges(" [" + Short.MIN_VALUE + ",'1'" + childViewScanKey + + "] - [" + Short.MIN_VALUE + ",'3'" + childViewScanKey + "]"); ResultSet rs = conn.createStatement().executeQuery(sql); assertTrue(rs.next()); @@ -193,15 +186,10 @@ private void assertQueryUsesIndex(final String baseTableName, final String viewN private void assertQueryUsesBaseTable(final String baseTableName, final String viewName, Connection conn) throws SQLException { String sql = "SELECT A0, A1, A2, A4 FROM " + viewName + " WHERE A4 IN ('1', '2', '3') "; - ExplainPlan plan = conn.prepareStatement(sql).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals( - "SERVER FILTER BY (A4 IN ('1','2','3') AND ((A1 = 'X' AND A2 = 'Y') AND A3 = 'Z'))", - explainPlanAttributes.getServerWhereFilter()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(baseTableName, explainPlanAttributes.getTableName()); + assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY") + .serverWhereFilter( + "SERVER FILTER BY (A4 IN ('1','2','3') AND ((A1 = 'X' AND A2 = 'Y') AND A3 = 'Z'))") + .scanType("FULL SCAN").table(baseTableName); ResultSet rs = conn.createStatement().executeQuery(sql); assertTrue(rs.next()); @@ -273,17 +261,11 @@ private void assertQueryIndex(String viewName, String baseTableName, Connection String sql = "SELECT WO_ID FROM " + viewName + " WHERE WO_ID IN ('003xxxxxxxxxxx1', '003xxxxxxxxxxx2', '003xxxxxxxxxxx3', '003xxxxxxxxxxx4', '003xxxxxxxxxxx5') " + " AND (A_DATE > TO_DATE('2016-01-01 06:00:00.0')) " + " ORDER BY WO_ID, A_DATE DESC"; - ExplainPlan plan = conn.prepareStatement(sql).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("SKIP SCAN ON 5 RANGES ", explainPlanAttributes.getExplainScanType()); - assertEquals("_IDX_" + baseTableName, explainPlanAttributes.getTableName()); - assertEquals( - " [" + Short.MIN_VALUE + ",'00Dxxxxxxxxxxx1','003xxxxxxxxxxx1',*] - [" + Short.MIN_VALUE - + ",'00Dxxxxxxxxxxx1','003xxxxxxxxxxx5',~'2016-01-01 06:00:00.000']", - explainPlanAttributes.getKeyRanges()); + assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").scanType("SKIP SCAN ON 5 RANGES") + .table("_IDX_" + baseTableName) + .keyRanges(" [" + Short.MIN_VALUE + ",'00Dxxxxxxxxxxx1','003xxxxxxxxxxx1',*] - [" + + Short.MIN_VALUE + ",'00Dxxxxxxxxxxx1','003xxxxxxxxxxx5',~'2016-01-01 06:00:00.000']"); ResultSet rs = conn.createStatement().executeQuery(sql); for (int i = 0; i < expectedRows; ++i) { diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java index 8d7d4c546ad..e174c37c56f 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; @@ -63,8 +66,8 @@ 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.SchemaUtil; import org.apache.phoenix.util.TestUtil; import org.junit.After; import org.junit.Assume; @@ -140,19 +143,25 @@ 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, String dataTableFullName, String indexTableFullName, int limit) throws SQLException { - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql); - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - IndexToolIT.assertExplainPlan(false, actualExplainPlan, dataTableFullName, indexTableFullName); - String expectedLimitPlan = String.format("SERVER %d ROW LIMIT", limit); - assertTrue(actualExplainPlan + "\n expected to contain \n" + expectedLimitPlan, - actualExplainPlan.contains(expectedLimitPlan)); + ExplainPlanAttributes attributes = getExplainAttributes(conn, selectSql); + assertPlan(attributes).scanType("RANGE SCAN").serverRowLimit((long) limit); + 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)); } private void populateTable(String tableName) throws Exception { @@ -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..c5240d9ff0c 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; @@ -70,8 +74,8 @@ 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.SchemaUtil; import org.apache.phoenix.util.TestUtil; import org.junit.After; import org.junit.Before; @@ -251,19 +255,25 @@ 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, String dataTableFullName, String indexTableFullName, int limit) throws SQLException { - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql); - String actualExplainPlan = QueryUtil.getExplainPlan(rs); - IndexToolIT.assertExplainPlan(false, actualExplainPlan, dataTableFullName, indexTableFullName); - String expectedLimitPlan = String.format("SERVER %d ROW LIMIT", limit); - assertTrue(actualExplainPlan + "\n expected to contain \n" + expectedLimitPlan, - actualExplainPlan.contains(expectedLimitPlan)); + ExplainPlanAttributes attributes = getExplainAttributes(conn, selectSql); + assertPlan(attributes).scanType("RANGE SCAN").serverRowLimit((long) limit); + 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)); } private void populateTable(String tableName) throws Exception { @@ -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/ImmutableIndexWithStatsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ImmutableIndexWithStatsIT.java index 575ba7be4d4..d998dd60a70 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ImmutableIndexWithStatsIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ImmutableIndexWithStatsIT.java @@ -17,8 +17,8 @@ */ 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; import static org.junit.Assert.assertTrue; @@ -27,11 +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.end2end.ParallelStatsEnabledIT; import org.apache.phoenix.end2end.ParallelStatsEnabledTest; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.util.PropertiesUtil; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -64,12 +61,7 @@ public void testIndexCreationDeadlockWithStats() throws Exception { conn.createStatement().execute("UPDATE STATISTICS " + tableName); query = "SELECT COUNT(*) FROM " + tableName; - - 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()); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN"); String indexName = "I_" + generateUniqueName(); conn.createStatement().execute("CREATE INDEX " + indexName + " ON " + tableName + " (v)"); 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..0abd7ca9135 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,11 +18,11 @@ 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; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -34,17 +34,14 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.end2end.ParallelStatsDisabledIT; import org.apache.phoenix.end2end.ParallelStatsDisabledTest; import org.apache.phoenix.exception.SQLExceptionCode; import org.apache.phoenix.execute.CommitException; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.QueryConstants; +import org.apache.phoenix.query.explain.ExplainPlanTestUtil; 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; @@ -133,24 +130,16 @@ protected void helpTestGroupByCount(boolean mutable, boolean localIndex) throws String groupBySql = "SELECT (int_col1+int_col2), COUNT(*) FROM " + fullDataTableName + " GROUP BY (int_col1+int_col2)"; - ExplainPlan plan = conn.prepareStatement(groupBySql).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals( - "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY " - + "[TO_BIGINT(\"(A.INT_COL1 + B.INT_COL2)\")]", - explainPlanAttributes.getServerAggregate()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, groupBySql) + .iteratorType("PARALLEL 1-WAY").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY " + + "[TO_BIGINT(\"(A.INT_COL1 + B.INT_COL2)\")]"); if (localIndex) { - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals("INDEX_TEST." + indexName + "(" + fullDataTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("RANGE SCAN") + .table("INDEX_TEST." + indexName + "(" + fullDataTableName + ")") + .clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals("INDEX_TEST." + indexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("FULL SCAN").table("INDEX_TEST." + indexName).clientSortAlgo(null); } ResultSet rs = conn.createStatement().executeQuery(groupBySql); assertTrue(rs.next()); @@ -195,26 +184,18 @@ protected void helpTestSelectDistinct(boolean mutable, boolean localIndex) throw conn.createStatement().execute(ddl); String sql = "SELECT distinct int_col1+1 FROM " + fullDataTableName + " where int_col1+1 > 0"; - ExplainPlan plan = conn.prepareStatement(sql).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); - assertEquals("SERVER DISTINCT PREFIX FILTER OVER [TO_BIGINT(\"(A.INT_COL1 + 1)\")]", - explainPlanAttributes.getServerDistinctFilter()); - assertEquals( - "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [TO_BIGINT(\"(A.INT_COL1 + 1)\")]", - explainPlanAttributes.getServerAggregate()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, sql) + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverDistinctFilter( + "SERVER DISTINCT PREFIX FILTER OVER " + "[TO_BIGINT(\"(A.INT_COL1 + 1)\")]") + .serverAggregate( + "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY " + "[TO_BIGINT(\"(A.INT_COL1 + 1)\")]"); if (localIndex) { - assertEquals("INDEX_TEST." + indexName + "(" + fullDataTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1,0] - [1,*]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.table("INDEX_TEST." + indexName + "(" + fullDataTableName + ")") + .keyRanges(" [1,0] - [1,*]").clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("INDEX_TEST." + indexName, explainPlanAttributes.getTableName()); - assertEquals(" [0] - [*]", explainPlanAttributes.getKeyRanges()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.table("INDEX_TEST." + indexName).keyRanges(" [0] - [*]").clientSortAlgo(null); } ResultSet rs = conn.createStatement().executeQuery(sql); @@ -262,21 +243,14 @@ protected void helpTestInClauseWithIndex(boolean mutable, boolean localIndex) th conn.createStatement().execute(ddl); String sql = "SELECT int_col1+1 FROM " + fullDataTableName + " where int_col1+1 IN (2)"; - ExplainPlan plan = conn.prepareStatement(sql).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); if (localIndex) { - assertEquals("INDEX_TEST." + indexName + "(" + fullDataTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1,2]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.table("INDEX_TEST." + indexName + "(" + fullDataTableName + ")") + .keyRanges(" [1,2]").clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("INDEX_TEST." + indexName, explainPlanAttributes.getTableName()); - assertEquals(" [2]", explainPlanAttributes.getKeyRanges()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.table("INDEX_TEST." + indexName).keyRanges(" [2]").clientSortAlgo(null); } ResultSet rs = conn.createStatement().executeQuery(sql); @@ -323,21 +297,14 @@ protected void helpTestSelectAliasAndOrderByWithIndex(boolean mutable, boolean l conn.createStatement().execute(ddl); String sql = "SELECT int_col1+1 AS foo FROM " + fullDataTableName + " ORDER BY foo"; - ExplainPlan plan = conn.prepareStatement(sql).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, sql) + .iteratorType("PARALLEL 1-WAY").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); if (localIndex) { - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals("INDEX_TEST." + indexName + "(" + fullDataTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("RANGE SCAN") + .table("INDEX_TEST." + indexName + "(" + fullDataTableName + ")").keyRanges(" [1]") + .clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals("INDEX_TEST." + indexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("FULL SCAN").table("INDEX_TEST." + indexName).clientSortAlgo(null); } ResultSet rs = conn.createStatement().executeQuery(sql); @@ -404,19 +371,13 @@ protected void helpTestIndexWithCaseSensitiveCols(boolean mutable, boolean local query = "SELECT (\"V1\" || '_' || \"v2\"), k, \"V1\", \"v2\" FROM " + dataTableName + " WHERE (\"V1\" || '_' || \"v2\") = 'x_1'"; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); if (localIndex) { - assertEquals(indexName + "(" + dataTableName + ")", explainPlanAttributes.getTableName()); - assertEquals(" [1,'x_1']", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.table(indexName + "(" + dataTableName + ")").keyRanges(" [1,'x_1']") + .clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals(indexName, explainPlanAttributes.getTableName()); - assertEquals(" ['x_1']", explainPlanAttributes.getKeyRanges()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.table(indexName).keyRanges(" ['x_1']").clientSortAlgo(null); } rs = conn.createStatement().executeQuery(query); @@ -435,19 +396,12 @@ protected void helpTestIndexWithCaseSensitiveCols(boolean mutable, boolean local query = "SELECT \"V1\", \"V1\" as foo1, (\"V1\" || '_' || \"v2\") as foo, (\"V1\" || '_' || \"v2\") as \"Foo1\", (\"V1\" || '_' || \"v2\") FROM " + dataTableName + " ORDER BY foo"; - plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); + basePlan = assertPlan(conn, query).iteratorType("PARALLEL 1-WAY"); if (localIndex) { - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(indexName + "(" + dataTableName + ")", explainPlanAttributes.getTableName()); - assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("RANGE SCAN").table(indexName + "(" + dataTableName + ")") + .keyRanges(" [1]").clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(indexName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.scanType("FULL SCAN").table(indexName).clientSortAlgo(null); } rs = conn.createStatement().executeQuery(query); @@ -517,24 +471,15 @@ protected void helpTestSelectColOnlyInDataTable(boolean mutable, boolean localIn conn.createStatement().execute(ddl); String sql = "SELECT int_col1+1, int_col2 FROM " + fullDataTableName + " WHERE int_col1+1=2"; - ExplainPlan plan = conn.prepareStatement(sql).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY"); if (localIndex) { - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals("INDEX_TEST." + indexName + "(" + fullDataTableName + ")", - explainPlanAttributes.getTableName()); - assertEquals(" [1,2]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", - explainPlanAttributes.getServerWhereFilter()); + basePlan.scanType("RANGE SCAN") + .table("INDEX_TEST." + indexName + "(" + fullDataTableName + ")").keyRanges(" [1,2]") + .clientSortAlgo("CLIENT MERGE SORT").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); } else { - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(fullDataTableName, explainPlanAttributes.getTableName()); - assertNull(explainPlanAttributes.getClientSortAlgo()); - assertEquals("SERVER FILTER BY (A.INT_COL1 + 1) = 2", - explainPlanAttributes.getServerWhereFilter()); + basePlan.scanType("FULL SCAN").table(fullDataTableName).clientSortAlgo(null) + .serverWhereFilter("SERVER FILTER BY (A.INT_COL1 + 1) = 2"); } ResultSet rs = conn.createStatement().executeQuery(sql); @@ -582,19 +527,14 @@ private void helpTestUpdatableViewIndex(boolean local) throws Exception { conn.commit(); String query = "SELECT k1, k2, k3, s1, s2 FROM " + viewName + " WHERE k1+k2+k3 = 173.0"; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN"); if (local) { - assertEquals(indexName1 + "(" + dataTableName + ")", explainPlanAttributes.getTableName()); - assertEquals(" [1,173]", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.table(indexName1 + "(" + dataTableName + ")").keyRanges(" [1,173]") + .clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("_IDX_" + dataTableName, explainPlanAttributes.getTableName()); - assertEquals(" [" + Short.MIN_VALUE + ",173]", explainPlanAttributes.getKeyRanges()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.table("_IDX_" + dataTableName).keyRanges(" [" + Short.MIN_VALUE + ",173]") + .clientSortAlgo(null); } rs = conn.createStatement().executeQuery(query); @@ -610,21 +550,14 @@ private void helpTestUpdatableViewIndex(boolean local) throws Exception { + " on " + viewName + "(s1||'_'||s2)"); query = "SELECT k1, k2, s1||'_'||s2 FROM " + viewName + " WHERE (s1||'_'||s2)='foo2_bar2'"; - plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); + basePlan = assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); if (local) { - assertEquals(indexName2 + "(" + dataTableName + ")", explainPlanAttributes.getTableName()); - assertEquals(" [" + (2) + ",'foo2_bar2']", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.table(indexName2 + "(" + dataTableName + ")") + .keyRanges(" [" + (2) + ",'foo2_bar2']").clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals("_IDX_" + dataTableName, explainPlanAttributes.getTableName()); - assertEquals(" [" + (Short.MIN_VALUE + 1) + ",'foo2_bar2']", - explainPlanAttributes.getKeyRanges()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.table("_IDX_" + dataTableName) + .keyRanges(" [" + (Short.MIN_VALUE + 1) + ",'foo2_bar2']").clientSortAlgo(null); } rs = conn.createStatement().executeQuery(query); @@ -680,14 +613,9 @@ private void helpTestViewUsesTableIndex(boolean immutable) throws Exception { String query = "SELECT s2||'_'||s3 FROM " + viewName + " WHERE k2=1 AND (s2||'_'||s3)='abc_cab'"; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(indexName2, explainPlanAttributes.getTableName()); - assertEquals(" [1,'abc_cab','foo']", explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .table(indexName2).keyRanges(" [1,'abc_cab','foo']") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -696,11 +624,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)); @@ -784,20 +710,14 @@ protected void helpTestCaseSensitiveFunctionIndex(boolean mutable, boolean local query = "SELECT k FROM " + dataTableName + " WHERE REGEXP_SUBSTR(v,'id:\\\\w+') = 'id:id1'"; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); if (localIndex) { - assertEquals(indexName + "(" + dataTableName + ")", explainPlanAttributes.getTableName()); - assertEquals(" [1,'id:id1']", explainPlanAttributes.getKeyRanges()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + basePlan.table(indexName + "(" + dataTableName + ")").keyRanges(" [1,'id:id1']") + .clientSortAlgo("CLIENT MERGE SORT"); } else { - assertEquals(indexName, explainPlanAttributes.getTableName()); - assertEquals(" ['id:id1']", explainPlanAttributes.getKeyRanges()); - assertNull(explainPlanAttributes.getClientSortAlgo()); + basePlan.table(indexName).keyRanges(" ['id:id1']").clientSortAlgo(null); } rs = conn.createStatement().executeQuery(query); @@ -860,21 +780,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..3577e7435d4 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; @@ -49,8 +50,6 @@ import org.apache.hadoop.hbase.coprocessor.SimpleRegionObserver; import org.apache.hadoop.hbase.regionserver.MiniBatchOperationInProgress; import org.apache.hadoop.hbase.util.Bytes; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.coprocessor.MetaDataRegionObserver; import org.apache.phoenix.coprocessor.MetaDataRegionObserver.BuildIndexScheduleTask; import org.apache.phoenix.end2end.NeedsOwnMiniClusterTest; @@ -60,7 +59,6 @@ import org.apache.phoenix.index.PhoenixIndexFailurePolicy; import org.apache.phoenix.jdbc.PhoenixConnection; import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.QueryConstants; import org.apache.phoenix.query.QueryServices; @@ -303,15 +301,10 @@ public void testIndexWriteFailure() throws Exception { addRowToTable(conn, fullTableName); query = "SELECT /*+ NO_INDEX */ k,v1 FROM " + fullTableName; - ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) - .optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 2-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals( - SchemaUtil.getPhysicalTableName(fullTableName.getBytes(), isNamespaceMapped).toString(), - explainPlanAttributes.getTableName()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + assertPlan(conn, query).iteratorType("PARALLEL 2-WAY").scanType("FULL SCAN") + .table( + SchemaUtil.getPhysicalTableName(fullTableName.getBytes(), isNamespaceMapped).toString()) + .clientSortAlgo("CLIENT MERGE SORT"); rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -353,15 +346,10 @@ public void testIndexWriteFailure() throws Exception { updateTableAgain(conn, false); // Verify previous writes succeeded to data table query = "SELECT /*+ NO_INDEX */ k,v1 FROM " + fullTableName; - plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class).optimizeQuery() - .getExplainPlan(); - explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("PARALLEL 2-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals( - SchemaUtil.getPhysicalTableName(fullTableName.getBytes(), isNamespaceMapped).toString(), - explainPlanAttributes.getTableName()); - assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); + assertPlan(conn, query).iteratorType("PARALLEL 2-WAY").scanType("FULL SCAN") + .table( + SchemaUtil.getPhysicalTableName(fullTableName.getBytes(), isNamespaceMapped).toString()) + .clientSortAlgo("CLIENT MERGE SORT"); rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -458,17 +446,14 @@ private void addRowsInTableDuringRetry(final String tableName) boolean wasFailWrite = FailingRegionObserver.FAIL_WRITE; boolean wasToggleFailWriteForRetry = FailingRegionObserver.TOGGLE_FAIL_WRITE_FOR_RETRY; try { - Callable callable = new Callable() { - + Callable callable = new Callable() { @Override public Boolean call() { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); props.put(QueryServices.IS_NAMESPACE_MAPPING_ENABLED, String.valueOf(isNamespaceMapped)); try (Connection conn = driver.connect(url, props)) { // In case of disable index on failure policy, INDEX will be in PENDING_DISABLE on first - // retry - // but will - // become active if retry is successfull + // retry but will become active if retry is successfull PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " + tableName + " VALUES(?,?,?)"); stmt.setString(1, "b"); @@ -511,18 +496,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..efa8528f0dd 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 @@ -47,8 +47,6 @@ import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.mapreduce.CounterGroup; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.end2end.IndexToolIT; import org.apache.phoenix.end2end.NeedsOwnMiniClusterTest; import org.apache.phoenix.exception.PhoenixParserException; @@ -59,12 +57,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; @@ -110,7 +108,7 @@ public static synchronized Collection data() { { false, true, true } }); } - public static void assertPlan(PhoenixResultSet rs, String schemaName, String tableName) { + public static void assertCurrentTable(PhoenixResultSet rs, String schemaName, String tableName) { PTable table = rs.getContext().getCurrentTable().getTable(); assertTrue(table.getSchemaName().getString().equals(schemaName) && table.getTableName().getString().equals(tableName)); @@ -239,7 +237,7 @@ public void testAtomicUpsert() throws Exception { String selectSql = "SELECT D from " + dataTableName + " WHERE A > 60"; ResultSet rs = conn.createStatement().executeQuery(selectSql); // Verify that the index table is used - assertPlan((PhoenixResultSet) rs, "", indexTableName); + assertCurrentTable((PhoenixResultSet) rs, "", indexTableName); assertTrue(rs.next()); assertEquals("b", rs.getString(1)); assertFalse(rs.next()); @@ -251,7 +249,7 @@ public void testAtomicUpsert() throws Exception { selectSql = "SELECT D from " + dataTableName + " WHERE A = 50"; rs = conn.createStatement().executeQuery(selectSql); // Verify that the index table is not used - assertPlan((PhoenixResultSet) rs, "", dataTableName); + assertCurrentTable((PhoenixResultSet) rs, "", dataTableName); // explain plan verify to check if partial index is not used rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql); assertTrue(rs.next()); @@ -289,7 +287,7 @@ public void testAtomicUpsert() throws Exception { // Retrieve update row from the data table and verify that the index table is not used selectSql = "SELECT ID from " + dataTableName + " WHERE A = 0"; rs = conn.createStatement().executeQuery(selectSql); - assertPlan((PhoenixResultSet) rs, "", dataTableName); + assertCurrentTable((PhoenixResultSet) rs, "", dataTableName); assertTrue(rs.next()); assertEquals("id2", rs.getString(1)); // explain plan verify to check if partial index is not used @@ -336,7 +334,7 @@ public void testComparisonOfColumns() throws Exception { ResultSet rs = conn.createStatement().executeQuery(selectSql); // Verify that the index table is used - assertPlan((PhoenixResultSet) rs, "", indexTableName); + assertCurrentTable((PhoenixResultSet) rs, "", indexTableName); assertTrue(rs.next()); assertEquals("a", rs.getString(1)); assertFalse(rs.next()); @@ -348,7 +346,7 @@ public void testComparisonOfColumns() throws Exception { selectSql = "SELECT D from " + dataTableName + " WHERE A > 100"; rs = conn.createStatement().executeQuery(selectSql); // Verify that the index table is not used - assertPlan((PhoenixResultSet) rs, "", dataTableName); + assertCurrentTable((PhoenixResultSet) rs, "", dataTableName); // explain plan verify to check if partial index is not used rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql); assertTrue(rs.next()); @@ -424,7 +422,7 @@ public void testIsNull() throws Exception { ResultSet rs = conn.createStatement().executeQuery(selectSql); // Verify that the index table is used - assertPlan((PhoenixResultSet) rs, "", indexTableName); + assertCurrentTable((PhoenixResultSet) rs, "", indexTableName); assertTrue(rs.next()); assertEquals(70, rs.getInt(1)); assertEquals("a", rs.getString(2)); @@ -457,7 +455,7 @@ public void testIsNull() throws Exception { rs = conn.createStatement().executeQuery("SELECT Count(*) from " + dataTableName); // Verify that the index table is not used - assertPlan((PhoenixResultSet) rs, "", dataTableName); + assertCurrentTable((PhoenixResultSet) rs, "", dataTableName); assertTrue(rs.next()); assertEquals(5, rs.getInt(1)); // explain plan verify to check if partial index is not used @@ -515,7 +513,7 @@ public void testLike() throws Exception { "SELECT D from " + dataTableName + " WHERE B is not NULL AND D like '%cde_'"; ResultSet rs = conn.createStatement().executeQuery(selectSql); // Verify that the index table is used - assertPlan((PhoenixResultSet) rs, "", indexTableName); + assertCurrentTable((PhoenixResultSet) rs, "", indexTableName); assertTrue(rs.next()); assertEquals("abcdef", rs.getString(1)); assertFalse(rs.next()); @@ -545,7 +543,7 @@ public void testLike() throws Exception { selectSql = "SELECT Count(*) from " + dataTableName; rs = conn.createStatement().executeQuery(selectSql); // Verify that the index table is not used - assertPlan((PhoenixResultSet) rs, "", dataTableName); + assertCurrentTable((PhoenixResultSet) rs, "", dataTableName); assertTrue(rs.next()); assertEquals(5, rs.getInt(1)); // explain plan verify to check if partial index is not used @@ -605,7 +603,7 @@ public void testPhoenixRowTimestamp() throws Exception { + "', 'yyyy-MM-dd HH:mm:ss.SSS', '" + timeZoneID + "')"; ResultSet rs = conn.createStatement().executeQuery(selectSql); // Verify that the index table is used - assertPlan((PhoenixResultSet) rs, "", indexTableName); + assertCurrentTable((PhoenixResultSet) rs, "", indexTableName); assertTrue(rs.next()); assertEquals(0, rs.getInt(1)); assertTrue(rs.next()); @@ -624,7 +622,7 @@ public void testPhoenixRowTimestamp() throws Exception { rs = conn.createStatement().executeQuery("SELECT Count(*) from " + dataTableName); // Verify that the index table is not used - assertPlan((PhoenixResultSet) rs, "", dataTableName); + assertCurrentTable((PhoenixResultSet) rs, "", dataTableName); assertTrue(rs.next()); assertEquals(4, rs.getInt(1)); // explain plan verify to check if partial index is not used @@ -645,7 +643,7 @@ public void testPhoenixRowTimestamp() throws Exception { rs = conn.createStatement().executeQuery(selectSql); // Verify that the index table is used - assertPlan((PhoenixResultSet) rs, "", indexTableName); + assertCurrentTable((PhoenixResultSet) rs, "", indexTableName); assertTrue(rs.next()); assertEquals(0, rs.getInt(1)); assertFalse(rs.next()); @@ -723,7 +721,7 @@ public void testViewIndexes() throws Exception { // Verify that query uses the tenant view index ResultSet rs = tenantConn.createStatement() .executeQuery("SELECT KV1 FROM " + tenantViewName + " WHERE PK3 = 5"); - assertPlan((PhoenixResultSet) rs, "", tenantViewIndexName); + assertCurrentTable((PhoenixResultSet) rs, "", tenantViewIndexName); assertTrue(rs.next()); assertEquals("KV10", rs.getString(1)); assertFalse(rs.next()); @@ -732,7 +730,7 @@ public void testViewIndexes() throws Exception { // where clause does not contain the query where clause rs = tenantConn.createStatement() .executeQuery("SELECT KV1 FROM " + tenantViewName + " WHERE PK3 = 4"); - assertPlan((PhoenixResultSet) rs, "", tenantViewName); + assertCurrentTable((PhoenixResultSet) rs, "", tenantViewName); assertTrue(rs.next()); assertEquals("KV8", rs.getString(1)); assertFalse(rs.next()); @@ -740,7 +738,7 @@ public void testViewIndexes() throws Exception { // Verify that the tenant view index has only one row rs = tenantConn.createStatement().executeQuery("SELECT Count(*) FROM " + tenantViewIndexName); - assertPlan((PhoenixResultSet) rs, "", tenantViewIndexName); + assertCurrentTable((PhoenixResultSet) rs, "", tenantViewIndexName); assertTrue(rs.next()); assertEquals(1, rs.getInt(1)); } @@ -751,7 +749,7 @@ public void testViewIndexes() throws Exception { // Verify that the query uses the global view index ResultSet rs = tenantConn.createStatement() .executeQuery("SELECT KV1 FROM " + tenantViewName + " WHERE PK3 = 1 AND KV3 = 'KV3'"); - assertPlan((PhoenixResultSet) rs, "", tenantViewName + "#" + globalViewIndexName); + assertCurrentTable((PhoenixResultSet) rs, "", tenantViewName + "#" + globalViewIndexName); assertTrue(rs.next()); assertEquals("KV1", rs.getString(1)); assertFalse(rs.next()); @@ -760,14 +758,14 @@ public void testViewIndexes() throws Exception { // Verify that the query uses the global view index ResultSet rs = conn.createStatement() .executeQuery("SELECT KV1 FROM " + globalViewName + " WHERE PK3 = 1 AND KV3 = 'KV3'"); - assertPlan((PhoenixResultSet) rs, "", globalViewIndexName); + assertCurrentTable((PhoenixResultSet) rs, "", globalViewIndexName); assertTrue(rs.next()); assertEquals("KV1", rs.getString(1)); assertFalse(rs.next()); // Verify that the global view index has five rows rs = conn.createStatement().executeQuery("SELECT Count(*) FROM " + globalViewIndexName); - assertPlan((PhoenixResultSet) rs, "", globalViewIndexName); + assertCurrentTable((PhoenixResultSet) rs, "", globalViewIndexName); assertTrue(rs.next()); assertEquals(5, rs.getInt(1)); } @@ -801,7 +799,7 @@ public void testPartialIndexPreferredOverFullIndex() throws Exception { String selectSql = "SELECT D from " + dataTableName + " WHERE A > 60"; // Verify that the partial index table is used ResultSet rs = conn.createStatement().executeQuery(selectSql); - assertPlan((PhoenixResultSet) rs, "", partialIndexTableName); + assertCurrentTable((PhoenixResultSet) rs, "", partialIndexTableName); assertTrue(rs.next()); assertEquals("b", rs.getString(1)); assertFalse(rs.next()); @@ -813,7 +811,7 @@ public void testPartialIndexPreferredOverFullIndex() throws Exception { selectSql = "SELECT D from " + dataTableName + " WHERE A < 50"; // Verify that the full index table is used rs = conn.createStatement().executeQuery(selectSql); - assertPlan((PhoenixResultSet) rs, "", fullIndexTableName); + assertCurrentTable((PhoenixResultSet) rs, "", fullIndexTableName); assertTrue(rs.next()); assertEquals("a", rs.getString(1)); assertFalse(rs.next()); @@ -855,7 +853,7 @@ public void testPartialIndexWithJson() throws Exception { + " WHERE (CAST(TO_NUMBER(JSON_VALUE(jsoncol, '$.info.age')) AS INTEGER)) > 60"; ResultSet rs = conn.createStatement().executeQuery(selectSql); // Verify that the index table is used - assertPlan((PhoenixResultSet) rs, "", indexTableName); + assertCurrentTable((PhoenixResultSet) rs, "", indexTableName); assertTrue(rs.next()); assertEquals("b", rs.getString(1)); assertFalse(rs.next()); @@ -864,7 +862,7 @@ public void testPartialIndexWithJson() throws Exception { + " WHERE (CAST(TO_NUMBER(JSON_VALUE(jsoncol, '$.info.age')) AS INTEGER)) = 50"; rs = conn.createStatement().executeQuery(selectSql); // Verify that the index table is not used - assertPlan((PhoenixResultSet) rs, "", dataTableName); + assertCurrentTable((PhoenixResultSet) rs, "", dataTableName); // Add more rows to test the index write path conn.createStatement().execute("upsert into " + dataTableName @@ -896,7 +894,7 @@ public void testPartialIndexWithJson() throws Exception { selectSql = "SELECT ID from " + dataTableName + " WHERE (CAST(TO_NUMBER(JSON_VALUE(jsoncol, '$.info.age')) AS INTEGER)) = 0"; rs = conn.createStatement().executeQuery(selectSql); - assertPlan((PhoenixResultSet) rs, "", dataTableName); + assertCurrentTable((PhoenixResultSet) rs, "", dataTableName); assertTrue(rs.next()); assertEquals("id2", rs.getString(1)); @@ -943,7 +941,7 @@ public void testPartialIndexWithJsonExists() throws Exception { + " WHERE A > 60 AND JSON_EXISTS(jsoncol, '$.info.address.exists')"; ResultSet rs = conn.createStatement().executeQuery(selectSql); // Verify that the index table is used - assertPlan((PhoenixResultSet) rs, "", indexTableName); + assertCurrentTable((PhoenixResultSet) rs, "", indexTableName); assertTrue(rs.next()); assertEquals(70, rs.getInt(1)); assertEquals("a", rs.getString(2)); @@ -969,7 +967,7 @@ public void testPartialIndexWithJsonExists() throws Exception { rs = conn.createStatement().executeQuery("SELECT Count(*) from " + dataTableName); // Verify that the index table is not used - assertPlan((PhoenixResultSet) rs, "", dataTableName); + assertCurrentTable((PhoenixResultSet) rs, "", dataTableName); assertTrue(rs.next()); assertEquals(5, rs.getInt(1)); @@ -1014,26 +1012,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)); @@ -1192,13 +1187,9 @@ public void testPartialIndexWithVarbinaryEncoded() throws Exception { private static void verifyIndexUsed(PreparedStatement preparedStatement, String partialIndexTableName, int buckets) throws SQLException { - ExplainPlan plan = - preparedStatement.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(partialIndexTableName, explainPlanAttributes.getTableName()); - assertEquals("PARALLEL " + buckets + "-WAY", - explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + ExplainPlanTestUtil.assertPlan(preparedStatement.unwrap(PhoenixPreparedStatement.class)) + .table(partialIndexTableName).iteratorType("PARALLEL " + buckets + "-WAY") + .scanType("RANGE SCAN"); } private static String toStringLiteral(byte[] b) { 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..59d69aa160f 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,44 +51,65 @@ 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)").sortMergeSkipMerge(false) + .lhs().scanType("FULL SCAN").table(supplierIndex) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"S.:supplier_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end().rhs() + .abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(true) + .clientSortedBy("[\"I.0:supplier_id\"]").lhs() + .scanType("FULL SCAN").table(itemIndex).serverSortedBy("[\"I.:item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end().rhs() + .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)").sortMergeSkipMerge(false) + .lhs().scanType("FULL SCAN").table(itemIndex) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"I1.:item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end().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)").sortMergeSkipMerge(false) + .clientRowLimit(4).lhs().scanType("FULL SCAN").table(itemIndex) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"I.:item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end().rhs().scanType("FULL SCAN") + .table(order).serverSortedBy(queryIndex == 0 ? "[\"O.item_id\"]" : "[\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end(); } - @Parameters(name = "SortMergeJoinGlobalIndexIT_{index}") // name is used by failsafe as file name - // in reports + // name is used by failsafe as file name in reports + @Parameters(name = "SortMergeJoinGlobalIndexIT_{index}") public static synchronized Collection data() { List testCases = Lists.newArrayList(); 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 (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..ec31377e1a7 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,74 @@ 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)").sortMergeSkipMerge(false) + .lhs().scanType("RANGE SCAN").table(supplierIndex + "(" + supplier + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"S.:supplier_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end().rhs() + .abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(true) + .clientSortedBy("[\"I.0:supplier_id\"]").lhs() + .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverSortedBy("[\"I.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT") + .end().rhs().scanType("FULL SCAN").table(order) + .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)").sortMergeSkipMerge(false) + .lhs().scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"I1.:item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end().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)").sortMergeSkipMerge(false) + .clientRowLimit(4).lhs().scanType("RANGE SCAN").table(itemIndex + "(" + item + ")") + .keyRanges(" [1]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverSortedBy("[\"I.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end().rhs() + .scanType("FULL SCAN").table(order) + .serverSortedBy(queryIndex == 0 ? "[\"O.item_id\"]" : "[\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end(); } - @Parameters(name = "SortMergeJoinLocalIndexIT_{index}") // name is used by failsafe as file name - // in reports + // name is used by failsafe as file name in reports + @Parameters(name = "SortMergeJoinLocalIndexIT_{index}") 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..4cd6b59dbb5 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); } - @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\"]", + @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)").sortMergeSkipMerge(false) + .lhs().scanType("FULL SCAN").table(supplier).end().rhs() + .abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(true) + .clientSortedBy("[\"I.supplier_id\"]").lhs().scanType("FULL SCAN").table(item).end().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)").sortMergeSkipMerge(false) + .lhs().scanType("FULL SCAN").table(item).end().rhs().scanType("FULL SCAN").table(item) + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); + } - "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", + @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)").sortMergeSkipMerge(false) + .clientRowLimit(4).lhs().scanType("FULL SCAN").table(item).end().rhs().scanType("FULL SCAN") + .table(order).serverSortedBy(queryIndex == 0 ? "[\"O.item_id\"]" : "[\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end(); + } - "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" } }); + // name is used by failsafe as file name in reports + @Parameters(name = "SortMergeJoinNoIndexIT_{index}") + public static synchronized Collection data() { + List testCases = Lists.newArrayList(); + 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..125c911d88f 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).lhs().lhs().scanType("FULL SCAN").table(tableName5) + .iteratorType("PARALLEL").end().end().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).lhs().lhs().scanType("FULL SCAN").table(tableName5) + .iteratorType("PARALLEL").end().end().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..612c521867a 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; @@ -110,7 +110,7 @@ public void testSimpleJsonValue() throws Exception { rs = conn.createStatement().executeQuery(query); assertFalse(rs.next()); - // check if the explain plan indicates server side execution + // Check here for the JSON server side projection rs = conn.createStatement().executeQuery("EXPLAIN " + query); assertTrue(QueryUtil.getExplainPlan(rs).contains(" SERVER JSON FUNCTION PROJECTION")); } @@ -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/monitoring/HBaseScanMetricsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/monitoring/HBaseScanMetricsIT.java index a8f86f7b277..354b72ab82d 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/monitoring/HBaseScanMetricsIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/monitoring/HBaseScanMetricsIT.java @@ -17,6 +17,8 @@ */ package org.apache.phoenix.monitoring; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; + import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; @@ -33,11 +35,8 @@ import org.apache.hadoop.hbase.regionserver.CompactSplit; import org.apache.hadoop.hbase.regionserver.HRegion; import org.apache.hadoop.hbase.util.VersionInfo; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.end2end.NeedsOwnMiniClusterTest; import org.apache.phoenix.hbase.index.IndexRegionObserver; -import org.apache.phoenix.jdbc.PhoenixStatement; import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.PhoenixRuntime; @@ -285,11 +284,7 @@ public void testQueryOnUncoveredIndex() throws Exception { stmt.execute("UPSERT INTO " + tableName + " (k1, k2, v1, v2) VALUES (3, 'c', 'c1', 'c2')"); conn.commit(); String sql = "SELECT k1, k2, v1, v2 FROM " + tableName + " WHERE v1 = 'b1'"; - ExplainPlan explainPlan = - stmt.unwrap(PhoenixStatement.class).optimizeQuery(sql).getExplainPlan(); - ExplainPlanAttributes planAttributes = explainPlan.getPlanStepsAsAttributes(); - String tableNameFromExplainPlan = planAttributes.getTableName(); - Assert.assertEquals(indexName, tableNameFromExplainPlan); + assertPlan(conn, sql).table(indexName); ResultSet rs = stmt.executeQuery(sql); assertOnReadsFromMemstore(indexName, getQueryReadMetrics(rs)); TestUtil.flush(utility, TableName.valueOf(tableName)); @@ -316,11 +311,7 @@ public void testQueryOnCoveredIndexWithoutReadRepair() throws Exception { stmt.execute("UPSERT INTO " + tableName + " (k1, k2, v1, v2) VALUES (3, 'c', 'c1', 'c2')"); conn.commit(); String sql = "SELECT k1, k2, v1, v2 FROM " + tableName + " WHERE v1 = 'b1'"; - ExplainPlan explainPlan = - stmt.unwrap(PhoenixStatement.class).optimizeQuery(sql).getExplainPlan(); - ExplainPlanAttributes planAttributes = explainPlan.getPlanStepsAsAttributes(); - String tableNameFromExplainPlan = planAttributes.getTableName(); - Assert.assertEquals(indexName, tableNameFromExplainPlan); + assertPlan(conn, sql).table(indexName); ResultSet rs = stmt.executeQuery(sql); assertOnReadsFromMemstore(indexName, getQueryReadMetrics(rs)); TestUtil.flush(utility, TableName.valueOf(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/it/java/org/apache/phoenix/schema/stats/BaseStatsCollectorIT.java b/phoenix-core/src/it/java/org/apache/phoenix/schema/stats/BaseStatsCollectorIT.java index 6622f208063..ab8f625b3e9 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/schema/stats/BaseStatsCollectorIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/schema/stats/BaseStatsCollectorIT.java @@ -21,12 +21,12 @@ import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_STATS_TABLE; import static org.apache.phoenix.mapreduce.util.PhoenixConfigurationUtil.MAPREDUCE_JOB_TYPE; import static org.apache.phoenix.mapreduce.util.PhoenixConfigurationUtil.MRJobType.UPDATE_STATS; +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.getAllSplits; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -54,12 +54,9 @@ import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.mapreduce.Job; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.coprocessor.UngroupedAggregateRegionObserver; import org.apache.phoenix.jdbc.PhoenixConnection; import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.ConnectionQueryServices; import org.apache.phoenix.query.KeyRange; @@ -71,7 +68,6 @@ import org.apache.phoenix.transaction.PhoenixTransactionProvider.Feature; import org.apache.phoenix.transaction.TransactionFactory; import org.apache.phoenix.util.MetaDataUtil; -import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.PropertiesUtil; import org.apache.phoenix.util.ReadOnlyProps; import org.apache.phoenix.util.SchemaUtil; @@ -258,17 +254,10 @@ public void testUpdateEmptyStats() throws Exception { conn.createStatement() .execute("CREATE TABLE " + fullTableName + " ( k CHAR(1) PRIMARY KEY )" + tableDDLOptions); collectStatistics(conn, fullTableName); - ExplainPlan plan = conn.prepareStatement("SELECT * FROM " + fullTableName) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(1, (int) planAttributes.getSplitsChunk()); - assertEquals(0, (long) planAttributes.getEstimatedRows()); - assertEquals(20, (long) planAttributes.getEstimatedSizeInBytes()); - assertEquals("PARALLEL 1-WAY", planAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", planAttributes.getExplainScanType()); - assertEquals(physicalTableName, planAttributes.getTableName()); - assertEquals("SERVER FILTER BY " + (columnEncoded ? "FIRST KEY ONLY" : "EMPTY COLUMN ONLY"), - planAttributes.getServerWhereFilter()); + assertPlan(conn, "SELECT * FROM " + fullTableName).splitsChunk(1).estimatedRows(0L) + .estimatedBytes(20L).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(physicalTableName).serverWhereFilter( + "SERVER FILTER BY " + (columnEncoded ? "FIRST KEY ONLY" : "EMPTY COLUMN ONLY")); conn.close(); } @@ -286,45 +275,24 @@ public void testSomeUpdateEmptyStats() throws Exception { // if we are using the ONE_CELL_PER_COLUMN_FAMILY storage scheme, we will have the single kv // even though there are no values for col family v2 - ExplainPlan plan = conn.prepareStatement("SELECT v2 FROM " + fullTableName + " WHERE v2='foo'") - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(columnEncoded && !mutable ? 4 : 3, (int) planAttributes.getSplitsChunk()); - assertEquals(columnEncoded && !mutable ? 1 : 0, (long) planAttributes.getEstimatedRows()); - assertEquals(columnEncoded && !mutable ? 38 : 20, - (long) planAttributes.getEstimatedSizeInBytes()); - assertEquals("PARALLEL 3-WAY", planAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", planAttributes.getExplainScanType()); - assertEquals(physicalTableName, planAttributes.getTableName()); - assertEquals("SERVER FILTER BY B.V2 = 'foo'", planAttributes.getServerWhereFilter()); - assertEquals("CLIENT MERGE SORT", planAttributes.getClientSortAlgo()); + assertPlan(conn, "SELECT v2 FROM " + fullTableName + " WHERE v2='foo'") + .splitsChunk(columnEncoded && !mutable ? 4 : 3) + .estimatedRows(columnEncoded && !mutable ? 1L : 0L) + .estimatedBytes(columnEncoded && !mutable ? 38L : 20L).iteratorType("PARALLEL 3-WAY") + .scanType("FULL SCAN").table(physicalTableName) + .serverWhereFilter("SERVER FILTER BY B.V2 = 'foo'").clientSortAlgo("CLIENT MERGE SORT"); long estimatedSizeInBytes = columnEncoded ? 28 : TransactionFactory.Provider.OMID.name().equals(transactionProvider) ? 38 : 34; - plan = conn.prepareStatement("SELECT * FROM " + fullTableName) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - planAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(4, (int) planAttributes.getSplitsChunk()); - assertEquals(1, (long) planAttributes.getEstimatedRows()); - assertEquals(estimatedSizeInBytes, (long) planAttributes.getEstimatedSizeInBytes()); - assertEquals("PARALLEL 3-WAY", planAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", planAttributes.getExplainScanType()); - assertEquals(physicalTableName, planAttributes.getTableName()); - assertNull(planAttributes.getServerWhereFilter()); - assertEquals("CLIENT MERGE SORT", planAttributes.getClientSortAlgo()); - - plan = conn.prepareStatement("SELECT * FROM " + fullTableName + " WHERE k = 'a'") - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - planAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(1, (int) planAttributes.getSplitsChunk()); - assertEquals(1, (long) planAttributes.getEstimatedRows()); - assertEquals(columnEncoded ? 204 : 202, (long) planAttributes.getEstimatedSizeInBytes()); - assertEquals("PARALLEL 1-WAY", planAttributes.getIteratorTypeAndScanSize()); - assertEquals("POINT LOOKUP ON 1 KEY ", planAttributes.getExplainScanType()); - assertEquals(physicalTableName, planAttributes.getTableName()); - assertNull(planAttributes.getServerWhereFilter()); - assertEquals("CLIENT MERGE SORT", planAttributes.getClientSortAlgo()); + assertPlan(conn, "SELECT * FROM " + fullTableName).splitsChunk(4).estimatedRows(1L) + .estimatedBytes(estimatedSizeInBytes).iteratorType("PARALLEL 3-WAY").scanType("FULL SCAN") + .table(physicalTableName).serverWhereFilter(null).clientSortAlgo("CLIENT MERGE SORT"); + + assertPlan(conn, "SELECT * FROM " + fullTableName + " WHERE k = 'a'").splitsChunk(1) + .estimatedRows(1L).estimatedBytes(columnEncoded ? 204L : 202L).iteratorType("PARALLEL 1-WAY") + .scanType("POINT LOOKUP ON 1 KEY").table(physicalTableName).serverWhereFilter(null) + .clientSortAlgo("CLIENT MERGE SORT"); conn.close(); } @@ -333,7 +301,6 @@ public void testSomeUpdateEmptyStats() throws Exception { public void testUpdateStats() throws Exception { Connection conn; PreparedStatement stmt; - ResultSet rs; Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); conn = getConnection(); conn.createStatement().execute("CREATE TABLE " + fullTableName @@ -343,9 +310,7 @@ public void testUpdateStats() throws Exception { Array array; conn = upsertValues(props, fullTableName); collectStatistics(conn, fullTableName); - rs = conn.createStatement().executeQuery("EXPLAIN SELECT k FROM " + fullTableName); - rs.next(); - long rows1 = (Long) rs.getObject(PhoenixRuntime.EXPLAIN_PLAN_ESTIMATED_ROWS_READ_COLUMN); + Long rows1 = assertPlan(conn, "SELECT k FROM " + fullTableName).attributes().getEstimatedRows(); stmt = upsertStmt(conn, fullTableName); stmt.setString(1, "z"); s = new String[] { "xyz", "def", "ghi", "jkll", null, null, "xxx" }; @@ -357,9 +322,7 @@ public void testUpdateStats() throws Exception { stmt.execute(); conn.commit(); collectStatistics(conn, fullTableName); - rs = conn.createStatement().executeQuery("EXPLAIN SELECT k FROM " + fullTableName); - rs.next(); - long rows2 = (Long) rs.getObject(PhoenixRuntime.EXPLAIN_PLAN_ESTIMATED_ROWS_READ_COLUMN); + Long rows2 = assertPlan(conn, "SELECT k FROM " + fullTableName).attributes().getEstimatedRows(); assertNotEquals(rows1, rows2); conn.close(); } @@ -611,15 +574,9 @@ public void testWithMultiCF() throws Exception { : (TransactionFactory.Provider.OMID.name().equals(transactionProvider)) ? 25044 : 12420; - ExplainPlan plan = conn.prepareStatement("SELECT * FROM " + fullTableName) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(26, (int) planAttributes.getSplitsChunk()); - assertEquals(25, (long) planAttributes.getEstimatedRows()); - assertEquals(sizeInBytes, (long) planAttributes.getEstimatedSizeInBytes()); - assertEquals("PARALLEL 1-WAY", planAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", planAttributes.getExplainScanType()); - assertEquals(physicalTableName, planAttributes.getTableName()); + assertPlan(conn, "SELECT * FROM " + fullTableName).splitsChunk(26).estimatedRows(25L) + .estimatedBytes(sizeInBytes).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(physicalTableName); ConnectionQueryServices services = conn.unwrap(PhoenixConnection.class).getQueryServices(); List regions = @@ -680,15 +637,9 @@ public void testWithMultiCF() throws Exception { assertEquals(0, rs.getLong(1)); assertFalse(rs.next()); - plan = conn.prepareStatement("SELECT * FROM " + fullTableName) - .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - planAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(1, (int) planAttributes.getSplitsChunk()); - assertNull(planAttributes.getEstimatedRows()); - assertNull(planAttributes.getEstimatedSizeInBytes()); - assertEquals("PARALLEL 1-WAY", planAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", planAttributes.getExplainScanType()); - assertEquals(physicalTableName, planAttributes.getTableName()); + assertPlan(conn, "SELECT * FROM " + fullTableName).splitsChunk(1).estimatedRows(null) + .estimatedBytes(null).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") + .table(physicalTableName); } @Test 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/compile/TenantSpecificViewIndexCompileTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java index 02af616cb44..c0436178405 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java @@ -17,20 +17,22 @@ */ package org.apache.phoenix.compile; +import static org.apache.phoenix.query.explain.ExplainPlanTestUtil.assertPlan; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.sql.SQLException; import java.util.Calendar; +import java.util.List; import java.util.Properties; import java.util.TimeZone; import org.apache.phoenix.query.BaseConnectionlessQueryTest; +import org.apache.phoenix.query.explain.ExplainPlanTestUtil; import org.apache.phoenix.util.DateUtil; import org.apache.phoenix.util.PhoenixRuntime; -import org.apache.phoenix.util.QueryUtil; import org.junit.Test; public class TenantSpecificViewIndexCompileTest extends BaseConnectionlessQueryTest { @@ -49,11 +51,9 @@ public void testOrderByOptimizedOut() throws Exception { conn.createStatement().execute("CREATE VIEW v(v2 VARCHAR) AS SELECT * FROM t WHERE k1 = 'a'"); conn.createStatement().execute("CREATE INDEX i1 ON v(v2) INCLUDE(v1)"); - ResultSet rs = - conn.createStatement().executeQuery("EXPLAIN SELECT v1,v2 FROM v WHERE v2 > 'a' ORDER BY v2"); - assertEquals( - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER _IDX_T [-9223372036854775808,'me','a'] - [-9223372036854775808,'me',*]", - QueryUtil.getExplainPlan(rs)); + assertPlan(conn, "SELECT v1,v2 FROM v WHERE v2 > 'a' ORDER BY v2") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table("_IDX_T") + .keyRanges(" [-9223372036854775808,'me','a'] - [-9223372036854775808,'me',*]"); } @Test @@ -69,35 +69,29 @@ public void testOrderByOptimizedOutWithoutPredicateInView() throws Exception { // Query without predicate ordered by full row key String sql = "SELECT * FROM v1 ORDER BY k1, k2, k3"; - String expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T ['tenant123456789']"; - assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput); + assertRangeScan(conn, sql, " ['tenant123456789']"); assertOrderByHasBeenOptimizedOut(conn, sql); // Predicate with valid partial PK sql = "SELECT * FROM v1 WHERE k1 = 'xyz' ORDER BY k1, k2, k3"; - expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T ['tenant123456789','xyz']"; - assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput); + assertRangeScan(conn, sql, " ['tenant123456789','xyz']"); assertOrderByHasBeenOptimizedOut(conn, sql); sql = "SELECT * FROM v1 WHERE k1 > 'xyz' ORDER BY k1, k2, k3"; - expectedExplainOutput = - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T ['tenant123456789','xy{'] - ['tenant123456789',*]"; - assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput); + assertRangeScan(conn, sql, " ['tenant123456789','xy{'] - ['tenant123456789',*]"); assertOrderByHasBeenOptimizedOut(conn, sql); String datePredicate = createStaticDate(); sql = "SELECT * FROM v1 WHERE k1 = 'xyz' AND k2 = '123456789012345' AND k3 < TO_DATE('" + datePredicate + "') ORDER BY k1, k2, k3"; - expectedExplainOutput = - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T ['tenant123456789','xyz','123456789012345',*] - ['tenant123456789','xyz','123456789012345','2015-01-01 08:00:00.000']"; - assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput); + assertRangeScan(conn, sql, " ['tenant123456789','xyz','123456789012345',*] - " + + "['tenant123456789','xyz','123456789012345','2015-01-01 08:00:00.000']"); assertOrderByHasBeenOptimizedOut(conn, sql); // Predicate without valid partial PK sql = "SELECT * FROM v1 WHERE k2 < 'abcde1234567890' ORDER BY k1, k2, k3"; - expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T ['tenant123456789']\n" - + " SERVER FILTER BY K2 < 'abcde1234567890'"; - assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput); + assertRangeScanWithFilter(conn, sql, " ['tenant123456789']", + "SERVER FILTER BY K2 < 'abcde1234567890'"); assertOrderByHasBeenOptimizedOut(conn, sql); } @@ -113,43 +107,36 @@ public void testOrderByOptimizedOutWithPredicateInView() throws Exception { // Query without predicate ordered by full row key String sql = "SELECT * FROM v1 ORDER BY k2, k3"; - String expectedExplainOutput = - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T ['tenant123456789','xyz']"; - assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput); + assertRangeScan(conn, sql, " ['tenant123456789','xyz']"); assertOrderByHasBeenOptimizedOut(conn, sql); // Query without predicate ordered by full row key, but without column view predicate sql = "SELECT * FROM v1 ORDER BY k2, k3"; - expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T ['tenant123456789','xyz']"; - assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput); + assertRangeScan(conn, sql, " ['tenant123456789','xyz']"); assertOrderByHasBeenOptimizedOut(conn, sql); // Predicate with valid partial PK sql = "SELECT * FROM v1 WHERE k1 = 'xyz' ORDER BY k2, k3"; - expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T ['tenant123456789','xyz']"; - assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput); + assertRangeScan(conn, sql, " ['tenant123456789','xyz']"); assertOrderByHasBeenOptimizedOut(conn, sql); sql = "SELECT * FROM v1 WHERE k2 < 'abcde1234567890' ORDER BY k2, k3"; - expectedExplainOutput = - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T ['tenant123456789','xyz',*] - ['tenant123456789','xyz','abcde1234567890']"; - assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput); + assertRangeScan(conn, sql, + " ['tenant123456789','xyz',*] - ['tenant123456789','xyz','abcde1234567890']"); assertOrderByHasBeenOptimizedOut(conn, sql); // Predicate with full PK String datePredicate = createStaticDate(); sql = "SELECT * FROM v1 WHERE k2 = '123456789012345' AND k3 < TO_DATE('" + datePredicate + "') ORDER BY k2, k3"; - expectedExplainOutput = - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T ['tenant123456789','xyz','123456789012345',*] - ['tenant123456789','xyz','123456789012345','2015-01-01 08:00:00.000']"; - assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput); + assertRangeScan(conn, sql, " ['tenant123456789','xyz','123456789012345',*] - " + + "['tenant123456789','xyz','123456789012345','2015-01-01 08:00:00.000']"); assertOrderByHasBeenOptimizedOut(conn, sql); // Predicate with valid partial PK sql = "SELECT * FROM v1 WHERE k3 < TO_DATE('" + datePredicate + "') ORDER BY k2, k3"; - expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T ['tenant123456789','xyz']\n" - + " SERVER FILTER BY K3 < DATE '" + datePredicate + "'"; - assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput); + assertRangeScanWithFilter(conn, sql, " ['tenant123456789','xyz']", + "SERVER FILTER BY K3 < DATE '" + datePredicate + "'"); assertOrderByHasBeenOptimizedOut(conn, sql); } @@ -166,30 +153,26 @@ public void testOrderByOptimizedOutWithMultiplePredicatesInView() throws Excepti // Query without predicate ordered by full row key String sql = "SELECT * FROM v1 ORDER BY k3 DESC"; - String expectedExplainOutput = - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T ['tenant123456789','xyz','abcde']"; - assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput); + assertRangeScan(conn, sql, " ['tenant123456789','xyz','abcde']"); assertOrderByHasBeenOptimizedOut(conn, sql); // Query without predicate ordered by full row key, but without column view predicate sql = "SELECT * FROM v1 ORDER BY k3 DESC"; - expectedExplainOutput = - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T ['tenant123456789','xyz','abcde']"; - assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput); + assertRangeScan(conn, sql, " ['tenant123456789','xyz','abcde']"); assertOrderByHasBeenOptimizedOut(conn, sql); // Query with predicate ordered by full row key sql = "SELECT * FROM v1 WHERE k3 <= TO_DATE('" + createStaticDate() + "') ORDER BY k3 DESC"; - expectedExplainOutput = - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T ['tenant123456789','xyz','abcde',~'2015-01-01 08:00:00.000'] - ['tenant123456789','xyz','abcde',*]"; - assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput); + assertRangeScan(conn, sql, " ['tenant123456789','xyz','abcde',~'2015-01-01 08:00:00.000'] - " + + "['tenant123456789','xyz','abcde',*]"); assertOrderByHasBeenOptimizedOut(conn, sql); // Query with predicate ordered by full row key with date in reverse order sql = "SELECT * FROM v1 WHERE k3 <= TO_DATE('" + createStaticDate() + "') ORDER BY k3"; - expectedExplainOutput = - "CLIENT PARALLEL 1-WAY REVERSE RANGE SCAN OVER T ['tenant123456789','xyz','abcde',~'2015-01-01 08:00:00.000'] - ['tenant123456789','xyz','abcde',*]"; - assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput); + assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table("T") + .clientSortedBy("REVERSE") + .keyRanges(" ['tenant123456789','xyz','abcde',~'2015-01-01 08:00:00.000'] - " + + "['tenant123456789','xyz','abcde',*]"); assertOrderByHasBeenOptimizedOut(conn, sql); } @@ -208,25 +191,26 @@ public void testViewConstantsOptimizedOut() throws Exception { conn.createStatement().execute("CREATE VIEW v(v2 VARCHAR) AS SELECT * FROM t WHERE k2 = 'a'"); conn.createStatement().execute("CREATE INDEX i1 ON v(v2)"); - ResultSet rs = conn.createStatement() - .executeQuery("EXPLAIN SELECT v2 FROM v WHERE v2 > 'a' and k2 = 'a' ORDER BY v2,k2"); - assertEquals( - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER _IDX_T [-9223372036854775808,'me','a'] - [-9223372036854775808,'me',*]\n" - + " SERVER FILTER BY FIRST KEY ONLY", - QueryUtil.getExplainPlan(rs)); + assertPlan(conn, "SELECT v2 FROM v WHERE v2 > 'a' and k2 = 'a' ORDER BY v2,k2") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table("_IDX_T") + .keyRanges(" [-9223372036854775808,'me','a'] - [-9223372036854775808,'me',*]") + .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); // Won't use index b/c v1 is not in index, but should optimize out k2 still from the order by // K2 will still be referenced in the filter, as these are automatically tacked on to the where // clause. - rs = conn.createStatement().executeQuery("EXPLAIN SELECT v1 FROM v WHERE v2 > 'a' ORDER BY k2"); - assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER T ['me']\n" - + " SERVER FILTER BY (V2 > 'a' AND K2 = 'a')", QueryUtil.getExplainPlan(rs)); + assertPlan(conn, "SELECT v1 FROM v WHERE v2 > 'a' ORDER BY k2").iteratorType("PARALLEL 1-WAY") + .scanType("RANGE SCAN").table("T").keyRanges(" ['me']") + .serverWhereFilter("SERVER FILTER BY (V2 > 'a' AND K2 = 'a')"); // If we match K2 against a constant not equal to it's view constant, we should get a degenerate - // plan - rs = conn.createStatement() - .executeQuery("EXPLAIN SELECT v1 FROM v WHERE v2 > 'a' and k2='b' ORDER BY k2"); - assertEquals("DEGENERATE SCAN OVER V", QueryUtil.getExplainPlan(rs)); + // plan. The DEGENERATE SCAN ExplainPlan does not populate structured attributes, so this + // single-literal check on the plan-steps text is retained. + List degenerateSteps = ExplainPlanTestUtil.getPlanSteps(conn, + "SELECT v1 FROM v WHERE v2 > 'a' and k2='b' ORDER BY k2"); + assertEquals(1, degenerateSteps.size()); + assertTrue("expected DEGENERATE SCAN OVER V, got " + degenerateSteps, + degenerateSteps.get(0).contains("DEGENERATE SCAN OVER V")); } @Test @@ -246,11 +230,9 @@ public void testViewConstantsOptimizedOutOnReadOnlyView() throws Exception { // Confirm that a read-only view on an updatable view still optimizes out the read-only parts of // the updatable view - ResultSet rs = conn.createStatement() - .executeQuery("EXPLAIN SELECT v2 FROM v2 WHERE v3 > 'a' and k2 = 'a' ORDER BY v3,k2"); - assertEquals( - "CLIENT PARALLEL 1-WAY RANGE SCAN OVER _IDX_T [-9223372036854775808,'me','a'] - [-9223372036854775808,'me',*]", - QueryUtil.getExplainPlan(rs)); + assertPlan(conn, "SELECT v2 FROM v2 WHERE v3 > 'a' and k2 = 'a' ORDER BY v3,k2") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table("_IDX_T") + .keyRanges(" [-9223372036854775808,'me','a'] - [-9223372036854775808,'me',*]"); } // ----------------------------------------------------------------- @@ -265,10 +247,15 @@ private Connection createTenantSpecificConnection() throws SQLException { return conn; } - private void assertExplainPlanIsCorrect(Connection conn, String sql, String expectedExplainOutput) - throws SQLException { - ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + sql); - assertEquals(expectedExplainOutput, QueryUtil.getExplainPlan(rs)); + private void assertRangeScan(Connection conn, String sql, String keyRanges) throws SQLException { + assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table("T") + .keyRanges(keyRanges); + } + + private void assertRangeScanWithFilter(Connection conn, String sql, String keyRanges, + String serverWhereFilter) throws SQLException { + assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table("T") + .keyRanges(keyRanges).serverWhereFilter(serverWhereFilter); } private void assertOrderByHasBeenOptimizedOut(Connection conn, String sql) throws SQLException { 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 f3ea93f12cd..38f44fabea3 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,6 +18,7 @@ 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.Iterator; @@ -39,8 +40,8 @@ public final class ExplainJsonNormalizer { */ public JsonNode normalize(JsonNode node) { // Temp-alias state is shared across the entire tree (top-level + recursive - // rhsJoinQueryExplainPlan) so that an alias appearing in multiple string fields renumbers - // consistently. + // lhsJoinQueryExplainPlan / rhsJoinQueryExplainPlan) so that an alias appearing in multiple + // string fields renumbers consistently. return normalize(node, new TempAliasRenumberer()); } @@ -53,6 +54,9 @@ private JsonNode normalize(JsonNode node, TempAliasRenumberer aliases) { if (obj.has("regionLocations")) { obj.set("regionLocations", NullNode.getInstance()); } + if (obj.has("regionLocationsTotalSize")) { + obj.set("regionLocationsTotalSize", NullNode.getInstance()); + } if (obj.has("numRegionLocationLookups")) { obj.put("numRegionLocationLookups", 0); } @@ -91,11 +95,24 @@ private JsonNode normalize(JsonNode node, TempAliasRenumberer aliases) { obj.put(u.getKey(), u.getValue()); } + JsonNode lhs = obj.get("lhsJoinQueryExplainPlan"); + if (lhs != null && lhs.isObject()) { + normalize(lhs, aliases); + } + JsonNode rhs = obj.get("rhsJoinQueryExplainPlan"); if (rhs != null && rhs.isObject()) { normalize(rhs, aliases); } + JsonNode subPlans = obj.get("subPlans"); + if (subPlans != null && subPlans.isArray()) { + ArrayNode subPlansArray = (ArrayNode) subPlans; + for (JsonNode subPlan : subPlansArray) { + normalize(subPlan, aliases); + } + } + 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 67% 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 e7905df91b0..66fe6b8b711 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,8 +25,10 @@ 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.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.sql.Connection; import java.sql.DriverManager; @@ -44,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; @@ -65,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"; @@ -203,7 +204,8 @@ public void testAggregateOrderedDistinct() throws Exception { " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]", "CLIENT MERGE SORT"), scanAttrs("FULL SCAN ", "ATABLE", "") .put("serverAggregate", "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]") - .put("clientSortAlgo", "CLIENT MERGE SORT")); + .put("clientSortAlgo", "CLIENT MERGE SORT") + .set("clientSteps", clientSteps("CLIENT MERGE SORT"))); } @Test @@ -217,7 +219,8 @@ public void testAggregateHashDistinct() throws Exception { scanAttrs("FULL SCAN ", "ATABLE", "") .put("serverWhereFilter", "SERVER FILTER BY A_INTEGER = 1") .put("serverAggregate", "SERVER AGGREGATE INTO DISTINCT ROWS BY [ENTITY_ID, ROUND(A_TIME)]") - .put("clientSortAlgo", "CLIENT MERGE SORT")); + .put("clientSortAlgo", "CLIENT MERGE SORT") + .set("clientSteps", clientSteps("CLIENT MERGE SORT"))); } @Test @@ -227,7 +230,8 @@ public void testTopNSortedBy() throws Exception { " SERVER TOP 3 ROWS SORTED BY [A_STRING DESC]", "CLIENT MERGE SORT", "CLIENT LIMIT 3"), scanAttrs("FULL SCAN ", "ATABLE", "").put("serverSortedBy", "[A_STRING DESC]") .put("serverRowLimit", 3).put("clientRowLimit", 3) - .put("clientSortAlgo", "CLIENT MERGE SORT")); + .put("clientSortAlgo", "CLIENT MERGE SORT") + .set("clientSteps", clientSteps("CLIENT MERGE SORT", "CLIENT LIMIT 3"))); } @Test @@ -239,7 +243,9 @@ public void testClientFilterByMax() throws Exception { "CLIENT FILTER BY MAX(A_STRING) = 'a'"), scanAttrs("FULL SCAN ", "ATABLE", "") .put("serverAggregate", "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]") - .put("clientFilterBy", "MAX(A_STRING) = 'a'").put("clientSortAlgo", "CLIENT MERGE SORT")); + .put("clientFilterBy", "MAX(A_STRING) = 'a'").put("clientSortAlgo", "CLIENT MERGE SORT") + .set("clientSteps", + clientSteps("CLIENT MERGE SORT", "CLIENT FILTER BY MAX(A_STRING) = 'a'"))); } @Test @@ -254,7 +260,8 @@ public void testClientLimit() throws Exception { scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']") .put("serverWhereFilter", "SERVER FILTER BY (ENTITY_ID != '00E00000000002' AND X_INTEGER = 2 AND A_INTEGER < 5)") - .put("serverRowLimit", 10).put("clientRowLimit", 10)); + .put("serverRowLimit", 10).put("clientRowLimit", 10) + .set("clientSteps", clientSteps("CLIENT 10 ROW LIMIT"))); } @Test @@ -265,9 +272,186 @@ 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") + .set("clientSteps", clientSteps("CLIENT MERGE SORT", "CLIENT 5 ROW LIMIT"))); + } + + @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") + .set("clientSteps", clientSteps("CLIENT MERGE SORT", "CLIENT LIMIT 10"))); + } + + @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") + .set("clientSteps", clientSteps("CLIENT MERGE SORT", "CLIENT LIMIT 10"))); + } + + @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") + .set("clientSteps", clientSteps("CLIENT MERGE SORT", "CLIENT 10 ROW LIMIT"))); + } + + @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") + .set("clientSteps", clientSteps("CLIENT MERGE SORT", "CLIENT FILTER BY MAX(A_STRING) = 'a'", + "CLIENT SORTED BY [B_STRING]"))); + } + @Test public void testSortMergeJoin() throws Exception { + // After the SMJ reshape the root is a synthetic node carrying just the join header and the + // two operand plans as separate lhs / rhs children. + ObjectNode lhs = scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']"); ObjectNode rhs = scanAttrs("FULL SCAN ", "ATABLE", ""); + ObjectNode expected = defaultAttrs(); + expected.put("abstractExplainPlan", "SORT-MERGE-JOIN (INNER)"); + expected.set("lhsJoinQueryExplainPlan", lhs); + expected.set("rhsJoinQueryExplainPlan", rhs); verifyQuery("sortMergeJoin", "SELECT /*+ USE_SORT_MERGE_JOIN */ a.a_string, b.a_string FROM atable a" + " JOIN atable b ON a.organization_id = b.organization_id" @@ -275,12 +459,19 @@ public void testSortMergeJoin() throws Exception { text("SORT-MERGE-JOIN (INNER) TABLES", " CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']", "AND", " CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE"), - scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']") - .put("abstractExplainPlan", "SORT-MERGE-JOIN (INNER)").set("rhsJoinQueryExplainPlan", rhs)); + expected); } @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" @@ -288,15 +479,21 @@ 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 { // Phoenix temp aliases are renamed by first appearance so this case asserts on the canonical // "$1.$2" form. + 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)); + expected.put("dynamicServerFilter", + "DYNAMIC SERVER FILTER BY ATABLE.ORGANIZATION_ID IN ($1.$2)"); verifyQuery("hashJoinSemiInSubquery", "SELECT a_string FROM atable" + " WHERE organization_id IN (SELECT organization_id FROM atable WHERE a_integer = 1)", @@ -305,7 +502,7 @@ public void testHashJoinSemiInSubquery() throws Exception { " SERVER FILTER BY A_INTEGER = 1", " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [ORGANIZATION_ID]", " DYNAMIC SERVER FILTER BY ATABLE.ORGANIZATION_ID IN ($1.$2)"), - defaultAttrs()); + expected); } @Test @@ -356,8 +553,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 @@ -384,7 +584,8 @@ public void testSequenceNextValue() throws Exception { text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " SERVER FILTER BY FIRST KEY ONLY", "CLIENT RESERVE VALUES FROM 1 SEQUENCE"), scanAttrs("FULL SCAN ", "ATABLE", "") - .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY").put("clientSequenceCount", 1)); + .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY").put("clientSequenceCount", 1) + .set("clientSteps", clientSteps("CLIENT RESERVE VALUES FROM 1 SEQUENCE"))); } @Test @@ -393,7 +594,8 @@ public void testSaltedTableScan() throws Exception { text("CLIENT PARALLEL -WAY FULL SCAN OVER EO_SALTED", " SERVER FILTER BY V = 7", "CLIENT MERGE SORT"), scanAttrs("FULL SCAN ", "EO_SALTED", "").put("serverWhereFilter", "SERVER FILTER BY V = 7") - .put("clientSortAlgo", "CLIENT MERGE SORT")); + .put("clientSortAlgo", "CLIENT MERGE SORT") + .set("clientSteps", clientSteps("CLIENT MERGE SORT"))); } @Test @@ -406,7 +608,8 @@ public void testMultiTenantView() throws Exception { " SERVER 1 ROW LIMIT", "CLIENT 1 ROW LIMIT"), attrs().put("iteratorTypeAndScanSize", "SERIAL -WAY").put("consistency", "STRONG") .put("explainScanType", "RANGE SCAN ").put("tableName", "EO_MT_BASE") - .put("keyRanges", " ['tenant42']").put("serverRowLimit", 1).put("clientRowLimit", 1)); + .put("keyRanges", " ['tenant42']").put("serverRowLimit", 1).put("clientRowLimit", 1) + .set("clientSteps", clientSteps("CLIENT 1 ROW LIMIT"))); } @Test @@ -447,22 +650,18 @@ public void testTextNormalizerRenumbersTempAliasesByFirstAppearance() { // Two distinct aliases ($7, $9) with $7 appearing first → $1, $9 → $2. assertEquals( Collections.singletonList(" DYNAMIC SERVER FILTER BY ATABLE.ORGANIZATION_ID IN ($1.$2)"), - new ExplainTextNormalizer() - .normalize(Collections.singletonList( - " DYNAMIC SERVER FILTER BY ATABLE.ORGANIZATION_ID IN ($7.$9)"))); + new ExplainTextNormalizer().normalize(Collections + .singletonList(" DYNAMIC SERVER FILTER BY ATABLE.ORGANIZATION_ID IN ($7.$9)"))); } @Test public void testTextNormalizerSharesAliasStateAcrossLines() { // The same alias ($5) appearing in two different lines gets the same renumbered token. A // distinct alias ($8) on the second line gets the next number. - List in = Arrays.asList( - "CLIENT PARALLEL 1-WAY FULL SCAN OVER ($5)", + List in = Arrays.asList("CLIENT PARALLEL 1-WAY FULL SCAN OVER ($5)", " DYNAMIC SERVER FILTER BY T.X IN ($5.$8)"); - assertEquals(Arrays.asList( - "CLIENT PARALLEL -WAY FULL SCAN OVER ($1)", - " DYNAMIC SERVER FILTER BY T.X IN ($1.$2)"), - new ExplainTextNormalizer().normalize(in)); + assertEquals(Arrays.asList("CLIENT PARALLEL -WAY FULL SCAN OVER ($1)", + " DYNAMIC SERVER FILTER BY T.X IN ($1.$2)"), new ExplainTextNormalizer().normalize(in)); } @Test @@ -470,8 +669,7 @@ public void testTextNormalizerLeavesNonAliasDollarTokensAlone() { // The "$" form below is the canonical renumbered form, not a temp alias, so the token // pattern only matches "$". A literal dollar followed by a non-digit is preserved. List in = Collections.singletonList("CLIENT FILTER BY price > $5.50 AND tag = '$abc'"); - assertEquals( - Collections.singletonList("CLIENT FILTER BY price > $1.50 AND tag = '$abc'"), + assertEquals(Collections.singletonList("CLIENT FILTER BY price > $1.50 AND tag = '$abc'"), new ExplainTextNormalizer().normalize(in)); } @@ -530,6 +728,38 @@ public void testJsonNormalizerErasesClusterFields() { assertEquals(0, root.get("numRegionLocationLookups").asInt()); } + @Test + public void testJsonNormalizerRecursesIntoLhsJoinQueryExplainPlan() { + ObjectNode root = mapper.createObjectNode(); + root.put("iteratorTypeAndScanSize", "PARALLEL 5-WAY"); + root.put("numRegionLocationLookups", 1); + ObjectNode lhs = mapper.createObjectNode(); + lhs.put("iteratorTypeAndScanSize", "PARALLEL 7-WAY"); + lhs.put("numRegionLocationLookups", 42); + lhs.set("regionLocations", mapper.createArrayNode().add(1)); + root.set("lhsJoinQueryExplainPlan", lhs); + new ExplainJsonNormalizer().normalize(root); + assertEquals("PARALLEL -WAY", root.get("iteratorTypeAndScanSize").asText()); + assertEquals(0, root.get("numRegionLocationLookups").asInt()); + JsonNode nestedLhs = root.get("lhsJoinQueryExplainPlan"); + assertEquals("PARALLEL -WAY", nestedLhs.get("iteratorTypeAndScanSize").asText()); + assertEquals(0, nestedLhs.get("numRegionLocationLookups").asInt()); + assertTrue(nestedLhs.get("regionLocations").isNull()); + } + + @Test + public void testJsonNormalizerSharesAliasStateWithLhsRecursion() { + ObjectNode root = mapper.createObjectNode(); + root.put("serverWhereFilter", "X IN ($3)"); + ObjectNode lhs = mapper.createObjectNode(); + lhs.put("serverWhereFilter", "Y = $3 AND Z = $5"); + root.set("lhsJoinQueryExplainPlan", lhs); + new ExplainJsonNormalizer().normalize(root); + assertEquals("X IN ($1)", root.get("serverWhereFilter").asText()); + assertEquals("Y = $1 AND Z = $2", + root.get("lhsJoinQueryExplainPlan").get("serverWhereFilter").asText()); + } + @Test public void testJsonNormalizerRecursesIntoRhsJoinQueryExplainPlan() { ObjectNode root = mapper.createObjectNode(); @@ -551,21 +781,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 @@ -628,8 +857,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); } } @@ -647,9 +875,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() { @@ -700,10 +926,19 @@ private static ObjectNode defaultAttrs() { n.putNull("clientSequenceCount"); n.putNull("clientCursorName"); n.putNull("clientSortAlgo"); + n.putNull("clientSteps"); + n.putNull("lhsJoinQueryExplainPlan"); n.putNull("rhsJoinQueryExplainPlan"); n.putNull("serverMergeColumns"); n.putNull("regionLocations"); + n.putNull("regionLocationsTotalSize"); 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; } @@ -733,6 +968,15 @@ private static ObjectNode attrs() { return defaultAttrs(); } + /** Build a {@code clientSteps} JSON array for embedding into an expected attributes object. */ + private static ArrayNode clientSteps(String... steps) { + ArrayNode arr = mapper.createArrayNode(); + for (String s : steps) { + arr.add(s); + } + return arr; + } + private static ExplainPlan samplePlan(String way, String scanType) { ExplainPlanAttributes a = new ExplainPlanAttributesBuilder().setIteratorTypeAndScanSize(way) .setExplainScanType(scanType).setTableName("T") 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..7c55ecbafc2 --- /dev/null +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTestUtil.java @@ -0,0 +1,475 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.List; +import org.apache.phoenix.compile.ExplainPlan; +import org.apache.phoenix.compile.ExplainPlanAttributes; +import org.apache.phoenix.compile.QueryPlan; +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()); + } + + /** Begin assertions on the plan attributes of a resolved {@link QueryPlan}. */ + public static ExplainPlanAssert assertPlan(QueryPlan queryPlan) throws SQLException { + return assertPlan(queryPlan.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 scan type starts with {@code prefix}, useful for variable width prefixes such as + * {@code "POINT LOOKUP ON "} where the number of keys is data dependent. + */ + public ExplainPlanAssert scanTypeStartsWith(String prefix) { + String actual = attributes.getExplainScanType(); + assertNotNull(at("explainScanType") + " must not be null", actual); + assertTrue( + at("explainScanType") + " expected to start with '" + prefix + "' but was '" + actual + "'", + actual.startsWith(prefix)); + 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; + } + + /** Assert that {@code serverWhereFilter} is equal to one of the {@code allowed} values. */ + public ExplainPlanAssert serverWhereFilterAnyOf(String... allowed) { + String actual = attributes.getServerWhereFilter(); + for (String s : allowed) { + if (s == null ? actual == null : s.equals(actual)) { + return this; + } + } + throw new AssertionError(at("serverWhereFilter") + " expected one of " + + Arrays.toString(allowed) + " but was " + actual); + } + + 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 estimatedRows(Long expected) { + assertEquals(at("estimatedRows"), expected, attributes.getEstimatedRows()); + return this; + } + + public ExplainPlanAssert estimatedBytes(Long expected) { + assertEquals(at("estimatedSizeInBytes"), expected, attributes.getEstimatedSizeInBytes()); + return this; + } + + public ExplainPlanAssert splitsChunk(Integer expected) { + assertEquals(at("splitsChunk"), expected, attributes.getSplitsChunk()); + return this; + } + + public ExplainPlanAssert regionLocationCount(int expected) { + int actual = + attributes.getRegionLocations() == null ? 0 : attributes.getRegionLocations().size(); + assertEquals(at("regionLocations.size"), expected, actual); + return this; + } + + /** Assert that the EXPLAIN plan emitted at least one region location. */ + public ExplainPlanAssert regionLocationsNotEmpty() { + assertNotNull(at("regionLocations") + " must not be null", attributes.getRegionLocations()); + assertTrue(at("regionLocations") + " must not be empty", + !attributes.getRegionLocations().isEmpty()); + return this; + } + + /** + * Assert the total distinct region locations seen before any trimming applied to + * {@code regionLocations}. When the trim limit was not reached, this equals + * {@link #regionLocationCount(int)}. + */ + public ExplainPlanAssert regionLocationsTotalSize(Integer expected) { + assertEquals(at("regionLocationsTotalSize"), expected, + attributes.getRegionLocationsTotalSize()); + return this; + } + + /** Assert the number of region location lookups performed by the planner. */ + public ExplainPlanAssert numRegionLocationLookups(int expected) { + assertEquals(at("numRegionLocationLookups"), expected, + attributes.getNumRegionLocationLookups()); + 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 left-hand side plan (sort-merge join). */ + public ExplainPlanAssert lhs() { + ExplainPlanAttributes lhs = attributes.getLhsJoinQueryExplainPlan(); + assertNotNull(at("lhsJoinQueryExplainPlan") + " must not be null", lhs); + return new ExplainPlanAssert(lhs, this, context + ".lhs"); + } + + /** 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 ordered client-side pipeline steps on this node. */ + public ExplainPlanAssert clientStepCount(int expected) { + List steps = attributes.getClientSteps(); + int actual = steps == null ? 0 : steps.size(); + assertEquals(at("clientSteps.size"), expected, actual); + return this; + } + + /** Assert the i-th ordered client-side pipeline step on this node. */ + public ExplainPlanAssert clientStep(int i, String expected) { + List steps = attributes.getClientSteps(); + assertNotNull(at("clientSteps") + " must not be null", steps); + assertTrue(at("clientSteps") + " has no index " + i + " (size=" + steps.size() + ")", + i >= 0 && i < steps.size()); + assertEquals(at("clientSteps[" + i + "]"), expected, steps.get(i)); + return this; + } + + /** Assert the entire ordered client-side pipeline on this node matches {@code expected}. */ + public ExplainPlanAssert clientSteps(String... expected) { + List actual = attributes.getClientSteps(); + List actualOrEmpty = actual == null ? Collections. emptyList() : actual; + assertEquals(at("clientSteps"), Arrays.asList(expected), actualOrEmpty); + return this; + } + + /** 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 56c2424bd98..24c3bee4a96 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 @@ -27,18 +27,16 @@ */ public final class ExplainTextNormalizer { - // CLIENT 5-CHUNK -> CLIENT -CHUNK ; matches any non-negative integer immediately before - // -CHUNK. + // Matches any non-negative integer immediately before -CHUNK. private static final Pattern CHUNK_COUNT = Pattern.compile("\\b\\d+-CHUNK\\b"); - // PARALLEL 400-WAY -> PARALLEL -WAY ; matches the iterator parallelism count. + // Matches the iterator parallelism count. private static final Pattern WAY_COUNT = Pattern.compile("\\b\\d+-WAY\\b"); - // 1234 ROWS 5678 BYTES (stats-row-count gated; we strip when present). + // Matches the stats-row-count gated row count and byte count. private static final Pattern ROWS_BYTES = Pattern.compile("\\d+ ROWS \\d+ BYTES\\s*"); - // " (region locations = [...]) " emitted via planSteps.add(regionLocationPlan); the line always - // begins with the leading-space form of ExplainTable.REGION_LOCATIONS. + // Matches the region locations line. private static final String REGION_LOCATIONS_PREFIX = " (region locations = "; /** From 8a7b5af4a906afb04cf73878fc2619036ebc42a0 Mon Sep 17 00:00:00 2001 From: Andrew Purtell Date: Tue, 9 Jun 2026 12:02:12 -0700 Subject: [PATCH 03/27] PHOENIX-7882 Per scan EXPLAIN output improvements (#2505) Co-authored-by: Claude Opus 4.8[1m] --- .../compile/ExplainPlanAttributes.java | 100 +++++++-- .../phoenix/iterate/BaseResultIterators.java | 5 + .../apache/phoenix/iterate/ExplainTable.java | 76 ++++++- .../phoenix/end2end/SortMergeJoinMoreIT.java | 4 +- .../join/SortMergeJoinGlobalIndexIT.java | 9 +- .../join/SortMergeJoinLocalIndexIT.java | 7 +- .../query/explain/ExplainJsonNormalizer.java | 3 + .../query/explain/ExplainPlanTest.java | 203 +++++++++++++----- .../query/explain/ExplainPlanTestUtil.java | 27 +++ .../query/explain/ExplainTextNormalizer.java | 6 +- 10 files changed, 341 insertions(+), 99 deletions(-) diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java index 74cd9833324..fcf858479aa 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java @@ -35,16 +35,16 @@ * Strings containing entire plan. */ @JsonPropertyOrder({ "abstractExplainPlan", "hint", "explainScanType", "consistency", "tableName", - "keyRanges", "scanTimeRangeMin", "scanTimeRangeMax", "splitsChunk", "useRoundRobinIterator", - "samplingRate", "hexStringRVCOffset", "iteratorTypeAndScanSize", "estimatedRows", - "estimatedSizeInBytes", "serverWhereFilter", "serverDistinctFilter", "serverMergeColumns", - "serverArrayElementProjection", "serverAggregate", "serverGroupByLimit", "serverSortedBy", - "serverOffset", "serverRowLimit", "clientFilterBy", "clientAggregate", "clientDistinctFilter", - "clientAfterAggregate", "clientSortAlgo", "clientSortedBy", "clientOffset", "clientRowLimit", - "clientSequenceCount", "clientCursorName", "clientSteps", "lhsJoinQueryExplainPlan", - "rhsJoinQueryExplainPlan", "subPlans", "dynamicServerFilter", "afterJoinFilter", - "joinScannerLimit", "sortMergeSkipMerge", "regionLocations", "regionLocationsTotalSize", - "numRegionLocationLookups" }) + "keyRanges", "indexName", "indexKind", "saltBuckets", "regionsPlanned", "scanTimeRangeMin", + "scanTimeRangeMax", "splitsChunk", "useRoundRobinIterator", "samplingRate", "hexStringRVCOffset", + "iteratorTypeAndScanSize", "estimatedRows", "estimatedSizeInBytes", "serverWhereFilter", + "serverDistinctFilter", "serverMergeColumns", "serverArrayElementProjection", "serverAggregate", + "serverGroupByLimit", "serverSortedBy", "serverOffset", "serverRowLimit", "clientFilterBy", + "clientAggregate", "clientDistinctFilter", "clientAfterAggregate", "clientSortAlgo", + "clientSortedBy", "clientOffset", "clientRowLimit", "clientSequenceCount", "clientCursorName", + "clientSteps", "lhsJoinQueryExplainPlan", "rhsJoinQueryExplainPlan", "subPlans", + "dynamicServerFilter", "afterJoinFilter", "joinScannerLimit", "sortMergeSkipMerge", + "regionLocations", "regionLocationsTotalSize", "numRegionLocationLookups" }) public class ExplainPlanAttributes { // Plan identity and scan-level metadata @@ -54,6 +54,10 @@ public class ExplainPlanAttributes { private final Consistency consistency; private final String tableName; private final String keyRanges; + private final String indexName; + private final String indexKind; + private final Integer saltBuckets; + private final Integer regionsPlanned; private final Long scanTimeRangeMin; private final Long scanTimeRangeMax; private final Integer splitsChunk; @@ -113,6 +117,10 @@ private ExplainPlanAttributes() { this.consistency = null; this.tableName = null; this.keyRanges = null; + this.indexName = null; + this.indexKind = null; + this.saltBuckets = null; + this.regionsPlanned = null; this.scanTimeRangeMin = null; this.scanTimeRangeMax = null; this.splitsChunk = null; @@ -155,8 +163,9 @@ private ExplainPlanAttributes() { } public ExplainPlanAttributes(String abstractExplainPlan, Hint hint, String explainScanType, - Consistency consistency, String tableName, String keyRanges, Long scanTimeRangeMin, - Long scanTimeRangeMax, Integer splitsChunk, boolean useRoundRobinIterator, Double samplingRate, + Consistency consistency, String tableName, String keyRanges, String indexName, String indexKind, + Integer saltBuckets, Integer regionsPlanned, Long scanTimeRangeMin, Long scanTimeRangeMax, + Integer splitsChunk, boolean useRoundRobinIterator, Double samplingRate, String hexStringRVCOffset, String iteratorTypeAndScanSize, Long estimatedRows, Long estimatedSizeInBytes, String serverWhereFilter, String serverDistinctFilter, Set serverMergeColumns, boolean serverArrayElementProjection, String serverAggregate, @@ -175,6 +184,10 @@ public ExplainPlanAttributes(String abstractExplainPlan, Hint hint, String expla this.consistency = consistency; this.tableName = tableName; this.keyRanges = keyRanges; + this.indexName = indexName; + this.indexKind = indexKind; + this.saltBuckets = saltBuckets; + this.regionsPlanned = regionsPlanned; this.scanTimeRangeMin = scanTimeRangeMin; this.scanTimeRangeMax = scanTimeRangeMax; this.splitsChunk = splitsChunk; @@ -242,6 +255,22 @@ public String getKeyRanges() { return keyRanges; } + public String getIndexName() { + return indexName; + } + + public String getIndexKind() { + return indexKind; + } + + public Integer getSaltBuckets() { + return saltBuckets; + } + + public Integer getRegionsPlanned() { + return regionsPlanned; + } + public Long getScanTimeRangeMin() { return scanTimeRangeMin; } @@ -411,6 +440,10 @@ public static class ExplainPlanAttributesBuilder { private Consistency consistency; private String tableName; private String keyRanges; + private String indexName; + private String indexKind; + private Integer saltBuckets; + private Integer regionsPlanned; private Long scanTimeRangeMin; private Long scanTimeRangeMax; private Integer splitsChunk; @@ -462,6 +495,10 @@ public ExplainPlanAttributesBuilder(ExplainPlanAttributes explainPlanAttributes) this.consistency = explainPlanAttributes.getConsistency(); this.tableName = explainPlanAttributes.getTableName(); this.keyRanges = explainPlanAttributes.getKeyRanges(); + this.indexName = explainPlanAttributes.getIndexName(); + this.indexKind = explainPlanAttributes.getIndexKind(); + this.saltBuckets = explainPlanAttributes.getSaltBuckets(); + this.regionsPlanned = explainPlanAttributes.getRegionsPlanned(); this.scanTimeRangeMin = explainPlanAttributes.getScanTimeRangeMin(); this.scanTimeRangeMax = explainPlanAttributes.getScanTimeRangeMax(); this.splitsChunk = explainPlanAttributes.getSplitsChunk(); @@ -534,6 +571,26 @@ public ExplainPlanAttributesBuilder setKeyRanges(String keyRanges) { return this; } + public ExplainPlanAttributesBuilder setIndexName(String indexName) { + this.indexName = indexName; + return this; + } + + public ExplainPlanAttributesBuilder setIndexKind(String indexKind) { + this.indexKind = indexKind; + return this; + } + + public ExplainPlanAttributesBuilder setSaltBuckets(Integer saltBuckets) { + this.saltBuckets = saltBuckets; + return this; + } + + public ExplainPlanAttributesBuilder setRegionsPlanned(Integer regionsPlanned) { + this.regionsPlanned = regionsPlanned; + return this; + } + public ExplainPlanAttributesBuilder setScanTimeRangeMin(Long scanTimeRangeMin) { this.scanTimeRangeMin = scanTimeRangeMin; return this; @@ -743,15 +800,16 @@ public ExplainPlanAttributesBuilder setNumRegionLocationLookups(int numRegionLoc public ExplainPlanAttributes build() { return new ExplainPlanAttributes(abstractExplainPlan, hint, explainScanType, consistency, - tableName, keyRanges, scanTimeRangeMin, scanTimeRangeMax, splitsChunk, - useRoundRobinIterator, samplingRate, hexStringRVCOffset, iteratorTypeAndScanSize, - estimatedRows, estimatedSizeInBytes, serverWhereFilter, serverDistinctFilter, - serverMergeColumns, serverArrayElementProjection, serverAggregate, serverGroupByLimit, - serverSortedBy, serverOffset, serverRowLimit, clientFilterBy, clientAggregate, - clientDistinctFilter, clientAfterAggregate, clientSortAlgo, clientSortedBy, clientOffset, - clientRowLimit, clientSequenceCount, clientCursorName, clientSteps, lhsJoinQueryExplainPlan, - rhsJoinQueryExplainPlan, subPlans, dynamicServerFilter, afterJoinFilter, joinScannerLimit, - sortMergeSkipMerge, regionLocations, regionLocationsTotalSize, numRegionLocationLookups); + tableName, keyRanges, indexName, indexKind, saltBuckets, regionsPlanned, scanTimeRangeMin, + scanTimeRangeMax, splitsChunk, useRoundRobinIterator, samplingRate, hexStringRVCOffset, + iteratorTypeAndScanSize, estimatedRows, estimatedSizeInBytes, serverWhereFilter, + serverDistinctFilter, serverMergeColumns, serverArrayElementProjection, serverAggregate, + serverGroupByLimit, serverSortedBy, serverOffset, serverRowLimit, clientFilterBy, + clientAggregate, clientDistinctFilter, clientAfterAggregate, clientSortAlgo, clientSortedBy, + clientOffset, clientRowLimit, clientSequenceCount, clientCursorName, clientSteps, + lhsJoinQueryExplainPlan, rhsJoinQueryExplainPlan, subPlans, dynamicServerFilter, + afterJoinFilter, joinScannerLimit, sortMergeSkipMerge, regionLocations, + regionLocationsTotalSize, numRegionLocationLookups); } } } diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java index 45e160fac8a..7e81d3a5346 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java @@ -640,6 +640,11 @@ public List getSplits() { else return splits; } + @Override + protected int getSplitCount() { + return splits == null ? 0 : splits.size(); + } + @Override public List> getScans() { if (scans == null) return Collections.emptyList(); diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java index 83821005614..1a1f32786f1 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java @@ -122,6 +122,33 @@ private String explainSkipScan() { return buf.toString(); } + /** + * Number of region scan splits the plan will hit, used to render the {@code REGIONS PLANNED} + * per-scan line. + * @return the split count, or 0 when unknown + */ + protected int getSplitCount() { + return 0; + } + + /** + * Logical name used to render a table or index in EXPLAIN output. Shared by both the scan + * {@code OVER} line's local index decoration and the per scan {@code INDEX} line. + * @param table the scanned table or index + * @return the display name with any child-view local-index prefix stripped + */ + private static String getExplainIndexName(PTable table) { + String indexName = table.getName().getString(); + if ( + table.getIndexType() == PTable.IndexType.LOCAL && table.getViewIndexId() != null + && indexName.contains(QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR) + ) { + int lastIndexOf = indexName.lastIndexOf(QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR); + indexName = indexName.substring(lastIndexOf + 1); + } + return indexName; + } + protected void explain(String prefix, List planSteps, ExplainPlanAttributesBuilder explainPlanAttributesBuilder, List regionLocations) { @@ -148,15 +175,7 @@ protected void explain(String prefix, List planSteps, String tableName = tableRef.getTable().getPhysicalName().getString(); if (tableRef.getTable().getIndexType() == PTable.IndexType.LOCAL) { - String indexName = tableRef.getTable().getName().getString(); - if ( - tableRef.getTable().getViewIndexId() != null - && indexName.contains(QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR) - ) { - int lastIndexOf = indexName.lastIndexOf(QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR); - indexName = indexName.substring(lastIndexOf + 1); - } - tableName = indexName + "(" + tableName + ")"; + tableName = getExplainIndexName(tableRef.getTable()) + "(" + tableName + ")"; } buf.append("OVER ").append(tableName); @@ -178,6 +197,45 @@ protected void explain(String prefix, List planSteps, explainPlanAttributesBuilder.setKeyRanges(appendKeyRanges()); } } + + PTable.IndexType indexType = tableRef.getTable().getIndexType(); + String explainIndexName = getExplainIndexName(tableRef.getTable()); + String indexKind = null; + if (indexType != null) { + switch (indexType) { + case LOCAL: + indexKind = "LOCAL"; + break; + case GLOBAL: + indexKind = "GLOBAL"; + break; + case UNCOVERED_GLOBAL: + indexKind = "UNCOVERED GLOBAL"; + break; + default: + indexKind = null; + } + } + planSteps.add(" INDEX " + explainIndexName + (indexKind == null ? "" : " " + indexKind)); + Integer bucketNum = tableRef.getTable().getBucketNum(); + if (bucketNum != null) { + planSteps.add(" SALT BUCKETS " + bucketNum); + } + int splitCount = getSplitCount(); + if (splitCount > 0) { + planSteps.add(" REGIONS PLANNED " + splitCount); + } + if (explainPlanAttributesBuilder != null) { + explainPlanAttributesBuilder.setIndexName(explainIndexName); + explainPlanAttributesBuilder.setIndexKind(indexKind); + if (bucketNum != null) { + explainPlanAttributesBuilder.setSaltBuckets(bucketNum); + } + if (splitCount > 0) { + explainPlanAttributesBuilder.setRegionsPlanned(splitCount); + } + } + if (context.getScan() != null && tableRef.getTable().getRowTimestampColPos() != -1) { TimeRange range = context.getScan().getTimeRange(); planSteps.add(" ROW TIMESTAMP FILTER [" + range.getMin() + ", " + range.getMax() + ")"); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortMergeJoinMoreIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortMergeJoinMoreIT.java index 1a57ae39e0d..26621c1c9bb 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortMergeJoinMoreIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortMergeJoinMoreIT.java @@ -434,8 +434,8 @@ public void testBug2894() throws Exception { .serverDistinctFilter("SERVER DISTINCT PREFIX FILTER OVER [BUCKET, TIMESTAMP, LOCATION]") .serverAggregate( "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [BUCKET, TIMESTAMP, LOCATION]") - .clientSortAlgo("CLIENT MERGE SORT").clientSortedBy("[BUCKET, TIMESTAMP]") - .end().rhs().iteratorType("PARALLEL").scanType("SKIP SCAN ON 2 RANGES").table(t[i]) + .clientSortAlgo("CLIENT MERGE SORT").clientSortedBy("[BUCKET, TIMESTAMP]").end().rhs() + .iteratorType("PARALLEL").scanType("SKIP SCAN ON 2 RANGES").table(t[i]) .keyRanges(rhsKeyRanges) .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND SRC_LOCATION = DST_LOCATION") .serverDistinctFilter( diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinGlobalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinGlobalIndexIT.java index 59d69aa160f..612768aabc5 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinGlobalIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinGlobalIndexIT.java @@ -65,9 +65,8 @@ protected void assertSkipMergeOptimizationPlan(Connection conn, String query) th .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"S.:supplier_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end().rhs() .abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(true) - .clientSortedBy("[\"I.0:supplier_id\"]").lhs() - .scanType("FULL SCAN").table(itemIndex).serverSortedBy("[\"I.:item_id\"]") - .clientSortAlgo("CLIENT MERGE SORT").end().rhs() + .clientSortedBy("[\"I.0:supplier_id\"]").lhs().scanType("FULL SCAN").table(itemIndex) + .serverSortedBy("[\"I.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end().rhs() .scanType("FULL SCAN").table(order).serverWhereFilter("SERVER FILTER BY QUANTITY < 5000") .serverSortedBy("[\"O.item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end().end(); } @@ -96,8 +95,8 @@ protected void assertSetMaxRowsPlan(Connection conn, String query, int queryInde assertPlan(attributes).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(false) .clientRowLimit(4).lhs().scanType("FULL SCAN").table(itemIndex) .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"I.:item_id\"]") - .clientSortAlgo("CLIENT MERGE SORT").end().rhs().scanType("FULL SCAN") - .table(order).serverSortedBy(queryIndex == 0 ? "[\"O.item_id\"]" : "[\"item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end().rhs().scanType("FULL SCAN").table(order) + .serverSortedBy(queryIndex == 0 ? "[\"O.item_id\"]" : "[\"item_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end(); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinLocalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinLocalIndexIT.java index ec31377e1a7..95d5f44a2a7 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinLocalIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/SortMergeJoinLocalIndexIT.java @@ -67,10 +67,9 @@ protected void assertSkipMergeOptimizationPlan(Connection conn, String query) th .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"S.:supplier_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end().rhs() .abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(true) - .clientSortedBy("[\"I.0:supplier_id\"]").lhs() - .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") - .serverSortedBy("[\"I.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT") - .end().rhs().scanType("FULL SCAN").table(order) + .clientSortedBy("[\"I.0:supplier_id\"]").lhs().scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]").serverSortedBy("[\"I.:item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end().rhs().scanType("FULL SCAN").table(order) .serverWhereFilter("SERVER FILTER BY QUANTITY < 5000").serverSortedBy("[\"O.item_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end().end(); } diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainJsonNormalizer.java b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainJsonNormalizer.java index 38f44fabea3..eb96912f959 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainJsonNormalizer.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainJsonNormalizer.java @@ -63,6 +63,9 @@ private JsonNode normalize(JsonNode node, TempAliasRenumberer aliases) { if (obj.has("splitsChunk")) { obj.set("splitsChunk", NullNode.getInstance()); } + if (obj.has("regionsPlanned")) { + obj.set("regionsPlanned", NullNode.getInstance()); + } if (obj.has("estimatedRows")) { obj.set("estimatedRows", NullNode.getInstance()); } diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTest.java b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTest.java index 66fe6b8b711..8e57faa7507 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTest.java @@ -109,8 +109,8 @@ public void testPointLookup() throws Exception { "SELECT a_string, b_string FROM atable" + " WHERE organization_id = '00D000000000001' AND entity_id = '00E00000000001'" + " AND x_integer = 2 AND a_integer < 5", - text("CLIENT PARALLEL -WAY POINT LOOKUP ON 1 KEY OVER ATABLE", - " SERVER FILTER BY (X_INTEGER = 2 AND A_INTEGER < 5)"), + text("CLIENT PARALLEL -WAY POINT LOOKUP ON 1 KEY OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED ", " SERVER FILTER BY (X_INTEGER = 2 AND A_INTEGER < 5)"), scanAttrs("POINT LOOKUP ON 1 KEY ", "ATABLE", null).put("serverWhereFilter", "SERVER FILTER BY (X_INTEGER = 2 AND A_INTEGER < 5)")); } @@ -121,7 +121,8 @@ public void testPointLookupMultiKey() throws Exception { "SELECT a_string, b_string FROM atable" + " WHERE organization_id IN ('00D000000000001', '00D000000000005')" + " AND entity_id IN ('00E00000000000X','00E00000000000Z')", - text("CLIENT PARALLEL -WAY POINT LOOKUP ON 4 KEYS OVER ATABLE"), + text("CLIENT PARALLEL -WAY POINT LOOKUP ON 4 KEYS OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED "), scanAttrs("POINT LOOKUP ON 4 KEYS ", "ATABLE", null)); } @@ -130,8 +131,10 @@ public void testRangeScan() throws Exception { verifyQuery("rangeScan", "SELECT a_string FROM atable WHERE organization_id = '00D000000000001'" + " AND entity_id > '00E00000000002' AND entity_id < '00E00000000008'", - text("CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE" - + " ['00D000000000001','00E00000000002!'] - ['00D000000000001','00E00000000008 ']"), + text( + "CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE" + + " ['00D000000000001','00E00000000002!'] - ['00D000000000001','00E00000000008 ']", + " INDEX ATABLE", " REGIONS PLANNED "), scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001','00E00000000002!'] - ['00D000000000001','00E00000000008 ']")); } @@ -140,7 +143,7 @@ public void testRangeScan() throws Exception { public void testSkipScanKeys() throws Exception { verifyQuery("skipScanKeys", "SELECT host FROM ptsdb3 WHERE host IN ('na1','na2','na3')", text("CLIENT PARALLEL -WAY SKIP SCAN ON 3 KEYS OVER PTSDB3 [~'na3'] - [~'na1']", - " SERVER FILTER BY FIRST KEY ONLY"), + " INDEX PTSDB3", " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY"), scanAttrs("SKIP SCAN ON 3 KEYS ", "PTSDB3", " [~'na3'] - [~'na1']").put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY")); } @@ -154,7 +157,7 @@ public void testSkipScanRanges() throws Exception { text( "CLIENT PARALLEL -WAY SKIP SCAN ON 6 RANGES OVER PTSDB" + " ['na1','a','2013-01-01'] - ['na3','b','2013-01-02']", - " SERVER FILTER BY FIRST KEY ONLY"), + " INDEX PTSDB", " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY"), scanAttrs("SKIP SCAN ON 6 RANGES ", "PTSDB", " ['na1','a','2013-01-01'] - ['na3','b','2013-01-02']").put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY")); @@ -163,15 +166,17 @@ public void testSkipScanRanges() throws Exception { @Test public void testFullScan() throws Exception { verifyQuery("fullScan", "SELECT * FROM atable", - text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE"), scanAttrs("FULL SCAN ", "ATABLE", "")); + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED "), + scanAttrs("FULL SCAN ", "ATABLE", "")); } @Test public void testReverseScan() throws Exception { verifyQuery("reverseScan", "SELECT inst,\"DATE\" FROM ptsdb2 WHERE inst = 'na1' ORDER BY inst DESC, \"DATE\" DESC", - text("CLIENT PARALLEL -WAY REVERSE RANGE SCAN OVER PTSDB2 ['na1']", - " SERVER FILTER BY FIRST KEY ONLY"), + text("CLIENT PARALLEL -WAY REVERSE RANGE SCAN OVER PTSDB2 ['na1']", " INDEX PTSDB2", + " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY"), scanAttrs("RANGE SCAN ", "PTSDB2", " ['na1']") .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY") .put("clientSortedBy", "REVERSE")); @@ -182,7 +187,7 @@ public void testSmallHint() throws Exception { verifyQuery("smallHint", "SELECT /*+ SMALL */ host FROM ptsdb3 WHERE host IN ('na1','na2','na3')", text("CLIENT PARALLEL -WAY SMALL SKIP SCAN ON 3 KEYS OVER PTSDB3 [~'na3'] - [~'na1']", - " SERVER FILTER BY FIRST KEY ONLY"), + " INDEX PTSDB3", " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY"), scanAttrs("SKIP SCAN ON 3 KEYS ", "PTSDB3", " [~'na3'] - [~'na1']").put("hint", "SMALL") .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY")); } @@ -190,7 +195,8 @@ public void testSmallHint() throws Exception { @Test public void testAggregateSingleRow() throws Exception { verifyQuery("aggregateSingleRow", "SELECT count(*) FROM atable", - text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " SERVER FILTER BY FIRST KEY ONLY", + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY", " SERVER AGGREGATE INTO SINGLE ROW"), scanAttrs("FULL SCAN ", "ATABLE", "") .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY") @@ -200,8 +206,9 @@ public void testAggregateSingleRow() throws Exception { @Test public void testAggregateOrderedDistinct() throws Exception { verifyQuery("aggregateOrderedDistinct", "SELECT count(1) FROM atable GROUP BY a_string", - text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", - " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]", "CLIENT MERGE SORT"), + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED ", " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]", + "CLIENT MERGE SORT"), scanAttrs("FULL SCAN ", "ATABLE", "") .put("serverAggregate", "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]") .put("clientSortAlgo", "CLIENT MERGE SORT") @@ -213,7 +220,8 @@ public void testAggregateHashDistinct() throws Exception { verifyQuery("aggregateHashDistinct", "SELECT count(1) FROM atable WHERE a_integer = 1" + " GROUP BY ROUND(a_time,'HOUR',2), entity_id", - text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " SERVER FILTER BY A_INTEGER = 1", + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED ", " SERVER FILTER BY A_INTEGER = 1", " SERVER AGGREGATE INTO DISTINCT ROWS BY [ENTITY_ID, ROUND(A_TIME)]", "CLIENT MERGE SORT"), scanAttrs("FULL SCAN ", "ATABLE", "") @@ -226,8 +234,9 @@ public void testAggregateHashDistinct() throws Exception { @Test public void testTopNSortedBy() throws Exception { verifyQuery("topNSortedBy", "SELECT a_string FROM atable ORDER BY a_string DESC LIMIT 3", - text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", - " SERVER TOP 3 ROWS SORTED BY [A_STRING DESC]", "CLIENT MERGE SORT", "CLIENT LIMIT 3"), + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED ", " SERVER TOP 3 ROWS SORTED BY [A_STRING DESC]", + "CLIENT MERGE SORT", "CLIENT LIMIT 3"), scanAttrs("FULL SCAN ", "ATABLE", "").put("serverSortedBy", "[A_STRING DESC]") .put("serverRowLimit", 3).put("clientRowLimit", 3) .put("clientSortAlgo", "CLIENT MERGE SORT") @@ -238,7 +247,8 @@ public void testTopNSortedBy() throws Exception { public void testClientFilterByMax() throws Exception { verifyQuery("clientFilterByMax", "SELECT count(1) FROM atable GROUP BY a_string, b_string HAVING max(a_string) = 'a'", - text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED ", " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]", "CLIENT MERGE SORT", "CLIENT FILTER BY MAX(A_STRING) = 'a'"), scanAttrs("FULL SCAN ", "ATABLE", "") @@ -254,7 +264,8 @@ public void testClientLimit() throws Exception { "SELECT a_string, b_string FROM atable" + " WHERE organization_id = '00D000000000001' AND entity_id != '00E00000000002'" + " AND x_integer = 2 AND a_integer < 5 LIMIT 10", - text("CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']", + text("CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']", " INDEX ATABLE", + " REGIONS PLANNED ", " SERVER FILTER BY (ENTITY_ID != '00E00000000002' AND X_INTEGER = 2 AND A_INTEGER < 5)", " SERVER 10 ROW LIMIT", "CLIENT 10 ROW LIMIT"), scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']") @@ -267,8 +278,8 @@ public void testClientLimit() throws Exception { @Test public void testArrayElementProjection() throws Exception { verifyQuery("arrayElementProjection", "SELECT a_string_array[1] FROM table_with_array", - text("CLIENT PARALLEL -WAY FULL SCAN OVER TABLE_WITH_ARRAY", - " SERVER ARRAY ELEMENT PROJECTION"), + text("CLIENT PARALLEL -WAY FULL SCAN OVER TABLE_WITH_ARRAY", " INDEX TABLE_WITH_ARRAY", + " REGIONS PLANNED ", " SERVER ARRAY ELEMENT PROJECTION"), scanAttrs("FULL SCAN ", "TABLE_WITH_ARRAY", "").put("serverArrayElementProjection", true)); } @@ -278,8 +289,10 @@ public void testRangeScanCompositeRvcUpperBound() throws Exception { "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']"), + text( + "CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE" + + " ['000000000000001','000000000000003'] - ['000000000000001','000000000000005']", + " INDEX ATABLE", " REGIONS PLANNED "), scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000001','000000000000003'] - ['000000000000001','000000000000005']")); } @@ -293,6 +306,7 @@ public void testRangeScanCompositeRvcOpenUpperBound() throws Exception { text( "CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE" + " ['000000000000003000000000000005'] - [*]", + " INDEX ATABLE", " REGIONS PLANNED ", " SERVER FILTER BY (ENTITY_ID > '000000000000002' AND ENTITY_ID < '000000000000008')"), scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000003000000000000005'] - [*]").put( "serverWhereFilter", @@ -304,7 +318,8 @@ 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]", + text("CLIENT PARALLEL -WAY RANGE SCAN OVER PTSDB [null,not null]", " INDEX PTSDB", + " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY AND \"DATE\" >= DATE '2013-01-01 00:00:00.000'"), scanAttrs("RANGE SCAN ", "PTSDB", " [null,not null]").put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY AND \"DATE\" >= DATE '2013-01-01 00:00:00.000'")); @@ -315,7 +330,8 @@ public 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]", + text("CLIENT PARALLEL -WAY RANGE SCAN OVER PTSDB [not null]", " INDEX PTSDB", + " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY AND (HOST IS NULL" + " AND \"DATE\" >= DATE '2013-01-01 00:00:00.000')"), scanAttrs("RANGE SCAN ", "PTSDB", " [not null]").put("serverWhereFilter", @@ -331,7 +347,7 @@ public void testSkipScanLikeRanges() throws Exception { 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"), + " INDEX PTSDB", " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY"), scanAttrs("SKIP SCAN ON 2 RANGES ", "PTSDB", " ['na','a','2013-01-01'] - ['nb','b','2013-01-02']").put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY")); @@ -342,6 +358,7 @@ public 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']", + " INDEX PTSDB", " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY AND" + " REGEXP_SUBSTR(INST, '[^-]+', 1) IN ('na1','na2','na3')"), scanAttrs("SKIP SCAN ON 3 RANGES ", "PTSDB", " ['na1'] - ['na4']").put("serverWhereFilter", @@ -353,8 +370,10 @@ public 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 ']"), + text( + "CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE" + + " ['000000000000001','003 '] - ['000000000000001','004 ']", + " INDEX ATABLE", " REGIONS PLANNED "), scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000001','003 '] - ['000000000000001','004 ']")); } @@ -364,17 +383,19 @@ 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']"), + text( + "CLIENT PARALLEL -WAY SKIP SCAN ON 2 KEYS OVER ATABLE" + + " ['000000000000001'] - ['000000000000005']", + " INDEX ATABLE", " REGIONS PLANNED "), 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"), + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED ", " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]", + "CLIENT MERGE SORT", "CLIENT 5 ROW LIMIT"), scanAttrs("FULL SCAN ", "ATABLE", "") .put("serverAggregate", "SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING]") .put("clientRowLimit", 5).put("clientSortAlgo", "CLIENT MERGE SORT") @@ -386,8 +407,9 @@ public 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"), + text("CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['000000000000001']", " INDEX ATABLE", + " REGIONS PLANNED ", " SERVER TOP 10 ROWS SORTED BY [A_STRING]", + "CLIENT MERGE SORT", "CLIENT LIMIT 10"), scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000001']").put("serverSortedBy", "[A_STRING]") .put("serverRowLimit", 10).put("clientRowLimit", 10) .put("clientSortAlgo", "CLIENT MERGE SORT") @@ -399,9 +421,9 @@ public 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"), + text("CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['000000000000001']", " INDEX ATABLE", + " REGIONS PLANNED ", " SERVER TOP 10 ROWS SORTED BY [A_STRING DESC NULLS LAST]", + "CLIENT MERGE SORT", "CLIENT LIMIT 10"), scanAttrs("RANGE SCAN ", "ATABLE", " ['000000000000001']") .put("serverSortedBy", "[A_STRING DESC NULLS LAST]").put("serverRowLimit", 10) .put("clientRowLimit", 10).put("clientSortAlgo", "CLIENT MERGE SORT") @@ -414,7 +436,8 @@ public void testAggregateOrderByClientLimit() throws Exception { "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']", + text("CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['000000000000001']", " INDEX ATABLE", + " REGIONS PLANNED ", " SERVER AGGREGATE INTO DISTINCT ROWS BY" + " [ORGANIZATION_ID, ENTITY_ID, ROUND(A_DATE)]", "CLIENT MERGE SORT", "CLIENT 10 ROW LIMIT"), @@ -430,7 +453,8 @@ public 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", + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED ", " SERVER FILTER BY A_INTEGER = 1", " SERVER AGGREGATE INTO DISTINCT ROWS BY [A_STRING, B_STRING]", "CLIENT MERGE SORT", "CLIENT FILTER BY MAX(A_STRING) = 'a'", "CLIENT SORTED BY [B_STRING]"), scanAttrs("FULL SCAN ", "ATABLE", "") @@ -457,8 +481,10 @@ public void testSortMergeJoin() throws Exception { + " JOIN atable b ON a.organization_id = b.organization_id" + " WHERE a.organization_id = '00D000000000001'", text("SORT-MERGE-JOIN (INNER) TABLES", - " CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']", "AND", - " CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE"), + " CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']", + " INDEX ATABLE", " REGIONS PLANNED ", "AND", + " CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED "), expected); } @@ -476,8 +502,10 @@ public void testHashJoinInner() throws Exception { "SELECT a.a_string, b.a_string FROM atable a" + " JOIN atable b ON a.organization_id = b.organization_id" + " WHERE a.organization_id = '00D000000000001'", - text("CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']", - " PARALLEL INNER-JOIN TABLE 0", " CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", + text("CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']", " INDEX ATABLE", + " REGIONS PLANNED ", " PARALLEL INNER-JOIN TABLE 0", + " CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED ", " DYNAMIC SERVER FILTER BY A.ORGANIZATION_ID IN (B.ORGANIZATION_ID)"), expected); } @@ -497,9 +525,10 @@ public void testHashJoinSemiInSubquery() throws Exception { verifyQuery("hashJoinSemiInSubquery", "SELECT a_string FROM atable" + " WHERE organization_id IN (SELECT organization_id FROM atable WHERE a_integer = 1)", - text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " SKIP-SCAN-JOIN TABLE 0", - " CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", - " SERVER FILTER BY A_INTEGER = 1", + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED ", " SKIP-SCAN-JOIN TABLE 0", + " CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED ", " SERVER FILTER BY A_INTEGER = 1", " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [ORGANIZATION_ID]", " DYNAMIC SERVER FILTER BY ATABLE.ORGANIZATION_ID IN ($1.$2)"), expected); @@ -513,7 +542,9 @@ public void testUnionAll() throws Exception { + " SELECT a_string FROM atable WHERE organization_id = '00D000000000002'", text("UNION ALL OVER 2 QUERIES", " CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']", - " CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000002']"), + " INDEX ATABLE", " REGIONS PLANNED ", + " CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000002']", + " INDEX ATABLE", " REGIONS PLANNED "), scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']") .put("abstractExplainPlan", "UNION ALL OVER 2 QUERIES") .set("rhsJoinQueryExplainPlan", rhs)); @@ -534,7 +565,8 @@ public void testUpsertSelectClient() throws Exception { + " SELECT organization_id, entity_id, a_string FROM atable" + " WHERE organization_id = '00D000000000001'", false, - text("UPSERT SELECT", "CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']"), + text("UPSERT SELECT", "CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']", + " INDEX ATABLE", " REGIONS PLANNED "), scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']").put("abstractExplainPlan", "UPSERT SELECT")); } @@ -546,7 +578,8 @@ public void testUpsertSelectServer() throws Exception { + " SELECT organization_id, entity_id, a_string FROM atable" + " WHERE organization_id = '00D000000000001'", true, - text("UPSERT ROWS", "CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']"), + text("UPSERT ROWS", "CLIENT PARALLEL -WAY RANGE SCAN OVER ATABLE ['00D000000000001']", + " INDEX ATABLE", " REGIONS PLANNED "), scanAttrs("RANGE SCAN ", "ATABLE", " ['00D000000000001']").put("abstractExplainPlan", "UPSERT ROWS")); } @@ -564,6 +597,7 @@ true, text("DELETE SINGLE ROW"), public void testDeleteServer() throws Exception { verifyMutation("deleteServer", "DELETE FROM atable WHERE entity_id = 'abc'", true, text("DELETE ROWS SERVER SELECT", "CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", + " INDEX ATABLE", " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID = 'abc'"), scanAttrs("FULL SCAN ", "ATABLE", "").put("abstractExplainPlan", "DELETE ROWS SERVER SELECT") .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID = 'abc'")); @@ -573,6 +607,7 @@ public void testDeleteServer() throws Exception { public void testDeleteClient() throws Exception { verifyMutation("deleteClient", "DELETE FROM atable WHERE entity_id = 'abc'", false, text("DELETE ROWS CLIENT SELECT", "CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", + " INDEX ATABLE", " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID = 'abc'"), scanAttrs("FULL SCAN ", "ATABLE", "").put("abstractExplainPlan", "DELETE ROWS CLIENT SELECT") .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID = 'abc'")); @@ -581,7 +616,8 @@ public void testDeleteClient() throws Exception { @Test public void testSequenceNextValue() throws Exception { verifyQuery("sequenceNextValue", "SELECT NEXT VALUE FOR " + SEQ + " FROM atable", - text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " SERVER FILTER BY FIRST KEY ONLY", + text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", + " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY", "CLIENT RESERVE VALUES FROM 1 SEQUENCE"), scanAttrs("FULL SCAN ", "ATABLE", "") .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY").put("clientSequenceCount", 1) @@ -591,9 +627,11 @@ public void testSequenceNextValue() throws Exception { @Test public void testSaltedTableScan() throws Exception { verifyQuery("saltedTableScan", "SELECT * FROM " + SALTED + " WHERE v = 7", - text("CLIENT PARALLEL -WAY FULL SCAN OVER EO_SALTED", " SERVER FILTER BY V = 7", + text("CLIENT PARALLEL -WAY FULL SCAN OVER EO_SALTED", " INDEX EO_SALTED", + " SALT BUCKETS 4", " REGIONS PLANNED ", " SERVER FILTER BY V = 7", "CLIENT MERGE SORT"), - scanAttrs("FULL SCAN ", "EO_SALTED", "").put("serverWhereFilter", "SERVER FILTER BY V = 7") + scanAttrs("FULL SCAN ", "EO_SALTED", "").put("saltBuckets", 4) + .put("serverWhereFilter", "SERVER FILTER BY V = 7") .put("clientSortAlgo", "CLIENT MERGE SORT") .set("clientSteps", clientSteps("CLIENT MERGE SORT"))); } @@ -604,12 +642,56 @@ public void testMultiTenantView() throws Exception { tenantProps.setProperty(DATE_FORMAT_ATTRIB, "yyyy-MM-dd"); tenantProps.setProperty(TENANT_ID_ATTRIB, TENANT_ID); verifyQuery("multiTenantView", "SELECT * FROM " + MT_VIEW + " LIMIT 1", tenantProps, - text("CLIENT SERIAL -WAY RANGE SCAN OVER EO_MT_BASE ['tenant42']", - " SERVER 1 ROW LIMIT", "CLIENT 1 ROW LIMIT"), + text("CLIENT SERIAL -WAY RANGE SCAN OVER EO_MT_BASE ['tenant42']", " INDEX EO_MT_VIEW", + " REGIONS PLANNED ", " SERVER 1 ROW LIMIT", "CLIENT 1 ROW LIMIT"), attrs().put("iteratorTypeAndScanSize", "SERIAL -WAY").put("consistency", "STRONG") .put("explainScanType", "RANGE SCAN ").put("tableName", "EO_MT_BASE") - .put("keyRanges", " ['tenant42']").put("serverRowLimit", 1).put("clientRowLimit", 1) - .set("clientSteps", clientSteps("CLIENT 1 ROW LIMIT"))); + .put("indexName", "EO_MT_VIEW").put("keyRanges", " ['tenant42']").put("serverRowLimit", 1) + .put("clientRowLimit", 1).set("clientSteps", clientSteps("CLIENT 1 ROW LIMIT"))); + } + + @Test + public void testIndexKindGlobal() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), defaultProps()); + java.sql.Statement stmt = conn.createStatement()) { + String base = generateUniqueName(); + String idx = generateUniqueName(); + stmt.execute("CREATE TABLE " + base + " (k VARCHAR PRIMARY KEY, v1 VARCHAR, v2 VARCHAR)"); + stmt.execute("CREATE INDEX " + idx + " ON " + base + " (v1) INCLUDE (v2)"); + ExplainPlanTestUtil.assertPlan(conn, "SELECT v1, v2 FROM " + base + " WHERE v1 = 'x'") + .table(idx).indexName(idx).indexKind("GLOBAL"); + } + } + + @Test + public void testIndexKindLocal() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), defaultProps()); + java.sql.Statement stmt = conn.createStatement()) { + String base = generateUniqueName(); + String idx = generateUniqueName(); + stmt.execute("CREATE TABLE " + base + " (k VARCHAR PRIMARY KEY, v1 VARCHAR, v2 VARCHAR)"); + stmt.execute("CREATE LOCAL INDEX " + idx + " ON " + base + " (v1)"); + // The OVER line decorates a local index as (); the INDEX line prints just . + ExplainPlanTestUtil + .assertPlan(conn, + "SELECT /*+ INDEX(" + base + " " + idx + ") */ k, v1 FROM " + base + " WHERE v1 = 'x'") + .indexName(idx).indexKind("LOCAL"); + } + } + + @Test + public void testIndexKindUncoveredGlobal() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), defaultProps()); + java.sql.Statement stmt = conn.createStatement()) { + String base = generateUniqueName(); + String idx = generateUniqueName(); + stmt.execute("CREATE TABLE " + base + " (k VARCHAR PRIMARY KEY, v1 VARCHAR, v2 VARCHAR)"); + stmt.execute("CREATE UNCOVERED INDEX " + idx + " ON " + base + " (v1)"); + ExplainPlanTestUtil + .assertPlan(conn, + "SELECT /*+ INDEX(" + base + " " + idx + ") */ k, v2 FROM " + base + " WHERE v1 = 'x'") + .indexName(idx).indexKind("UNCOVERED GLOBAL"); + } } @Test @@ -908,6 +990,10 @@ private static ObjectNode defaultAttrs() { n.putNull("explainScanType"); n.putNull("tableName"); n.putNull("keyRanges"); + n.putNull("indexName"); + n.putNull("indexKind"); + n.putNull("saltBuckets"); + n.putNull("regionsPlanned"); n.putNull("scanTimeRangeMin"); n.putNull("scanTimeRangeMax"); n.putNull("serverWhereFilter"); @@ -957,6 +1043,9 @@ private static ObjectNode scanAttrs(String scanType, String table, String keys) n.put("consistency", "STRONG"); n.put("explainScanType", scanType); n.put("tableName", table); + // For a data table scan the per scan INDEX line names the same entity as tableName. View and + // index scans that diverge override indexName on the returned node. + n.put("indexName", table); if (keys != null) { n.put("keyRanges", keys); } diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTestUtil.java b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTestUtil.java index 7c55ecbafc2..2f44a07e58c 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTestUtil.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTestUtil.java @@ -175,6 +175,33 @@ public ExplainPlanAssert tableContains(String expected) { return this; } + /** Assert the chosen per-scan index (or data table) name. */ + public ExplainPlanAssert indexName(String expected) { + assertEquals(at("indexName"), expected, attributes.getIndexName()); + return this; + } + + /** + * Assert the per-scan index kind token: {@code "LOCAL"}, {@code "GLOBAL"}, or + * {@code "UNCOVERED GLOBAL"} (null for a data-table target). + */ + public ExplainPlanAssert indexKind(String expected) { + assertEquals(at("indexKind"), expected, attributes.getIndexKind()); + return this; + } + + /** Assert the salt bucket count of the scanned table (null when not salted). */ + public ExplainPlanAssert saltBuckets(Integer expected) { + assertEquals(at("saltBuckets"), expected, attributes.getSaltBuckets()); + return this; + } + + /** Assert the number of regions the scan is planned to hit (null when unknown). */ + public ExplainPlanAssert regionsPlanned(Integer expected) { + assertEquals(at("regionsPlanned"), expected, attributes.getRegionsPlanned()); + return this; + } + /** Assert the hex-string row-value-constructor offset marker. */ public ExplainPlanAssert hexStringRVCOffset(String expected) { assertEquals(at("hexStringRVCOffset"), expected, attributes.getHexStringRVCOffset()); diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainTextNormalizer.java b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainTextNormalizer.java index 24c3bee4a96..c8e1d1ef460 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainTextNormalizer.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainTextNormalizer.java @@ -33,9 +33,12 @@ public final class ExplainTextNormalizer { // Matches the iterator parallelism count. private static final Pattern WAY_COUNT = Pattern.compile("\\b\\d+-WAY\\b"); - // Matches the stats-row-count gated row count and byte count. + // Matches the stats row and byte counts. private static final Pattern ROWS_BYTES = Pattern.compile("\\d+ ROWS \\d+ BYTES\\s*"); + // Matches the planned regions count on the REGIONS PLANNED line. + private static final Pattern REGIONS_PLANNED = Pattern.compile("REGIONS PLANNED \\d+"); + // Matches the region locations line. private static final String REGION_LOCATIONS_PREFIX = " (region locations = "; @@ -62,6 +65,7 @@ public 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 = REGIONS_PLANNED.matcher(normalized).replaceAll("REGIONS PLANNED "); normalized = aliases.rewrite(normalized); out.add(normalized); } From 07e24111849fb3bf511f826d47034b8084c9015b Mon Sep 17 00:00:00 2001 From: Andrew Purtell Date: Tue, 9 Jun 2026 15:43:22 -0700 Subject: [PATCH 04/27] PHOENIX-7886 Server filter and projection EXPLAIN output improvements (#2507) Co-authored-by: Claude Opus 4.8[1m] --- .../compile/ExplainPlanAttributes.java | 44 +++++++-- .../apache/phoenix/iterate/ExplainTable.java | 24 ++--- .../phoenix/end2end/BaseAggregateIT.java | 8 +- .../BaseAggregateWithRegionMoves2IT.java | 8 +- .../BaseAggregateWithRegionMovesIT.java | 4 +- .../BaseTenantSpecificViewIndexIT.java | 5 +- .../apache/phoenix/end2end/BaseViewIT.java | 13 +-- .../phoenix/end2end/CostBasedDecisionIT.java | 19 ++-- ...CountDistinctApproximateHyperLogLogIT.java | 3 +- .../apache/phoenix/end2end/EmptyColumnIT.java | 2 +- .../ExplainPlanWithStatsDisabledIT.java | 24 ++--- .../phoenix/end2end/FlappingLocalIndexIT.java | 10 +-- .../phoenix/end2end/IndexExtendedIT.java | 2 +- .../org/apache/phoenix/end2end/KeyOnlyIT.java | 2 +- .../end2end/LocalIndexSplitMergeIT.java | 9 +- .../phoenix/end2end/QueryWithLimitIT.java | 2 +- .../phoenix/end2end/QueryWithOffsetIT.java | 5 +- .../end2end/QueryWithTableSampleIT.java | 14 ++- .../apache/phoenix/end2end/ReverseScanIT.java | 6 +- .../end2end/SequenceBulkAllocationIT.java | 2 +- .../apache/phoenix/end2end/SequenceIT.java | 2 +- .../phoenix/end2end/SortMergeJoinMoreIT.java | 7 +- .../end2end/TenantSpecificViewIndexIT.java | 10 +-- .../end2end/UserDefinedFunctionsIT.java | 4 +- .../org/apache/phoenix/end2end/ViewIT.java | 16 +--- .../phoenix/end2end/index/BaseIndexIT.java | 16 ++-- .../index/BaseIndexWithRegionMovesIT.java | 16 ++-- .../index/ChildViewsUseParentViewIndexIT.java | 13 ++- .../end2end/index/GlobalIndexCheckerIT.java | 10 +-- .../GlobalIndexCheckerWithRegionMovesIT.java | 10 +-- .../index/GlobalIndexOptimizationIT.java | 29 +++--- .../phoenix/end2end/index/IndexUsageIT.java | 42 ++++----- .../phoenix/end2end/index/LocalIndexIT.java | 46 +++++----- .../phoenix/end2end/index/SaltedIndexIT.java | 18 ++-- .../phoenix/end2end/index/ViewIndexIT.java | 4 +- .../end2end/join/HashJoinGlobalIndexIT.java | 61 ++++++------- .../end2end/join/HashJoinLocalIndexIT.java | 78 +++++++--------- .../phoenix/end2end/join/HashJoinMoreIT.java | 5 +- .../end2end/join/HashJoinNoIndexIT.java | 24 +++-- .../join/SortMergeJoinGlobalIndexIT.java | 16 ++-- .../join/SortMergeJoinLocalIndexIT.java | 14 ++- .../end2end/join/SortMergeJoinNoIndexIT.java | 2 +- .../schema/stats/BaseStatsCollectorIT.java | 3 +- .../compile/JoinQueryCompilerTest.java | 2 +- .../phoenix/compile/QueryCompilerTest.java | 31 +++---- .../StatementHintsCompilationTest.java | 3 +- .../TenantSpecificViewIndexCompileTest.java | 2 +- .../apache/phoenix/query/QueryPlanTest.java | 8 +- .../query/explain/ExplainPlanTest.java | 90 ++++++++++--------- .../query/explain/ExplainPlanTestUtil.java | 36 ++++++++ 50 files changed, 416 insertions(+), 408 deletions(-) diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java index fcf858479aa..6c80e407d51 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 @@ -38,7 +38,8 @@ "keyRanges", "indexName", "indexKind", "saltBuckets", "regionsPlanned", "scanTimeRangeMin", "scanTimeRangeMax", "splitsChunk", "useRoundRobinIterator", "samplingRate", "hexStringRVCOffset", "iteratorTypeAndScanSize", "estimatedRows", "estimatedSizeInBytes", "serverWhereFilter", - "serverDistinctFilter", "serverMergeColumns", "serverArrayElementProjection", "serverAggregate", + "serverDistinctFilter", "serverMergeColumns", "serverArrayElementProjection", + "serverFirstKeyOnlyProjection", "serverEmptyColumnOnlyProjection", "serverAggregate", "serverGroupByLimit", "serverSortedBy", "serverOffset", "serverRowLimit", "clientFilterBy", "clientAggregate", "clientDistinctFilter", "clientAfterAggregate", "clientSortAlgo", "clientSortedBy", "clientOffset", "clientRowLimit", "clientSequenceCount", "clientCursorName", @@ -73,6 +74,8 @@ public class ExplainPlanAttributes { private final String serverDistinctFilter; private final Set serverMergeColumns; private final boolean serverArrayElementProjection; + private final boolean serverFirstKeyOnlyProjection; + private final boolean serverEmptyColumnOnlyProjection; private final String serverAggregate; private final Integer serverGroupByLimit; private final String serverSortedBy; @@ -134,6 +137,8 @@ private ExplainPlanAttributes() { this.serverDistinctFilter = null; this.serverMergeColumns = null; this.serverArrayElementProjection = false; + this.serverFirstKeyOnlyProjection = false; + this.serverEmptyColumnOnlyProjection = false; this.serverAggregate = null; this.serverGroupByLimit = null; this.serverSortedBy = null; @@ -168,9 +173,10 @@ public ExplainPlanAttributes(String abstractExplainPlan, Hint hint, String expla 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, + Set serverMergeColumns, boolean serverArrayElementProjection, + boolean serverFirstKeyOnlyProjection, boolean serverEmptyColumnOnlyProjection, + String serverAggregate, Integer serverGroupByLimit, String serverSortedBy, Integer serverOffset, + Long serverRowLimit, String clientFilterBy, String clientAggregate, String clientDistinctFilter, String clientAfterAggregate, String clientSortAlgo, String clientSortedBy, Integer clientOffset, Integer clientRowLimit, Integer clientSequenceCount, String clientCursorName, List clientSteps, ExplainPlanAttributes lhsJoinQueryExplainPlan, @@ -201,6 +207,8 @@ public ExplainPlanAttributes(String abstractExplainPlan, Hint hint, String expla this.serverDistinctFilter = serverDistinctFilter; this.serverMergeColumns = serverMergeColumns; this.serverArrayElementProjection = serverArrayElementProjection; + this.serverFirstKeyOnlyProjection = serverFirstKeyOnlyProjection; + this.serverEmptyColumnOnlyProjection = serverEmptyColumnOnlyProjection; this.serverAggregate = serverAggregate; this.serverGroupByLimit = serverGroupByLimit; this.serverSortedBy = serverSortedBy; @@ -324,6 +332,14 @@ public boolean isServerArrayElementProjection() { return serverArrayElementProjection; } + public boolean isServerFirstKeyOnlyProjection() { + return serverFirstKeyOnlyProjection; + } + + public boolean isServerEmptyColumnOnlyProjection() { + return serverEmptyColumnOnlyProjection; + } + public String getServerAggregate() { return serverAggregate; } @@ -457,6 +473,8 @@ public static class ExplainPlanAttributesBuilder { private String serverDistinctFilter; private Set serverMergeColumns; private boolean serverArrayElementProjection; + private boolean serverFirstKeyOnlyProjection; + private boolean serverEmptyColumnOnlyProjection; private String serverAggregate; private Integer serverGroupByLimit; private String serverSortedBy; @@ -512,6 +530,9 @@ public ExplainPlanAttributesBuilder(ExplainPlanAttributes explainPlanAttributes) this.serverDistinctFilter = explainPlanAttributes.getServerDistinctFilter(); this.serverMergeColumns = explainPlanAttributes.getServerMergeColumns(); this.serverArrayElementProjection = explainPlanAttributes.isServerArrayElementProjection(); + this.serverFirstKeyOnlyProjection = explainPlanAttributes.isServerFirstKeyOnlyProjection(); + this.serverEmptyColumnOnlyProjection = + explainPlanAttributes.isServerEmptyColumnOnlyProjection(); this.serverAggregate = explainPlanAttributes.getServerAggregate(); this.serverGroupByLimit = explainPlanAttributes.getServerGroupByLimit(); this.serverSortedBy = explainPlanAttributes.getServerSortedBy(); @@ -657,6 +678,18 @@ public ExplainPlanAttributesBuilder setServerMergeColumns(Set columns) return this; } + public ExplainPlanAttributesBuilder + setServerFirstKeyOnlyProjection(boolean serverFirstKeyOnlyProjection) { + this.serverFirstKeyOnlyProjection = serverFirstKeyOnlyProjection; + return this; + } + + public ExplainPlanAttributesBuilder + setServerEmptyColumnOnlyProjection(boolean serverEmptyColumnOnlyProjection) { + this.serverEmptyColumnOnlyProjection = serverEmptyColumnOnlyProjection; + return this; + } + public ExplainPlanAttributesBuilder setServerAggregate(String serverAggregate) { this.serverAggregate = serverAggregate; return this; @@ -803,7 +836,8 @@ public ExplainPlanAttributes build() { tableName, keyRanges, indexName, indexKind, saltBuckets, regionsPlanned, scanTimeRangeMin, scanTimeRangeMax, splitsChunk, useRoundRobinIterator, samplingRate, hexStringRVCOffset, iteratorTypeAndScanSize, estimatedRows, estimatedSizeInBytes, serverWhereFilter, - serverDistinctFilter, serverMergeColumns, serverArrayElementProjection, serverAggregate, + serverDistinctFilter, serverMergeColumns, serverArrayElementProjection, + serverFirstKeyOnlyProjection, serverEmptyColumnOnlyProjection, serverAggregate, serverGroupByLimit, serverSortedBy, serverOffset, serverRowLimit, clientFilterBy, clientAggregate, clientDistinctFilter, clientAfterAggregate, clientSortAlgo, clientSortedBy, clientOffset, clientRowLimit, clientSequenceCount, clientCursorName, clientSteps, diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java index 1a1f32786f1..badc779744f 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java @@ -287,23 +287,23 @@ protected void explain(String prefix, List planSteps, whereFilterStr = Bytes.toString(expBytes); } } - if (whereFilterStr != null) { - String serverWhereFilter = - "SERVER FILTER BY " + (firstKeyOnlyFilter == null ? "" : "FIRST KEY ONLY AND ") - + (emptyColumnOnlyFilter == null ? "" : "EMPTY COLUMN ONLY AND ") + whereFilterStr; - planSteps.add(" " + serverWhereFilter); + if (firstKeyOnlyFilter != null) { + planSteps.add(" SERVER PROJECTION FILTER BY FIRST KEY ONLY"); if (explainPlanAttributesBuilder != null) { - explainPlanAttributesBuilder.setServerWhereFilter(serverWhereFilter); + explainPlanAttributesBuilder.setServerFirstKeyOnlyProjection(true); } - } else if (firstKeyOnlyFilter != null) { - planSteps.add(" SERVER FILTER BY FIRST KEY ONLY"); + } + if (emptyColumnOnlyFilter != null) { + planSteps.add(" SERVER PROJECTION FILTER BY EMPTY COLUMN ONLY"); if (explainPlanAttributesBuilder != null) { - explainPlanAttributesBuilder.setServerWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + explainPlanAttributesBuilder.setServerEmptyColumnOnlyProjection(true); } - } else if (emptyColumnOnlyFilter != null) { - planSteps.add(" SERVER FILTER BY EMPTY COLUMN ONLY"); + } + if (whereFilterStr != null) { + String serverWhereFilter = "SERVER FILTER BY " + whereFilterStr; + planSteps.add(" " + serverWhereFilter); if (explainPlanAttributesBuilder != null) { - explainPlanAttributesBuilder.setServerWhereFilter("SERVER FILTER BY EMPTY COLUMN ONLY"); + explainPlanAttributesBuilder.setServerWhereFilter(serverWhereFilter); } } if (distinctFilter != null) { diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateIT.java index 2be8e8f7721..3a40ef58186 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateIT.java @@ -515,7 +515,7 @@ public void testGroupByOrderPreserving() throws Exception { .table(tableName) .keyRanges( " ['000001111122222','333334444455555',0,*] - ['000001111122222','333334444455555',0,1]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverFirstKeyOnlyProjection(true) .serverAggregate( "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [MATCH_STATUS, EXTERNAL_DATASOURCE_KEY]") .clientFilterBy("COUNT(1) > 1"); @@ -557,7 +557,7 @@ public void testGroupByOrderPreservingDescSort() throws Exception { assertEquals(4, rs.getLong(2)); assertFalse(rs.next()); assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").clientSortedBy("REVERSE") - .scanType("FULL SCAN").table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .scanType("FULL SCAN").table(tableName).serverFirstKeyOnlyProjection(true) .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]") .regionLocationsNotEmpty(); } @@ -606,7 +606,7 @@ public void testSumGroupByOrderPreservingDesc() throws Exception { assertEquals(10, rs.getLong(2)); assertFalse(rs.next()); assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").clientSortedBy("REVERSE") - .scanType("FULL SCAN").table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .scanType("FULL SCAN").table(tableName).serverFirstKeyOnlyProjection(true) .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]") .regionLocationsNotEmpty(); } @@ -665,7 +665,7 @@ protected void testAvgGroupByOrderPreserving(Connection conn, String tableName, assertEquals(2, rs.getDouble(2), 1e-6); assertFalse(rs.next()); assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") - .table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .table(tableName).serverFirstKeyOnlyProjection(true) .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]") .regionLocationsNotEmpty(); TestUtil.analyzeTable(conn, tableName); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateWithRegionMoves2IT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateWithRegionMoves2IT.java index 7d908eecc19..bde2c42b837 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateWithRegionMoves2IT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateWithRegionMoves2IT.java @@ -139,7 +139,7 @@ public void testGroupByOrderPreserving() throws Exception { .table(tableName) .keyRanges( " ['000001111122222','333334444455555',0,*] - ['000001111122222','333334444455555',0,1]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverFirstKeyOnlyProjection(true) .serverAggregate( "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [MATCH_STATUS, EXTERNAL_DATASOURCE_KEY]") .clientFilterBy("COUNT(1) > 1"); @@ -183,7 +183,7 @@ public void testGroupByOrderPreservingDescSort() throws Exception { assertEquals(4, rs.getLong(2)); assertFalse(rs.next()); assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").clientSortedBy("REVERSE") - .scanType("FULL SCAN").table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .scanType("FULL SCAN").table(tableName).serverFirstKeyOnlyProjection(true) .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]"); } @@ -235,7 +235,7 @@ public void testSumGroupByOrderPreservingDesc() throws Exception { assertEquals(10, rs.getLong(2)); assertFalse(rs.next()); assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").clientSortedBy("REVERSE") - .scanType("FULL SCAN").table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .scanType("FULL SCAN").table(tableName).serverFirstKeyOnlyProjection(true) .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]"); } @@ -437,7 +437,7 @@ protected void testAvgGroupByOrderPreserving(Connection conn, String tableName, assertEquals(2, rs.getDouble(2), 1e-6); assertFalse(rs.next()); assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") - .table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .table(tableName).serverFirstKeyOnlyProjection(true) .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]"); TestUtil.analyzeTable(conn, tableName); List splits = TestUtil.getAllSplits(conn, tableName); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateWithRegionMovesIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateWithRegionMovesIT.java index e363b0bf29b..83910c50ed9 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateWithRegionMovesIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseAggregateWithRegionMovesIT.java @@ -364,7 +364,7 @@ protected void testAvgGroupByOrderPreserving(Connection conn, String tableName, assertEquals(2, rs.getDouble(2), 1e-6); assertFalse(rs.next()); assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") - .table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .table(tableName).serverFirstKeyOnlyProjection(true) .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]"); TestUtil.analyzeTable(conn, tableName); List splits = TestUtil.getAllSplits(conn, tableName); @@ -395,7 +395,7 @@ protected void testAvgGroupByDescOrderPreserving(Connection conn, String tableNa assertEquals(3, rs.getDouble(2), 1e-6); assertFalse(rs.next()); assertPlan(conn, queryBuilder.build()).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") - .table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .table(tableName).serverFirstKeyOnlyProjection(true) .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [K1]"); TestUtil.analyzeTable(conn, tableName); List splits = TestUtil.getAllSplits(conn, tableName); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificViewIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificViewIndexIT.java index 1fab8de99b8..70708610c28 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificViewIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificViewIndexIT.java @@ -195,7 +195,7 @@ private void createAndVerifyIndex(Connection conn, String viewName, String table expectedTableName = "_IDX_" + tableName; } assertPlan(conn, "SELECT k1, k2, v2 FROM " + viewName + " WHERE v2='" + valuePrefix + "v2-1'") - .iteratorType(iteratorTypeAndScanSize).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .iteratorType(iteratorTypeAndScanSize).serverFirstKeyOnlyProjection(true) .scanType("RANGE SCAN").clientSortAlgo(clientSortAlgo).table(expectedTableName) .keyRanges(keyRanges); } @@ -209,8 +209,7 @@ private void createAndVerifyIndexNonStringTenantId(Connection conn, String viewN .execute("UPSERT INTO " + viewName + "(k2,v1,v2) VALUES (-1, 'blah', 'superblah')"); conn.commit(); assertPlan(conn, "SELECT k1, k2, v2 FROM " + viewName + " WHERE v2='" + valuePrefix + "v2-1'") - .iteratorType("PARALLEL 1-WAY").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") - .scanType("RANGE SCAN") + .iteratorType("PARALLEL 1-WAY").serverFirstKeyOnlyProjection(true).scanType("RANGE SCAN") .table(SchemaUtil.getTableName(SchemaUtil.getSchemaNameFromFullName(viewName), indexName) + "(" + tableName + ")") .keyRanges(" [1," + tenantId + ",'" + valuePrefix + "v2-1']") diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java index 3b3f6f81d42..9f9a3e96a77 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java @@ -202,14 +202,14 @@ protected Pair testUpdatableViewIndex(Integer saltBuckets, boolean String clientSortAlgo; String expectedTableName; String keyRanges; - String serverFilterBy; + boolean firstKeyOnlyProjection; if (localIndex) { iteratorTypeAndScanSize = "PARALLEL " + (saltBuckets == null ? 1 : saltBuckets) + "-WAY"; expectedTableName = fullTableName; keyRanges = "[1,51]"; clientSortAlgo = "CLIENT MERGE SORT"; - serverFilterBy = "SERVER FILTER BY FIRST KEY ONLY"; + firstKeyOnlyProjection = true; } else { iteratorTypeAndScanSize = saltBuckets == null ? "PARALLEL 1-WAY" : "PARALLEL " + saltBuckets + "-WAY"; @@ -219,11 +219,12 @@ protected Pair testUpdatableViewIndex(Integer saltBuckets, boolean : " [0," + Short.MIN_VALUE + ",51] - [" + (saltBuckets - 1) + "," + Short.MIN_VALUE + ",51]"; clientSortAlgo = saltBuckets == null ? null : "CLIENT MERGE SORT"; - serverFilterBy = null; + firstKeyOnlyProjection = false; } assertPlan(conn, query).iteratorType(iteratorTypeAndScanSize).scanType("RANGE SCAN") .table(expectedTableName).clientSortAlgo(clientSortAlgo).keyRanges(keyRanges) - .serverWhereFilter(serverFilterBy); + .serverFirstKeyOnlyProjection(firstKeyOnlyProjection).serverEmptyColumnOnlyProjection(false) + .serverWhereFilter(null); String viewIndexName2 = "I_" + generateUniqueName(); if (localIndex) { @@ -269,8 +270,8 @@ protected Pair testUpdatableViewIndex(Integer saltBuckets, boolean clientSortAlgo = saltBuckets == null ? null : "CLIENT MERGE SORT"; } assertPlan(conn, query).table(physicalTableName).iteratorType(iteratorTypeAndScanSize) - .scanType("RANGE SCAN").keyRanges(keyRanges) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo(clientSortAlgo); + .scanType("RANGE SCAN").keyRanges(keyRanges).serverFirstKeyOnlyProjection(true) + .clientSortAlgo(clientSortAlgo); conn.close(); return new Pair<>(physicalTableName, scan); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CostBasedDecisionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CostBasedDecisionIT.java index e2bd9a66ede..8e9518db9b8 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CostBasedDecisionIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CostBasedDecisionIT.java @@ -21,6 +21,7 @@ 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; @@ -148,7 +149,8 @@ public void testCostOverridesStaticPlanOrdering2() throws Exception { assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); assertEquals(indexName + "(" + tableName + ")", explainPlanAttributes.getTableName()); assertEquals(" [1]", explainPlanAttributes.getKeyRanges()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY AND \"ROWKEY\" <= 'z'", + assertTrue(explainPlanAttributes.isServerFirstKeyOnlyProjection()); + assertEquals("SERVER FILTER BY \"ROWKEY\" <= 'z'", explainPlanAttributes.getServerWhereFilter()); assertEquals("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]", explainPlanAttributes.getServerAggregate()); @@ -355,12 +357,12 @@ public void testCostOverridesStaticPlanOrderingInUnionQuery() throws Exception { // Use the optimal plan based on cost when stats become available. assertPlan(conn, query).abstractExplainPlan("UNION ALL OVER 2 QUERIES") .iteratorType("PARALLEL").scanType("RANGE SCAN").table(indexName + "(" + tableName + ")") - .keyRanges(" [1]").serverMergeColumns("[0.C2]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND \"ROWKEY\" <= 'z'") + .keyRanges(" [1]").serverMergeColumns("[0.C2]").serverFirstKeyOnlyProjection(true) + .serverWhereFilter("SERVER FILTER BY \"ROWKEY\" <= 'z'") .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]") .clientSortAlgo("CLIENT MERGE SORT").rhs().iteratorType("PARALLEL").scanType("RANGE SCAN") .table(indexName + "(" + tableName + ")").keyRanges(" [1]").serverMergeColumns("[0.C2]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND \"ROWKEY\" >= 'a'") + .serverFirstKeyOnlyProjection(true).serverWhereFilter("SERVER FILTER BY \"ROWKEY\" >= 'a'") .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]") .clientSortAlgo("CLIENT MERGE SORT"); } finally { @@ -409,13 +411,13 @@ public void testCostOverridesStaticPlanOrderingInJoinQuery() throws Exception { // Use the optimal plan based on cost when stats become available. assertPlan(conn, query).iteratorType("PARALLEL 626-WAY").scanType("RANGE SCAN") .table(indexName + "(" + tableName + ")").keyRanges(" [1,'X0'] - [1,'X1']") - .serverMergeColumns("[0.C2]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverMergeColumns("[0.C2]").serverFirstKeyOnlyProjection(true) .serverSortedBy("[\"T1.:ROWKEY\"]").clientSortAlgo("CLIENT MERGE SORT") .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"T1.:ROWKEY\" IN (T2.MRK)").subPlanCount(1) .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") .table(indexName + "(" + tableName + ")").keyRanges(" [1]").serverMergeColumns("[0.C2]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND \"ROWKEY\" <= 'z'") + .serverFirstKeyOnlyProjection(true).serverWhereFilter("SERVER FILTER BY \"ROWKEY\" <= 'z'") .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]") .clientSortAlgo("CLIENT MERGE SORT"); } finally { @@ -440,8 +442,7 @@ public void testHintOverridesCost() throws Exception { String hintedQuery = query.replaceFirst("SELECT", "SELECT /*+ INDEX(" + tableName + " " + tableName + "_idx) */"); String dataPlan = "[C1]"; - String indexPlan = - "SERVER FILTER BY FIRST KEY ONLY AND (\"ROWKEY\" >= 1 AND \"ROWKEY\" <= 10)"; + String indexPlan = "SERVER FILTER BY (\"ROWKEY\" >= 1 AND \"ROWKEY\" <= 10)"; // Use the index table plan that opts out order-by when stats are not available. ExplainPlan plan = conn.prepareStatement(query).unwrap(PhoenixPreparedStatement.class) @@ -503,7 +504,7 @@ public void testJoinStrategy2() throws Exception { .clientAggregate("CLIENT AGGREGATE INTO SINGLE ROW").lhs().iteratorType("PARALLEL 1-WAY") .scanType("FULL SCAN").table(testTable500).serverWhereFilter("SERVER FILTER BY COL1 < 200") .end().rhs().iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable1000) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .serverFirstKeyOnlyProjection(true); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CountDistinctApproximateHyperLogLogIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CountDistinctApproximateHyperLogLogIT.java index 0d30793c23b..8b4dc54e01a 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CountDistinctApproximateHyperLogLogIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CountDistinctApproximateHyperLogLogIT.java @@ -123,8 +123,7 @@ public void testDistinctCountPlanExplain() throws Exception { try (Connection conn = DriverManager.getConnection(getUrl(), props)) { prepareTableWithValues(conn, 100); assertPlan(conn, query).table(tableName).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") - .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW"); + .serverFirstKeyOnlyProjection(true).serverAggregate("SERVER AGGREGATE INTO SINGLE ROW"); } } 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 45ba6fc22db..d52749615d1 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 @@ -748,7 +748,7 @@ public void testMaskingWithDistinctPrefixFilter() throws Exception { EnvironmentEdgeManager.injectEdge(injectEdge); String distinctQuery = "SELECT DISTINCT id1 FROM " + dataTableName; ExplainPlanAttributes attributes = getExplainAttributes(conn, distinctQuery); - assertPlan(attributes).serverWhereFilter("SERVER FILTER BY EMPTY COLUMN ONLY"); + assertPlan(attributes).serverEmptyColumnOnlyProjection(true); assertNotNull(attributes.getServerDistinctFilter()); try (ResultSet rs = conn.createStatement().executeQuery(distinctQuery)) { // all the rows should have been masked 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 7606aec6fd8..0bee64c4ebd 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 @@ -256,7 +256,7 @@ public void testDescTimestampAtBoundary() throws Exception { 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"); + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT"); } } @@ -276,7 +276,7 @@ public void testUseOfRoundRobinIteratorSurfaced() throws Exception { 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"); + .serverFirstKeyOnlyProjection(true); } } @@ -341,32 +341,32 @@ public void testRangeScanWithMetadataLookup() throws Exception { assertTrue(rs.next()); assertEquals(53, rs.getInt(1)); assertPlan(conn, query).scanType("RANGE SCAN").table(tableName).keyRanges(" [*] - ['b']") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") - .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW").numRegionLocationLookups(2); + .serverFirstKeyOnlyProjection(true).serverAggregate("SERVER AGGREGATE INTO SINGLE ROW") + .numRegionLocationLookups(2); query = "select count(*) from " + tableName + " where PK1 <= 'cd'"; rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals(128, rs.getInt(1)); assertPlan(conn, query).scanType("RANGE SCAN").table(tableName).keyRanges(" [*] - ['cd']") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") - .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW").numRegionLocationLookups(3); + .serverFirstKeyOnlyProjection(true).serverAggregate("SERVER AGGREGATE INTO SINGLE ROW") + .numRegionLocationLookups(3); query = "select count(*) from " + tableName + " where PK1 LIKE 'ef%'"; rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals(25, rs.getInt(1)); assertPlan(conn, query).scanType("RANGE SCAN").table(tableName).keyRanges(" ['ef'] - ['eg']") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") - .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW").numRegionLocationLookups(1); + .serverFirstKeyOnlyProjection(true).serverAggregate("SERVER AGGREGATE INTO SINGLE ROW") + .numRegionLocationLookups(1); query = "select count(*) from " + tableName + " where PK1 > 'de'"; rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals(75, rs.getInt(1)); assertPlan(conn, query).scanType("RANGE SCAN").table(tableName).keyRanges(" ['de'] - [*]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") - .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW").numRegionLocationLookups(1); + .serverFirstKeyOnlyProjection(true).serverAggregate("SERVER AGGREGATE INTO SINGLE ROW") + .numRegionLocationLookups(1); } } @@ -461,8 +461,8 @@ public void testMultiTenantWithMetadataLookup() throws Exception { assertEquals(50, rs.getInt(1)); assertPlan(tenantConn, query).scanType("RANGE SCAN").table(tableName).keyRanges(" ['ab12']") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") - .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW").numRegionLocationLookups(1); + .serverFirstKeyOnlyProjection(true).serverAggregate("SERVER AGGREGATE INTO SINGLE ROW") + .numRegionLocationLookups(1); } try (Connection tenantConn = getTenantConnection("cd12")) { diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/FlappingLocalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/FlappingLocalIndexIT.java index 9fa2e49bd50..b0adc333fc0 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/FlappingLocalIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/FlappingLocalIndexIT.java @@ -169,7 +169,7 @@ public void testLocalIndexScan() throws Exception { // full number of regions is reported via regionLocationsTotalSize. assertPlan(conn1, query).iteratorType("PARALLEL " + numRegions + "-WAY") .scanType("RANGE SCAN").table(indexTableName + "(" + indexPhysicalTableName + ")") - .keyRanges(" [1,'a'] - [1,'b']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .keyRanges(" [1,'a'] - [1,'b']").serverFirstKeyOnlyProjection(true) .clientSortAlgo("CLIENT MERGE SORT").regionLocationCount(trimmedRegionLocations) .regionLocationsTotalSize(numRegions); @@ -191,7 +191,7 @@ public void testLocalIndexScan() throws Exception { assertPlan(conn1, query).iteratorType("PARALLEL " + numRegions + "-WAY") .scanType("RANGE SCAN").table(indexTableName + "(" + indexPhysicalTableName + ")") - .keyRanges(" [1,'a']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .keyRanges(" [1,'a']").serverFirstKeyOnlyProjection(true) .clientSortAlgo("CLIENT MERGE SORT"); rs = conn1.createStatement().executeQuery(query); @@ -208,7 +208,7 @@ public void testLocalIndexScan() throws Exception { assertPlan(conn1, query).iteratorType("PARALLEL " + numRegions + "-WAY") .scanType("RANGE SCAN").table(indexTableName + "(" + indexPhysicalTableName + ")") - .keyRanges(" [1,*] - [1,'z']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .keyRanges(" [1,*] - [1,'z']").serverFirstKeyOnlyProjection(true) .clientSortAlgo("CLIENT MERGE SORT").serverSortedBy("[\"K3\"]"); rs = conn1.createStatement().executeQuery(query); @@ -230,8 +230,8 @@ public void testLocalIndexScan() throws Exception { assertPlan(conn1, query).iteratorType("PARALLEL " + numRegions + "-WAY") .scanType("RANGE SCAN").table(indexTableName + "(" + indexPhysicalTableName + ")") - .keyRanges(" [1]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") - .clientSortAlgo("CLIENT MERGE SORT").serverSortedBy(null); + .keyRanges(" [1]").serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT") + .serverSortedBy(null); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexExtendedIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexExtendedIT.java index eda03aaa9c1..90317a3cd0f 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexExtendedIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexExtendedIT.java @@ -250,7 +250,7 @@ public void testDeleteFromImmutable() throws Exception { String query = "SELECT pk3 from " + dataTableFullName + " ORDER BY pk3"; assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") - .table(indexTableFullName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .table(indexTableFullName).serverFirstKeyOnlyProjection(true); ResultSet rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/KeyOnlyIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/KeyOnlyIT.java index dffcea8379a..584827d92e8 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/KeyOnlyIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/KeyOnlyIT.java @@ -144,7 +144,7 @@ public void testQueryWithLimitAndStats() throws Exception { assertFalse(rs.next()); assertPlan(conn, query).iteratorType("SERIAL 1-WAY").scanType("FULL SCAN").table(tableName) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverRowLimit(1L).clientRowLimit(1); + .serverFirstKeyOnlyProjection(true).serverRowLimit(1L).clientRowLimit(1); } @Test diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/LocalIndexSplitMergeIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/LocalIndexSplitMergeIT.java index eebcfaf6fdc..f8df8a1e79c 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/LocalIndexSplitMergeIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/LocalIndexSplitMergeIT.java @@ -143,15 +143,14 @@ public void testLocalIndexScanAfterRegionSplit() throws Exception { assertPlan(conn1, query).iteratorType("PARALLEL " + (4 + i) + "-WAY").scanType("RANGE SCAN") .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT"); query = "SELECT t_id,k1,k3 FROM " + tableName; assertPlan(conn1, query) .iteratorType( "PARALLEL " + ((strings[3 * i].compareTo("j") < 0) ? (4 + i) : (4 + i - 1)) + "-WAY") .scanType("RANGE SCAN").table(fullIndexName + "_2(" + indexPhysicalTableName + ")") - .keyRanges(" [2]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") - .clientSortAlgo("CLIENT MERGE SORT"); + .keyRanges(" [2]").serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT"); rs = conn1.createStatement().executeQuery(query); Thread.sleep(1000); @@ -229,12 +228,12 @@ public void testLocalIndexScanAfterRegionsMerge() throws Exception { assertPlan(conn1, query).iteratorType("PARALLEL 3-WAY").scanType("RANGE SCAN") .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT"); query = "SELECT t_id,k1,k3 FROM " + tableName; assertPlan(conn1, query).iteratorType("PARALLEL 3-WAY").scanType("RANGE SCAN") .table(fullIndexName + "_2(" + indexPhysicalTableName + ")").keyRanges(" [2]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT"); rs = conn1.createStatement().executeQuery(query); Thread.sleep(1000); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithLimitIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithLimitIT.java index e7666f9a68f..cef4e03c8c8 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithLimitIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithLimitIT.java @@ -90,7 +90,7 @@ public void testQueryWithLimitAndStats() throws Exception { assertFalse(rs.next()); assertPlan(conn, query).iteratorType("SERIAL 1-WAY").scanType("FULL SCAN").table(tableName) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverRowLimit(1L).clientRowLimit(1); + .serverFirstKeyOnlyProjection(true).serverRowLimit(1L).clientRowLimit(1); } finally { conn.close(); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithOffsetIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithOffsetIT.java index 6cda98ab04a..232b9dc264a 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithOffsetIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryWithOffsetIT.java @@ -129,11 +129,10 @@ public void testOffsetSerialQueryExecutedOnServer() throws SQLException { String query = "SELECT t_id from " + tableName + " offset " + offset; if (!isSalted) { assertPlan(conn, query).scanType("FULL SCAN").table(tableName) - .serverWhereFilter("SERVER FILTER BY EMPTY COLUMN ONLY").iteratorType("SERIAL 1-WAY") - .serverOffset(offset); + .serverEmptyColumnOnlyProjection(true).iteratorType("SERIAL 1-WAY").serverOffset(offset); } else { assertPlan(conn, query).scanType("FULL SCAN").table(tableName) - .serverWhereFilter("SERVER FILTER BY EMPTY COLUMN ONLY").iteratorType("PARALLEL 10-WAY") + .serverEmptyColumnOnlyProjection(true).iteratorType("PARALLEL 10-WAY") .clientSortAlgo("CLIENT MERGE SORT").clientOffset(offset); } 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 ddbf213c45c..93702373e9c 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 @@ -212,7 +212,7 @@ public void testExplainSingleQuery() throws Exception { prepareTableWithValues(conn, 100); String query = "SELECT i1, i2 FROM " + tableName + " tablesample (45) "; assertPlan(conn, query).iteratorType("PARALLEL").scanType("FULL SCAN").samplingRate(0.45d) - .table(tableName).serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .table(tableName).serverFirstKeyOnlyProjection(true); } } @@ -227,9 +227,9 @@ public void testExplainSingleQueryWithUnion() throws Exception { + 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(); + .serverFirstKeyOnlyProjection(true).rhs().scanType("FULL SCAN").table(tableName) + .samplingRate(0.02d).serverFirstKeyOnlyProjection(true) + .serverWhereFilter("SERVER FILTER BY I2 < 6000").end(); } finally { conn.close(); } @@ -245,12 +245,10 @@ public void testExplainSingleQueryWithJoins() throws Exception { + joinedTableName + " as B tablesample (75) where A.i1=B.i1"; assertPlan(conn, query).scanType("FULL SCAN").table(tableName).samplingRate(0.45d) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") - .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW") + .serverFirstKeyOnlyProjection(true).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(); + .table(joinedTableName).samplingRate(0.75d).serverFirstKeyOnlyProjection(true).end(); } finally { conn.close(); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ReverseScanIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ReverseScanIT.java index d3eb6177869..c1bab87569b 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ReverseScanIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ReverseScanIT.java @@ -80,8 +80,8 @@ public void testReverseRangeScan() throws Exception { assertFalse(rs.next()); assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").clientSortedBy("REVERSE") - .scanType("FULL SCAN").table(tableName) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID >= '00A323122312312'"); + .scanType("FULL SCAN").table(tableName).serverFirstKeyOnlyProjection(true) + .serverWhereFilter("SERVER FILTER BY ENTITY_ID >= '00A323122312312'"); PreparedStatement statement = conn.prepareStatement("SELECT entity_id FROM " + tableName + " WHERE organization_id = ? AND entity_id >= ? ORDER BY organization_id DESC, entity_id DESC"); @@ -179,7 +179,7 @@ public void testReverseScanIndex() throws Exception { assertPlan(conn, query).iteratorType("SERIAL 1-WAY").clientSortedBy("REVERSE") .scanType("RANGE SCAN").table(indexName).keyRanges(" [not null]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverRowLimit(1L).clientRowLimit(1); + .serverFirstKeyOnlyProjection(true).serverRowLimit(1L).clientRowLimit(1); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SequenceBulkAllocationIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SequenceBulkAllocationIT.java index 697b1aad8b1..4080d5f30a0 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SequenceBulkAllocationIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SequenceBulkAllocationIT.java @@ -824,7 +824,7 @@ public void testExplainPlanForNextValuesFor() throws Exception { // Assert output for Explain Plain result is as expected assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(tableName) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSequenceCount(1); + .serverFirstKeyOnlyProjection(true).clientSequenceCount(1); } /** diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SequenceIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SequenceIT.java index 0ff1bb53db7..83f1fae5b95 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SequenceIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SequenceIT.java @@ -823,7 +823,7 @@ public void testExplainPlanValidatesSequences() throws Exception { DriverManager.getConnection(getUrl(), PropertiesUtil.deepCopy(TEST_PROPERTIES)); String query = "SELECT NEXT VALUE FOR " + sequenceName + " FROM " + tableName; assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(tableName) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSequenceCount(1); + .serverFirstKeyOnlyProjection(true).clientSequenceCount(1); ResultSet rs = conn.createStatement().executeQuery( "SELECT sequence_name, current_value FROM \"SYSTEM\".\"SEQUENCE\" WHERE sequence_name='" 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 26621c1c9bb..46c0747d77a 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 @@ -429,15 +429,14 @@ public void testBug2894() throws Exception { assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(true) .clientAggregate("CLIENT AGGREGATE INTO ORDERED DISTINCT ROWS BY [E.BUCKET, E.TIMESTAMP]") .lhs().iteratorType("PARALLEL").scanType("SKIP SCAN ON 2 RANGES") - .table(eventCountTableName).keyRanges(lhsKeyRanges) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .table(eventCountTableName).keyRanges(lhsKeyRanges).serverFirstKeyOnlyProjection(true) .serverDistinctFilter("SERVER DISTINCT PREFIX FILTER OVER [BUCKET, TIMESTAMP, LOCATION]") .serverAggregate( "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [BUCKET, TIMESTAMP, LOCATION]") .clientSortAlgo("CLIENT MERGE SORT").clientSortedBy("[BUCKET, TIMESTAMP]").end().rhs() .iteratorType("PARALLEL").scanType("SKIP SCAN ON 2 RANGES").table(t[i]) - .keyRanges(rhsKeyRanges) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND SRC_LOCATION = DST_LOCATION") + .keyRanges(rhsKeyRanges).serverFirstKeyOnlyProjection(true) + .serverWhereFilter("SERVER FILTER BY SRC_LOCATION = DST_LOCATION") .serverDistinctFilter( "SERVER DISTINCT PREFIX FILTER OVER [BUCKET, \"TIMESTAMP\", SRC_LOCATION, DST_LOCATION]") .serverAggregate( diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificViewIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificViewIndexIT.java index 0223b8bca54..000dad55ab3 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificViewIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificViewIndexIT.java @@ -224,7 +224,7 @@ private void createViewAndIndexesWithTenantId(String tableName, String viewName, explainPlanAttributes = plan.getPlanStepsAsAttributes(); assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals("SERVER FILTER BY FIRST KEY ONLY", explainPlanAttributes.getServerWhereFilter()); + assertTrue(explainPlanAttributes.isServerFirstKeyOnlyProjection()); if (localIndex) { assertEquals(fullIndexName + "(" + SchemaUtil.getPhysicalTableName(Bytes.toBytes(tableName), isNamespaceMapped).toString() @@ -366,8 +366,8 @@ public void testOverlappingDatesFilter() throws Exception { .table(expectedIndexName) .keyRanges(" ['tenant1 ','001','2011-01-01 00:00:00.001']" + " - ['tenant1 ','001','2016-10-31 00:00:00.000']") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverRowLimit(501L) - .clientRowLimit(501).clientSteps("CLIENT 501 ROW LIMIT"); + .serverFirstKeyOnlyProjection(true).serverRowLimit(501L).clientRowLimit(501) + .clientSteps("CLIENT 501 ROW LIMIT"); String query2 = "SELECT PARENT_ID FROM " + viewName + " WHERE PARENT_TYPE='001' " + " AND (CREATED_DATE >= to_date('2011-01-01') AND CREATED_DATE <= to_date('2016-01-01'))" @@ -377,8 +377,8 @@ public void testOverlappingDatesFilter() throws Exception { .table(expectedIndexName) .keyRanges(" ['tenant1 ','001','2012-10-21 00:00:00.001']" + " - ['tenant1 ','001','2016-01-01 00:00:00.000']") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverRowLimit(501L) - .clientRowLimit(501).clientSteps("CLIENT 501 ROW LIMIT"); + .serverFirstKeyOnlyProjection(true).serverRowLimit(501L).clientRowLimit(501) + .clientSteps("CLIENT 501 ROW LIMIT"); } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UserDefinedFunctionsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UserDefinedFunctionsIT.java index 231d877ecdb..88980c4dfa3 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UserDefinedFunctionsIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UserDefinedFunctionsIT.java @@ -841,7 +841,7 @@ public void testFunctionalIndexesWithUDFFunction() throws Exception { String query = "select myreverse5(lastname_reverse) from t5"; assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table("IDX") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .serverFirstKeyOnlyProjection(true); ResultSet rs = stmt.executeQuery(query); assertTrue(rs.next()); @@ -852,7 +852,7 @@ public void testFunctionalIndexesWithUDFFunction() throws Exception { "select k,k1,myreverse5(lastname_reverse) from t5 where myreverse5(lastname_reverse)='kcoj'"; assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table("IDX2(T5)") - .keyRanges(" [1,'kcoj']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .keyRanges(" [1,'kcoj']").serverFirstKeyOnlyProjection(true) .clientSortAlgo("CLIENT MERGE SORT"); rs = stmt.executeQuery(query); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java index e3bf620dc62..656dffc8470 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java @@ -967,9 +967,7 @@ public static Pair testUpdatableViewIndex(String fullTableName, In assertPlan(conn, query).scanType("RANGE SCAN") .iteratorType("PARALLEL " + (saltBuckets == null ? 1 : saltBuckets) + "-WAY") .table(viewIndexFullName1 + "(" + fullTableName + ")").keyRanges(" [1,51]") - .serverWhereFilterAnyOf("SERVER FILTER BY FIRST KEY ONLY", - "SERVER FILTER BY EMPTY COLUMN ONLY") - .clientSortAlgo("CLIENT MERGE SORT"); + .serverProjectionFilterAnyOf().clientSortAlgo("CLIENT MERGE SORT"); } else if (saltBuckets == null) { assertPlan(conn, query).scanType("RANGE SCAN").iteratorType("PARALLEL 1-WAY") .table(viewIndexPhysicalName).keyRanges(" [" + Short.MIN_VALUE + ",51]") @@ -1012,23 +1010,17 @@ public static Pair testUpdatableViewIndex(String fullTableName, In if (localIndex) { physicalTableName = fullTableName; - assertPlan(conn, query).scanType("RANGE SCAN") - .serverWhereFilterAnyOf("SERVER FILTER BY FIRST KEY ONLY", - "SERVER FILTER BY EMPTY COLUMN ONLY") + assertPlan(conn, query).scanType("RANGE SCAN").serverProjectionFilterAnyOf() .iteratorType("PARALLEL " + (saltBuckets == null ? 1 : saltBuckets) + "-WAY") .table(viewIndexFullName2 + "(" + fullTableName + ")").keyRanges(" [" + (2) + ",'foo']"); } else if (saltBuckets == null) { physicalTableName = viewIndexPhysicalName; - assertPlan(conn, query).scanType("RANGE SCAN") - .serverWhereFilterAnyOf("SERVER FILTER BY FIRST KEY ONLY", - "SERVER FILTER BY EMPTY COLUMN ONLY") + assertPlan(conn, query).scanType("RANGE SCAN").serverProjectionFilterAnyOf() .iteratorType("PARALLEL 1-WAY").table(viewIndexPhysicalName) .keyRanges(" [" + (Short.MIN_VALUE + 1) + ",'foo']").clientSortAlgo(null); } else { physicalTableName = viewIndexPhysicalName; - assertPlan(conn, query).scanType("RANGE SCAN") - .serverWhereFilterAnyOf("SERVER FILTER BY FIRST KEY ONLY", - "SERVER FILTER BY EMPTY COLUMN ONLY") + assertPlan(conn, query).scanType("RANGE SCAN").serverProjectionFilterAnyOf() .iteratorType("PARALLEL " + saltBuckets + "-WAY").table(viewIndexPhysicalName) .keyRanges(" [X'00'," + (Short.MIN_VALUE + 1) + ",'foo'] - [" + PVarbinary.INSTANCE.toStringLiteral(new byte[] { (byte) (saltBuckets - 1) }) + "," 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 3a1ffdcc9e8..a29b8c50286 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 @@ -154,8 +154,7 @@ public void testIndexWithNullableFixedWithCols() throws Exception { assertPlan(conn, query).iteratorType("PARALLEL 1-WAY"); if (!uncovered) { // Optimizer would not select the uncovered index for this query - basePlan.serverWhereFilter( - columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY"); + basePlan.serverProjectionFilter(columnEncoded); } if (localIndex) { basePlan.table(fullIndexName + "(" + fullTableName + ")").keyRanges(" [1]") @@ -572,9 +571,8 @@ public void testIndexWithNullableDateCol() throws Exception { String query = "SELECT" + (uncovered ? " /*+ INDEX(" + fullTableName + " " + indexName + ")*/ " : " ") + "int_pk from " + fullTableName; - ExplainPlanTestUtil.ExplainPlanAssert basePlan = - assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").serverWhereFilter( - columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY"); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, query) + .iteratorType("PARALLEL 1-WAY").serverProjectionFilter(columnEncoded); if (localIndex) { basePlan.scanType("RANGE SCAN").table(fullIndexName + "(" + fullTableName + ")") .clientSortAlgo("CLIENT MERGE SORT").keyRanges(" [1]"); @@ -593,8 +591,8 @@ public void testIndexWithNullableDateCol() throws Exception { assertFalse(rs.next()); query = "SELECT date_col from " + fullTableName + " order by date_col"; - basePlan = assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").serverWhereFilter( - columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY"); + basePlan = assertPlan(conn, query).iteratorType("PARALLEL 1-WAY") + .serverProjectionFilter(columnEncoded); if (localIndex) { basePlan.scanType("RANGE SCAN").table(fullIndexName + "(" + fullTableName + ")") .clientSortAlgo("CLIENT MERGE SORT").keyRanges(" [1]"); @@ -910,8 +908,8 @@ public void testMultipleUpdatesAcrossRegions() throws Exception { // make sure the index is working as expected query = "SELECT" + (uncovered ? " /*+ INDEX(" + testTable + " " + indexName + ")*/ " : " ") + "* FROM " + testTable; - ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, query).serverWhereFilter( - columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY"); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).serverProjectionFilter(columnEncoded); if (localIndex) { basePlan.iteratorType("PARALLEL 2-WAY").scanType("RANGE SCAN") .table(fullIndexName + "(" + testTable + ")").keyRanges(" [1]") 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 256e4ef8d24..c8c768b17f0 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 @@ -170,8 +170,7 @@ public void testIndexWithNullableFixedWithCols() throws Exception { assertPlan(conn, query).iteratorType("PARALLEL 1-WAY"); if (!uncovered) { // Optimizer would not select the uncovered index for this query - basePlan.serverWhereFilter( - columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY"); + basePlan.serverProjectionFilter(columnEncoded); } if (localIndex) { basePlan.table(fullTableName).keyRanges(" [1]").clientSortAlgo("CLIENT MERGE SORT") @@ -632,9 +631,8 @@ public void testIndexWithNullableDateCol() throws Exception { String query = "SELECT" + (uncovered ? " /*+ INDEX(" + fullTableName + " " + indexName + ")*/ " : " ") + "int_pk from " + fullTableName; - ExplainPlanTestUtil.ExplainPlanAssert basePlan = - assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").serverWhereFilter( - columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY"); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, query) + .iteratorType("PARALLEL 1-WAY").serverProjectionFilter(columnEncoded); if (localIndex) { basePlan.scanType("RANGE SCAN").table(fullTableName).clientSortAlgo("CLIENT MERGE SORT") .keyRanges(" [1]"); @@ -656,8 +654,8 @@ public void testIndexWithNullableDateCol() throws Exception { assertFalse(rs.next()); query = "SELECT date_col from " + fullTableName + " order by date_col"; - basePlan = assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").serverWhereFilter( - columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY"); + basePlan = assertPlan(conn, query).iteratorType("PARALLEL 1-WAY") + .serverProjectionFilter(columnEncoded); if (localIndex) { basePlan.scanType("RANGE SCAN").table(fullTableName).clientSortAlgo("CLIENT MERGE SORT") .keyRanges(" [1]"); @@ -1004,8 +1002,8 @@ public void testMultipleUpdatesAcrossRegions() throws Exception { // make sure the index is working as expected query = "SELECT" + (uncovered ? " /*+ INDEX(" + testTable + " " + indexName + ")*/ " : " ") + "* FROM " + testTable; - ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, query).serverWhereFilter( - columnEncoded ? "SERVER FILTER BY FIRST KEY ONLY" : "SERVER FILTER BY EMPTY COLUMN ONLY"); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, query).serverProjectionFilter(columnEncoded); if (localIndex) { basePlan.iteratorType("PARALLEL 2-WAY").scanType("RANGE SCAN").table(testTable) .keyRanges(" [1]").clientSortAlgo("CLIENT MERGE SORT"); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ChildViewsUseParentViewIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ChildViewsUseParentViewIndexIT.java index 5a8c41b2668..f0bcbab7ee4 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ChildViewsUseParentViewIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ChildViewsUseParentViewIndexIT.java @@ -164,10 +164,10 @@ private void assertQueryUsesIndex(final String baseTableName, final String viewN String sql = "SELECT A0, A1, A2, A4 FROM " + viewName + " WHERE A4 IN ('1', '2', '3') ORDER BY A4, A2"; String childViewScanKey = isChildView ? ",'Y'" : ""; - assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").scanType("SKIP SCAN ON 3 KEYS") - .table("_IDX_" + baseTableName).keyRanges(" [" + Short.MIN_VALUE + ",'1'" + childViewScanKey - + "] - [" + Short.MIN_VALUE + ",'3'" + childViewScanKey + "]"); + assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY").serverFirstKeyOnlyProjection(true) + .scanType("SKIP SCAN ON 3 KEYS").table("_IDX_" + baseTableName) + .keyRanges(" [" + Short.MIN_VALUE + ",'1'" + childViewScanKey + "] - [" + Short.MIN_VALUE + + ",'3'" + childViewScanKey + "]"); ResultSet rs = conn.createStatement().executeQuery(sql); assertTrue(rs.next()); @@ -261,9 +261,8 @@ private void assertQueryIndex(String viewName, String baseTableName, Connection String sql = "SELECT WO_ID FROM " + viewName + " WHERE WO_ID IN ('003xxxxxxxxxxx1', '003xxxxxxxxxxx2', '003xxxxxxxxxxx3', '003xxxxxxxxxxx4', '003xxxxxxxxxxx5') " + " AND (A_DATE > TO_DATE('2016-01-01 06:00:00.0')) " + " ORDER BY WO_ID, A_DATE DESC"; - assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").scanType("SKIP SCAN ON 5 RANGES") - .table("_IDX_" + baseTableName) + assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY").serverFirstKeyOnlyProjection(true) + .scanType("SKIP SCAN ON 5 RANGES").table("_IDX_" + baseTableName) .keyRanges(" [" + Short.MIN_VALUE + ",'00Dxxxxxxxxxxx1','003xxxxxxxxxxx1',*] - [" + Short.MIN_VALUE + ",'00Dxxxxxxxxxxx1','003xxxxxxxxxxx5',~'2016-01-01 06:00:00.000']"); 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 e174c37c56f..31e55b55d32 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 @@ -1566,12 +1566,10 @@ public void testUnverifiedIndexRowWithFirstKeyOnlyFilter() throws Exception { "SELECT id, val3 from " + dataTableName + " WHERE val1 = 'bc' and val2 = 'bcd' "; // 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)); + assertPlan(attributes).scanType("RANGE SCAN").table(indexName) + .serverProjectionFilter(encoded); + assertNotNull("serverWhereFilter=" + attributes.getServerWhereFilter(), + attributes.getServerWhereFilter()); try (ResultSet rs = conn.createStatement().executeQuery(selectSql)) { assertTrue(rs.next()); assertEquals("b", rs.getString("id")); 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 c5240d9ff0c..3fbc8f4a626 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 @@ -1526,12 +1526,10 @@ public void testUnverifiedIndexRowWithFirstKeyOnlyFilter() throws Exception { "SELECT id, val3 from " + dataTableName + " WHERE val1 = 'bc' and val2 = 'bcd' "; // 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)); + assertPlan(attributes).scanType("RANGE SCAN").table(indexName) + .serverProjectionFilter(encoded); + assertNotNull("serverWhereFilter=" + attributes.getServerWhereFilter(), + attributes.getServerWhereFilter()); 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 a9a43e32042..1f900e2f5f0 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 @@ -82,7 +82,7 @@ public void testIndexDeleteOptimizationWithLocalIndex() throws Exception { } 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") + .keyRanges(" [1,*] - [1,100]").serverFirstKeyOnlyProjection(true) .clientSortAlgo("CLIENT MERGE SORT"); ResultSet rs = conn1.createStatement().executeQuery("SELECT COUNT(*) FROM " + dataTableName); rs.next(); @@ -154,7 +154,7 @@ private void testOptimization(String dataTableName, String dataTableFullName, String query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ * FROM " + dataTableName + " where v1='a'"; assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName).keyRanges(" ['a']") - .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true); ResultSet rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -172,7 +172,7 @@ private void testOptimization(String dataTableName, String dataTableFullName, query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ * FROM " + dataTableName + " where v1='a'"; assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName).keyRanges(" ['a']") - .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -192,8 +192,8 @@ private void testOptimization(String dataTableName, String dataTableFullName, query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ * FROM " + dataTableName + " where v1='a' limit 1"; 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); + .keyRanges(" ['a']").serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true) + .serverRowLimit(1L).clientRowLimit(1); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -208,8 +208,8 @@ private void testOptimization(String dataTableName, String dataTableFullName, + ")*/ t_id, k1, k2, k3, V1 from " + dataTableFullName + " where v1<='z' and k3 > 1 order by V1,t_id"; assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName) - .keyRanges(" [*] - ['z']").serverMergeColumns("[0.K3]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND \"K3\" > 1"); + .keyRanges(" [*] - ['z']").serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true) + .serverWhereFilter("SERVER FILTER BY \"K3\" > 1"); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -242,7 +242,7 @@ private void testOptimization(String dataTableName, String dataTableFullName, .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(); + .serverFirstKeyOnlyProjection(true).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(); @@ -279,8 +279,7 @@ 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"; assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName) - .keyRanges(" [*] - ['z']").serverMergeColumns("[0.K3]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .keyRanges(" [*] - ['z']").serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true) .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"V1\", \"T_ID\", \"K3\"]") .clientSortAlgo("CLIENT MERGE SORT"); @@ -308,8 +307,7 @@ private void testOptimization(String dataTableName, String dataTableFullName, + dataTableFullName + " where v1 <='z' group by v1 order by v1"; assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName) - .keyRanges(" [*] - ['z']").serverMergeColumns("[0.K3]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .keyRanges(" [*] - ['z']").serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true) .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"V1\"]"); rs = conn1.createStatement().executeQuery(query); @@ -343,8 +341,7 @@ private void testOptimizationTenantSpecific(String dataTableName, String indexTa String query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ k1,k2,k3,v1 FROM " + dataTableName + " where v1='a'"; assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName) - .keyRanges(" ['tid1','a']").serverMergeColumns("[0.K3]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .keyRanges(" ['tid1','a']").serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true); ResultSet rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -397,7 +394,7 @@ public void testGlobalIndexOptimizationOnSharedIndex() throws Exception { 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"); + .serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true); rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -435,7 +432,7 @@ public void testNoGlobalIndexOptimization() throws Exception { String query = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ t_id, k1, k2, V1 FROM " + dataTableName + " where v1='a'"; assertPlan(conn1, query).scanType("RANGE SCAN").table(indexTableName).keyRanges(" ['a']") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .serverFirstKeyOnlyProjection(true); ResultSet rs = conn1.createStatement().executeQuery(query); assertTrue(rs.next()); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexUsageIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexUsageIT.java index 0abd7ca9135..9acbc4848e2 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 @@ -131,7 +131,7 @@ protected void helpTestGroupByCount(boolean mutable, boolean localIndex) throws + " GROUP BY (int_col1+int_col2)"; ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, groupBySql) - .iteratorType("PARALLEL 1-WAY").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .iteratorType("PARALLEL 1-WAY").serverFirstKeyOnlyProjection(true) .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY " + "[TO_BIGINT(\"(A.INT_COL1 + B.INT_COL2)\")]"); if (localIndex) { @@ -185,8 +185,7 @@ protected void helpTestSelectDistinct(boolean mutable, boolean localIndex) throw String sql = "SELECT distinct int_col1+1 FROM " + fullDataTableName + " where int_col1+1 > 0"; ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, sql) - .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").serverFirstKeyOnlyProjection(true) .serverDistinctFilter( "SERVER DISTINCT PREFIX FILTER OVER " + "[TO_BIGINT(\"(A.INT_COL1 + 1)\")]") .serverAggregate( @@ -243,9 +242,8 @@ protected void helpTestInClauseWithIndex(boolean mutable, boolean localIndex) th conn.createStatement().execute(ddl); String sql = "SELECT int_col1+1 FROM " + fullDataTableName + " where int_col1+1 IN (2)"; - ExplainPlanTestUtil.ExplainPlanAssert basePlan = - assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, sql) + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").serverFirstKeyOnlyProjection(true); if (localIndex) { basePlan.table("INDEX_TEST." + indexName + "(" + fullDataTableName + ")") .keyRanges(" [1,2]").clientSortAlgo("CLIENT MERGE SORT"); @@ -297,8 +295,8 @@ protected void helpTestSelectAliasAndOrderByWithIndex(boolean mutable, boolean l conn.createStatement().execute(ddl); String sql = "SELECT int_col1+1 AS foo FROM " + fullDataTableName + " ORDER BY foo"; - ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, sql) - .iteratorType("PARALLEL 1-WAY").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = + assertPlan(conn, sql).iteratorType("PARALLEL 1-WAY").serverFirstKeyOnlyProjection(true); if (localIndex) { basePlan.scanType("RANGE SCAN") .table("INDEX_TEST." + indexName + "(" + fullDataTableName + ")").keyRanges(" [1]") @@ -476,7 +474,7 @@ protected void helpTestSelectColOnlyInDataTable(boolean mutable, boolean localIn if (localIndex) { basePlan.scanType("RANGE SCAN") .table("INDEX_TEST." + indexName + "(" + fullDataTableName + ")").keyRanges(" [1,2]") - .clientSortAlgo("CLIENT MERGE SORT").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .clientSortAlgo("CLIENT MERGE SORT").serverFirstKeyOnlyProjection(true); } else { basePlan.scanType("FULL SCAN").table(fullDataTableName).clientSortAlgo(null) .serverWhereFilter("SERVER FILTER BY (A.INT_COL1 + 1) = 2"); @@ -551,7 +549,7 @@ private void helpTestUpdatableViewIndex(boolean local) throws Exception { query = "SELECT k1, k2, s1||'_'||s2 FROM " + viewName + " WHERE (s1||'_'||s2)='foo2_bar2'"; basePlan = assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .serverFirstKeyOnlyProjection(true); if (local) { basePlan.table(indexName2 + "(" + dataTableName + ")") .keyRanges(" [" + (2) + ",'foo2_bar2']").clientSortAlgo("CLIENT MERGE SORT"); @@ -614,8 +612,7 @@ private void helpTestViewUsesTableIndex(boolean immutable) throws Exception { "SELECT s2||'_'||s3 FROM " + viewName + " WHERE k2=1 AND (s2||'_'||s3)='abc_cab'"; assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") - .table(indexName2).keyRanges(" [1,'abc_cab','foo']") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .table(indexName2).keyRanges(" [1,'abc_cab','foo']").serverFirstKeyOnlyProjection(true); rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); @@ -625,8 +622,8 @@ 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 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')"); + .serverFirstKeyOnlyProjection(true).serverWhereFilter( + "SERVER FILTER BY ((\"S2\" || '_' || \"S3\") = 'abc_cab' AND \"S1\" = 'foo')"); rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals("abc_cab", rs.getString(1)); @@ -710,9 +707,8 @@ protected void helpTestCaseSensitiveFunctionIndex(boolean mutable, boolean local query = "SELECT k FROM " + dataTableName + " WHERE REGEXP_SUBSTR(v,'id:\\\\w+') = 'id:id1'"; - ExplainPlanTestUtil.ExplainPlanAssert basePlan = - assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + ExplainPlanTestUtil.ExplainPlanAssert basePlan = assertPlan(conn, query) + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").serverFirstKeyOnlyProjection(true); if (localIndex) { basePlan.table(indexName + "(" + dataTableName + ")").keyRanges(" [1,'id:id1']") .clientSortAlgo("CLIENT MERGE SORT"); @@ -783,16 +779,14 @@ public void helpTestIndexExpressionWithJoin(boolean mutable, boolean localIndex) if (localIndex) { 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") + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1) + .subPlan(0).scanType("RANGE SCAN").tableContains(indexName + "(" + tableName + ")") + .keyRanges(" [1]").serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT") .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 (SKIP MERGE)").end(); } else { 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") + .keyRanges(" ['1David']").serverFirstKeyOnlyProjection(true).subPlanCount(1).subPlan(0) + .scanType("FULL SCAN").tableContains(indexName).serverFirstKeyOnlyProjection(true) .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 (SKIP MERGE)").end(); } 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 fffacd4fc34..02c38fe89e2 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 @@ -443,7 +443,7 @@ public void testUseUncoveredLocalIndexWithPrefix() throws Exception { 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"); + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT"); // 3. same prefix length, but there's a column not on the index assertPlan(conn, "SELECT v2 FROM " + tableName + " WHERE pk1 = 3 AND pk2 = 4 AND v3 = 1") @@ -455,9 +455,8 @@ public void testUseUncoveredLocalIndexWithPrefix() throws Exception { "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"); + .serverMergeColumns("[0.V3]").serverFirstKeyOnlyProjection(true) + .serverWhereFilter("SERVER FILTER BY \"V3\" = 1").clientSortAlgo("CLIENT MERGE SORT"); } @Test @@ -486,7 +485,7 @@ public void testUseUncoveredLocalIndexWithSplitPrefix() throws Exception { 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") + .serverMergeColumns("[0.V1]").serverFirstKeyOnlyProjection(true) .clientSortAlgo("CLIENT MERGE SORT"); } @@ -513,7 +512,7 @@ public void testUseUncoveredLocalIndex() throws Exception { // 1. COUNT(*) should still use the index - fewer bytes to scan 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") + .keyRanges(" [1]").serverFirstKeyOnlyProjection(true) .serverAggregate("SERVER AGGREGATE INTO SINGLE ROW"); // 2. All column projected, no filtering by indexed column, not using the index @@ -523,8 +522,8 @@ public void testUseUncoveredLocalIndex() throws Exception { // 3. if the index can avoid a sort operation, use it assertPlan(conn, "SELECT * FROM " + tableName + " ORDER BY v2").iteratorType("PARALLEL 1-WAY") .scanType("RANGE SCAN").table(fullIndexName + "(" + indexPhysicalTableName + ")") - .keyRanges(" [1]").serverMergeColumns("[0.V1]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); + .keyRanges(" [1]").serverMergeColumns("[0.V1]").serverFirstKeyOnlyProjection(true) + .clientSortAlgo("CLIENT MERGE SORT"); // 4. but can't use the index if not ORDERing by a prefix of the index key. assertPlan(conn, "SELECT * FROM " + tableName + " ORDER BY v3").iteratorType("PARALLEL 1-WAY") @@ -535,7 +534,7 @@ public void testUseUncoveredLocalIndex() throws Exception { 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") + .serverMergeColumns("[0.V1]").serverFirstKeyOnlyProjection(true) .clientSortAlgo("CLIENT MERGE SORT"); // 6. Filtering by a non-indexed column will not use the index @@ -551,24 +550,23 @@ public void testUseUncoveredLocalIndex() throws Exception { // 8. Filtering along a prefix of the index key can use the index assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v2 = 2").iteratorType("PARALLEL 1-WAY") .scanType("RANGE SCAN").table(fullIndexName + "(" + indexPhysicalTableName + ")") - .keyRanges(" [1,2]").serverMergeColumns("[0.V1]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); + .keyRanges(" [1,2]").serverMergeColumns("[0.V1]").serverFirstKeyOnlyProjection(true) + .clientSortAlgo("CLIENT MERGE SORT"); // 9. Make sure a gap in the index columns still uses the index as long as a prefix is specified assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v2 = 2 AND v4 = 4") .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") + .serverMergeColumns("[0.V1]").serverFirstKeyOnlyProjection(true) + .serverWhereFilter("SERVER FILTER BY TO_INTEGER(\"V4\") = 4") .clientSortAlgo("CLIENT MERGE SORT"); // 10. Use index even when also filtering on non-indexed column assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v2 = 2 AND v1 = 3") .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,2]") - .serverMergeColumns("[0.V1]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY AND \"V1\" = 3.0") - .clientSortAlgo("CLIENT MERGE SORT"); + .serverMergeColumns("[0.V1]").serverFirstKeyOnlyProjection(true) + .serverWhereFilter("SERVER FILTER BY \"V1\" = 3.0").clientSortAlgo("CLIENT MERGE SORT"); // 11. Another case of not using a prefix of the index key assertPlan(conn, "SELECT * FROM " + tableName + " WHERE v1 = 3 AND v3 = 1 AND v4 = 1") @@ -780,7 +778,7 @@ public void testLocalIndexUsedForUncoveredOrderBy() throws Exception { String query = "SELECT * FROM " + tableName + " ORDER BY V1"; assertPlan(conn1, query).scanType("RANGE SCAN") .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1]") - .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true) .clientSortAlgo("CLIENT MERGE SORT"); ResultSet rs = conn1.createStatement().executeQuery(query); @@ -799,7 +797,7 @@ public void testLocalIndexUsedForUncoveredOrderBy() throws Exception { query = "SELECT * FROM " + tableName + " ORDER BY V1 DESC NULLS LAST"; assertPlan(conn1, query).scanType("RANGE SCAN").clientSortedBy("REVERSE") .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1]") - .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true) .clientSortAlgo("CLIENT MERGE SORT"); rs = conn1.createStatement().executeQuery(query); @@ -840,7 +838,7 @@ public void testLocalIndexReverseScanShouldReturnAllRows() throws Exception { String query = "SELECT V1 FROM " + tableName + " ORDER BY V1 DESC NULLS LAST"; assertPlan(conn1, query).scanType("RANGE SCAN").clientSortedBy("REVERSE") .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").clientSortAlgo("CLIENT MERGE SORT"); + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT"); ResultSet rs = conn1.createStatement().executeQuery(query); String v = "zz"; @@ -885,7 +883,7 @@ public void testLocalIndexScanJoinColumnsFromDataTable() throws Exception { String query = "SELECT t_id, k1, k2, k3, V1 FROM " + tableName + " where v1='a'"; assertPlan(conn1, query).scanType("RANGE SCAN") .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,'a']") - .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true) .clientSortAlgo("CLIENT MERGE SORT"); rs = conn1.createStatement().executeQuery(query); @@ -904,7 +902,7 @@ public void testLocalIndexScanJoinColumnsFromDataTable() throws Exception { query = "SELECT t_id, k1, k2, k3, V1 from " + tableName + " where v1<='z' order by V1,t_id"; assertPlan(conn1, query).scanType("RANGE SCAN") .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,*] - [1,'z']") - .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true) .clientSortAlgo("CLIENT MERGE SORT"); rs = conn1.createStatement().executeQuery(query); @@ -936,7 +934,7 @@ public void testLocalIndexScanJoinColumnsFromDataTable() throws Exception { query = "SELECT t_id, V1, k3 from " + tableName + " where v1 <='z' group by v1,t_id, k3"; assertPlan(conn1, query).scanType("RANGE SCAN") .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,*] - [1,'z']") - .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true) .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"V1\", \"T_ID\", \"K3\"]") .clientSortAlgo("CLIENT MERGE SORT"); @@ -962,7 +960,7 @@ public void testLocalIndexScanJoinColumnsFromDataTable() throws Exception { assertPlan(conn1, query).scanType("RANGE SCAN") .table(fullIndexName + "(" + indexPhysicalTableName + ")").keyRanges(" [1,*] - [1,'z']") - .serverMergeColumns("[0.K3]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverMergeColumns("[0.K3]").serverFirstKeyOnlyProjection(true) .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"V1\"]") .clientSortAlgo("CLIENT MERGE SORT"); @@ -1009,7 +1007,7 @@ public void testIndexPlanSelectionIfBothGlobalAndLocalIndexesHasSameColumnsAndOr 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"); + .keyRanges(" ['a']").serverFirstKeyOnlyProjection(true); conn1.close(); } 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 c908e50b922..3dbd13f5bc9 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 @@ -160,15 +160,14 @@ private void testMutableTableIndexMaintanence(String dataTableName, String dataT if (indexSaltBuckets == null) { assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") - .table(indexTableFullName).keyRanges(" [~'y']") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .table(indexTableFullName).keyRanges(" [~'y']").serverFirstKeyOnlyProjection(true); } 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"); + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT"); } // Will use index, so rows returned in DESC order. @@ -185,15 +184,14 @@ private void testMutableTableIndexMaintanence(String dataTableName, String dataT assertFalse(rs.next()); if (indexSaltBuckets == null) { assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") - .table(indexTableFullName).keyRanges(" [*] - [~'x']") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .table(indexTableFullName).keyRanges(" [*] - [~'x']").serverFirstKeyOnlyProjection(true); } 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"); + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT"); } // Use data table, since point lookup trumps order by @@ -232,12 +230,12 @@ private void testMutableTableIndexMaintanence(String dataTableName, String dataT query = "SELECT * FROM " + dataTableFullName + " ORDER BY v DESC LIMIT 1"; 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); + .table(indexTableFullName).serverFirstKeyOnlyProjection(true).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); + .table(indexTableFullName).serverFirstKeyOnlyProjection(true).serverRowLimit(1L) + .clientSortAlgo("CLIENT MERGE SORT").clientRowLimit(1); } } } 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 6da69d4316d..7566387a622 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 @@ -259,8 +259,8 @@ public void testMultiTenantViewLocalIndex() throws Exception { 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"); + .keyRanges(" [1,'10',100]").serverMergeColumns("[0.V1]").serverFirstKeyOnlyProjection(true) + .clientSortAlgo("CLIENT MERGE SORT"); ResultSet rs = conn1.prepareStatement(sql).executeQuery(); assertTrue(rs.next()); assertFalse(rs.next()); 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 8c6c5bc7591..a3ee5b48bed 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 @@ -63,7 +63,7 @@ protected void assertLeftJoinWithAggPlan1(Connection conn, String query) throws .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(); + .serverFirstKeyOnlyProjection(true).end(); } @Override @@ -74,15 +74,14 @@ protected void assertLeftJoinWithAggPlan2(Connection conn, String query) throws .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(); + .table(idxItem).serverFirstKeyOnlyProjection(true).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") + assertPlan(conn, query).scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true) .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(); @@ -92,8 +91,7 @@ protected void assertLeftJoinWithAggPlan3(Connection conn, String query) throws 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") + assertPlan(conn, query).scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true) .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(); @@ -103,8 +101,7 @@ protected void assertRightJoinWithAggPlan1(Connection conn, String query) throws 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") + assertPlan(conn, query).scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true) .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(); @@ -124,9 +121,9 @@ protected void assertJoinPlanWithIndexPlan1(Connection conn, String query) throw 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) + .serverFirstKeyOnlyProjection(true).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(); + .keyRanges(" ['S1'] - ['S5']").serverFirstKeyOnlyProjection(true).end(); } @Override @@ -136,8 +133,7 @@ protected void assertJoinPlanWithIndexPlan2(Connection conn, String query) throw 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(); + .table(idxSupplier).keyRanges(" ['S1'] - ['S5']").serverFirstKeyOnlyProjection(true).end(); } @Override @@ -149,7 +145,7 @@ protected void assertSkipMergeOptimizationPlan(Connection conn, String query) th .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(); + .serverFirstKeyOnlyProjection(true).end(); } @Override @@ -159,15 +155,13 @@ protected void assertSelfJoinPlan1(Connection conn, String query) throws Excepti 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(); + .scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true).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") + assertPlan(conn, query).scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true) .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(); @@ -182,17 +176,16 @@ protected void assertStarJoinPlan(Connection conn, String query, boolean noStarJ 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) + .serverFirstKeyOnlyProjection(true).end().subPlan(1) .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1").scanType("FULL SCAN").table(idxItem) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); + .serverFirstKeyOnlyProjection(true).end(); } else { assertPlan(conn, query).scanType("FULL SCAN").table(idxItem) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"O.order_id\"]") + .serverFirstKeyOnlyProjection(true).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(); + .scanType("FULL SCAN").table(idxCustomer).serverFirstKeyOnlyProjection(true).end().end(); } } @@ -223,7 +216,7 @@ protected void assertSubqueryAggPlan1(Connection conn, String query) throws Exce .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(); + .serverFirstKeyOnlyProjection(true).end(); } @Override @@ -234,16 +227,14 @@ protected void assertSubqueryAggPlan2(Connection conn, String query) throws Exce .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(); + .scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true).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") + assertPlan(conn, query).scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true) .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) @@ -255,11 +246,10 @@ protected void assertSubqueryAggPlan3(Connection conn, String query) throws Exce 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\"]") + assertPlan(conn, query).scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true) + .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(); } @@ -314,10 +304,9 @@ protected void assertSetMaxRowsPlan(Connection conn, String query) throws Except 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(); + assertPlan(attributes).scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true) + .clientRowLimit(4).joinScannerLimit(4L).subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(order).end(); } @Override diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinLocalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinLocalIndexIT.java index a44ac276e9f..9f46bea9a8d 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 @@ -78,9 +78,8 @@ protected void assertLeftJoinWithAggPlan1(Connection conn, String query) throws .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(); + .table(itemIndex + "(" + item + ")").keyRanges(" [1]").serverFirstKeyOnlyProjection(true) + .clientSortAlgo("CLIENT MERGE SORT").end(); } @Override @@ -92,17 +91,15 @@ protected void assertLeftJoinWithAggPlan2(Connection conn, String query) throws .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(); + .table(itemIndex + "(" + item + ")").keyRanges(" [1]").serverFirstKeyOnlyProjection(true) + .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") + assertPlan(conn, query).scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true) .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(); @@ -114,7 +111,7 @@ protected void assertRightJoinWithAggPlan1(Connection conn, String query) throws 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") + .keyRanges(" [1]").serverFirstKeyOnlyProjection(true) .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(); @@ -124,8 +121,7 @@ protected void assertRightJoinWithAggPlan1(Connection conn, String query) throws 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") + assertPlan(conn, query).scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true) .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(); @@ -147,12 +143,11 @@ protected void assertJoinPlanWithIndexPlan1(Connection conn, String query) throw 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") + .keyRanges(" [1,'T1'] - [1,'T5']").serverFirstKeyOnlyProjection(true) .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(); + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end(); } @Override @@ -165,8 +160,7 @@ protected void assertJoinPlanWithIndexPlan2(Connection conn, String query) throw .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(); + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end(); } @Override @@ -183,8 +177,7 @@ protected void assertSkipMergeOptimizationPlan(Connection conn, String query) th .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(); + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end(); } @Override @@ -195,8 +188,7 @@ protected void assertSelfJoinPlan1(Connection conn, String query) throws Excepti .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(); + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end(); } @Override @@ -204,7 +196,7 @@ protected void assertSelfJoinPlan2(Connection conn, String query) throws Excepti 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") + .keyRanges(" [1]").serverFirstKeyOnlyProjection(true) .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") @@ -224,22 +216,20 @@ protected void assertStarJoinPlan(Connection conn, String query, boolean noStarJ 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(); + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end().subPlan(1) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1").scanType("RANGE SCAN") + .table(itemIndex + "(" + item + ")").keyRanges(" [1]").serverFirstKeyOnlyProjection(true) + .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") + .keyRanges(" [1]").serverFirstKeyOnlyProjection(true).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(); + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end().end(); } } @@ -273,9 +263,8 @@ protected void assertSubqueryAggPlan1(Connection conn, String query) throws Exce .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(); + .table(itemIndex + "(" + item + ")").keyRanges(" [1]").serverFirstKeyOnlyProjection(true) + .clientSortAlgo("CLIENT MERGE SORT").end(); } @Override @@ -288,8 +277,7 @@ protected void assertSubqueryAggPlan2(Connection conn, String query) throws Exce .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(); + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end(); } @Override @@ -298,7 +286,7 @@ protected void assertSubqueryAggPlan3(Connection conn, String query) throws Exce 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") + .keyRanges(" [1]").serverFirstKeyOnlyProjection(true) .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) @@ -312,10 +300,10 @@ protected void assertSubqueryAggPlan4(Connection conn, String query) throws Exce 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\"]") + .keyRanges(" [1]").serverFirstKeyOnlyProjection(true).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(); } @@ -378,8 +366,8 @@ protected void assertSetMaxRowsPlan(Connection conn, String query) throws Except 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) + .keyRanges(" [1]").serverFirstKeyOnlyProjection(true).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(); @@ -451,7 +439,7 @@ public void testJoinWithLocalIndex() throws Exception { assertFalse(rs.next()); assertPlan(conn, query).scanType("RANGE SCAN") .table(supplierIndex + "(" + supplierTable + ")").keyRanges(" [1,'S1']") - .serverMergeColumns("[0.PHONE]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverMergeColumns("[0.PHONE]").serverFirstKeyOnlyProjection(true) .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") @@ -468,7 +456,7 @@ public void testJoinWithLocalIndex() throws Exception { assertFalse(rs.next()); assertPlan(conn, query).scanType("RANGE SCAN") .table(supplierIndex + "(" + supplierTable + ")").keyRanges(" [1,'S1']") - .serverMergeColumns("[0.PHONE]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverMergeColumns("[0.PHONE]").serverFirstKeyOnlyProjection(true) .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\")") @@ -486,7 +474,7 @@ public void testJoinWithLocalIndex() throws Exception { assertFalse(rs.next()); assertPlan(conn, query).scanType("RANGE SCAN") .table(supplierIndex + "(" + supplierTable + ")").keyRanges(" [1,*] - [1,'S3']") - .serverMergeColumns("[0.PHONE]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") + .serverMergeColumns("[0.PHONE]").serverFirstKeyOnlyProjection(true) .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']") 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 16f0144c691..4e338f31d57 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 @@ -782,12 +782,13 @@ public void testBug2894() throws Exception { 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") + .serverFirstKeyOnlyProjection(true) .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") + .serverFirstKeyOnlyProjection(true) + .serverWhereFilter("SERVER FILTER BY SRC_LOCATION = DST_LOCATION") .clientSortAlgo("CLIENT MERGE SORT").end(); ResultSet rs = conn.createStatement().executeQuery(q); 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 166cc338adf..87da5547daf 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 @@ -73,15 +73,14 @@ protected void assertLeftJoinWithAggPlan2(Connection conn, String query) throws .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(); + .table(item).serverFirstKeyOnlyProjection(true).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") + assertPlan(conn, query).scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true) .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(); @@ -101,8 +100,7 @@ protected void assertRightJoinWithAggPlan1(Connection conn, String query) throws 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") + assertPlan(conn, query).scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true) .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(); @@ -156,7 +154,7 @@ protected void assertSelfJoinPlan1(Connection conn, String query) throws Excepti 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(); + .scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true).end(); } @Override @@ -228,15 +226,14 @@ protected void assertSubqueryAggPlan2(Connection conn, String query) throws Exce .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(); + .scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true).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") + assertPlan(conn, query).scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true) .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) @@ -248,11 +245,10 @@ protected void assertSubqueryAggPlan3(Connection conn, String query) throws Exce 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\"]") + assertPlan(conn, query).scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true) + .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(); } 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 612768aabc5..a0ee9bc4905 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 @@ -61,9 +61,8 @@ protected void assertSkipMergeOptimizationPlan(Connection conn, String query) th String itemIndex = getSchemaName() + ".idx_item"; String supplierIndex = getSchemaName() + ".idx_supplier"; assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)").sortMergeSkipMerge(false) - .lhs().scanType("FULL SCAN").table(supplierIndex) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"S.:supplier_id\"]") - .clientSortAlgo("CLIENT MERGE SORT").end().rhs() + .lhs().scanType("FULL SCAN").table(supplierIndex).serverFirstKeyOnlyProjection(true) + .serverSortedBy("[\"S.:supplier_id\"]").clientSortAlgo("CLIENT MERGE SORT").end().rhs() .abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(true) .clientSortedBy("[\"I.0:supplier_id\"]").lhs().scanType("FULL SCAN").table(itemIndex) .serverSortedBy("[\"I.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end().rhs() @@ -75,11 +74,10 @@ protected void assertSkipMergeOptimizationPlan(Connection conn, String query) th protected void assertSelfJoinPlan(Connection conn, String query) throws Exception { String itemIndex = getSchemaName() + ".idx_item"; assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(false) - .lhs().scanType("FULL SCAN").table(itemIndex) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"I1.:item_id\"]") - .clientSortAlgo("CLIENT MERGE SORT").end().rhs().scanType("FULL SCAN").table(itemIndex) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"I2.:item_id\"]") - .clientSortAlgo("CLIENT MERGE SORT").end(); + .lhs().scanType("FULL SCAN").table(itemIndex).serverFirstKeyOnlyProjection(true) + .serverSortedBy("[\"I1.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end().rhs() + .scanType("FULL SCAN").table(itemIndex).serverFirstKeyOnlyProjection(true) + .serverSortedBy("[\"I2.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end(); } @Override @@ -94,7 +92,7 @@ protected void assertSetMaxRowsPlan(Connection conn, String query, int queryInde statement.optimizeQuery().getExplainPlan().getPlanStepsAsAttributes(); assertPlan(attributes).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(false) .clientRowLimit(4).lhs().scanType("FULL SCAN").table(itemIndex) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"I.:item_id\"]") + .serverFirstKeyOnlyProjection(true).serverSortedBy("[\"I.:item_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end().rhs().scanType("FULL SCAN").table(order) .serverSortedBy(queryIndex == 0 ? "[\"O.item_id\"]" : "[\"item_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end(); 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 95d5f44a2a7..8e5fbb489ae 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 @@ -64,7 +64,7 @@ protected void assertSkipMergeOptimizationPlan(Connection conn, String query) th String supplierIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_SUPPLIER_INDEX); assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (LEFT)").sortMergeSkipMerge(false) .lhs().scanType("RANGE SCAN").table(supplierIndex + "(" + supplier + ")").keyRanges(" [1]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"S.:supplier_id\"]") + .serverFirstKeyOnlyProjection(true).serverSortedBy("[\"S.:supplier_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end().rhs() .abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(true) .clientSortedBy("[\"I.0:supplier_id\"]").lhs().scanType("RANGE SCAN") @@ -80,11 +80,10 @@ protected void assertSelfJoinPlan(Connection conn, String query) throws Exceptio String itemIndex = SchemaUtil.getTableName(getSchemaName(), JOIN_ITEM_INDEX); assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(false) .lhs().scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").serverSortedBy("[\"I1.:item_id\"]") + .serverFirstKeyOnlyProjection(true).serverSortedBy("[\"I1.:item_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end().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(); + .table(itemIndex + "(" + item + ")").keyRanges(" [1]").serverFirstKeyOnlyProjection(true) + .serverSortedBy("[\"I2.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end(); } @Override @@ -100,9 +99,8 @@ protected void assertSetMaxRowsPlan(Connection conn, String query, int queryInde statement.optimizeQuery().getExplainPlan().getPlanStepsAsAttributes(); assertPlan(attributes).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(false) .clientRowLimit(4).lhs().scanType("RANGE SCAN").table(itemIndex + "(" + item + ")") - .keyRanges(" [1]").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY") - .serverSortedBy("[\"I.:item_id\"]").clientSortAlgo("CLIENT MERGE SORT").end().rhs() - .scanType("FULL SCAN").table(order) + .keyRanges(" [1]").serverFirstKeyOnlyProjection(true).serverSortedBy("[\"I.:item_id\"]") + .clientSortAlgo("CLIENT MERGE SORT").end().rhs().scanType("FULL SCAN").table(order) .serverSortedBy(queryIndex == 0 ? "[\"O.item_id\"]" : "[\"item_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end(); } 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 4cd6b59dbb5..20c7fe620c7 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 @@ -73,7 +73,7 @@ protected void assertSelfJoinPlan(Connection conn, String query) throws Exceptio String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); assertPlan(conn, query).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").sortMergeSkipMerge(false) .lhs().scanType("FULL SCAN").table(item).end().rhs().scanType("FULL SCAN").table(item) - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY").end(); + .serverFirstKeyOnlyProjection(true).end(); } @Override diff --git a/phoenix-core/src/it/java/org/apache/phoenix/schema/stats/BaseStatsCollectorIT.java b/phoenix-core/src/it/java/org/apache/phoenix/schema/stats/BaseStatsCollectorIT.java index ab8f625b3e9..0b731fff6ee 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/schema/stats/BaseStatsCollectorIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/schema/stats/BaseStatsCollectorIT.java @@ -256,8 +256,7 @@ public void testUpdateEmptyStats() throws Exception { collectStatistics(conn, fullTableName); assertPlan(conn, "SELECT * FROM " + fullTableName).splitsChunk(1).estimatedRows(0L) .estimatedBytes(20L).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN") - .table(physicalTableName).serverWhereFilter( - "SERVER FILTER BY " + (columnEncoded ? "FIRST KEY ONLY" : "EMPTY COLUMN ONLY")); + .table(physicalTableName).serverProjectionFilter(columnEncoded); conn.close(); } 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 8ee65259d45..c280192ee3a 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 @@ -82,7 +82,7 @@ public void testExplainPlan() throws Exception { + " s ON s.\"supplier_id\" = i.\"supplier_id\" WHERE i.name LIKE 'T%'"; // 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") + .serverFirstKeyOnlyProjection(true) .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) 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 0016d9d4cc1..8100742e671 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 @@ -2548,20 +2548,18 @@ public void testFuncIndexUsage() throws SQLException { conn.createStatement().execute("CREATE INDEX idx ON t1 (col1 || col2)"); 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"); + .serverFirstKeyOnlyProjection(true); 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") + .scanType("FULL SCAN").table("T3").serverFirstKeyOnlyProjection(true) .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(); + .table("IDX").keyRanges(" ['foobar']").serverFirstKeyOnlyProjection(true).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") + .scanType("FULL SCAN").table("T2").serverFirstKeyOnlyProjection(true) .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(); + .table("IDX").keyRanges(" ['foobar']").serverFirstKeyOnlyProjection(true).end(); } finally { conn.close(); } @@ -7150,7 +7148,7 @@ public void testReverseIndexRangeBugPhoenix6916() throws Exception { + " where ts >= TIMESTAMP '2023-02-23 13:30:00' and ts < TIMESTAMP '2023-02-23 13:40:00'"; assertPlan(conn, query).scanType("RANGE SCAN").table(indexName) .keyRanges(" [~1,677,159,600,000] - [~1,677,159,000,000]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .serverFirstKeyOnlyProjection(true); } } @@ -7168,7 +7166,7 @@ public void testReverseVarLengthRange6916() throws Exception { assertEquals("\\x9E\\x9E\\x9F\\x00", Bytes.toStringBinary(openScan.getStartRow())); assertEquals("\\x9E\\xFF", Bytes.toStringBinary(openScan.getStopRow())); assertPlan(conn, openQry).scanType("RANGE SCAN").table(tableName) - .keyRanges(" [~'aaa'] - [~'a']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .keyRanges(" [~'aaa'] - [~'a']").serverFirstKeyOnlyProjection(true); String closedQry = "select * from " + tableName + " where k >= 'a' and k <= 'aaa'"; Scan closedScan = @@ -7176,7 +7174,7 @@ public void testReverseVarLengthRange6916() throws Exception { assertEquals("\\x9E\\x9E\\x9E\\xFF", Bytes.toStringBinary(closedScan.getStartRow())); assertEquals("\\x9F\\x00", Bytes.toStringBinary(closedScan.getStopRow())); assertPlan(conn, closedQry).scanType("RANGE SCAN").table(tableName) - .keyRanges(" [~'aaa'] - [~'a']").serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .keyRanges(" [~'aaa'] - [~'a']").serverFirstKeyOnlyProjection(true); } } @@ -7193,10 +7191,9 @@ public void testUncoveredPhoenix6969() throws Exception { 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"; 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"); + .serverMergeColumns("[0.V1, 0.V2, 0.V3, 0.V4]").serverFirstKeyOnlyProjection(true) + .serverWhereFilter("SERVER FILTER BY \"K2\" = 1").serverSortedBy("[\"K1\", \"V1\"]") + .serverRowLimit(1L).clientRowLimit(1).clientSortAlgo("CLIENT MERGE SORT"); } } @@ -7219,8 +7216,8 @@ public void testUncoveredPhoenix6984() throws Exception { .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(); + .scanType("RANGE SCAN").table("I").keyRanges(" ['XXX']").serverFirstKeyOnlyProjection(true) + .end(); } } @@ -7249,7 +7246,7 @@ public void testUncoveredPhoenix6986() throws Exception { + " 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(); + .serverFirstKeyOnlyProjection(true).end(); } } 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 4cdd5dbf921..abbb829c9ad 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 @@ -103,7 +103,8 @@ public void testSelectForceRangeScanForEH() throws Exception { 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" + .serverFirstKeyOnlyProjection(true) + .serverWhereFilter("SERVER FILTER BY (CREATED_DATE >= DATE" + " '2012-11-01 00:00:00.000' AND CREATED_DATE < DATE '2012-11-30 00:00:00.000')") .serverSortedBy("[ORGANIZATION_ID, PARENT_ID, CREATED_DATE DESC, ENTITY_HISTORY_ID]") .serverRowLimit(100L).clientSortAlgo("CLIENT MERGE SORT").clientRowLimit(100); diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java index c0436178405..1a3e82c9c24 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java @@ -194,7 +194,7 @@ public void testViewConstantsOptimizedOut() throws Exception { assertPlan(conn, "SELECT v2 FROM v WHERE v2 > 'a' and k2 = 'a' ORDER BY v2,k2") .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table("_IDX_T") .keyRanges(" [-9223372036854775808,'me','a'] - [-9223372036854775808,'me',*]") - .serverWhereFilter("SERVER FILTER BY FIRST KEY ONLY"); + .serverFirstKeyOnlyProjection(true); // Won't use index b/c v1 is not in index, but should optimize out k2 still from the order by // K2 will still be referenced in the filter, as these are automatically tacked on to the where 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 a783ccc0a15..09849e3db15 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 @@ -82,7 +82,7 @@ public void testDescTimestampAtBoundary() throws Exception { 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"); + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT"); } finally { conn.close(); } @@ -107,7 +107,7 @@ public void testUseOfRoundRobinIteratorSurfaced() throws Exception { 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"); + .serverFirstKeyOnlyProjection(true); } finally { conn.close(); } @@ -126,8 +126,8 @@ public void testSerialHintIgnoredForNonRowkeyOrderBy() throws Exception { // 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"); + .keyRanges(" ['a']").serverFirstKeyOnlyProjection(true).serverSortedBy("[B, C]") + .clientSortAlgo("CLIENT MERGE SORT"); } finally { conn.close(); } diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTest.java b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTest.java index 8e57faa7507..5445b5b8551 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTest.java @@ -143,9 +143,10 @@ public void testRangeScan() throws Exception { public void testSkipScanKeys() throws Exception { verifyQuery("skipScanKeys", "SELECT host FROM ptsdb3 WHERE host IN ('na1','na2','na3')", text("CLIENT PARALLEL -WAY SKIP SCAN ON 3 KEYS OVER PTSDB3 [~'na3'] - [~'na1']", - " INDEX PTSDB3", " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY"), - scanAttrs("SKIP SCAN ON 3 KEYS ", "PTSDB3", " [~'na3'] - [~'na1']").put("serverWhereFilter", - "SERVER FILTER BY FIRST KEY ONLY")); + " INDEX PTSDB3", " REGIONS PLANNED ", + " SERVER PROJECTION FILTER BY FIRST KEY ONLY"), + scanAttrs("SKIP SCAN ON 3 KEYS ", "PTSDB3", " [~'na3'] - [~'na1']") + .put("serverFirstKeyOnlyProjection", true)); } @Test @@ -157,10 +158,11 @@ public void testSkipScanRanges() throws Exception { text( "CLIENT PARALLEL -WAY SKIP SCAN ON 6 RANGES OVER PTSDB" + " ['na1','a','2013-01-01'] - ['na3','b','2013-01-02']", - " INDEX PTSDB", " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY"), + " INDEX PTSDB", " REGIONS PLANNED ", + " SERVER PROJECTION FILTER BY FIRST KEY ONLY"), scanAttrs("SKIP SCAN ON 6 RANGES ", "PTSDB", - " ['na1','a','2013-01-01'] - ['na3','b','2013-01-02']").put("serverWhereFilter", - "SERVER FILTER BY FIRST KEY ONLY")); + " ['na1','a','2013-01-01'] - ['na3','b','2013-01-02']").put("serverFirstKeyOnlyProjection", + true)); } @Test @@ -176,9 +178,8 @@ public void testReverseScan() throws Exception { verifyQuery("reverseScan", "SELECT inst,\"DATE\" FROM ptsdb2 WHERE inst = 'na1' ORDER BY inst DESC, \"DATE\" DESC", text("CLIENT PARALLEL -WAY REVERSE RANGE SCAN OVER PTSDB2 ['na1']", " INDEX PTSDB2", - " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY"), - scanAttrs("RANGE SCAN ", "PTSDB2", " ['na1']") - .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY") + " REGIONS PLANNED ", " SERVER PROJECTION FILTER BY FIRST KEY ONLY"), + scanAttrs("RANGE SCAN ", "PTSDB2", " ['na1']").put("serverFirstKeyOnlyProjection", true) .put("clientSortedBy", "REVERSE")); } @@ -187,19 +188,19 @@ public void testSmallHint() throws Exception { verifyQuery("smallHint", "SELECT /*+ SMALL */ host FROM ptsdb3 WHERE host IN ('na1','na2','na3')", text("CLIENT PARALLEL -WAY SMALL SKIP SCAN ON 3 KEYS OVER PTSDB3 [~'na3'] - [~'na1']", - " INDEX PTSDB3", " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY"), + " INDEX PTSDB3", " REGIONS PLANNED ", + " SERVER PROJECTION FILTER BY FIRST KEY ONLY"), scanAttrs("SKIP SCAN ON 3 KEYS ", "PTSDB3", " [~'na3'] - [~'na1']").put("hint", "SMALL") - .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY")); + .put("serverFirstKeyOnlyProjection", true)); } @Test public void testAggregateSingleRow() throws Exception { verifyQuery("aggregateSingleRow", "SELECT count(*) FROM atable", text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", - " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY", + " REGIONS PLANNED ", " SERVER PROJECTION FILTER BY FIRST KEY ONLY", " SERVER AGGREGATE INTO SINGLE ROW"), - scanAttrs("FULL SCAN ", "ATABLE", "") - .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY") + scanAttrs("FULL SCAN ", "ATABLE", "").put("serverFirstKeyOnlyProjection", true) .put("serverAggregate", "SERVER AGGREGATE INTO SINGLE ROW")); } @@ -319,10 +320,11 @@ public void testRangeScanNullNotNull() throws Exception { "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]", " INDEX PTSDB", - " REGIONS PLANNED ", - " 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'")); + " REGIONS PLANNED ", " SERVER PROJECTION FILTER BY FIRST KEY ONLY", + " SERVER FILTER BY \"DATE\" >= DATE '2013-01-01 00:00:00.000'"), + scanAttrs("RANGE SCAN ", "PTSDB", " [null,not null]") + .put("serverFirstKeyOnlyProjection", true) + .put("serverWhereFilter", "SERVER FILTER BY \"DATE\" >= DATE '2013-01-01 00:00:00.000'")); } @Test @@ -331,12 +333,11 @@ public void testRangeScanNotNull() throws Exception { "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]", " INDEX PTSDB", - " REGIONS PLANNED ", - " 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')")); + " REGIONS PLANNED ", " SERVER PROJECTION FILTER BY FIRST KEY ONLY", + " SERVER FILTER BY (HOST IS NULL" + " AND \"DATE\" >= DATE '2013-01-01 00:00:00.000')"), + scanAttrs("RANGE SCAN ", "PTSDB", " [not null]").put("serverFirstKeyOnlyProjection", true) + .put("serverWhereFilter", + "SERVER FILTER BY (HOST IS NULL" + " AND \"DATE\" >= DATE '2013-01-01 00:00:00.000')")); } @Test @@ -347,10 +348,11 @@ public void testSkipScanLikeRanges() throws Exception { text( "CLIENT PARALLEL -WAY SKIP SCAN ON 2 RANGES OVER PTSDB" + " ['na','a','2013-01-01'] - ['nb','b','2013-01-02']", - " INDEX PTSDB", " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY"), + " INDEX PTSDB", " REGIONS PLANNED ", + " SERVER PROJECTION 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")); + " ['na','a','2013-01-01'] - ['nb','b','2013-01-02']").put("serverFirstKeyOnlyProjection", + true)); } @Test @@ -359,10 +361,11 @@ public void testSkipScanRegexpRanges() throws Exception { "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']", " INDEX PTSDB", " REGIONS PLANNED ", - " 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')")); + " SERVER PROJECTION FILTER BY FIRST KEY ONLY", + " SERVER FILTER BY REGEXP_SUBSTR(INST, '[^-]+', 1) IN ('na1','na2','na3')"), + scanAttrs("SKIP SCAN ON 3 RANGES ", "PTSDB", " ['na1'] - ['na4']") + .put("serverFirstKeyOnlyProjection", true).put("serverWhereFilter", + "SERVER FILTER BY REGEXP_SUBSTR(INST, '[^-]+', 1) IN ('na1','na2','na3')")); } @Test @@ -598,9 +601,10 @@ public void testDeleteServer() throws Exception { verifyMutation("deleteServer", "DELETE FROM atable WHERE entity_id = 'abc'", true, text("DELETE ROWS SERVER SELECT", "CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", " REGIONS PLANNED ", - " SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID = 'abc'"), + " SERVER PROJECTION FILTER BY FIRST KEY ONLY", " SERVER FILTER BY ENTITY_ID = 'abc'"), scanAttrs("FULL SCAN ", "ATABLE", "").put("abstractExplainPlan", "DELETE ROWS SERVER SELECT") - .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID = 'abc'")); + .put("serverFirstKeyOnlyProjection", true) + .put("serverWhereFilter", "SERVER FILTER BY ENTITY_ID = 'abc'")); } @Test @@ -608,19 +612,20 @@ public void testDeleteClient() throws Exception { verifyMutation("deleteClient", "DELETE FROM atable WHERE entity_id = 'abc'", false, text("DELETE ROWS CLIENT SELECT", "CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", " REGIONS PLANNED ", - " SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID = 'abc'"), + " SERVER PROJECTION FILTER BY FIRST KEY ONLY", " SERVER FILTER BY ENTITY_ID = 'abc'"), scanAttrs("FULL SCAN ", "ATABLE", "").put("abstractExplainPlan", "DELETE ROWS CLIENT SELECT") - .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY AND ENTITY_ID = 'abc'")); + .put("serverFirstKeyOnlyProjection", true) + .put("serverWhereFilter", "SERVER FILTER BY ENTITY_ID = 'abc'")); } @Test public void testSequenceNextValue() throws Exception { verifyQuery("sequenceNextValue", "SELECT NEXT VALUE FOR " + SEQ + " FROM atable", text("CLIENT PARALLEL -WAY FULL SCAN OVER ATABLE", " INDEX ATABLE", - " REGIONS PLANNED ", " SERVER FILTER BY FIRST KEY ONLY", + " REGIONS PLANNED ", " SERVER PROJECTION FILTER BY FIRST KEY ONLY", "CLIENT RESERVE VALUES FROM 1 SEQUENCE"), - scanAttrs("FULL SCAN ", "ATABLE", "") - .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY").put("clientSequenceCount", 1) + scanAttrs("FULL SCAN ", "ATABLE", "").put("serverFirstKeyOnlyProjection", true) + .put("clientSequenceCount", 1) .set("clientSteps", clientSteps("CLIENT RESERVE VALUES FROM 1 SEQUENCE"))); } @@ -924,7 +929,7 @@ public void testDiffMessageShowsExpectedAndActualForTextMismatch() { } catch (AssertionError expected) { String msg = expected.getMessage(); assertTrue(msg.contains("Text mismatch for case 'x'")); - assertTrue(msg.contains("SERVER FILTER BY FIRST KEY ONLY")); + assertTrue(msg.contains("SERVER PROJECTION FILTER BY FIRST KEY ONLY")); assertTrue(msg.contains("SERVER FILTER BY (X = 9)")); } catch (Exception e) { fail("Unexpected exception type: " + e); @@ -1001,6 +1006,8 @@ private static ObjectNode defaultAttrs() { n.putNull("serverOffset"); n.putNull("serverRowLimit"); n.put("serverArrayElementProjection", false); + n.put("serverFirstKeyOnlyProjection", false); + n.put("serverEmptyColumnOnlyProjection", false); n.putNull("serverAggregate"); n.putNull("clientFilterBy"); n.putNull("clientAggregate"); @@ -1068,10 +1075,9 @@ private static ArrayNode clientSteps(String... steps) { private static ExplainPlan samplePlan(String way, String scanType) { ExplainPlanAttributes a = new ExplainPlanAttributesBuilder().setIteratorTypeAndScanSize(way) - .setExplainScanType(scanType).setTableName("T") - .setServerWhereFilter("SERVER FILTER BY FIRST KEY ONLY").build(); + .setExplainScanType(scanType).setTableName("T").setServerFirstKeyOnlyProjection(true).build(); return new ExplainPlan(Arrays.asList("CLIENT " + way + " " + scanType.trim() + " OVER T", - " SERVER FILTER BY FIRST KEY ONLY"), a); + " SERVER PROJECTION FILTER BY FIRST KEY ONLY"), a); } private static PColumn column(String family, String name) { diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTestUtil.java b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTestUtil.java index 2f44a07e58c..25a3347255d 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTestUtil.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTestUtil.java @@ -343,6 +343,42 @@ public ExplainPlanAssert serverArrayElementProjection(boolean expected) { return this; } + public ExplainPlanAssert serverFirstKeyOnlyProjection(boolean expected) { + assertEquals(at("serverFirstKeyOnlyProjection"), expected, + attributes.isServerFirstKeyOnlyProjection()); + return this; + } + + /** + * Assert when {@code firstKeyOnly} is true the scan carries the {@code FIRST KEY ONLY} + * projection, otherwise it carries the {@code EMPTY COLUMN ONLY} projection. Convenience for + * callers that pick the expected kind from a flag. + */ + public ExplainPlanAssert serverProjectionFilter(boolean firstKeyOnly) { + return firstKeyOnly + ? serverFirstKeyOnlyProjection(true) + : serverEmptyColumnOnlyProjection(true); + } + + /** + * Assert either the {@code FIRST KEY ONLY} or the {@code EMPTY COLUMN ONLY} projection + * optimization is present. + */ + public ExplainPlanAssert serverProjectionFilterAnyOf() { + assertTrue( + at("serverFirstKeyOnlyProjection|serverEmptyColumnOnlyProjection") + + " expected one to be true", + attributes.isServerFirstKeyOnlyProjection() + || attributes.isServerEmptyColumnOnlyProjection()); + return this; + } + + public ExplainPlanAssert serverEmptyColumnOnlyProjection(boolean expected) { + assertEquals(at("serverEmptyColumnOnlyProjection"), expected, + attributes.isServerEmptyColumnOnlyProjection()); + return this; + } + public ExplainPlanAssert useRoundRobinIterator(boolean expected) { assertEquals(at("useRoundRobinIterator"), expected, attributes.isUseRoundRobinIterator()); return this; From 94b5036f93482a517ab383fa3980d9bb5e9fc524 Mon Sep 17 00:00:00 2001 From: Andrew Purtell Date: Tue, 9 Jun 2026 22:26:37 -0700 Subject: [PATCH 05/27] PHOENIX-7890 Improve EXPLAIN for joins and unions (#2510) Co-authored-by: Claude Opus 4.8[1m] --- .../apache/phoenix/compile/QueryCompiler.java | 7 +- .../apache/phoenix/execute/HashJoinPlan.java | 76 +- .../phoenix/execute/SortMergeJoinPlan.java | 13 +- .../org/apache/phoenix/execute/UnionPlan.java | 52 +- .../phoenix/iterate/UnionResultIterators.java | 58 +- .../apache/phoenix/jdbc/PhoenixStatement.java | 2 +- .../phoenix/end2end/CostBasedDecisionIT.java | 61 +- .../end2end/QueryWithTableSampleIT.java | 15 +- .../apache/phoenix/end2end/UnionAllIT.java | 46 +- .../index/GlobalIndexOptimizationIT.java | 3 +- .../phoenix/end2end/index/IndexUsageIT.java | 6 +- .../end2end/join/HashJoinGlobalIndexIT.java | 125 ++- .../end2end/join/HashJoinLocalIndexIT.java | 149 +-- .../phoenix/end2end/join/HashJoinMoreIT.java | 18 +- .../end2end/join/HashJoinNoIndexIT.java | 121 ++- .../end2end/tpcds/TPCDSLikeAssertions.java | 212 ++++ .../end2end/tpcds/TPCDSLikeBaseIT.java | 103 ++ .../tpcds/TPCDSLikeCrossChannelIT.java | 238 +++++ .../end2end/tpcds/TPCDSLikeFixture.java | 922 ++++++++++++++++++ .../tpcds/TPCDSLikeSingleChannelIT.java | 395 ++++++++ .../phoenix/compile/QueryCompilerTest.java | 16 +- .../tpcds/TPCDSLikeExpectedRegenerator.java | 63 ++ .../query/explain/ExplainPlanTest.java | 69 +- 23 files changed, 2473 insertions(+), 297 deletions(-) create mode 100644 phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeAssertions.java create mode 100644 phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeBaseIT.java create mode 100644 phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeCrossChannelIT.java create mode 100644 phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeFixture.java create mode 100644 phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeSingleChannelIT.java create mode 100644 phoenix-core/src/test/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeExpectedRegenerator.java diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/QueryCompiler.java b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/QueryCompiler.java index f4d78ce0e69..86f8b500c05 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/QueryCompiler.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/QueryCompiler.java @@ -486,7 +486,7 @@ protected QueryPlan compileJoinQuery(JoinCompiler.Strategy strategy, StatementCo joinTypes, starJoinVector, tables, fieldPositions, postJoinFilterExpression, QueryUtil.getOffsetLimit(limit, offset)); return HashJoinPlan.create(joinTable.getOriginalJoinSelectStatement(), plan, joinInfo, - hashPlans); + hashPlans, JoinCompiler.Strategy.HASH_BUILD_RIGHT); } case HASH_BUILD_LEFT: { JoinSpec lastJoinSpec = joinSpecs.get(joinSpecs.size() - 1); @@ -562,7 +562,8 @@ protected QueryPlan compileJoinQuery(JoinCompiler.Strategy strategy, StatementCo hashExpressions); return HashJoinPlan.create(joinTable.getOriginalJoinSelectStatement(), rhsPlan, joinInfo, new HashSubPlan[] { new HashSubPlan(0, lhsPlan, hashExpressions, false, - usePersistentCache, keyRangeExpressions.getFirst(), keyRangeExpressions.getSecond()) }); + usePersistentCache, keyRangeExpressions.getFirst(), keyRangeExpressions.getSecond()) }, + JoinCompiler.Strategy.HASH_BUILD_LEFT); } case SORT_MERGE: { JoinTable lhsJoin = joinTable.createSubJoinTable(statement.getConnection()); @@ -895,7 +896,7 @@ protected QueryPlan compileSingleFlatQuery(StatementContext context, SelectState subPlans[i++] = new WhereClauseSubPlan(compileSubquery(stmt, false), stmt, subqueryNode.expectSingleRow()); } - plan = HashJoinPlan.create(planSelect, plan, null, subPlans); + plan = HashJoinPlan.create(planSelect, plan, null, subPlans, null); } if (innerPlan != null) { 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 aa18e0e6cc8..a558c1832ae 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 @@ -43,6 +43,7 @@ import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.compile.ExplainPlanAttributes.ExplainPlanAttributesBuilder; import org.apache.phoenix.compile.FromCompiler; +import org.apache.phoenix.compile.JoinCompiler; import org.apache.phoenix.compile.QueryPlan; import org.apache.phoenix.compile.RowProjector; import org.apache.phoenix.compile.ScanRanges; @@ -99,6 +100,7 @@ public class HashJoinPlan extends DelegateQueryPlan { private final SelectStatement statement; private final HashJoinInfo joinInfo; + private JoinCompiler.Strategy strategy; private final SubPlan[] subPlans; private final boolean recompileWhereClause; private final Set tableRefs; @@ -115,9 +117,10 @@ public class HashJoinPlan extends DelegateQueryPlan { private boolean hasSubPlansWithPersistentCache; public static HashJoinPlan create(SelectStatement statement, QueryPlan plan, - HashJoinInfo joinInfo, SubPlan[] subPlans) throws SQLException { - if (!(plan instanceof HashJoinPlan)) return new HashJoinPlan(statement, plan, joinInfo, - subPlans, joinInfo == null, Collections. emptyMap()); + HashJoinInfo joinInfo, SubPlan[] subPlans, JoinCompiler.Strategy strategy) throws SQLException { + if (!(plan instanceof HashJoinPlan)) + return new HashJoinPlan(statement, plan, joinInfo, subPlans, joinInfo == null, + Collections. emptyMap(), strategy); HashJoinPlan hashJoinPlan = (HashJoinPlan) plan; assert (hashJoinPlan.joinInfo == null && hashJoinPlan.delegate instanceof BaseQueryPlan); @@ -130,16 +133,18 @@ public static HashJoinPlan create(SelectStatement statement, QueryPlan plan, mergedSubPlans[i++] = subPlan; } return new HashJoinPlan(statement, hashJoinPlan.delegate, joinInfo, mergedSubPlans, true, - hashJoinPlan.dependencies); + hashJoinPlan.dependencies, strategy); } private HashJoinPlan(SelectStatement statement, QueryPlan plan, HashJoinInfo joinInfo, SubPlan[] subPlans, boolean recompileWhereClause, - Map dependencies) throws SQLException { + Map dependencies, JoinCompiler.Strategy strategy) + throws SQLException { super(plan); this.dependencies.putAll(dependencies); this.statement = statement; this.joinInfo = joinInfo; + this.strategy = strategy; this.subPlans = subPlans; this.recompileWhereClause = recompileWhereClause; this.tableRefs = Sets.newHashSetWithExpectedSize(subPlans.length + plan.getSourceRefs().size()); @@ -382,6 +387,14 @@ public HashJoinInfo getJoinInfo() { return joinInfo; } + public JoinCompiler.Strategy getStrategy() { + return strategy; + } + + public void setStrategy(JoinCompiler.Strategy strategy) { + this.strategy = strategy; + } + public SubPlan[] getSubPlans() { return subPlans; } @@ -683,18 +696,41 @@ public void postProcess(ServerCache result, HashJoinPlan parent) throws SQLExcep } } + /** + * Builds the join operator line (without leading indentation) for this subplan, with the chosen + * {@link JoinCompiler.Strategy} and the {@code SKIP MERGE} / {@code DELAYED EVALUATION} + * decorators rendered as a single trailing SQL comment. + */ + private String buildHeader(HashJoinPlan parent) { + boolean isHashJoin = hashExpressions != null; + String op = isHashJoin + ? "PARALLEL " + parent.joinInfo.getJoinTypes()[index].toString().toUpperCase() + + "-JOIN TABLE " + index + : "SKIP-SCAN-JOIN TABLE " + index; + JoinCompiler.Strategy strategy = parent.getStrategy(); + if (strategy == null) { + return op; + } + StringBuilder comment = new StringBuilder("/* HASH BUILD "); + comment.append(strategy == JoinCompiler.Strategy.HASH_BUILD_LEFT ? "LEFT" : "RIGHT"); + if (isHashJoin) { + boolean skipMerge = parent.joinInfo.getSchemas()[index].getFieldCount() == 0; + boolean earlyEvaluation = parent.joinInfo.earlyEvaluation()[index]; + if (skipMerge) { + comment.append(", SKIP MERGE"); + } + if (!earlyEvaluation) { + comment.append(", DELAYED EVALUATION"); + } + } + comment.append(" */"); + return op + " " + comment; + } + @Override public List getPreSteps(HashJoinPlan parent) throws SQLException { List steps = Lists.newArrayList(); - boolean earlyEvaluation = parent.joinInfo.earlyEvaluation()[index]; - boolean skipMerge = parent.joinInfo.getSchemas()[index].getFieldCount() == 0; - if (hashExpressions != null) { - steps.add(" PARALLEL " + parent.joinInfo.getJoinTypes()[index].toString().toUpperCase() - + "-JOIN TABLE " + index + (earlyEvaluation ? "" : "(DELAYED EVALUATION)") - + (skipMerge ? " (SKIP MERGE)" : "")); - } else { - steps.add(" SKIP-SCAN-JOIN TABLE " + index); - } + steps.add(" " + buildHeader(parent)); for (String step : plan.getExplainPlan().getPlanSteps()) { steps.add(" " + step); } @@ -703,17 +739,7 @@ public List getPreSteps(HashJoinPlan parent) throws SQLException { @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); + return subPlanAttributesWithHeader(plan, buildHeader(parent)); } @Override 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 9e1cd2290ba..bf36c03a58b 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 @@ -41,6 +41,7 @@ import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.compile.ExplainPlanAttributes.ExplainPlanAttributesBuilder; import org.apache.phoenix.compile.GroupByCompiler.GroupBy; +import org.apache.phoenix.compile.JoinCompiler; import org.apache.phoenix.compile.OrderByCompiler.OrderBy; import org.apache.phoenix.compile.QueryCompiler; import org.apache.phoenix.compile.QueryPlan; @@ -98,6 +99,7 @@ public class SortMergeJoinPlan implements QueryPlan { * {@link JoinType#Left}. */ private final JoinType joinType; + private JoinCompiler.Strategy strategy = JoinCompiler.Strategy.SORT_MERGE; private final QueryPlan lhsPlan; private final QueryPlan rhsPlan; private final List lhsKeyExpressions; @@ -188,7 +190,8 @@ public ResultIterator iterator() throws SQLException { @Override public ExplainPlan getExplainPlan() throws SQLException { List steps = Lists.newArrayList(); - steps.add("SORT-MERGE-JOIN (" + joinType.toString().toUpperCase() + ") TABLES"); + steps + .add("SORT-MERGE-JOIN (" + joinType.toString().toUpperCase() + ") TABLES /* SORT_MERGE */"); ExplainPlan lhsExplainPlan = lhsPlan.getExplainPlan(); List lhsPlanSteps = lhsExplainPlan.getPlanSteps(); ExplainPlanAttributes lhsPlanAttributes = lhsExplainPlan.getPlanStepsAsAttributes(); @@ -303,6 +306,14 @@ public JoinType getJoinType() { return joinType; } + public JoinCompiler.Strategy getStrategy() { + return strategy; + } + + public void setStrategy(JoinCompiler.Strategy strategy) { + this.strategy = strategy; + } + private static SQLException closeIterators(ResultIterator lhsIterator, ResultIterator rhsIterator) { SQLException e = null; diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/UnionPlan.java b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/UnionPlan.java index 1a1e76f6904..8d15872e7bf 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/UnionPlan.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/UnionPlan.java @@ -238,18 +238,52 @@ public ExplainPlan getExplainPlan() throws SQLException { String abstractExplainPlan = "UNION ALL OVER " + this.plans.size() + " QUERIES"; builder.setAbstractExplainPlan(abstractExplainPlan); steps.add(abstractExplainPlan); - ResultIterator iterator = iterator(); - iterator.explain(steps, builder); - // Indent plans steps nested under union, except last client-side merge/concat step (if there is - // one) - int offset = - !orderBy.getOrderByExpressions().isEmpty() && limit != null ? 2 : limit != null ? 1 : 0; - for (int i = 1; i < steps.size() - offset; i++) { - steps.set(i, " " + steps.get(i)); - } + // Compose each branch from its own getExplainPlan() so the full sub-plan structure of every + // branch is preserved and explaining the union does not trigger sub-plan execution. + UnionResultIterators.explainBranches(plans, steps, builder); + addUnionTailLines(steps, builder); return new ExplainPlan(steps, builder.build()); } + /** + * Appends the client-side merge/offset/limit lines that follow the union branches, mirroring the + * iterator chain assembled in {@link #iterator(ParallelScanGrouper, Scan)}. These lines stay at + * column 0, after the indented branch steps. + */ + private void addUnionTailLines(List steps, ExplainPlanAttributesBuilder builder) { + if (!orderBy.isEmpty() || this.supportOrderByOptimize) { + String mergeSort = "CLIENT MERGE SORT"; + steps.add(mergeSort); + builder.setClientSortAlgo(mergeSort); + builder.addClientStep(mergeSort); + if (offset != null && offset > 0) { + String step = "CLIENT OFFSET " + offset; + steps.add(step); + builder.setClientOffset(offset); + builder.addClientStep(step); + } + if (limit != null && limit > 0) { + String step = "CLIENT LIMIT " + limit; + steps.add(step); + builder.setClientRowLimit(limit); + builder.addClientStep(step); + } + } else { + if (offset != null) { + String step = "CLIENT OFFSET " + offset; + steps.add(step); + builder.setClientOffset(offset); + builder.addClientStep(step); + } + if (limit != null) { + String step = "CLIENT " + limit + " ROW LIMIT"; + steps.add(step); + builder.setClientRowLimit(limit); + builder.addClientStep(step); + } + } + } + @Override public long getEstimatedSize() { return DEFAULT_ESTIMATED_SIZE; diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/UnionResultIterators.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/UnionResultIterators.java index e5b981e7a64..d31e38df6d1 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/UnionResultIterators.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/UnionResultIterators.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.List; import org.apache.hadoop.hbase.client.Scan; +import org.apache.phoenix.compile.ExplainPlan; import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.compile.ExplainPlanAttributes.ExplainPlanAttributesBuilder; import org.apache.phoenix.compile.QueryPlan; @@ -43,10 +44,12 @@ public class UnionResultIterators implements ResultIterators { private final List overAllQueryMetricsList; private boolean closed; private final StatementContext parentStmtCtx; + private final List plans; public UnionResultIterators(List plans, StatementContext parentStmtCtx) throws SQLException { this.parentStmtCtx = parentStmtCtx; + this.plans = plans; int nPlans = plans.size(); iterators = Lists.newArrayListWithExpectedSize(nPlans); splits = Lists.newArrayListWithExpectedSize(nPlans * 30); @@ -125,8 +128,10 @@ public int size() { @Override public void explain(List planSteps) { - for (PeekingResultIterator iterator : iterators) { - iterator.explain(planSteps); + try { + explainBranches(plans, planSteps, null); + } catch (SQLException e) { + throw new RuntimeException(e); } } @@ -138,22 +143,41 @@ public List getIterators() throws SQLException { @Override public void explain(List planSteps, ExplainPlanAttributesBuilder explainPlanAttributesBuilder) { - boolean moreThanOneIters = false; - ExplainPlanAttributesBuilder lhsPointer = null; - // For more than one iterators, explainPlanAttributes will create - // chain of objects as lhs and rhs query plans. - for (PeekingResultIterator iterator : iterators) { - if (moreThanOneIters) { - ExplainPlanAttributesBuilder rhsBuilder = new ExplainPlanAttributesBuilder(); - iterator.explain(planSteps, rhsBuilder); - ExplainPlanAttributes rhsPlans = rhsBuilder.build(); - lhsPointer.setRhsJoinQueryExplainPlan(rhsPlans); - lhsPointer = rhsBuilder; - } else { - iterator.explain(planSteps, explainPlanAttributesBuilder); - lhsPointer = explainPlanAttributesBuilder; + try { + explainBranches(plans, planSteps, explainPlanAttributesBuilder); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + /** + * Single source of truth for composing a union's branches into the explain output. Each branch is + * rendered from its own {@link QueryPlan#getExplainPlan()} (rather than from its iterator) so the + * full sub-plan structure of each branch is preserved, and so explaining a union does not trigger + * sub-plan execution. The branch text steps are indented one level and, when a builder is + * supplied, the branches' structured attributes are collected onto the builder's {@code subPlans} + * list. + *

+ * Every branch contributes exactly one entry in branch order, so {@code getSubPlans().size()} + * always equals {@code plans.size()}. + */ + public static void explainBranches(List plans, List planSteps, + ExplainPlanAttributesBuilder builder) throws SQLException { + List subPlans = + builder == null ? null : Lists.newArrayListWithExpectedSize(plans.size()); + for (QueryPlan plan : plans) { + ExplainPlan explainPlan = plan.getExplainPlan(); + for (String step : explainPlan.getPlanSteps()) { + planSteps.add(" " + step); + } + if (subPlans != null) { + ExplainPlanAttributes attributes = explainPlan.getPlanStepsAsAttributes(); + subPlans + .add(attributes != null ? attributes : ExplainPlanAttributes.getDefaultExplainPlan()); } - moreThanOneIters = true; + } + if (subPlans != null) { + builder.setSubPlans(subPlans); } } } diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java index 20edcbe34d2..2a76e8a6fac 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java @@ -1263,7 +1263,7 @@ public MutationPlan compilePlan(PhoenixStatement stmt, Sequence.ValueOp seqActio return new BaseMutationPlan(context, this.getOperation()) { @Override public ExplainPlan getExplainPlan() throws SQLException { - return new ExplainPlan(Collections.singletonList("Truncate Table")); + return new ExplainPlan(Collections.singletonList("TRUNCATE TABLE")); } @Override diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CostBasedDecisionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CostBasedDecisionIT.java index 8e9518db9b8..15fe361aaa4 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CostBasedDecisionIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CostBasedDecisionIT.java @@ -334,13 +334,13 @@ public void testCostOverridesStaticPlanOrderingInUnionQuery() throws Exception { + " where rowkey <= 'z' GROUP BY c1 " + "UNION ALL SELECT c1, max(rowkey), max(c2) FROM " + tableName + " where rowkey >= 'a' GROUP BY c1"; // Use the default plan when stats are not available. - assertPlan(conn, query).abstractExplainPlan("UNION ALL OVER 2 QUERIES") - .iteratorType("PARALLEL").scanType("RANGE SCAN").table(tableName).keyRanges(" [*] - ['z']") - .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]") - .clientSortAlgo("CLIENT MERGE SORT").rhs().iteratorType("PARALLEL").scanType("RANGE SCAN") - .table(tableName).keyRanges(" ['a'] - [*]") + assertPlan(conn, query).abstractExplainPlan("UNION ALL OVER 2 QUERIES").subPlanCount(2) + .subPlan(0).iteratorType("PARALLEL").scanType("RANGE SCAN").table(tableName) + .keyRanges(" [*] - ['z']").serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]") + .clientSortAlgo("CLIENT MERGE SORT").end().subPlan(1).iteratorType("PARALLEL") + .scanType("RANGE SCAN").table(tableName).keyRanges(" ['a'] - [*]") .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]") - .clientSortAlgo("CLIENT MERGE SORT"); + .clientSortAlgo("CLIENT MERGE SORT").end(); PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " + tableName + " (rowkey, c1, c2) VALUES (?, ?, ?)"); @@ -355,16 +355,17 @@ public void testCostOverridesStaticPlanOrderingInUnionQuery() throws Exception { conn.createStatement().execute("UPDATE STATISTICS " + tableName); // Use the optimal plan based on cost when stats become available. - assertPlan(conn, query).abstractExplainPlan("UNION ALL OVER 2 QUERIES") - .iteratorType("PARALLEL").scanType("RANGE SCAN").table(indexName + "(" + tableName + ")") - .keyRanges(" [1]").serverMergeColumns("[0.C2]").serverFirstKeyOnlyProjection(true) - .serverWhereFilter("SERVER FILTER BY \"ROWKEY\" <= 'z'") - .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]") - .clientSortAlgo("CLIENT MERGE SORT").rhs().iteratorType("PARALLEL").scanType("RANGE SCAN") + assertPlan(conn, query).abstractExplainPlan("UNION ALL OVER 2 QUERIES").subPlanCount(2) + .subPlan(0).iteratorType("PARALLEL").scanType("RANGE SCAN") .table(indexName + "(" + tableName + ")").keyRanges(" [1]").serverMergeColumns("[0.C2]") - .serverFirstKeyOnlyProjection(true).serverWhereFilter("SERVER FILTER BY \"ROWKEY\" >= 'a'") + .serverFirstKeyOnlyProjection(true).serverWhereFilter("SERVER FILTER BY \"ROWKEY\" <= 'z'") .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]") - .clientSortAlgo("CLIENT MERGE SORT"); + .clientSortAlgo("CLIENT MERGE SORT").end().subPlan(1).iteratorType("PARALLEL") + .scanType("RANGE SCAN").table(indexName + "(" + tableName + ")").keyRanges(" [1]") + .serverMergeColumns("[0.C2]").serverFirstKeyOnlyProjection(true) + .serverWhereFilter("SERVER FILTER BY \"ROWKEY\" >= 'a'") + .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"C1\"]") + .clientSortAlgo("CLIENT MERGE SORT").end(); } finally { conn.close(); } @@ -391,7 +392,7 @@ public void testCostOverridesStaticPlanOrderingInJoinQuery() throws Exception { assertPlan(conn, query).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(tableName) .serverWhereFilter("SERVER FILTER BY C1 LIKE 'X0%'") .dynamicServerFilter("DYNAMIC SERVER FILTER BY T1.ROWKEY IN (T2.MRK)").subPlanCount(1) - .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(tableName) .keyRanges(" [*] - ['z']").serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [C1]") .clientSortAlgo("CLIENT MERGE SORT"); @@ -414,7 +415,7 @@ public void testCostOverridesStaticPlanOrderingInJoinQuery() throws Exception { .serverMergeColumns("[0.C2]").serverFirstKeyOnlyProjection(true) .serverSortedBy("[\"T1.:ROWKEY\"]").clientSortAlgo("CLIENT MERGE SORT") .dynamicServerFilter("DYNAMIC SERVER FILTER BY \"T1.:ROWKEY\" IN (T2.MRK)").subPlanCount(1) - .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN") .table(indexName + "(" + tableName + ")").keyRanges(" [1]").serverMergeColumns("[0.C2]") .serverFirstKeyOnlyProjection(true).serverWhereFilter("SERVER FILTER BY \"ROWKEY\" <= 'z'") @@ -517,7 +518,7 @@ public void testJoinStrategy3() throws Exception { try (Connection conn = DriverManager.getConnection(getUrl(), props)) { assertPlan(conn, q).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable1000) .dynamicServerFilter("DYNAMIC SERVER FILTER BY T2.ID IN (T1.COL1)").subPlanCount(1) - .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD LEFT */") .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(testTable500) .keyRanges(" [201] - [*]"); } @@ -535,7 +536,7 @@ public void testJoinStrategy4() throws Exception { try (Connection conn = DriverManager.getConnection(getUrl(), props)) { assertPlan(conn, q).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable990) .dynamicServerFilter("DYNAMIC SERVER FILTER BY T1.ID IN (T2.COL1)").subPlanCount(1) - .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable1000); } } @@ -548,7 +549,8 @@ public void testJoinStrategy5() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); try (Connection conn = DriverManager.getConnection(getUrl(), props)) { assertPlan(conn, q).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable1000) - .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD LEFT */") .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(testTable500) .keyRanges(" [201] - [*]"); } @@ -562,7 +564,8 @@ public void testJoinStrategy6() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); try (Connection conn = DriverManager.getConnection(getUrl(), props)) { assertPlan(conn, q).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable1000) - .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD LEFT */") .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(testTable500) .keyRanges(" [201] - [*]"); } @@ -582,8 +585,8 @@ public void testJoinStrategy7() throws Exception { assertPlan(conn, q).iteratorType("PARALLEL 1001-WAY").scanType("FULL SCAN") .table(testTable1000).serverSortedBy("[T1.COL1]").clientSortAlgo("CLIENT MERGE SORT") .dynamicServerFilter("DYNAMIC SERVER FILTER BY T2.ID IN (T1.ID)").subPlanCount(1).subPlan(0) - .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").iteratorType("PARALLEL 1-WAY") - .scanType("FULL SCAN").table(testTable500); + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD LEFT */") + .iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable500); } } @@ -637,7 +640,7 @@ public void testJoinStrategy10() throws Exception { assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").lhs() .iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable1000) .dynamicServerFilter("DYNAMIC SERVER FILTER BY T1.ID IN (T2.COL1)").subPlanCount(1) - .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(testTable500) .keyRanges(" [201] - [*]").end().end().rhs().iteratorType("PARALLEL 1-WAY") .scanType("RANGE SCAN").table(testTable990).keyRanges(" [*] - [100]"); @@ -656,11 +659,13 @@ public void testJoinStrategy11() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); try (Connection conn = DriverManager.getConnection(getUrl(), props)) { assertPlan(conn, q).iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable1000) - .subPlanCount(2).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(testTable500) .keyRanges(" [201] - [*]").end().subPlan(1) - .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1").iteratorType("PARALLEL 1-WAY") - .scanType("RANGE SCAN").table(testTable990).keyRanges(" [*] - [100]"); + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1 /* HASH BUILD RIGHT */") + .iteratorType("PARALLEL 1-WAY").scanType("RANGE SCAN").table(testTable990) + .keyRanges(" [*] - [100]"); } } @@ -677,8 +682,8 @@ public void testJoinStrategy12() throws Exception { assertPlan(conn, q).abstractExplainPlan("SORT-MERGE-JOIN (INNER)").lhs() .iteratorType("PARALLEL 1001-WAY").scanType("FULL SCAN").table(testTable1000) .serverSortedBy("[T1.COL1]").clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1).subPlan(0) - .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").iteratorType("PARALLEL 1-WAY") - .scanType("FULL SCAN").table(testTable990).end().end().rhs() + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .iteratorType("PARALLEL 1-WAY").scanType("FULL SCAN").table(testTable990).end().end().rhs() .iteratorType("PARALLEL 991-WAY").scanType("FULL SCAN").table(testTable990) .serverSortedBy("[T3.COL2]").clientSortAlgo("CLIENT MERGE SORT"); } 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 93702373e9c..bccd30040f6 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 @@ -225,11 +225,11 @@ public void testExplainSingleQueryWithUnion() throws Exception { 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) - .serverFirstKeyOnlyProjection(true).rhs().scanType("FULL SCAN").table(tableName) - .samplingRate(0.02d).serverFirstKeyOnlyProjection(true) - .serverWhereFilter("SERVER FILTER BY I2 < 6000").end(); + assertPlan(conn, query).abstractExplainPlan("UNION ALL OVER 2 QUERIES").subPlanCount(2) + .subPlan(0).scanType("RANGE SCAN").table(tableName).keyRanges(" [*] - [2]") + .samplingRate(1.0d).serverFirstKeyOnlyProjection(true).end().subPlan(1) + .scanType("FULL SCAN").table(tableName).samplingRate(0.02d) + .serverFirstKeyOnlyProjection(true).serverWhereFilter("SERVER FILTER BY I2 < 6000").end(); } finally { conn.close(); } @@ -247,8 +247,9 @@ public void testExplainSingleQueryWithJoins() throws Exception { assertPlan(conn, query).scanType("FULL SCAN").table(tableName).samplingRate(0.45d) .serverFirstKeyOnlyProjection(true).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).serverFirstKeyOnlyProjection(true).end(); + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT, SKIP MERGE */") + .scanType("FULL SCAN").table(joinedTableName).samplingRate(0.75d) + .serverFirstKeyOnlyProjection(true).end(); } finally { conn.close(); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UnionAllIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UnionAllIT.java index e63972ba38e..699c1973915 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UnionAllIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UnionAllIT.java @@ -30,6 +30,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.List; import java.util.Properties; import org.apache.phoenix.compile.ExplainPlan; import org.apache.phoenix.compile.ExplainPlanAttributes; @@ -624,15 +625,23 @@ public void testExplainUnionAll() throws Exception { ExplainPlan plan = conn.prepareStatement(ddl).unwrap(PhoenixPreparedStatement.class) .optimizeQuery().getExplainPlan(); ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); + // The union composes each branch recursively into subPlans. The root carries only the union + // level + // client side, and each branch's scan attributes live on its own subPlan entry. assertEquals("UNION ALL OVER 2 QUERIES", explainPlanAttributes.getAbstractExplainPlan()); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName1, explainPlanAttributes.getTableName()); - assertEquals("[COL1]", explainPlanAttributes.getServerSortedBy()); - assertEquals(1L, explainPlanAttributes.getServerRowLimit().longValue()); - assertEquals(1, explainPlanAttributes.getClientRowLimit().intValue()); assertEquals("CLIENT MERGE SORT", explainPlanAttributes.getClientSortAlgo()); - ExplainPlanAttributes rhsPlanAttributes = explainPlanAttributes.getRhsJoinQueryExplainPlan(); + assertEquals(1, explainPlanAttributes.getClientRowLimit().intValue()); + List subPlans = explainPlanAttributes.getSubPlans(); + assertEquals(2, subPlans.size()); + ExplainPlanAttributes lhsPlanAttributes = subPlans.get(0); + assertEquals("PARALLEL 1-WAY", lhsPlanAttributes.getIteratorTypeAndScanSize()); + assertEquals("FULL SCAN ", lhsPlanAttributes.getExplainScanType()); + assertEquals(tableName1, lhsPlanAttributes.getTableName()); + assertEquals("[COL1]", lhsPlanAttributes.getServerSortedBy()); + assertEquals(1L, lhsPlanAttributes.getServerRowLimit().longValue()); + assertEquals(1, lhsPlanAttributes.getClientRowLimit().intValue()); + assertEquals("CLIENT MERGE SORT", lhsPlanAttributes.getClientSortAlgo()); + ExplainPlanAttributes rhsPlanAttributes = subPlans.get(1); assertEquals("PARALLEL 1-WAY", rhsPlanAttributes.getIteratorTypeAndScanSize()); assertEquals("FULL SCAN ", rhsPlanAttributes.getExplainScanType()); assertEquals(tableName2, rhsPlanAttributes.getTableName()); @@ -642,15 +651,15 @@ public void testExplainUnionAll() throws Exception { assertEquals("CLIENT MERGE SORT", rhsPlanAttributes.getClientSortAlgo()); // Each branch emits a SERIAL 1-WAY FULL SCAN with SERVER 2 ROW LIMIT and an inner CLIENT 2 - // ROW LIMIT, and the union root carries an outer CLIENT 2 ROW LIMIT. The outer wrap shows - // up as a second "CLIENT 2 ROW LIMIT" entry in the root's clientSteps. + // ROW LIMIT. The union root carries the single outer CLIENT 2 ROW LIMIT. ddl = "select a_string, col1 from " + tableName1 + " union all select a_string, col1 from " + tableName2 + " limit 2"; - assertPlan(conn, ddl).abstractExplainPlan("UNION ALL OVER 2 QUERIES").iteratorType("SERIAL") + assertPlan(conn, ddl).abstractExplainPlan("UNION ALL OVER 2 QUERIES").clientRowLimit(2) + .clientSteps("CLIENT 2 ROW LIMIT").subPlanCount(2).subPlan(0).iteratorType("SERIAL") .scanType("FULL SCAN").table(tableName1).serverSortedBy(null).serverRowLimit(2L) - .clientRowLimit(2).clientSteps("CLIENT 2 ROW LIMIT", "CLIENT 2 ROW LIMIT").rhs() - .iteratorType("SERIAL").scanType("FULL SCAN").table(tableName2).serverSortedBy(null) - .serverRowLimit(2L).clientRowLimit(2).clientSteps("CLIENT 2 ROW LIMIT"); + .clientRowLimit(2).clientSteps("CLIENT 2 ROW LIMIT").end().subPlan(1).iteratorType("SERIAL") + .scanType("FULL SCAN").table(tableName2).serverSortedBy(null).serverRowLimit(2L) + .clientRowLimit(2).clientSteps("CLIENT 2 ROW LIMIT").end(); ddl = "select a_string, col1 from " + tableName1 + " union all select a_string, col1 from " + tableName2; @@ -658,10 +667,13 @@ public void testExplainUnionAll() throws Exception { .getExplainPlan(); explainPlanAttributes = plan.getPlanStepsAsAttributes(); assertEquals("UNION ALL OVER 2 QUERIES", explainPlanAttributes.getAbstractExplainPlan()); - assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("FULL SCAN ", explainPlanAttributes.getExplainScanType()); - assertEquals(tableName1, explainPlanAttributes.getTableName()); - rhsPlanAttributes = explainPlanAttributes.getRhsJoinQueryExplainPlan(); + subPlans = explainPlanAttributes.getSubPlans(); + assertEquals(2, subPlans.size()); + lhsPlanAttributes = subPlans.get(0); + assertEquals("PARALLEL 1-WAY", lhsPlanAttributes.getIteratorTypeAndScanSize()); + assertEquals("FULL SCAN ", lhsPlanAttributes.getExplainScanType()); + assertEquals(tableName1, lhsPlanAttributes.getTableName()); + rhsPlanAttributes = subPlans.get(1); assertEquals("PARALLEL 1-WAY", rhsPlanAttributes.getIteratorTypeAndScanSize()); assertEquals("FULL SCAN ", rhsPlanAttributes.getExplainScanType()); assertEquals(tableName2, rhsPlanAttributes.getTableName()); 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 1f900e2f5f0..39800cf2c44 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 @@ -240,7 +240,8 @@ private void testOptimization(String dataTableName, String dataTableFullName, .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") + skipScanJoinPlan.subPlanCount(1).subPlan(0) + .abstractExplainPlan("SKIP-SCAN-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("RANGE SCAN").table(indexTableName).keyRanges(" [*] - ['z']") .serverFirstKeyOnlyProjection(true).end(); // The dynamic server filter references compiler-generated positional aliases ($N.$N) whose 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 9acbc4848e2..f1fbc4175c6 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 @@ -782,12 +782,14 @@ public void helpTestIndexExpressionWithJoin(boolean mutable, boolean localIndex) .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").subPlanCount(1) .subPlan(0).scanType("RANGE SCAN").tableContains(indexName + "(" + tableName + ")") .keyRanges(" [1]").serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT") - .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 (SKIP MERGE)").end(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT, SKIP MERGE */") + .end(); } else { assertPlan(conn, query).scanType("RANGE SCAN").tableContains(indexName) .keyRanges(" ['1David']").serverFirstKeyOnlyProjection(true).subPlanCount(1).subPlan(0) .scanType("FULL SCAN").tableContains(indexName).serverFirstKeyOnlyProjection(true) - .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 (SKIP MERGE)").end(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT, SKIP MERGE */") + .end(); } rs = conn.createStatement().executeQuery(query); 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 a3ee5b48bed..a6319944e09 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 @@ -62,8 +62,8 @@ protected void assertLeftJoinWithAggPlan1(Connection conn, String query) throws 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) - .serverFirstKeyOnlyProjection(true).end(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true).end(); } @Override @@ -73,8 +73,8 @@ protected void assertLeftJoinWithAggPlan2(Connection conn, String query) throws 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).serverFirstKeyOnlyProjection(true).end(); + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true).end(); } @Override @@ -84,7 +84,8 @@ protected void assertLeftJoinWithAggPlan3(Connection conn, String query) throws assertPlan(conn, query).scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true) .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(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(order).end(); } @Override @@ -93,7 +94,8 @@ protected void assertRightJoinWithAggPlan1(Connection conn, String query) throws String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); assertPlan(conn, query).scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true) .serverAggregate("SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [\"I.0:NAME\"]") - .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD LEFT */") .scanType("FULL SCAN").table(order).end(); } @@ -104,7 +106,8 @@ protected void assertRightJoinWithAggPlan2(Connection conn, String query) throws assertPlan(conn, query).scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true) .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(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD LEFT */") + .scanType("FULL SCAN").table(order).end(); } @Override @@ -112,8 +115,8 @@ protected void assertJoinWithWildcardPlan(Connection conn, String query) throws 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(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(supplier).end(); } @Override @@ -122,8 +125,9 @@ protected void assertJoinPlanWithIndexPlan1(Connection conn, String query) throw String idxSupplier = getSchemaName() + ".idx_supplier"; assertPlan(conn, query).scanType("RANGE SCAN").table(idxItem).keyRanges(" ['T1'] - ['T5']") .serverFirstKeyOnlyProjection(true).subPlanCount(1).subPlan(0) - .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0").scanType("RANGE SCAN").table(idxSupplier) - .keyRanges(" ['S1'] - ['S5']").serverFirstKeyOnlyProjection(true).end(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("RANGE SCAN").table(idxSupplier).keyRanges(" ['S1'] - ['S5']") + .serverFirstKeyOnlyProjection(true).end(); } @Override @@ -132,8 +136,9 @@ protected void assertJoinPlanWithIndexPlan2(Connection conn, String query) throw 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']").serverFirstKeyOnlyProjection(true).end(); + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("SKIP SCAN ON 2 KEYS").table(idxSupplier).keyRanges(" ['S1'] - ['S5']") + .serverFirstKeyOnlyProjection(true).end(); } @Override @@ -142,10 +147,10 @@ protected void assertSkipMergeOptimizationPlan(Connection conn, String query) th 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) - .serverFirstKeyOnlyProjection(true).end(); + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT, SKIP MERGE */") + .scanType("FULL SCAN").table(order).serverWhereFilter("SERVER FILTER BY QUANTITY < 5000") + .end().subPlan(1).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(idxSupplier).serverFirstKeyOnlyProjection(true).end(); } @Override @@ -154,7 +159,8 @@ protected void assertSelfJoinPlan1(Connection conn, String query) throws Excepti 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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true).end(); } @@ -163,7 +169,8 @@ protected void assertSelfJoinPlan2(Connection conn, String query) throws Excepti String idxItem = getSchemaName() + ".idx_item"; assertPlan(conn, query).scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true) .serverSortedBy("[\"I1.0:NAME\", \"I2.0:NAME\"]").clientSortAlgo("CLIENT MERGE SORT") - .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("FULL SCAN").table(idxItem).end(); } @@ -175,16 +182,17 @@ protected void assertStarJoinPlan(Connection conn, String query, boolean noStarJ 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) - .serverFirstKeyOnlyProjection(true).end().subPlan(1) - .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1").scanType("FULL SCAN").table(idxItem) - .serverFirstKeyOnlyProjection(true).end(); + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(idxCustomer).serverFirstKeyOnlyProjection(true).end() + .subPlan(1).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true).end(); } else { assertPlan(conn, query).scanType("FULL SCAN").table(idxItem) .serverFirstKeyOnlyProjection(true).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") + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD LEFT */") + .scanType("FULL SCAN").table(order).subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("FULL SCAN").table(idxCustomer).serverFirstKeyOnlyProjection(true).end().end(); } } @@ -199,13 +207,15 @@ protected void assertSubJoinPlan(Connection conn, String query) throws Exception .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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .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(); + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(idxItem).serverWhereFilter("SERVER FILTER BY \"NAME\" != 'T3'") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD LEFT */") + .scanType("FULL SCAN").table(supplier).end().end().end(); } @Override @@ -215,8 +225,8 @@ protected void assertSubqueryAggPlan1(Connection conn, String query) throws Exce 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) - .serverFirstKeyOnlyProjection(true).end(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true).end(); } @Override @@ -226,7 +236,8 @@ protected void assertSubqueryAggPlan2(Connection conn, String query) throws Exce 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)") + .subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT, SKIP MERGE */") .scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true).end(); } @@ -236,7 +247,8 @@ protected void assertSubqueryAggPlan3(Connection conn, String query) throws Exce String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); assertPlan(conn, query).scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true) .serverSortedBy("[O.Q DESC NULLS LAST, I.IID]").clientSortAlgo("CLIENT MERGE SORT") - .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("FULL SCAN").table(order) .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end(); @@ -248,8 +260,9 @@ protected void assertSubqueryAggPlan4(Connection conn, String query) throws Exce String order = getTableName(conn, JOIN_ORDER_TABLE_FULL_NAME); assertPlan(conn, query).scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true) .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\"]") + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD LEFT */") + .scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end(); } @@ -262,12 +275,14 @@ protected void assertNestedSubqueriesPlan(Connection conn, String query) throws 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) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .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(); + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(idxItem).serverWhereFilter("SERVER FILTER BY \"NAME\" != 'T3'") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD LEFT */") + .scanType("FULL SCAN").table(supplier).end().end().end(); } @Override @@ -277,8 +292,9 @@ protected void assertJoinWithLimitPlan1(Connection conn, String query) throws Ex 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)") + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(idxItem).end().subPlan(1) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 1 /* HASH BUILD RIGHT, DELAYED EVALUATION */") .scanType("FULL SCAN").table(order).end(); } @@ -290,8 +306,10 @@ protected void assertJoinWithLimitPlan2(Connection conn, String query) throws Ex 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)") + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(idxItem).end().subPlan(1) + .abstractExplainPlan( + "PARALLEL INNER-JOIN TABLE 1 /* HASH BUILD RIGHT, DELAYED EVALUATION */") .scanType("FULL SCAN").table(order).end(); } @@ -306,7 +324,8 @@ protected void assertSetMaxRowsPlan(Connection conn, String query) throws Except statement.optimizeQuery().getExplainPlan().getPlanStepsAsAttributes(); assertPlan(attributes).scanType("FULL SCAN").table(idxItem).serverFirstKeyOnlyProjection(true) .clientRowLimit(4).joinScannerLimit(4L).subPlanCount(1).subPlan(0) - .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(order).end(); + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(order).end(); } @Override @@ -316,10 +335,10 @@ protected void assertJoinWithOffsetPlan1(Connection conn, String query) throws E 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(); + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(idxItem).end().subPlan(1) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 1 /* HASH BUILD RIGHT, DELAYED EVALUATION */") + .scanType("FULL SCAN").table(order).end(); } @Override @@ -331,8 +350,10 @@ protected void assertJoinWithOffsetPlan2(Connection conn, String query) throws E .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)") + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(idxItem).end().subPlan(1) + .abstractExplainPlan( + "PARALLEL INNER-JOIN TABLE 1 /* HASH BUILD RIGHT, DELAYED EVALUATION */") .scanType("FULL SCAN").table(order).end(); } 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 9f46bea9a8d..2ae8c4d42ff 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 @@ -77,9 +77,9 @@ protected void assertLeftJoinWithAggPlan1(Connection conn, String query) throws 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]").serverFirstKeyOnlyProjection(true) - .clientSortAlgo("CLIENT MERGE SORT").end(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end(); } @Override @@ -90,9 +90,9 @@ protected void assertLeftJoinWithAggPlan2(Connection conn, String query) throws 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]").serverFirstKeyOnlyProjection(true) - .clientSortAlgo("CLIENT MERGE SORT").end(); + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end(); } @Override @@ -102,7 +102,8 @@ protected void assertLeftJoinWithAggPlan3(Connection conn, String query) throws assertPlan(conn, query).scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true) .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(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(order).end(); } @Override @@ -114,7 +115,8 @@ protected void assertRightJoinWithAggPlan1(Connection conn, String query) throws .keyRanges(" [1]").serverFirstKeyOnlyProjection(true) .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(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD LEFT */") + .scanType("FULL SCAN").table(order).end(); } @Override @@ -124,7 +126,8 @@ protected void assertRightJoinWithAggPlan2(Connection conn, String query) throws assertPlan(conn, query).scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true) .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(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD LEFT */") + .scanType("FULL SCAN").table(order).end(); } @Override @@ -132,8 +135,8 @@ protected void assertJoinWithWildcardPlan(Connection conn, String query) throws 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(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(supplier).end(); } @Override @@ -145,9 +148,10 @@ protected void assertJoinPlanWithIndexPlan1(Connection conn, String query) throw assertPlan(conn, query).scanType("RANGE SCAN").table(itemIndex + "(" + item + ")") .keyRanges(" [1,'T1'] - [1,'T5']").serverFirstKeyOnlyProjection(true) .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']") - .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("RANGE SCAN").table(supplierIndex + "(" + supplier + ")") + .keyRanges(" [1,'S1'] - [1,'S5']").serverFirstKeyOnlyProjection(true) + .clientSortAlgo("CLIENT MERGE SORT").end(); } @Override @@ -158,9 +162,10 @@ protected void assertJoinPlanWithIndexPlan2(Connection conn, String query) throw 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']") - .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end(); + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("SKIP SCAN ON 2 KEYS").table(supplierIndex + "(" + supplier + ")") + .keyRanges(" [1,'S1'] - [1,'S5']").serverFirstKeyOnlyProjection(true) + .clientSortAlgo("CLIENT MERGE SORT").end(); } @Override @@ -173,10 +178,11 @@ protected void assertSkipMergeOptimizationPlan(Connection conn, String query) th 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)") + .subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT, 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]") + .end().subPlan(1).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1 /* HASH BUILD RIGHT */") + .scanType("RANGE SCAN").table(supplierIndex + "(" + supplier + ")").keyRanges(" [1]") .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end(); } @@ -186,7 +192,8 @@ protected void assertSelfJoinPlan1(Connection conn, String query) throws Excepti 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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end(); } @@ -199,7 +206,8 @@ protected void assertSelfJoinPlan2(Connection conn, String query) throws Excepti .keyRanges(" [1]").serverFirstKeyOnlyProjection(true) .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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") .clientSortAlgo("CLIENT MERGE SORT").end(); } @@ -214,21 +222,22 @@ protected void assertStarJoinPlan(Connection conn, String query, boolean noStarJ 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]") + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("RANGE SCAN").table(customerIndex + "(" + customer + ")").keyRanges(" [1]") .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end().subPlan(1) - .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1").scanType("RANGE SCAN") - .table(itemIndex + "(" + item + ")").keyRanges(" [1]").serverFirstKeyOnlyProjection(true) - .clientSortAlgo("CLIENT MERGE SORT").end(); + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1 /* HASH BUILD RIGHT */") + .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end(); } else { assertPlan(conn, query).scanType("RANGE SCAN").table(itemIndex + "(" + item + ")") .keyRanges(" [1]").serverFirstKeyOnlyProjection(true).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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD LEFT */") .scanType("FULL SCAN").table(order).subPlanCount(1).subPlan(0) - .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("RANGE SCAN") - .table(customerIndex + "(" + customer + ")").keyRanges(" [1]") + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("RANGE SCAN").table(customerIndex + "(" + customer + ")").keyRanges(" [1]") .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end().end(); } } @@ -244,13 +253,15 @@ protected void assertSubJoinPlan(Connection conn, String query) throws Exception .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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .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]") + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD LEFT */") .scanType("FULL SCAN").table(supplier).end().end().end(); } @@ -262,9 +273,9 @@ protected void assertSubqueryAggPlan1(Connection conn, String query) throws Exce 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]").serverFirstKeyOnlyProjection(true) - .clientSortAlgo("CLIENT MERGE SORT").end(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end(); } @Override @@ -275,7 +286,8 @@ protected void assertSubqueryAggPlan2(Connection conn, String query) throws Exce 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)") + .subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT, SKIP MERGE */") .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") .serverFirstKeyOnlyProjection(true).clientSortAlgo("CLIENT MERGE SORT").end(); } @@ -288,7 +300,8 @@ protected void assertSubqueryAggPlan3(Connection conn, String query) throws Exce assertPlan(conn, query).scanType("RANGE SCAN").table(itemIndex + "(" + item + ")") .keyRanges(" [1]").serverFirstKeyOnlyProjection(true) .serverSortedBy("[O.Q DESC NULLS LAST, I.IID]").clientSortAlgo("CLIENT MERGE SORT") - .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("FULL SCAN").table(order) .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end(); @@ -302,7 +315,8 @@ protected void assertSubqueryAggPlan4(Connection conn, String query) throws Exce assertPlan(conn, query).scanType("RANGE SCAN").table(itemIndex + "(" + item + ")") .keyRanges(" [1]").serverFirstKeyOnlyProjection(true).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) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD LEFT */") + .scanType("FULL SCAN").table(order) .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end(); } @@ -317,12 +331,14 @@ protected void assertNestedSubqueriesPlan(Connection conn, String query) throws 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) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .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]") + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD LEFT */") .scanType("FULL SCAN").table(supplier).end().end().end(); } @@ -334,9 +350,10 @@ protected void assertJoinWithLimitPlan1(Connection conn, String query) throws Ex 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)") + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .clientSortAlgo("CLIENT MERGE SORT").end().subPlan(1) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 1 /* HASH BUILD RIGHT, DELAYED EVALUATION */") .scanType("FULL SCAN").table(order).end(); } @@ -349,9 +366,11 @@ protected void assertJoinWithLimitPlan2(Connection conn, String query) throws Ex 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)") + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .clientSortAlgo("CLIENT MERGE SORT").end().subPlan(1) + .abstractExplainPlan( + "PARALLEL INNER-JOIN TABLE 1 /* HASH BUILD RIGHT, DELAYED EVALUATION */") .scanType("FULL SCAN").table(order).end(); } @@ -370,7 +389,8 @@ protected void assertSetMaxRowsPlan(Connection conn, String query) throws Except .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(); + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(order).end(); } @Override @@ -381,9 +401,10 @@ protected void assertJoinWithOffsetPlan1(Connection conn, String query) throws E 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)") + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .clientSortAlgo("CLIENT MERGE SORT").end().subPlan(1) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 1 /* HASH BUILD RIGHT, DELAYED EVALUATION */") .scanType("FULL SCAN").table(order).end(); } @@ -397,9 +418,11 @@ protected void assertJoinWithOffsetPlan2(Connection conn, String query) throws E .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)") + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("RANGE SCAN").table(itemIndex + "(" + item + ")").keyRanges(" [1]") + .clientSortAlgo("CLIENT MERGE SORT").end().subPlan(1) + .abstractExplainPlan( + "PARALLEL INNER-JOIN TABLE 1 /* HASH BUILD RIGHT, DELAYED EVALUATION */") .scanType("FULL SCAN").table(order).end(); } @@ -442,7 +465,8 @@ public void testJoinWithLocalIndex() throws Exception { .serverMergeColumns("[0.PHONE]").serverFirstKeyOnlyProjection(true) .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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("RANGE SCAN").table(itemIndex + "(" + itemTable + ")") .keyRanges(" [1,*] - [1,'T6']").clientSortAlgo("CLIENT MERGE SORT").end(); @@ -460,7 +484,8 @@ public void testJoinWithLocalIndex() throws Exception { .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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("RANGE SCAN").table(itemIndex + "(" + itemTable + ")") .keyRanges(" [1,*] - [1,'T6']").clientSortAlgo("CLIENT MERGE SORT").end(); @@ -476,9 +501,9 @@ public void testJoinWithLocalIndex() throws Exception { .table(supplierIndex + "(" + supplierTable + ")").keyRanges(" [1,*] - [1,'S3']") .serverMergeColumns("[0.PHONE]").serverFirstKeyOnlyProjection(true) .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(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .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 4e338f31d57..d5bb67cd483 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 @@ -294,8 +294,9 @@ public void testJoinWithKeyRangeOptimization() throws Exception { 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(); + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(tempTableWithCompositePK).clientSortAlgo("CLIENT MERGE SORT") + .end(); // Two parts of PK but only one leading part query = @@ -319,8 +320,9 @@ public void testJoinWithKeyRangeOptimization() throws Exception { 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(); + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(tempTableWithCompositePK).clientSortAlgo("CLIENT MERGE SORT") + .end(); // Two leading parts of PK query = @@ -354,7 +356,8 @@ public void testJoinWithKeyRangeOptimization() throws Exception { .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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("FULL SCAN").table(tempTableWithCompositePK).clientSortAlgo("CLIENT MERGE SORT") .end(); @@ -390,7 +393,8 @@ public void testJoinWithKeyRangeOptimization() throws Exception { .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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("FULL SCAN").table(tempTableWithCompositePK).clientSortAlgo("CLIENT MERGE SORT") .end(); } finally { @@ -785,7 +789,7 @@ public void testBug2894() throws Exception { .serverFirstKeyOnlyProjection(true) .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)") + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT, SKIP MERGE */") .scanType("SKIP SCAN ON 2 RANGES").table(t[i]).keyRanges(innerKeyRanges) .serverFirstKeyOnlyProjection(true) .serverWhereFilter("SERVER FILTER BY SRC_LOCATION = DST_LOCATION") 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 87da5547daf..337adbf7f52 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 @@ -62,7 +62,8 @@ protected void assertLeftJoinWithAggPlan1(Connection conn, String query) throws 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(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(item).end(); } @Override @@ -72,8 +73,8 @@ protected void assertLeftJoinWithAggPlan2(Connection conn, String query) throws 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).serverFirstKeyOnlyProjection(true).end(); + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true).end(); } @Override @@ -83,7 +84,8 @@ protected void assertLeftJoinWithAggPlan3(Connection conn, String query) throws assertPlan(conn, query).scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true) .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(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(order).end(); } @Override @@ -93,7 +95,8 @@ protected void assertRightJoinWithAggPlan1(Connection conn, String query) throws 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(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD LEFT */") + .scanType("FULL SCAN").table(order).end(); } @Override @@ -103,7 +106,8 @@ protected void assertRightJoinWithAggPlan2(Connection conn, String query) throws assertPlan(conn, query).scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true) .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(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD LEFT */") + .scanType("FULL SCAN").table(order).end(); } @Override @@ -111,8 +115,8 @@ protected void assertJoinWithWildcardPlan(Connection conn, String query) throws 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(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(supplier).end(); } @Override @@ -121,8 +125,9 @@ protected void assertJoinPlanWithIndexPlan1(Connection conn, String query) throw 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(); + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(supplier) + .serverWhereFilter("SERVER FILTER BY (NAME >= 'S1' AND NAME <= 'S5')").end(); } @Override @@ -131,7 +136,8 @@ protected void assertJoinPlanWithIndexPlan2(Connection conn, String query) throw 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) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(supplier) .serverWhereFilter("SERVER FILTER BY (NAME = 'S1' OR NAME = 'S5')").end(); } @@ -142,10 +148,11 @@ protected void assertSkipMergeOptimizationPlan(Connection conn, String query) th 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)") + .subPlanCount(2).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT, 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(); + .end().subPlan(1).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(supplier).end(); } @Override @@ -153,7 +160,8 @@ protected void assertSelfJoinPlan1(Connection conn, String query) throws Excepti 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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true).end(); } @@ -163,7 +171,8 @@ protected void assertSelfJoinPlan2(Connection conn, String query) throws Excepti 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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("FULL SCAN").table(item).end(); } @@ -175,17 +184,19 @@ protected void assertStarJoinPlan(Connection conn, String query, boolean noStarJ 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(); + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(customer).end().subPlan(1) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 1 /* HASH BUILD RIGHT */") + .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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD LEFT */") .scanType("FULL SCAN").table(order).subPlanCount(1).subPlan(0) - .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0").scanType("FULL SCAN").table(customer) - .end().end(); + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(customer).end().end(); } } @@ -199,13 +210,15 @@ protected void assertSubJoinPlan(Connection conn, String query) throws Exception .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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") .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(); + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(item).serverWhereFilter("SERVER FILTER BY NAME != 'T3'") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD LEFT */") + .scanType("FULL SCAN").table(supplier).end().end().end(); } @Override @@ -215,7 +228,8 @@ protected void assertSubqueryAggPlan1(Connection conn, String query) throws Exce 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(); + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(item).end(); } @Override @@ -225,7 +239,8 @@ protected void assertSubqueryAggPlan2(Connection conn, String query) throws Exce 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)") + .subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT, SKIP MERGE */") .scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true).end(); } @@ -235,7 +250,8 @@ protected void assertSubqueryAggPlan3(Connection conn, String query) throws Exce String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); assertPlan(conn, query).scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true) .serverSortedBy("[O.Q DESC NULLS LAST, I.IID]").clientSortAlgo("CLIENT MERGE SORT") - .subPlanCount(1).subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("FULL SCAN").table(order) .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end(); @@ -247,8 +263,9 @@ protected void assertSubqueryAggPlan4(Connection conn, String query) throws Exce String item = getTableName(conn, JOIN_ITEM_TABLE_FULL_NAME); assertPlan(conn, query).scanType("FULL SCAN").table(item).serverFirstKeyOnlyProjection(true) .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\"]") + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD LEFT */") + .scanType("FULL SCAN").table(order) + .serverAggregate("SERVER AGGREGATE INTO DISTINCT ROWS BY [\"item_id\"]") .clientSortAlgo("CLIENT MERGE SORT").end(); } @@ -261,12 +278,14 @@ protected void assertNestedSubqueriesPlan(Connection conn, String query) throws 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) + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .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(); + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(item).serverWhereFilter("SERVER FILTER BY NAME != 'T3'") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD LEFT */") + .scanType("FULL SCAN").table(supplier).end().end().end(); } @Override @@ -276,8 +295,9 @@ protected void assertJoinWithLimitPlan1(Connection conn, String query) throws Ex 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)") + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(item).end().subPlan(1) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 1 /* HASH BUILD RIGHT, DELAYED EVALUATION */") .scanType("FULL SCAN").table(order).end(); } @@ -289,8 +309,10 @@ protected void assertJoinWithLimitPlan2(Connection conn, String query) throws Ex 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)") + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(item).end().subPlan(1) + .abstractExplainPlan( + "PARALLEL INNER-JOIN TABLE 1 /* HASH BUILD RIGHT, DELAYED EVALUATION */") .scanType("FULL SCAN").table(order).end(); } @@ -306,7 +328,8 @@ protected void assertSetMaxRowsPlan(Connection conn, String query) throws Except 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(); + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(order).end(); } @Override @@ -316,10 +339,10 @@ protected void assertJoinWithOffsetPlan1(Connection conn, String query) throws E 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(); + .subPlan(0).abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(item).end().subPlan(1) + .abstractExplainPlan("PARALLEL LEFT-JOIN TABLE 1 /* HASH BUILD RIGHT, DELAYED EVALUATION */") + .scanType("FULL SCAN").table(order).end(); } @Override @@ -331,8 +354,10 @@ protected void assertJoinWithOffsetPlan2(Connection conn, String query) throws E .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)") + .abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("FULL SCAN").table(item).end().subPlan(1) + .abstractExplainPlan( + "PARALLEL INNER-JOIN TABLE 1 /* HASH BUILD RIGHT, DELAYED EVALUATION */") .scanType("FULL SCAN").table(order).end(); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeAssertions.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeAssertions.java new file mode 100644 index 00000000000..e2a43291be4 --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeAssertions.java @@ -0,0 +1,212 @@ +/* + * 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.end2end.tpcds; + +import static org.junit.Assert.fail; + +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.apache.phoenix.query.explain.ExplainPlanTestUtil; +import org.apache.phoenix.query.explain.ExplainPlanTestUtil.ExplainPlanAssert; + +/** + * Result and EXPLAIN assertion helpers for the TPC-DS derived ITs. + *

+ * {@link #assertResult(Connection, String, String, String[][])} compares the full, ordered result + * set of {@code sql} against an embedded {@code expected} array captured from the deterministic + * {@link TPCDSLikeFixture}. Each adapted query carries a total {@code ORDER BY} so the row order is + * stable. Cell values are compared via {@link ResultSet#getString(int)}. + *

+ * Regenerating expected arrays. Run either IT with + * {@code -D}{@value #REGENERATE_PROPERTY}{@code =true}. In that mode {@code assertResult} prints a + * paste-ready literal to stdout for each query. Paste the printed blocks back into the IT and + * commit. + */ +public final class TPCDSLikeAssertions { + + public static final String REGENERATE_PROPERTY = "phoenix.tpcds.regenerate"; + + public static final String REGENERATE_ENV = "PHOENIX_TPCDS_REGENERATE"; + + private static final boolean REGENERATE = Boolean.getBoolean(REGENERATE_PROPERTY) + || "true".equalsIgnoreCase(System.getenv(REGENERATE_ENV)); + + private TPCDSLikeAssertions() { + } + + public static boolean isRegenerating() { + return REGENERATE; + } + + /** + * Execute {@code sql} and either assert its rows equal {@code expected} (normal mode) or print a + * paste-ready expected-array literal (regenerate mode). + * @param conn a Phoenix connection + * @param label the query label, e.g. {@code "Q03"}; only used to name the printed literal + * @param sql the query to run + * @param expected the embedded expected rows (ignored in capture mode) + * @return {@code true} if the call captured/printed instead of asserting (regenerate mode, or an + * empty {@code expected} that has not yet been populated); {@code false} if it asserted + */ + public static boolean assertResult(Connection conn, String label, String sql, String[][] expected) + throws SQLException { + List actual = run(conn, sql); + // Capture mode: either forced via the flag, or bootstrapping an as-yet-unpopulated query. + if (REGENERATE || expected == null || expected.length == 0) { + System.out.println((expected == null || expected.length == 0 + ? "// NOTE: " + label + " has no embedded expected rows yet; paste the block below.\n" + : "") + format(label, actual)); + return true; + } + String mismatch = diff(expected, actual); + if (mismatch != null) { + fail("Result mismatch for " + label + ": " + mismatch + "\nRe-run with -D" + + REGENERATE_PROPERTY + "=true (or env " + REGENERATE_ENV + + "=true) to capture the current output.\nFull query:\n" + sql); + } + return false; + } + + public static String captureLiteral(Connection conn, String label, String sql) + throws SQLException { + return format(label, run(conn, sql)); + } + + /** Begin EXPLAIN-plan assertions for {@code sql} (delegates to {@link ExplainPlanTestUtil}). */ + public static ExplainPlanAssert assertPlan(Connection conn, String sql) throws SQLException { + return ExplainPlanTestUtil.assertPlan(conn, sql); + } + + /** Assert the rendered EXPLAIN plan text for {@code sql} contains every {@code needle}. */ + public static void assertPlanContains(Connection conn, String sql, String... needles) + throws SQLException { + List steps = ExplainPlanTestUtil.getPlanSteps(conn, sql); + String text = String.join("\n", steps); + for (String needle : needles) { + if (!text.contains(needle)) { + fail("EXPLAIN plan missing expected fragment:\n expected to contain: " + needle + + "\nactual plan:\n" + text + "\nquery:\n" + sql); + } + } + } + + /** + * Assert the rendered EXPLAIN plan text for {@code sql} contains at least one of {@code needles}. + */ + public static void assertPlanContainsAny(Connection conn, String sql, String... needles) + throws SQLException { + List steps = ExplainPlanTestUtil.getPlanSteps(conn, sql); + String text = String.join("\n", steps); + for (String needle : needles) { + if (text.contains(needle)) { + return; + } + } + fail("EXPLAIN plan missing all expected fragments " + Arrays.toString(needles) + ":\n" + text + + "\nquery:\n" + sql); + } + + private static List run(Connection conn, String sql) throws SQLException { + List rows = new ArrayList<>(); + try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) { + ResultSetMetaData md = rs.getMetaData(); + int n = md.getColumnCount(); + while (rs.next()) { + String[] row = new String[n]; + for (int i = 1; i <= n; i++) { + row[i - 1] = canonical(rs.getObject(i)); + } + rows.add(row); + } + } + return rows; + } + + /** + * Canonicalize a JDBC value into a stable locale independent string. Numbers are normalized via + * {@link BigDecimal} with trailing zeros stripped (so {@code 37.00 -> "37"}, {@code 1081.50 -> + * "1081.5"}, {@code 0 -> "0"}). Everything else uses {@code toString()}. + */ + private static String canonical(Object value) { + if (value == null) { + return null; + } + if (value instanceof Number) { + BigDecimal b = new BigDecimal(value.toString()); + return b.signum() == 0 ? "0" : b.stripTrailingZeros().toPlainString(); + } + return value.toString(); + } + + private static String diff(String[][] expected, List actual) { + if (expected == null) { + expected = new String[0][]; + } + if (expected.length != actual.size()) { + return "row count expected=" + expected.length + " actual=" + actual.size(); + } + for (int r = 0; r < expected.length; r++) { + String[] e = expected[r]; + String[] a = actual.get(r); + if (e.length != a.length) { + return "row " + r + " column count expected=" + e.length + " actual=" + a.length; + } + for (int c = 0; c < e.length; c++) { + if (!eq(e[c], a[c])) { + return "row " + r + " col " + c + " expected=" + lit(e[c]) + " actual=" + lit(a[c]) + + "\n expectedRow=" + Arrays.toString(e) + "\n actualRow= " + Arrays.toString(a); + } + } + } + return null; + } + + private static boolean eq(String a, String b) { + return a == null ? b == null : a.equals(b); + } + + private static String lit(String s) { + return s == null ? "null" : "\"" + s + "\""; + } + + private static String format(String label, List rows) { + StringBuilder sb = new StringBuilder(); + sb.append("\n // ---- ").append(label).append(" (").append(rows.size()) + .append(" rows) ----\n"); + sb.append(" private static final String[][] ").append(label).append("_EXPECTED = {\n"); + for (String[] row : rows) { + sb.append(" { "); + for (int c = 0; c < row.length; c++) { + if (c > 0) { + sb.append(", "); + } + sb.append(lit(row[c])); + } + sb.append(" },\n"); + } + sb.append(" };\n"); + return sb.toString(); + } +} diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeBaseIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeBaseIT.java new file mode 100644 index 00000000000..cdeb2310e64 --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeBaseIT.java @@ -0,0 +1,103 @@ +/* + * 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.end2end.tpcds; + +import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Properties; +import org.apache.phoenix.end2end.ParallelStatsDisabledIT; +import org.apache.phoenix.util.PropertiesUtil; + +/** + * Common plumbing for the TPC-DS derived ITs. The suite is parameterized over the two schemas + * materialized by {@link TPCDSLikeFixture}. + *

+ * Adapted query constants are written against {@link TPCDSLikeFixture#SCHEMA}. + */ +public abstract class TPCDSLikeBaseIT extends ParallelStatsDisabledIT { + + protected final String schema; + protected final boolean noIndex; + + protected TPCDSLikeBaseIT(String label, String schema, boolean noIndex) { + this.schema = schema; + this.noIndex = noIndex; + } + + protected static Collection indexParameters() { + return Arrays.asList(new Object[][] { { "NO_INDEX", TPCDSLikeFixture.SCHEMA, true }, + { "GLOBAL_INDEX", TPCDSLikeFixture.SCHEMA_INDEXED, false } }); + } + + protected static void loadFixture() throws SQLException { + try (Connection conn = newConnection()) { + TPCDSLikeFixture.load(conn); + } + } + + protected static Connection newConnection() throws SQLException { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + return DriverManager.getConnection(getUrl(), props); + } + + /** Rewrite the canonical {@code TPCDS.} table prefix to the parameter's schema. */ + protected String sql(String base) { + if (TPCDSLikeFixture.SCHEMA.equals(schema)) { + return base; + } + return base.replace(TPCDSLikeFixture.SCHEMA + ".", schema + "."); + } + + private static final String[] NO_INDEXES = new String[0]; + + /** Assert a query's full result. */ + protected void check(String label, String baseSql, String[][] expected, String... markers) + throws SQLException { + check(label, baseSql, expected, markers, NO_INDEXES); + } + + /** + * As {@link #check(String, String, String[][], String...)}, but additionally asserts that the + * plan scans each covering index in {@code indexNames}. + */ + protected void check(String label, String baseSql, String[][] expected, String[] markers, + String[] indexNames) throws SQLException { + String resolved = sql(baseSql); + try (Connection conn = newConnection()) { + boolean captured = TPCDSLikeAssertions.assertResult(conn, label, resolved, expected); + if (captured) { + return; + } + if (markers.length > 0) { + TPCDSLikeAssertions.assertPlanContains(conn, resolved, markers); + } + if (!noIndex && indexNames.length > 0) { + String[] qualified = new String[indexNames.length]; + for (int i = 0; i < indexNames.length; i++) { + qualified[i] = schema + "." + indexNames[i]; + } + TPCDSLikeAssertions.assertPlanContains(conn, resolved, qualified); + } + } + } +} diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeCrossChannelIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeCrossChannelIT.java new file mode 100644 index 00000000000..7b32b98a91b --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeCrossChannelIT.java @@ -0,0 +1,238 @@ +/* + * 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.end2end.tpcds; + +import java.sql.SQLException; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.phoenix.end2end.ParallelStatsDisabledTest; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * TPC-DS derived integration tests, exercising unions and sort-merge joins. + *

+ * See {@link TPCDSLikeFixture} for the schema and the catalog of queries intentionally not ported, + * and {@link TPCDSLikeSingleChannelIT} for the single channel queries. + */ +@Category(ParallelStatsDisabledTest.class) +@RunWith(Parameterized.class) +public class TPCDSLikeCrossChannelIT extends TPCDSLikeBaseIT { + + public TPCDSLikeCrossChannelIT(String label, String schema, boolean noIndex) { + super(label, schema, noIndex); + } + + @Parameters(name = "{0}") + public static Collection params() { + return indexParameters(); + } + + @BeforeClass + public static synchronized void setup() throws SQLException { + loadFixture(); + } + + // TPC-DS Q56: total ext sales price by item across store/catalog/web for a color set and region. + static final String Q56 = "SELECT i_item_id, SUM(total_sales) agg_total_sales FROM (" + + " SELECT i.i_item_id i_item_id, SUM(ss.ss_ext_sales_price) total_sales" + + " FROM TPCDS.store_sales ss, TPCDS.date_dim d, TPCDS.customer_address ca, TPCDS.item i" + + " WHERE ss.ss_sold_date_sk = d.d_date_sk AND ss.ss_addr_sk = ca.ca_address_sk" + + " AND ss.ss_item_sk = i.i_item_sk AND i.i_color IN ('red', 'blue', 'green')" + + " AND d.d_year = 2000 AND ca.ca_gmt_offset = -5 GROUP BY i.i_item_id" + " UNION ALL" + + " SELECT i.i_item_id i_item_id, SUM(cs.cs_ext_sales_price) total_sales" + + " FROM TPCDS.catalog_sales cs, TPCDS.date_dim d, TPCDS.customer_address ca, TPCDS.item i" + + " WHERE cs.cs_sold_date_sk = d.d_date_sk AND cs.cs_ship_addr_sk = ca.ca_address_sk" + + " AND cs.cs_item_sk = i.i_item_sk AND i.i_color IN ('red', 'blue', 'green')" + + " AND d.d_year = 2000 AND ca.ca_gmt_offset = -5 GROUP BY i.i_item_id" + " UNION ALL" + + " SELECT i.i_item_id i_item_id, SUM(ws.ws_ext_sales_price) total_sales" + + " FROM TPCDS.web_sales ws, TPCDS.date_dim d, TPCDS.customer_address ca, TPCDS.item i" + + " WHERE ws.ws_sold_date_sk = d.d_date_sk AND ws.ws_ship_addr_sk = ca.ca_address_sk" + + " AND ws.ws_item_sk = i.i_item_sk AND i.i_color IN ('red', 'blue', 'green')" + + " AND d.d_year = 2000 AND ca.ca_gmt_offset = -5 GROUP BY i.i_item_id" + + " ) tmp GROUP BY i_item_id ORDER BY agg_total_sales, i_item_id LIMIT 100"; + + // TPC-DS Q60: cross-channel total ext sales price by item for a category set and region. + static final String Q60 = "SELECT i_item_id, SUM(total_sales) agg_total_sales FROM (" + + " SELECT i.i_item_id i_item_id, SUM(ss.ss_ext_sales_price) total_sales" + + " FROM TPCDS.store_sales ss, TPCDS.date_dim d, TPCDS.customer_address ca, TPCDS.item i" + + " WHERE ss.ss_sold_date_sk = d.d_date_sk AND ss.ss_addr_sk = ca.ca_address_sk" + + " AND ss.ss_item_sk = i.i_item_sk AND i.i_category IN ('Books', 'Music', 'Home')" + + " AND d.d_year = 2001 AND ca.ca_gmt_offset = -6 GROUP BY i.i_item_id" + " UNION ALL" + + " SELECT i.i_item_id i_item_id, SUM(cs.cs_ext_sales_price) total_sales" + + " FROM TPCDS.catalog_sales cs, TPCDS.date_dim d, TPCDS.customer_address ca, TPCDS.item i" + + " WHERE cs.cs_sold_date_sk = d.d_date_sk AND cs.cs_ship_addr_sk = ca.ca_address_sk" + + " AND cs.cs_item_sk = i.i_item_sk AND i.i_category IN ('Books', 'Music', 'Home')" + + " AND d.d_year = 2001 AND ca.ca_gmt_offset = -6 GROUP BY i.i_item_id" + " UNION ALL" + + " SELECT i.i_item_id i_item_id, SUM(ws.ws_ext_sales_price) total_sales" + + " FROM TPCDS.web_sales ws, TPCDS.date_dim d, TPCDS.customer_address ca, TPCDS.item i" + + " WHERE ws.ws_sold_date_sk = d.d_date_sk AND ws.ws_ship_addr_sk = ca.ca_address_sk" + + " AND ws.ws_item_sk = i.i_item_sk AND i.i_category IN ('Books', 'Music', 'Home')" + + " AND d.d_year = 2001 AND ca.ca_gmt_offset = -6 GROUP BY i.i_item_id" + + " ) tmp GROUP BY i_item_id ORDER BY agg_total_sales, i_item_id LIMIT 100"; + + // TPC-DS Q83: cross-channel return quantities by item. + static final String Q83 = "SELECT sr.item_id, sr.sr_item_qty, cr.cr_item_qty, wr.wr_item_qty" + + " FROM (SELECT i.i_item_id item_id, SUM(srt.sr_return_quantity) sr_item_qty" + + " FROM TPCDS.store_returns srt, TPCDS.item i, TPCDS.date_dim d" + + " WHERE srt.sr_item_sk = i.i_item_sk AND srt.sr_returned_date_sk = d.d_date_sk" + + " AND d.d_year IN (2000, 2001) GROUP BY i.i_item_id) sr" + + " JOIN (SELECT i.i_item_id item_id, SUM(crt.cr_return_quantity) cr_item_qty" + + " FROM TPCDS.catalog_returns crt, TPCDS.item i, TPCDS.date_dim d" + + " WHERE crt.cr_item_sk = i.i_item_sk AND crt.cr_returned_date_sk = d.d_date_sk" + + " AND d.d_year IN (2000, 2001) GROUP BY i.i_item_id) cr ON sr.item_id = cr.item_id" + + " JOIN (SELECT i.i_item_id item_id, SUM(wrt.wr_return_quantity) wr_item_qty" + + " FROM TPCDS.web_returns wrt, TPCDS.item i, TPCDS.date_dim d" + + " WHERE wrt.wr_item_sk = i.i_item_sk AND wrt.wr_returned_date_sk = d.d_date_sk" + + " AND d.d_year IN (2000, 2001) GROUP BY i.i_item_id) wr ON sr.item_id = wr.item_id" + + " ORDER BY sr.item_id LIMIT 100"; + + // TPC-DS Q97: store-only / catalog-only / both customer-item pairs via a FULL OUTER JOIN. + // The USE_SORT_MERGE_JOIN hint forces the sort-merge strategy Phoenix requires for FULL OUTER. + static final String Q97 = "SELECT /*+ USE_SORT_MERGE_JOIN */" + + " SUM(CASE WHEN ssci.customer_sk IS NOT NULL AND csci.customer_sk IS NULL" + + " THEN 1 ELSE 0 END) store_only," + + " SUM(CASE WHEN ssci.customer_sk IS NULL AND csci.customer_sk IS NOT NULL" + + " THEN 1 ELSE 0 END) catalog_only," + + " SUM(CASE WHEN ssci.customer_sk IS NOT NULL AND csci.customer_sk IS NOT NULL" + + " THEN 1 ELSE 0 END) store_and_catalog" + + " FROM (SELECT ss.ss_customer_sk customer_sk, ss.ss_item_sk item_sk" + + " FROM TPCDS.store_sales ss, TPCDS.date_dim d" + + " WHERE ss.ss_sold_date_sk = d.d_date_sk AND d.d_year = 2000" + + " GROUP BY ss.ss_customer_sk, ss.ss_item_sk) ssci" + + " FULL OUTER JOIN (SELECT cs.cs_bill_customer_sk customer_sk, cs.cs_item_sk item_sk" + + " FROM TPCDS.catalog_sales cs, TPCDS.date_dim d" + + " WHERE cs.cs_sold_date_sk = d.d_date_sk AND d.d_year = 2000" + + " GROUP BY cs.cs_bill_customer_sk, cs.cs_item_sk) csci" + + " ON ssci.customer_sk = csci.customer_sk AND ssci.item_sk = csci.item_sk LIMIT 100"; + + // TPC-DS Q88: counts of store sales in several time-of-day buckets, one per derived table, + // combined by cross join. + static final String Q88 = "SELECT * FROM" + + " (SELECT COUNT(*) h8 FROM TPCDS.store_sales ss, TPCDS.household_demographics hd," + + " TPCDS.time_dim t, TPCDS.store s" + + " WHERE ss.ss_sold_time_sk = t.t_time_sk AND ss.ss_hdemo_sk = hd.hd_demo_sk" + + " AND ss.ss_store_sk = s.s_store_sk AND t.t_hour = 8 AND hd.hd_dep_count >= 0" + + " AND s.s_company_id = 1) s1," + + " (SELECT COUNT(*) h10 FROM TPCDS.store_sales ss, TPCDS.household_demographics hd," + + " TPCDS.time_dim t, TPCDS.store s" + + " WHERE ss.ss_sold_time_sk = t.t_time_sk AND ss.ss_hdemo_sk = hd.hd_demo_sk" + + " AND ss.ss_store_sk = s.s_store_sk AND t.t_hour = 10 AND hd.hd_dep_count >= 0" + + " AND s.s_company_id = 1) s2," + + " (SELECT COUNT(*) h12 FROM TPCDS.store_sales ss, TPCDS.household_demographics hd," + + " TPCDS.time_dim t, TPCDS.store s" + + " WHERE ss.ss_sold_time_sk = t.t_time_sk AND ss.ss_hdemo_sk = hd.hd_demo_sk" + + " AND ss.ss_store_sk = s.s_store_sk AND t.t_hour = 12 AND hd.hd_dep_count >= 0" + + " AND s.s_company_id = 1) s3"; + + // TPC-DS Q1: customers whose store-return total exceeds 1.2x their store's average. + static final String Q01 = "SELECT c.c_customer_id" + + " FROM (SELECT sr.sr_customer_sk ctr_customer_sk, sr.sr_store_sk ctr_store_sk," + + " SUM(sr.sr_return_amt) ctr_total_return" + " FROM TPCDS.store_returns sr, TPCDS.date_dim d" + + " WHERE sr.sr_returned_date_sk = d.d_date_sk AND d.d_year = 2000" + + " GROUP BY sr.sr_customer_sk, sr.sr_store_sk) ctr1," + + " (SELECT x.ctr_store_sk ctr_store_sk, AVG(x.ctr_total_return) * 1.2 avg_return FROM" + + " (SELECT sr.sr_customer_sk ctr_customer_sk, sr.sr_store_sk ctr_store_sk," + + " SUM(sr.sr_return_amt) ctr_total_return" + " FROM TPCDS.store_returns sr, TPCDS.date_dim d" + + " WHERE sr.sr_returned_date_sk = d.d_date_sk AND d.d_year = 2000" + + " GROUP BY sr.sr_customer_sk, sr.sr_store_sk) x GROUP BY x.ctr_store_sk) ctr2," + + " TPCDS.store s, TPCDS.customer c" + + " WHERE ctr1.ctr_total_return > ctr2.avg_return AND s.s_store_sk = ctr1.ctr_store_sk" + + " AND ctr2.ctr_store_sk = ctr1.ctr_store_sk AND ctr1.ctr_customer_sk = c.c_customer_sk" + + " ORDER BY c.c_customer_id LIMIT 100"; + + private static final String[][] Q56_EXPECTED = + { { "ITEM0000000024", "47" }, { "ITEM0000000012", "237.5" }, { "ITEM0000000020", "758" }, + { "ITEM0000000002", "962" }, { "ITEM0000000018", "1081.5" }, { "ITEM0000000001", "1442.5" }, + { "ITEM0000000007", "1818" }, { "ITEM0000000008", "1957.5" }, { "ITEM0000000014", "2092" }, + { "ITEM0000000006", "2725" }, { "ITEM0000000013", "2854.5" }, { "ITEM0000000019", "5817" }, }; + private static final String[][] Q60_EXPECTED = { { "ITEM0000000001", "849" }, + { "ITEM0000000007", "897" }, { "ITEM0000000018", "1293.5" }, { "ITEM0000000013", "1650" }, + { "ITEM0000000019", "1784.5" }, { "ITEM0000000006", "2093.5" }, { "ITEM0000000024", "2345" }, + { "ITEM0000000020", "3031.5" }, { "ITEM0000000012", "3220" }, { "ITEM0000000002", "3226.5" }, + { "ITEM0000000014", "3287.5" }, { "ITEM0000000008", "3913.5" }, }; + private static final String[][] Q83_EXPECTED = + { { "ITEM0000000001", "17", "13", "11" }, { "ITEM0000000002", "20", "17", "10" }, + { "ITEM0000000003", "16", "12", "14" }, { "ITEM0000000004", "17", "12", "12" }, + { "ITEM0000000005", "13", "12", "13" }, { "ITEM0000000006", "18", "9", "7" }, + { "ITEM0000000007", "16", "15", "13" }, { "ITEM0000000008", "17", "9", "12" }, + { "ITEM0000000009", "23", "10", "12" }, { "ITEM0000000010", "16", "15", "17" }, + { "ITEM0000000011", "17", "13", "9" }, { "ITEM0000000012", "15", "14", "10" }, + { "ITEM0000000013", "12", "11", "5" }, { "ITEM0000000014", "18", "15", "10" }, + { "ITEM0000000015", "21", "16", "14" }, { "ITEM0000000016", "15", "15", "15" }, + { "ITEM0000000017", "19", "11", "12" }, { "ITEM0000000018", "14", "10", "11" }, + { "ITEM0000000019", "17", "11", "10" }, { "ITEM0000000020", "21", "8", "11" }, + { "ITEM0000000021", "19", "7", "11" }, { "ITEM0000000022", "19", "9", "11" }, + { "ITEM0000000023", "19", "13", "15" }, { "ITEM0000000024", "20", "15", "13" }, }; + private static final String[][] Q97_EXPECTED = { { "130", "82", "14" }, }; + private static final String[][] Q88_EXPECTED = { { "24", "24", "24" }, }; + private static final String[][] Q01_EXPECTED = { { "CUST000000000003" }, { "CUST000000000005" }, + { "CUST000000000006" }, { "CUST000000000009" }, { "CUST000000000014" }, { "CUST000000000014" }, + { "CUST000000000014" }, { "CUST000000000015" }, { "CUST000000000016" }, { "CUST000000000019" }, + { "CUST000000000020" }, { "CUST000000000021" }, { "CUST000000000023" }, { "CUST000000000023" }, + { "CUST000000000024" }, { "CUST000000000024" }, { "CUST000000000025" }, { "CUST000000000025" }, + { "CUST000000000026" }, { "CUST000000000027" }, { "CUST000000000029" }, }; + + /** For {@link TPCDSLikeExpectedRegenerator}. */ + public static Map queries() { + Map q = new LinkedHashMap<>(); + q.put("Q56", Q56); + q.put("Q60", Q60); + q.put("Q83", Q83); + q.put("Q97", Q97); + q.put("Q88", Q88); + q.put("Q01", Q01); + return q; + } + + @Test + public void testQ56() throws SQLException { + check("Q56", Q56, Q56_EXPECTED, "UNION ALL OVER 3 QUERIES"); + } + + @Test + public void testQ60() throws SQLException { + check("Q60", Q60, Q60_EXPECTED, "UNION ALL OVER 3 QUERIES"); + } + + @Test + public void testQ83() throws SQLException { + check("Q83", Q83, Q83_EXPECTED, "HASH BUILD"); + } + + @Test + public void testQ97() throws SQLException { + check("Q97", Q97, Q97_EXPECTED, new String[] { "SORT-MERGE-JOIN (FULL)" }, + new String[] { "SS_I", "CS_I" }); + } + + @Test + public void testQ88() throws SQLException { + check("Q88", Q88, Q88_EXPECTED, "HASH BUILD"); + } + + @Test + public void testQ01() throws SQLException { + check("Q01", Q01, Q01_EXPECTED, "HASH BUILD"); + } +} diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeFixture.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeFixture.java new file mode 100644 index 00000000000..c50cbc555aa --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeFixture.java @@ -0,0 +1,922 @@ +/* + * 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.end2end.tpcds; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Random; + +/** + * Shared deterministic TPC-DS derived fixture for {@link TPCDSLikeSingleChannelIT} and + * {@link TPCDSLikeCrossChannelIT}. + *

+ * The schema is a trimmed subset of the TPC-DS v4.0.0 schema, only the tables and columns touched + * by the adapted queries, with the following uniform Phoenix adaptation rules applied: + *

    + *
  • Surrogate keys ({@code *_sk}) become single-column {@code BIGINT NOT NULL PRIMARY KEY} + * columns on the dimension tables.
  • + *
  • Sales and returns fact tables use a composite primary key on + * {@code (*_item_sk, *_ticket_number)} or {@code *_order_number}. {@code inventory} uses + * {@code (inv_date_sk, inv_item_sk, inv_warehouse_sk)}.
  • + *
  • Fact tables are salted ({@code SALT_BUCKETS=4}).
  • + *
  • Foreign key columns are plain nullable columns. Referential consistency is guaranteed by this + * loader, not by the schema.
  • + *
+ *

+ * The data is generated with a fixed {@link Random} seed so that every adapted query has a stable + * reference answer. + *

+ * The fixture is materialized into two schemas that hold identical data: + *

    + *
  • {@link #SCHEMA} ({@value #SCHEMA}) -- base tables, no secondary indexes.
  • + *
  • {@link #SCHEMA_INDEXED} ({@value #SCHEMA_INDEXED}) -- the same, plus a covering global index + * per fact table.
  • + *
+ */ +public final class TPCDSLikeFixture { + + /** Base schema with no secondary indexes */ + public static final String SCHEMA = "TPCDS"; + /** Schema holding identical data plus a covering global index per fact table. */ + public static final String SCHEMA_INDEXED = "TPCDSI"; + + private static final long SEED = 42L; + + private static volatile boolean loaded = false; + + private TPCDSLikeFixture() { + } + + /** All base (dimension + fact) table short names, in dependency order, dimensions first. */ + static final String[] TABLES = + { "DATE_DIM", "TIME_DIM", "ITEM", "CUSTOMER_ADDRESS", "CUSTOMER_DEMOGRAPHICS", "INCOME_BAND", + "HOUSEHOLD_DEMOGRAPHICS", "CUSTOMER", "STORE", "CALL_CENTER", "CATALOG_PAGE", "WEB_SITE", + "WEB_PAGE", "WAREHOUSE", "SHIP_MODE", "PROMOTION", "REASON", "STORE_SALES", "STORE_RETURNS", + "CATALOG_SALES", "CATALOG_RETURNS", "WEB_SALES", "WEB_RETURNS", "INVENTORY" }; + + /** Create both schemas and populate them with deterministic data. */ + public static synchronized void load(Connection conn) throws SQLException { + if (loaded && tablesPopulated(conn)) { + return; + } + conn.setAutoCommit(false); + createTables(conn, SCHEMA); + createTables(conn, SCHEMA_INDEXED); + if (!tablesPopulated(conn)) { + loadData(conn, SCHEMA); + copyData(conn, SCHEMA, SCHEMA_INDEXED); + } + createIndexes(conn, SCHEMA_INDEXED); + conn.commit(); + loaded = true; + } + + /** + * Both schemas must carry data. A partial prior run or manual cleanup can leave the base schema + * populated while {@link #SCHEMA_INDEXED} is empty. + */ + private static boolean tablesPopulated(Connection conn) { + return rowsPresent(conn, SCHEMA) && rowsPresent(conn, SCHEMA_INDEXED); + } + + private static boolean rowsPresent(Connection conn, String s) { + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT COUNT(*) FROM " + s + ".STORE_SALES")) { + return rs.next() && rs.getInt(1) > 0; + } catch (SQLException e) { + return false; + } + } + + private static void createTables(Connection conn, String s) throws SQLException { + try (Statement st = conn.createStatement()) { + for (String ddl : ddl(s)) { + st.execute(ddl); + } + } + } + + private static void createIndexes(Connection conn, String s) throws SQLException { + try (Statement st = conn.createStatement()) { + for (String ddl : indexDdl(s)) { + st.execute(ddl); + } + } + } + + private static String[] ddl(String s) { + return new String[] { + // Dimensions + "CREATE TABLE IF NOT EXISTS " + s + ".DATE_DIM (" + "d_date_sk BIGINT NOT NULL PRIMARY KEY," + + "d_date DATE, d_year INTEGER, d_moy INTEGER, d_dom INTEGER, d_qoy INTEGER," + + "d_dow INTEGER, d_week_seq INTEGER, d_day_name VARCHAR(16), d_quarter_name VARCHAR(8))", + "CREATE TABLE IF NOT EXISTS " + s + ".TIME_DIM (" + "t_time_sk BIGINT NOT NULL PRIMARY KEY," + + "t_hour INTEGER, t_minute INTEGER, t_am_pm VARCHAR(2), t_shift VARCHAR(16)," + + "t_meal_time VARCHAR(16))", + "CREATE TABLE IF NOT EXISTS " + s + ".ITEM (" + "i_item_sk BIGINT NOT NULL PRIMARY KEY," + + "i_item_id VARCHAR(16), i_item_desc VARCHAR(100), i_current_price DECIMAL(7,2)," + + "i_brand_id INTEGER, i_brand VARCHAR(50), i_class_id INTEGER, i_class VARCHAR(50)," + + "i_category_id INTEGER, i_category VARCHAR(50), i_manufact_id INTEGER," + + "i_manufact VARCHAR(50), i_manager_id INTEGER, i_size VARCHAR(20), i_color VARCHAR(20)," + + "i_units VARCHAR(10), i_product_name VARCHAR(50))", + "CREATE TABLE IF NOT EXISTS " + s + ".CUSTOMER_ADDRESS (" + + "ca_address_sk BIGINT NOT NULL PRIMARY KEY, ca_city VARCHAR(60), ca_county VARCHAR(30)," + + "ca_state VARCHAR(2), ca_zip VARCHAR(10), ca_country VARCHAR(20)," + + "ca_gmt_offset DECIMAL(5,2), ca_street_number VARCHAR(10), ca_street_name VARCHAR(60))", + "CREATE TABLE IF NOT EXISTS " + s + ".CUSTOMER_DEMOGRAPHICS (" + + "cd_demo_sk BIGINT NOT NULL PRIMARY KEY, cd_gender VARCHAR(1)," + + "cd_marital_status VARCHAR(1), cd_education_status VARCHAR(20)," + + "cd_purchase_estimate INTEGER, cd_credit_rating VARCHAR(10), cd_dep_count INTEGER," + + "cd_dep_employed_count INTEGER, cd_dep_college_count INTEGER)", + "CREATE TABLE IF NOT EXISTS " + s + ".INCOME_BAND (" + + "ib_income_band_sk BIGINT NOT NULL PRIMARY KEY, ib_lower_bound INTEGER," + + "ib_upper_bound INTEGER)", + "CREATE TABLE IF NOT EXISTS " + s + ".HOUSEHOLD_DEMOGRAPHICS (" + + "hd_demo_sk BIGINT NOT NULL PRIMARY KEY, hd_income_band_sk BIGINT," + + "hd_buy_potential VARCHAR(15), hd_dep_count INTEGER, hd_vehicle_count INTEGER)", + "CREATE TABLE IF NOT EXISTS " + s + ".CUSTOMER (" + + "c_customer_sk BIGINT NOT NULL PRIMARY KEY, c_customer_id VARCHAR(16)," + + "c_current_cdemo_sk BIGINT, c_current_hdemo_sk BIGINT, c_current_addr_sk BIGINT," + + "c_first_name VARCHAR(20), c_last_name VARCHAR(30), c_birth_country VARCHAR(20)," + + "c_birth_year INTEGER, c_preferred_cust_flag VARCHAR(1), c_salutation VARCHAR(10)," + + "c_email_address VARCHAR(50))", + "CREATE TABLE IF NOT EXISTS " + s + ".STORE (" + "s_store_sk BIGINT NOT NULL PRIMARY KEY," + + "s_store_id VARCHAR(16), s_store_name VARCHAR(50), s_company_id INTEGER," + + "s_company_name VARCHAR(50), s_state VARCHAR(2), s_zip VARCHAR(10)," + + "s_gmt_offset DECIMAL(5,2), s_city VARCHAR(60), s_county VARCHAR(30)," + + "s_manager VARCHAR(40), s_number_employees INTEGER)", + "CREATE TABLE IF NOT EXISTS " + s + ".CALL_CENTER (" + + "cc_call_center_sk BIGINT NOT NULL PRIMARY KEY, cc_call_center_id VARCHAR(16)," + + "cc_name VARCHAR(50), cc_manager VARCHAR(40), cc_mkt_id INTEGER, cc_class VARCHAR(50)," + + "cc_company INTEGER)", + "CREATE TABLE IF NOT EXISTS " + s + ".CATALOG_PAGE (" + + "cp_catalog_page_sk BIGINT NOT NULL PRIMARY KEY, cp_catalog_page_id VARCHAR(16)," + + "cp_catalog_number INTEGER, cp_catalog_page_number INTEGER, cp_department VARCHAR(50))", + "CREATE TABLE IF NOT EXISTS " + s + ".WEB_SITE (" + "web_site_sk BIGINT NOT NULL PRIMARY KEY," + + "web_site_id VARCHAR(16), web_name VARCHAR(50), web_company_id INTEGER," + + "web_company_name VARCHAR(50))", + "CREATE TABLE IF NOT EXISTS " + s + ".WEB_PAGE (" + + "wp_web_page_sk BIGINT NOT NULL PRIMARY KEY, wp_web_page_id VARCHAR(16)," + + "wp_char_count INTEGER, wp_type VARCHAR(50))", + "CREATE TABLE IF NOT EXISTS " + s + ".WAREHOUSE (" + + "w_warehouse_sk BIGINT NOT NULL PRIMARY KEY, w_warehouse_id VARCHAR(16)," + + "w_warehouse_name VARCHAR(50), w_state VARCHAR(2), w_country VARCHAR(20)," + + "w_gmt_offset DECIMAL(5,2))", + "CREATE TABLE IF NOT EXISTS " + s + ".SHIP_MODE (" + + "sm_ship_mode_sk BIGINT NOT NULL PRIMARY KEY, sm_ship_mode_id VARCHAR(16)," + + "sm_type VARCHAR(30), sm_carrier VARCHAR(20))", + "CREATE TABLE IF NOT EXISTS " + s + ".PROMOTION (" + + "p_promo_sk BIGINT NOT NULL PRIMARY KEY, p_promo_id VARCHAR(16)," + + "p_channel_email VARCHAR(1), p_channel_event VARCHAR(1), p_channel_tv VARCHAR(1)," + + "p_channel_dmail VARCHAR(1), p_channel_catalog VARCHAR(1))", + "CREATE TABLE IF NOT EXISTS " + s + ".REASON (" + "r_reason_sk BIGINT NOT NULL PRIMARY KEY," + + "r_reason_id VARCHAR(16), r_reason_desc VARCHAR(100))", + // Facts + "CREATE TABLE IF NOT EXISTS " + s + ".STORE_SALES (" + "ss_item_sk BIGINT NOT NULL," + + "ss_ticket_number BIGINT NOT NULL, ss_sold_date_sk BIGINT, ss_sold_time_sk BIGINT," + + "ss_customer_sk BIGINT, ss_cdemo_sk BIGINT, ss_hdemo_sk BIGINT, ss_addr_sk BIGINT," + + "ss_store_sk BIGINT, ss_promo_sk BIGINT, ss_quantity INTEGER, ss_sales_price DECIMAL(7,2)," + + "ss_ext_sales_price DECIMAL(7,2), ss_ext_discount_amt DECIMAL(7,2)," + + "ss_list_price DECIMAL(7,2), ss_ext_list_price DECIMAL(7,2), ss_coupon_amt DECIMAL(7,2)," + + "ss_net_profit DECIMAL(7,2), ss_net_paid DECIMAL(7,2) " + + "CONSTRAINT pk PRIMARY KEY (ss_item_sk, ss_ticket_number)) SALT_BUCKETS=4", + "CREATE TABLE IF NOT EXISTS " + s + ".STORE_RETURNS (" + "sr_item_sk BIGINT NOT NULL," + + "sr_ticket_number BIGINT NOT NULL, sr_returned_date_sk BIGINT, sr_customer_sk BIGINT," + + "sr_cdemo_sk BIGINT, sr_hdemo_sk BIGINT, sr_addr_sk BIGINT, sr_store_sk BIGINT," + + "sr_reason_sk BIGINT, sr_return_quantity INTEGER, sr_return_amt DECIMAL(7,2)," + + "sr_net_loss DECIMAL(7,2), sr_fee DECIMAL(7,2) " + + "CONSTRAINT pk PRIMARY KEY (sr_item_sk, sr_ticket_number)) SALT_BUCKETS=4", + "CREATE TABLE IF NOT EXISTS " + s + ".CATALOG_SALES (" + "cs_item_sk BIGINT NOT NULL," + + "cs_order_number BIGINT NOT NULL, cs_sold_date_sk BIGINT, cs_bill_customer_sk BIGINT," + + "cs_bill_cdemo_sk BIGINT, cs_ship_customer_sk BIGINT, cs_ship_addr_sk BIGINT," + + "cs_call_center_sk BIGINT, cs_catalog_page_sk BIGINT, cs_warehouse_sk BIGINT," + + "cs_ship_mode_sk BIGINT, cs_promo_sk BIGINT, cs_quantity INTEGER," + + "cs_sales_price DECIMAL(7,2), cs_ext_sales_price DECIMAL(7,2)," + + "cs_ext_discount_amt DECIMAL(7,2), cs_list_price DECIMAL(7,2)," + + "cs_ext_list_price DECIMAL(7,2), cs_coupon_amt DECIMAL(7,2), cs_net_profit DECIMAL(7,2)," + + "cs_net_paid DECIMAL(7,2) " + + "CONSTRAINT pk PRIMARY KEY (cs_item_sk, cs_order_number)) SALT_BUCKETS=4", + "CREATE TABLE IF NOT EXISTS " + s + ".CATALOG_RETURNS (" + "cr_item_sk BIGINT NOT NULL," + + "cr_order_number BIGINT NOT NULL, cr_returned_date_sk BIGINT," + + "cr_returning_customer_sk BIGINT, cr_call_center_sk BIGINT, cr_catalog_page_sk BIGINT," + + "cr_warehouse_sk BIGINT, cr_reason_sk BIGINT, cr_return_quantity INTEGER," + + "cr_return_amount DECIMAL(7,2), cr_net_loss DECIMAL(7,2) " + + "CONSTRAINT pk PRIMARY KEY (cr_item_sk, cr_order_number)) SALT_BUCKETS=4", + "CREATE TABLE IF NOT EXISTS " + s + ".WEB_SALES (" + "ws_item_sk BIGINT NOT NULL," + + "ws_order_number BIGINT NOT NULL, ws_sold_date_sk BIGINT, ws_sold_time_sk BIGINT," + + "ws_bill_customer_sk BIGINT, ws_ship_customer_sk BIGINT, ws_ship_addr_sk BIGINT," + + "ws_web_site_sk BIGINT, ws_web_page_sk BIGINT, ws_warehouse_sk BIGINT," + + "ws_ship_mode_sk BIGINT, ws_promo_sk BIGINT, ws_quantity INTEGER," + + "ws_sales_price DECIMAL(7,2), ws_ext_sales_price DECIMAL(7,2)," + + "ws_ext_discount_amt DECIMAL(7,2), ws_list_price DECIMAL(7,2)," + + "ws_ext_list_price DECIMAL(7,2), ws_coupon_amt DECIMAL(7,2), ws_net_profit DECIMAL(7,2)," + + "ws_net_paid DECIMAL(7,2) " + + "CONSTRAINT pk PRIMARY KEY (ws_item_sk, ws_order_number)) SALT_BUCKETS=4", + "CREATE TABLE IF NOT EXISTS " + s + ".WEB_RETURNS (" + "wr_item_sk BIGINT NOT NULL," + + "wr_order_number BIGINT NOT NULL, wr_returned_date_sk BIGINT," + + "wr_returning_customer_sk BIGINT, wr_web_page_sk BIGINT, wr_reason_sk BIGINT," + + "wr_return_quantity INTEGER, wr_return_amt DECIMAL(7,2), wr_net_loss DECIMAL(7,2) " + + "CONSTRAINT pk PRIMARY KEY (wr_item_sk, wr_order_number)) SALT_BUCKETS=4", + "CREATE TABLE IF NOT EXISTS " + s + ".INVENTORY (" + "inv_date_sk BIGINT NOT NULL," + + "inv_item_sk BIGINT NOT NULL, inv_warehouse_sk BIGINT NOT NULL," + + "inv_quantity_on_hand INTEGER " + + "CONSTRAINT pk PRIMARY KEY (inv_date_sk, inv_item_sk, inv_warehouse_sk)) SALT_BUCKETS=4" }; + } + + private static String[] indexDdl(String s) { + String suffix = "_I"; + return new String[] { + "CREATE INDEX IF NOT EXISTS SS" + suffix + " ON " + s + + ".STORE_SALES (ss_sold_date_sk, ss_item_sk) INCLUDE (ss_store_sk, ss_customer_sk," + + " ss_quantity, ss_sales_price, ss_ext_sales_price, ss_ext_discount_amt, ss_list_price," + + " ss_net_profit, ss_net_paid)", + "CREATE INDEX IF NOT EXISTS CS" + suffix + " ON " + s + + ".CATALOG_SALES (cs_sold_date_sk, cs_item_sk) INCLUDE (cs_bill_customer_sk," + + " cs_warehouse_sk, cs_quantity, cs_sales_price, cs_ext_sales_price, cs_ext_discount_amt," + + " cs_list_price, cs_net_profit, cs_net_paid)", + "CREATE INDEX IF NOT EXISTS WS" + suffix + " ON " + s + + ".WEB_SALES (ws_sold_date_sk, ws_item_sk) INCLUDE (ws_bill_customer_sk, ws_warehouse_sk," + + " ws_quantity, ws_sales_price, ws_ext_sales_price, ws_ext_discount_amt, ws_list_price," + + " ws_net_profit, ws_net_paid)", + "CREATE INDEX IF NOT EXISTS INV" + suffix + " ON " + s + + ".INVENTORY (inv_item_sk, inv_date_sk) INCLUDE (inv_warehouse_sk, inv_quantity_on_hand)" }; + } + + static final int[] YEARS = { 2000, 2001 }; + static final String[] CATEGORIES = + { "Books", "Home", "Electronics", "Sports", "Jewelry", "Music" }; + static final String[] STATES = { "CA", "TX", "NY", "WA", "OR" }; + static final String[] GENDERS = { "M", "F" }; + static final String[] MARITAL = { "M", "S", "D", "W", "U" }; + static final String[] EDUCATION = + { "Primary", "Secondary", "College", "2 yr Degree", "4 yr Degree", "Advanced Degree" }; + static final String[] BUY_POTENTIAL = + { ">10000", "5001-10000", "1001-5000", "501-1000", "0-500", "Unknown" }; + static final String[] DAY_NAMES = + { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; + static final double[] GMT_OFFSETS = { -5, -6, -7, -8 }; + + private static final int N_ITEMS = 24; + private static final int N_CUSTOMERS = 30; + private static final int N_ADDR = 20; + private static final int N_CDEMO = 12; + private static final int N_HDEMO = 12; + private static final int N_STORES = 6; + private static final int N_CALL_CENTERS = 4; + private static final int N_CATALOG_PAGES = 6; + private static final int N_WEB_SITES = 4; + private static final int N_WEB_PAGES = 6; + private static final int N_WAREHOUSES = 4; + private static final int N_SHIP_MODES = 5; + private static final int N_PROMOS = 10; + private static final int N_REASONS = 6; + + // date_dim: 24 rows = 12 months x 2 years, d_date_sk = 1..24. + private static final int N_DATES = YEARS.length * 12; + private static final int N_TIMES = 12; + + private static void loadData(Connection conn, String s) throws SQLException { + Random rnd = new Random(SEED); + loadDateDim(conn, s); + loadTimeDim(conn, s); + loadItem(conn, s, rnd); + loadCustomerAddress(conn, s, rnd); + loadCustomerDemographics(conn, s); + loadIncomeBand(conn, s); + loadHouseholdDemographics(conn, s, rnd); + loadCustomer(conn, s, rnd); + loadStore(conn, s, rnd); + loadCallCenter(conn, s); + loadCatalogPage(conn, s); + loadWebSite(conn, s); + loadWebPage(conn, s); + loadWarehouse(conn, s, rnd); + loadShipMode(conn, s); + loadPromotion(conn, s, rnd); + loadReason(conn, s); + conn.commit(); + loadStoreSales(conn, s, rnd); + loadStoreReturns(conn, s, rnd); + loadCatalogSales(conn, s, rnd); + loadCatalogReturns(conn, s, rnd); + loadWebSales(conn, s, rnd); + loadWebReturns(conn, s, rnd); + loadInventory(conn, s, rnd); + conn.commit(); + } + + private static void copyData(Connection conn, String from, String to) throws SQLException { + try (Statement st = conn.createStatement()) { + for (String t : TABLES) { + st.executeUpdate("UPSERT INTO " + to + "." + t + " SELECT * FROM " + from + "." + t); + } + } + conn.commit(); + } + + private static void loadDateDim(Connection conn, String s) throws SQLException { + String sql = "UPSERT INTO " + s + ".DATE_DIM (d_date_sk, d_date, d_year, d_moy, d_dom, d_qoy," + + " d_dow, d_week_seq, d_day_name, d_quarter_name) VALUES (?,?,?,?,?,?,?,?,?,?)"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + int sk = 0; + for (int yi = 0; yi < YEARS.length; yi++) { + int year = YEARS[yi]; + for (int moy = 1; moy <= 12; moy++) { + sk++; + int dom = 15; + int qoy = (moy - 1) / 3 + 1; + int dow = (sk % 7); + int weekSeq = (year - 2000) * 52 + moy * 4; + ps.setLong(1, sk); + ps.setDate(2, java.sql.Date.valueOf(String.format("%04d-%02d-%02d", year, moy, dom))); + ps.setInt(3, year); + ps.setInt(4, moy); + ps.setInt(5, dom); + ps.setInt(6, qoy); + ps.setInt(7, dow); + ps.setInt(8, weekSeq); + ps.setString(9, DAY_NAMES[dow]); + ps.setString(10, year + "Q" + qoy); + ps.executeUpdate(); + } + } + } + } + + private static void loadTimeDim(Connection conn, String s) throws SQLException { + String sql = "UPSERT INTO " + s + ".TIME_DIM (t_time_sk, t_hour, t_minute, t_am_pm, t_shift," + + " t_meal_time) VALUES (?,?,?,?,?,?)"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 1; i <= N_TIMES; i++) { + int hour = (i * 2) % 24; + ps.setLong(1, i); + ps.setInt(2, hour); + ps.setInt(3, 0); + ps.setString(4, hour < 12 ? "AM" : "PM"); + ps.setString(5, hour < 8 ? "third" : hour < 16 ? "first" : "second"); + ps.setString(6, + hour >= 6 && hour < 10 ? "breakfast" + : hour >= 11 && hour < 14 ? "lunch" + : hour >= 17 && hour < 21 ? "dinner" + : null); + ps.executeUpdate(); + } + } + } + + private static void loadItem(Connection conn, String s, Random rnd) throws SQLException { + String sql = "UPSERT INTO " + s + ".ITEM (i_item_sk, i_item_id, i_item_desc, i_current_price," + + " i_brand_id, i_brand, i_class_id, i_class, i_category_id, i_category, i_manufact_id," + + " i_manufact, i_manager_id, i_size, i_color, i_units, i_product_name)" + + " VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + String[] sizes = { "small", "medium", "large", "petite", "economy", "N/A" }; + String[] colors = { "red", "blue", "green", "white", "black", "almond" }; + String[] units = { "Each", "Dozen", "Case", "Box", "Pallet" }; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 1; i <= N_ITEMS; i++) { + int catId = (i - 1) % CATEGORIES.length; + int classId = (i - 1) % 5 + 1; + int brandId = 1000 + ((i - 1) % 8); + int manufactId = (i - 1) % 5 + 1; + int managerId = (i - 1) % 10 + 1; + ps.setLong(1, i); + ps.setString(2, String.format("ITEM%010d", i)); + ps.setString(3, "Item description " + i); + ps.setBigDecimal(4, bd(10 + (i % 90) + 0.99)); + ps.setInt(5, brandId); + ps.setString(6, "brand#" + brandId); + ps.setInt(7, classId); + ps.setString(8, "class#" + classId); + ps.setInt(9, catId + 1); + ps.setString(10, CATEGORIES[catId]); + ps.setInt(11, manufactId); + ps.setString(12, "manufact#" + manufactId); + ps.setInt(13, managerId); + ps.setString(14, sizes[i % sizes.length]); + ps.setString(15, colors[i % colors.length]); + ps.setString(16, units[i % units.length]); + ps.setString(17, "product#" + i); + ps.executeUpdate(); + } + } + } + + private static void loadCustomerAddress(Connection conn, String s, Random rnd) + throws SQLException { + String sql = "UPSERT INTO " + s + ".CUSTOMER_ADDRESS (ca_address_sk, ca_city, ca_county," + + " ca_state, ca_zip, ca_country, ca_gmt_offset, ca_street_number, ca_street_name)" + + " VALUES (?,?,?,?,?,?,?,?,?)"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 1; i <= N_ADDR; i++) { + String state = STATES[(i - 1) % STATES.length]; + ps.setLong(1, i); + ps.setString(2, "City" + ((i - 1) % 7)); + ps.setString(3, "County" + ((i - 1) % 4)); + ps.setString(4, state); + ps.setString(5, String.format("%05d", 10000 + i)); + ps.setString(6, "United States"); + ps.setBigDecimal(7, bd(GMT_OFFSETS[(i - 1) % GMT_OFFSETS.length])); + ps.setString(8, Integer.toString(100 + i)); + ps.setString(9, "Street " + i); + ps.executeUpdate(); + } + } + } + + private static void loadCustomerDemographics(Connection conn, String s) throws SQLException { + String sql = "UPSERT INTO " + s + ".CUSTOMER_DEMOGRAPHICS (cd_demo_sk, cd_gender," + + " cd_marital_status, cd_education_status, cd_purchase_estimate, cd_credit_rating," + + " cd_dep_count, cd_dep_employed_count, cd_dep_college_count) VALUES (?,?,?,?,?,?,?,?,?)"; + String[] credit = { "Low Risk", "Good", "High Risk", "Unknown" }; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 1; i <= N_CDEMO; i++) { + ps.setLong(1, i); + ps.setString(2, GENDERS[(i - 1) % GENDERS.length]); + ps.setString(3, MARITAL[(i - 1) % MARITAL.length]); + ps.setString(4, EDUCATION[(i - 1) % EDUCATION.length]); + ps.setInt(5, 500 * (((i - 1) % 6) + 1)); + ps.setString(6, credit[(i - 1) % credit.length]); + ps.setInt(7, (i - 1) % 4); + ps.setInt(8, (i - 1) % 3); + ps.setInt(9, (i - 1) % 2); + ps.executeUpdate(); + } + } + } + + private static void loadIncomeBand(Connection conn, String s) throws SQLException { + String sql = "UPSERT INTO " + s + ".INCOME_BAND (ib_income_band_sk, ib_lower_bound," + + " ib_upper_bound) VALUES (?,?,?)"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 1; i <= 10; i++) { + ps.setLong(1, i); + ps.setInt(2, (i - 1) * 10000); + ps.setInt(3, i * 10000); + ps.executeUpdate(); + } + } + } + + private static void loadHouseholdDemographics(Connection conn, String s, Random rnd) + throws SQLException { + String sql = "UPSERT INTO " + s + ".HOUSEHOLD_DEMOGRAPHICS (hd_demo_sk, hd_income_band_sk," + + " hd_buy_potential, hd_dep_count, hd_vehicle_count) VALUES (?,?,?,?,?)"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 1; i <= N_HDEMO; i++) { + ps.setLong(1, i); + ps.setLong(2, ((i - 1) % 10) + 1); + ps.setString(3, BUY_POTENTIAL[(i - 1) % BUY_POTENTIAL.length]); + ps.setInt(4, (i - 1) % 5); + ps.setInt(5, (i - 1) % 4); + ps.executeUpdate(); + } + } + } + + private static void loadCustomer(Connection conn, String s, Random rnd) throws SQLException { + String sql = "UPSERT INTO " + s + ".CUSTOMER (c_customer_sk, c_customer_id, c_current_cdemo_sk," + + " c_current_hdemo_sk, c_current_addr_sk, c_first_name, c_last_name, c_birth_country," + + " c_birth_year, c_preferred_cust_flag, c_salutation, c_email_address)" + + " VALUES (?,?,?,?,?,?,?,?,?,?,?,?)"; + String[] countries = { "UNITED STATES", "CANADA", "MEXICO", "BRAZIL" }; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 1; i <= N_CUSTOMERS; i++) { + ps.setLong(1, i); + ps.setString(2, String.format("CUST%012d", i)); + ps.setLong(3, ((i - 1) % N_CDEMO) + 1); + ps.setLong(4, ((i - 1) % N_HDEMO) + 1); + ps.setLong(5, ((i - 1) % N_ADDR) + 1); + ps.setString(6, "First" + i); + ps.setString(7, "Last" + i); + ps.setString(8, countries[(i - 1) % countries.length]); + ps.setInt(9, 1950 + (i % 40)); + ps.setString(10, (i % 2 == 0) ? "Y" : "N"); + ps.setString(11, (i % 2 == 0) ? "Mr." : "Ms."); + ps.setString(12, "cust" + i + "@example.com"); + ps.executeUpdate(); + } + } + } + + private static void loadStore(Connection conn, String s, Random rnd) throws SQLException { + String sql = "UPSERT INTO " + s + ".STORE (s_store_sk, s_store_id, s_store_name, s_company_id," + + " s_company_name, s_state, s_zip, s_gmt_offset, s_city, s_county, s_manager," + + " s_number_employees) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 1; i <= N_STORES; i++) { + String state = STATES[(i - 1) % STATES.length]; + ps.setLong(1, i); + ps.setString(2, String.format("STORE%010d", i)); + ps.setString(3, "Store " + i); + ps.setInt(4, 1); + ps.setString(5, "company#1"); + ps.setString(6, state); + ps.setString(7, String.format("%05d", 20000 + i)); + ps.setBigDecimal(8, bd(GMT_OFFSETS[(i - 1) % GMT_OFFSETS.length])); + ps.setString(9, "City" + ((i - 1) % 7)); + ps.setString(10, "County" + ((i - 1) % 4)); + ps.setString(11, "Manager" + i); + ps.setInt(12, 100 + i * 10); + ps.executeUpdate(); + } + } + } + + private static void loadCallCenter(Connection conn, String s) throws SQLException { + String sql = "UPSERT INTO " + s + ".CALL_CENTER (cc_call_center_sk, cc_call_center_id, cc_name," + + " cc_manager, cc_mkt_id, cc_class, cc_company) VALUES (?,?,?,?,?,?,?)"; + String[] classes = { "small", "medium", "large" }; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 1; i <= N_CALL_CENTERS; i++) { + ps.setLong(1, i); + ps.setString(2, String.format("CC%014d", i)); + ps.setString(3, "Call Center " + i); + ps.setString(4, "CCManager" + i); + ps.setInt(5, (i - 1) % 3 + 1); + ps.setString(6, classes[(i - 1) % classes.length]); + ps.setInt(7, 1); + ps.executeUpdate(); + } + } + } + + private static void loadCatalogPage(Connection conn, String s) throws SQLException { + String sql = "UPSERT INTO " + s + ".CATALOG_PAGE (cp_catalog_page_sk, cp_catalog_page_id," + + " cp_catalog_number, cp_catalog_page_number, cp_department) VALUES (?,?,?,?,?)"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 1; i <= N_CATALOG_PAGES; i++) { + ps.setLong(1, i); + ps.setString(2, String.format("CP%014d", i)); + ps.setInt(3, (i - 1) % 3 + 1); + ps.setInt(4, i); + ps.setString(5, "DEPARTMENT"); + ps.executeUpdate(); + } + } + } + + private static void loadWebSite(Connection conn, String s) throws SQLException { + String sql = "UPSERT INTO " + s + ".WEB_SITE (web_site_sk, web_site_id, web_name," + + " web_company_id, web_company_name) VALUES (?,?,?,?,?)"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 1; i <= N_WEB_SITES; i++) { + ps.setLong(1, i); + ps.setString(2, String.format("WS%014d", i)); + ps.setString(3, "Web Site " + i); + ps.setInt(4, 1); + ps.setString(5, "company#1"); + ps.executeUpdate(); + } + } + } + + private static void loadWebPage(Connection conn, String s) throws SQLException { + String sql = "UPSERT INTO " + s + ".WEB_PAGE (wp_web_page_sk, wp_web_page_id, wp_char_count," + + " wp_type) VALUES (?,?,?,?)"; + String[] types = { "welcome", "protected", "feedback", "general", "ad", "order" }; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 1; i <= N_WEB_PAGES; i++) { + ps.setLong(1, i); + ps.setString(2, String.format("WP%014d", i)); + ps.setInt(3, 1000 + i * 100); + ps.setString(4, types[(i - 1) % types.length]); + ps.executeUpdate(); + } + } + } + + private static void loadWarehouse(Connection conn, String s, Random rnd) throws SQLException { + String sql = "UPSERT INTO " + s + ".WAREHOUSE (w_warehouse_sk, w_warehouse_id," + + " w_warehouse_name, w_state, w_country, w_gmt_offset) VALUES (?,?,?,?,?,?)"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 1; i <= N_WAREHOUSES; i++) { + ps.setLong(1, i); + ps.setString(2, String.format("WH%014d", i)); + ps.setString(3, "Warehouse " + i); + ps.setString(4, STATES[(i - 1) % STATES.length]); + ps.setString(5, "United States"); + ps.setBigDecimal(6, bd(GMT_OFFSETS[(i - 1) % GMT_OFFSETS.length])); + ps.executeUpdate(); + } + } + } + + private static void loadShipMode(Connection conn, String s) throws SQLException { + String sql = "UPSERT INTO " + s + ".SHIP_MODE (sm_ship_mode_sk, sm_ship_mode_id, sm_type," + + " sm_carrier) VALUES (?,?,?,?)"; + String[] types = { "EXPRESS", "NEXT DAY", "OVERNIGHT", "TWO DAY", "LIBRARY" }; + String[] carriers = { "DHL", "FEDEX", "UPS", "USPS", "ZHOU" }; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 1; i <= N_SHIP_MODES; i++) { + ps.setLong(1, i); + ps.setString(2, String.format("SM%014d", i)); + ps.setString(3, types[(i - 1) % types.length]); + ps.setString(4, carriers[(i - 1) % carriers.length]); + ps.executeUpdate(); + } + } + } + + private static void loadPromotion(Connection conn, String s, Random rnd) throws SQLException { + String sql = "UPSERT INTO " + s + ".PROMOTION (p_promo_sk, p_promo_id, p_channel_email," + + " p_channel_event, p_channel_tv, p_channel_dmail, p_channel_catalog) VALUES (?,?,?,?,?,?,?)"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 1; i <= N_PROMOS; i++) { + ps.setLong(1, i); + ps.setString(2, String.format("PROMO%010d", i)); + ps.setString(3, (i % 2 == 0) ? "Y" : "N"); + ps.setString(4, (i % 3 == 0) ? "Y" : "N"); + ps.setString(5, (i % 2 == 1) ? "Y" : "N"); + ps.setString(6, (i % 4 == 0) ? "Y" : "N"); + ps.setString(7, (i % 5 == 0) ? "Y" : "N"); + ps.executeUpdate(); + } + } + } + + private static void loadReason(Connection conn, String s) throws SQLException { + String sql = + "UPSERT INTO " + s + ".REASON (r_reason_sk, r_reason_id, r_reason_desc)" + " VALUES (?,?,?)"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int i = 1; i <= N_REASONS; i++) { + ps.setLong(1, i); + ps.setString(2, String.format("REASON%010d", i)); + ps.setString(3, "Reason " + i); + ps.executeUpdate(); + } + } + } + + private static void loadStoreSales(Connection conn, String s, Random rnd) throws SQLException { + String sql = "UPSERT INTO " + s + ".STORE_SALES (ss_item_sk, ss_ticket_number, ss_sold_date_sk," + + " ss_sold_time_sk, ss_customer_sk, ss_cdemo_sk, ss_hdemo_sk, ss_addr_sk, ss_store_sk," + + " ss_promo_sk, ss_quantity, ss_sales_price, ss_ext_sales_price, ss_ext_discount_amt," + + " ss_list_price, ss_ext_list_price, ss_coupon_amt, ss_net_profit, ss_net_paid)" + + " VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + long ticket = 0; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int item = 1; item <= N_ITEMS; item++) { + // Each item sold on a handful of dates, giving broad coverage of (year, moy). + for (int dateSk = 1; dateSk <= N_DATES; dateSk += 2) { + ticket++; + int qty = 1 + rnd.nextInt(20); + double salesPrice = 5 + rnd.nextInt(95) + 0.5; + double listPrice = salesPrice + rnd.nextInt(20); + long cust = 1 + ((item + dateSk) % N_CUSTOMERS); + ps.setLong(1, item); + ps.setLong(2, ticket); + ps.setLong(3, dateSk); + ps.setLong(4, 1 + (ticket % N_TIMES)); + ps.setLong(5, cust); + ps.setLong(6, 1 + ((cust - 1) % N_CDEMO)); + ps.setLong(7, 1 + ((cust - 1) % N_HDEMO)); + ps.setLong(8, 1 + ((cust - 1) % N_ADDR)); + ps.setLong(9, 1 + (int) (ticket % N_STORES)); + ps.setLong(10, 1 + (int) (ticket % N_PROMOS)); + ps.setInt(11, qty); + ps.setBigDecimal(12, bd(salesPrice)); + ps.setBigDecimal(13, bd(salesPrice * qty)); + ps.setBigDecimal(14, bd(rnd.nextInt(50))); + ps.setBigDecimal(15, bd(listPrice)); + ps.setBigDecimal(16, bd(listPrice * qty)); + ps.setBigDecimal(17, bd(rnd.nextInt(20))); + ps.setBigDecimal(18, bd((salesPrice - 3) * qty)); + ps.setBigDecimal(19, bd(salesPrice * qty)); + ps.executeUpdate(); + } + } + } + conn.commit(); + } + + private static void loadStoreReturns(Connection conn, String s, Random rnd) throws SQLException { + // Return every other store_sales row (even tickets), reusing its (item, ticket) so the natural + // join key lines up with store_sales. + String sql = "UPSERT INTO " + s + ".STORE_RETURNS (sr_item_sk, sr_ticket_number," + + " sr_returned_date_sk, sr_customer_sk, sr_cdemo_sk, sr_hdemo_sk, sr_addr_sk, sr_store_sk," + + " sr_reason_sk, sr_return_quantity, sr_return_amt, sr_net_loss, sr_fee)" + + " VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)"; + long ticket = 0; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int item = 1; item <= N_ITEMS; item++) { + for (int dateSk = 1; dateSk <= N_DATES; dateSk += 2) { + ticket++; + if (ticket % 2 != 0) { + continue; + } + long cust = 1 + ((item + dateSk) % N_CUSTOMERS); + int retDate = Math.min(N_DATES, dateSk + 1); + ps.setLong(1, item); + ps.setLong(2, ticket); + ps.setLong(3, retDate); + ps.setLong(4, cust); + ps.setLong(5, 1 + ((cust - 1) % N_CDEMO)); + ps.setLong(6, 1 + ((cust - 1) % N_HDEMO)); + ps.setLong(7, 1 + ((cust - 1) % N_ADDR)); + ps.setLong(8, 1 + (int) (ticket % N_STORES)); + ps.setLong(9, 1 + (int) (ticket % N_REASONS)); + ps.setInt(10, 1 + rnd.nextInt(5)); + ps.setBigDecimal(11, bd(5 + rnd.nextInt(50))); + ps.setBigDecimal(12, bd(rnd.nextInt(30))); + ps.setBigDecimal(13, bd(rnd.nextInt(10))); + ps.executeUpdate(); + } + } + } + conn.commit(); + } + + private static void loadCatalogSales(Connection conn, String s, Random rnd) throws SQLException { + String sql = "UPSERT INTO " + s + ".CATALOG_SALES (cs_item_sk, cs_order_number," + + " cs_sold_date_sk, cs_bill_customer_sk, cs_bill_cdemo_sk, cs_ship_customer_sk," + + " cs_ship_addr_sk, cs_call_center_sk, cs_catalog_page_sk, cs_warehouse_sk, cs_ship_mode_sk," + + " cs_promo_sk, cs_quantity, cs_sales_price, cs_ext_sales_price, cs_ext_discount_amt," + + " cs_list_price, cs_ext_list_price, cs_coupon_amt, cs_net_profit, cs_net_paid)" + + " VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + long order = 0; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int item = 1; item <= N_ITEMS; item++) { + for (int dateSk = 2; dateSk <= N_DATES; dateSk += 3) { + order++; + int qty = 1 + rnd.nextInt(20); + double salesPrice = 5 + rnd.nextInt(95) + 0.5; + double listPrice = salesPrice + rnd.nextInt(20); + long cust = 1 + ((item * 2 + dateSk) % N_CUSTOMERS); + ps.setLong(1, item); + ps.setLong(2, order); + ps.setLong(3, dateSk); + ps.setLong(4, cust); + ps.setLong(5, 1 + ((cust - 1) % N_CDEMO)); + ps.setLong(6, 1 + (cust % N_CUSTOMERS)); + ps.setLong(7, 1 + ((cust - 1) % N_ADDR)); + ps.setLong(8, 1 + (int) (order % N_CALL_CENTERS)); + ps.setLong(9, 1 + (int) (order % N_CATALOG_PAGES)); + ps.setLong(10, 1 + (int) (order % N_WAREHOUSES)); + ps.setLong(11, 1 + (int) (order % N_SHIP_MODES)); + ps.setLong(12, 1 + (int) (order % N_PROMOS)); + ps.setInt(13, qty); + ps.setBigDecimal(14, bd(salesPrice)); + ps.setBigDecimal(15, bd(salesPrice * qty)); + ps.setBigDecimal(16, bd(rnd.nextInt(50))); + ps.setBigDecimal(17, bd(listPrice)); + ps.setBigDecimal(18, bd(listPrice * qty)); + ps.setBigDecimal(19, bd(rnd.nextInt(20))); + ps.setBigDecimal(20, bd((salesPrice - 3) * qty)); + ps.setBigDecimal(21, bd(salesPrice * qty)); + ps.executeUpdate(); + } + } + } + conn.commit(); + } + + private static void loadCatalogReturns(Connection conn, String s, Random rnd) + throws SQLException { + String sql = "UPSERT INTO " + s + ".CATALOG_RETURNS (cr_item_sk, cr_order_number," + + " cr_returned_date_sk, cr_returning_customer_sk, cr_call_center_sk, cr_catalog_page_sk," + + " cr_warehouse_sk, cr_reason_sk, cr_return_quantity, cr_return_amount, cr_net_loss)" + + " VALUES (?,?,?,?,?,?,?,?,?,?,?)"; + long order = 0; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int item = 1; item <= N_ITEMS; item++) { + for (int dateSk = 2; dateSk <= N_DATES; dateSk += 3) { + order++; + if (order % 2 != 0) { + continue; + } + long cust = 1 + ((item * 2 + dateSk) % N_CUSTOMERS); + int retDate = Math.min(N_DATES, dateSk + 1); + ps.setLong(1, item); + ps.setLong(2, order); + ps.setLong(3, retDate); + ps.setLong(4, cust); + ps.setLong(5, 1 + (int) (order % N_CALL_CENTERS)); + ps.setLong(6, 1 + (int) (order % N_CATALOG_PAGES)); + ps.setLong(7, 1 + (int) (order % N_WAREHOUSES)); + ps.setLong(8, 1 + (int) (order % N_REASONS)); + ps.setInt(9, 1 + rnd.nextInt(5)); + ps.setBigDecimal(10, bd(5 + rnd.nextInt(50))); + ps.setBigDecimal(11, bd(rnd.nextInt(10))); + ps.executeUpdate(); + } + } + } + conn.commit(); + } + + private static void loadWebSales(Connection conn, String s, Random rnd) throws SQLException { + String sql = "UPSERT INTO " + s + ".WEB_SALES (ws_item_sk, ws_order_number, ws_sold_date_sk," + + " ws_sold_time_sk, ws_bill_customer_sk, ws_ship_customer_sk, ws_ship_addr_sk," + + " ws_web_site_sk, ws_web_page_sk, ws_warehouse_sk, ws_ship_mode_sk, ws_promo_sk," + + " ws_quantity, ws_sales_price, ws_ext_sales_price, ws_ext_discount_amt, ws_list_price," + + " ws_ext_list_price, ws_coupon_amt, ws_net_profit, ws_net_paid)" + + " VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + long order = 0; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int item = 1; item <= N_ITEMS; item++) { + for (int dateSk = 3; dateSk <= N_DATES; dateSk += 3) { + order++; + int qty = 1 + rnd.nextInt(20); + double salesPrice = 5 + rnd.nextInt(95) + 0.5; + double listPrice = salesPrice + rnd.nextInt(20); + long cust = 1 + ((item * 3 + dateSk) % N_CUSTOMERS); + ps.setLong(1, item); + ps.setLong(2, order); + ps.setLong(3, dateSk); + ps.setLong(4, 1 + (order % N_TIMES)); + ps.setLong(5, cust); + ps.setLong(6, 1 + (cust % N_CUSTOMERS)); + ps.setLong(7, 1 + ((cust - 1) % N_ADDR)); + ps.setLong(8, 1 + (int) (order % N_WEB_SITES)); + ps.setLong(9, 1 + (int) (order % N_WEB_PAGES)); + ps.setLong(10, 1 + (int) (order % N_WAREHOUSES)); + ps.setLong(11, 1 + (int) (order % N_SHIP_MODES)); + ps.setLong(12, 1 + (int) (order % N_PROMOS)); + ps.setInt(13, qty); + ps.setBigDecimal(14, bd(salesPrice)); + ps.setBigDecimal(15, bd(salesPrice * qty)); + ps.setBigDecimal(16, bd(rnd.nextInt(50))); + ps.setBigDecimal(17, bd(listPrice)); + ps.setBigDecimal(18, bd(listPrice * qty)); + ps.setBigDecimal(19, bd(rnd.nextInt(20))); + ps.setBigDecimal(20, bd((salesPrice - 3) * qty)); + ps.setBigDecimal(21, bd(salesPrice * qty)); + ps.executeUpdate(); + } + } + } + conn.commit(); + } + + private static void loadWebReturns(Connection conn, String s, Random rnd) throws SQLException { + String sql = "UPSERT INTO " + s + ".WEB_RETURNS (wr_item_sk, wr_order_number," + + " wr_returned_date_sk, wr_returning_customer_sk, wr_web_page_sk, wr_reason_sk," + + " wr_return_quantity, wr_return_amt, wr_net_loss) VALUES (?,?,?,?,?,?,?,?,?)"; + long order = 0; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + for (int item = 1; item <= N_ITEMS; item++) { + for (int dateSk = 3; dateSk <= N_DATES; dateSk += 3) { + order++; + if (order % 2 != 0) { + continue; + } + long cust = 1 + ((item * 3 + dateSk) % N_CUSTOMERS); + int retDate = Math.min(N_DATES, dateSk + 1); + ps.setLong(1, item); + ps.setLong(2, order); + ps.setLong(3, retDate); + ps.setLong(4, cust); + ps.setLong(5, 1 + (int) (order % N_WEB_PAGES)); + ps.setLong(6, 1 + (int) (order % N_REASONS)); + ps.setInt(7, 1 + rnd.nextInt(5)); + ps.setBigDecimal(8, bd(5 + rnd.nextInt(50))); + ps.setBigDecimal(9, bd(rnd.nextInt(10))); + ps.executeUpdate(); + } + } + } + conn.commit(); + } + + private static void loadInventory(Connection conn, String s, Random rnd) throws SQLException { + String sql = "UPSERT INTO " + s + ".INVENTORY (inv_date_sk, inv_item_sk, inv_warehouse_sk," + + " inv_quantity_on_hand) VALUES (?,?,?,?)"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + // Weekly-ish snapshots: every 3rd date, all items, all warehouses. + for (int dateSk = 1; dateSk <= N_DATES; dateSk += 3) { + for (int item = 1; item <= N_ITEMS; item++) { + for (int wh = 1; wh <= N_WAREHOUSES; wh++) { + ps.setLong(1, dateSk); + ps.setLong(2, item); + ps.setLong(3, wh); + ps.setInt(4, 50 + rnd.nextInt(600)); + ps.executeUpdate(); + } + } + } + } + conn.commit(); + } + + private static java.math.BigDecimal bd(double v) { + return new java.math.BigDecimal(v).setScale(2, java.math.RoundingMode.HALF_UP); + } +} diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeSingleChannelIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeSingleChannelIT.java new file mode 100644 index 00000000000..5c91e48bd7e --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeSingleChannelIT.java @@ -0,0 +1,395 @@ +/* + * 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.end2end.tpcds; + +import java.sql.SQLException; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.phoenix.end2end.ParallelStatsDisabledTest; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * TPC-DS derived integration tests over a single sales and returns channel. Each {@code @Test} + * adapts a public TPC-DS query to Phoenix, asserts the full ordered result, and asserts the + * rendered EXPLAIN plan. + */ +@Category(ParallelStatsDisabledTest.class) +@RunWith(Parameterized.class) +public class TPCDSLikeSingleChannelIT extends TPCDSLikeBaseIT { + + public TPCDSLikeSingleChannelIT(String label, String schema, boolean noIndex) { + super(label, schema, noIndex); + } + + @Parameters(name = "{0}") + public static Collection params() { + return indexParameters(); + } + + @BeforeClass + public static synchronized void setup() throws SQLException { + loadFixture(); + } + + // TPC-DS Q3: store sales discount by brand for a manufacturer/month. + static final String Q03 = "SELECT d.d_year, i.i_brand_id brand_id, i.i_brand brand," + + " SUM(ss.ss_ext_discount_amt) sum_agg" + + " FROM TPCDS.date_dim d, TPCDS.store_sales ss, TPCDS.item i" + + " WHERE d.d_date_sk = ss.ss_sold_date_sk AND ss.ss_item_sk = i.i_item_sk" + + " AND i.i_manufact_id = 1 AND d.d_moy = 11" + " GROUP BY d.d_year, i.i_brand, i.i_brand_id" + + " ORDER BY d.d_year, sum_agg DESC, brand_id LIMIT 100"; + + // TPC-DS Q7: average sale measures by item for a gender/marital/promo segment. + static final String Q07 = "SELECT i.i_item_id, AVG(ss.ss_quantity) agg1," + + " AVG(ss.ss_list_price) agg2, AVG(ss.ss_coupon_amt) agg3, AVG(ss.ss_sales_price) agg4" + + " FROM TPCDS.store_sales ss, TPCDS.customer_demographics cd, TPCDS.date_dim d," + + " TPCDS.item i, TPCDS.promotion p" + + " WHERE ss.ss_sold_date_sk = d.d_date_sk AND ss.ss_item_sk = i.i_item_sk" + + " AND ss.ss_cdemo_sk = cd.cd_demo_sk AND ss.ss_promo_sk = p.p_promo_sk" + + " AND cd.cd_gender = 'M' AND cd.cd_marital_status = 'S'" + + " AND (p.p_channel_email = 'N' OR p.p_channel_event = 'N') AND d.d_year = 2000" + + " GROUP BY i.i_item_id ORDER BY i.i_item_id LIMIT 100"; + + // TPC-DS Q42: store ext sales price by category for a manager/month/year. + static final String Q42 = + "SELECT d.d_year, i.i_category_id, i.i_category," + " SUM(ss.ss_ext_sales_price) sum_price" + + " FROM TPCDS.date_dim d, TPCDS.store_sales ss, TPCDS.item i" + + " WHERE d.d_date_sk = ss.ss_sold_date_sk AND ss.ss_item_sk = i.i_item_sk" + + " AND i.i_manager_id = 1 AND d.d_moy = 11 AND d.d_year = 2000" + + " GROUP BY d.d_year, i.i_category_id, i.i_category" + + " ORDER BY sum_price DESC, d.d_year, i.i_category_id, i.i_category LIMIT 100"; + + // TPC-DS Q52: store ext sales price by brand for a manager/month/year. + static final String Q52 = "SELECT d.d_year, i.i_brand_id brand_id, i.i_brand brand," + + " SUM(ss.ss_ext_sales_price) ext_price" + + " FROM TPCDS.date_dim d, TPCDS.store_sales ss, TPCDS.item i" + + " WHERE d.d_date_sk = ss.ss_sold_date_sk AND ss.ss_item_sk = i.i_item_sk" + + " AND i.i_manager_id = 1 AND d.d_moy = 11 AND d.d_year = 2000" + + " GROUP BY d.d_year, i.i_brand, i.i_brand_id" + + " ORDER BY d.d_year, ext_price DESC, brand_id LIMIT 100"; + + // TPC-DS Q55: store ext sales price by brand for a manager/month/year. + static final String Q55 = + "SELECT i.i_brand_id brand_id, i.i_brand brand," + " SUM(ss.ss_ext_sales_price) ext_price" + + " FROM TPCDS.date_dim d, TPCDS.store_sales ss, TPCDS.item i" + + " WHERE d.d_date_sk = ss.ss_sold_date_sk AND ss.ss_item_sk = i.i_item_sk" + + " AND i.i_manager_id = 2 AND d.d_moy = 11 AND d.d_year = 2001" + + " GROUP BY i.i_brand, i.i_brand_id ORDER BY ext_price DESC, brand_id LIMIT 100"; + + // TPC-DS Q43: store sales by day of week (CASE pivot) for a region/year. + static final String Q43 = "SELECT s.s_store_name, s.s_store_id," + + " SUM(CASE WHEN d.d_day_name = 'Sunday' THEN ss.ss_sales_price ELSE 0 END) sun_sales," + + " SUM(CASE WHEN d.d_day_name = 'Monday' THEN ss.ss_sales_price ELSE 0 END) mon_sales," + + " SUM(CASE WHEN d.d_day_name = 'Tuesday' THEN ss.ss_sales_price ELSE 0 END) tue_sales," + + " SUM(CASE WHEN d.d_day_name = 'Wednesday' THEN ss.ss_sales_price ELSE 0 END) wed_sales," + + " SUM(CASE WHEN d.d_day_name = 'Thursday' THEN ss.ss_sales_price ELSE 0 END) thu_sales," + + " SUM(CASE WHEN d.d_day_name = 'Friday' THEN ss.ss_sales_price ELSE 0 END) fri_sales," + + " SUM(CASE WHEN d.d_day_name = 'Saturday' THEN ss.ss_sales_price ELSE 0 END) sat_sales" + + " FROM TPCDS.date_dim d, TPCDS.store_sales ss, TPCDS.store s" + + " WHERE d.d_date_sk = ss.ss_sold_date_sk AND ss.ss_store_sk = s.s_store_sk" + + " AND s.s_gmt_offset = -5 AND d.d_year = 2000" + " GROUP BY s.s_store_name, s.s_store_id" + + " ORDER BY s.s_store_name, s.s_store_id, sun_sales, mon_sales, tue_sales, wed_sales," + + " thu_sales, fri_sales, sat_sales LIMIT 100"; + + // TPC-DS Q96: count of store sales in an hour band / demographic / company. + static final String Q96 = "SELECT COUNT(*) cnt" + + " FROM TPCDS.store_sales ss, TPCDS.household_demographics hd, TPCDS.time_dim t," + + " TPCDS.store s" + + " WHERE ss.ss_sold_time_sk = t.t_time_sk AND ss.ss_hdemo_sk = hd.hd_demo_sk" + + " AND ss.ss_store_sk = s.s_store_sk AND t.t_hour >= 8 AND hd.hd_dep_count >= 0" + + " AND s.s_company_id = 1 ORDER BY cnt LIMIT 100"; + + // TPC-DS Q19: brand revenue where customer zip differs from store zip. + static final String Q19 = "SELECT i.i_brand_id brand_id, i.i_brand brand, i.i_manufact_id," + + " i.i_manufact, SUM(ss.ss_ext_sales_price) ext_price" + + " FROM TPCDS.date_dim d, TPCDS.store_sales ss, TPCDS.item i, TPCDS.customer c," + + " TPCDS.customer_address ca, TPCDS.store s" + + " WHERE d.d_date_sk = ss.ss_sold_date_sk AND ss.ss_item_sk = i.i_item_sk" + + " AND ss.ss_customer_sk = c.c_customer_sk AND c.c_current_addr_sk = ca.ca_address_sk" + + " AND ss.ss_store_sk = s.s_store_sk AND i.i_manager_id = 1 AND d.d_moy = 11" + + " AND d.d_year = 2000 AND SUBSTR(ca.ca_zip, 1, 5) <> SUBSTR(s.s_zip, 1, 5)" + + " GROUP BY i.i_brand, i.i_brand_id, i.i_manufact_id, i.i_manufact" + + " ORDER BY ext_price DESC, i.i_brand, i.i_brand_id, i.i_manufact_id, i.i_manufact LIMIT 100"; + + // TPC-DS Q46: coupon amount and profit by customer/city for a demographic segment. + static final String Q46 = "SELECT c.c_last_name, c.c_first_name, ca.ca_city," + + " SUM(ss.ss_coupon_amt) amt, SUM(ss.ss_net_profit) profit" + + " FROM TPCDS.store_sales ss, TPCDS.date_dim d, TPCDS.store s," + + " TPCDS.household_demographics hd, TPCDS.customer_address ca, TPCDS.customer c" + + " WHERE ss.ss_sold_date_sk = d.d_date_sk AND ss.ss_store_sk = s.s_store_sk" + + " AND ss.ss_hdemo_sk = hd.hd_demo_sk AND ss.ss_addr_sk = ca.ca_address_sk" + + " AND ss.ss_customer_sk = c.c_customer_sk" + + " AND (hd.hd_dep_count = 2 OR hd.hd_vehicle_count = 1) AND d.d_year = 2000" + + " AND s.s_city IN ('City0', 'City1', 'City2', 'City3', 'City4')" + + " GROUP BY c.c_last_name, c.c_first_name, ca.ca_city" + + " ORDER BY c.c_last_name, c.c_first_name, ca.ca_city, amt, profit LIMIT 100"; + + // TPC-DS Q50: store return lag buckets by store (self-join on date_dim). + static final String Q50 = "SELECT s.s_store_name, s.s_store_id," + + " SUM(CASE WHEN (d2.d_date_sk - d1.d_date_sk <= 1) THEN 1 ELSE 0 END) days_le_1," + + " SUM(CASE WHEN (d2.d_date_sk - d1.d_date_sk > 1 AND d2.d_date_sk - d1.d_date_sk <= 3)" + + " THEN 1 ELSE 0 END) days_2_3," + + " SUM(CASE WHEN (d2.d_date_sk - d1.d_date_sk > 3) THEN 1 ELSE 0 END) days_gt_3" + + " FROM TPCDS.store_sales ss, TPCDS.store_returns sr, TPCDS.date_dim d1," + + " TPCDS.date_dim d2, TPCDS.store s" + + " WHERE ss.ss_ticket_number = sr.sr_ticket_number AND ss.ss_item_sk = sr.sr_item_sk" + + " AND ss.ss_sold_date_sk = d1.d_date_sk AND sr.sr_returned_date_sk = d2.d_date_sk" + + " AND ss.ss_store_sk = s.s_store_sk AND d2.d_year = 2000" + + " GROUP BY s.s_store_name, s.s_store_id ORDER BY s.s_store_name, s.s_store_id LIMIT 100"; + + // TPC-DS Q73: tickets per customer via a grouped derived table joined to customer. + static final String Q73 = "SELECT c.c_last_name, c.c_first_name, dj.ss_ticket_number, dj.cnt" + + " FROM (SELECT ss.ss_ticket_number ss_ticket_number, ss.ss_customer_sk ss_customer_sk," + + " COUNT(*) cnt FROM TPCDS.store_sales ss, TPCDS.date_dim d, TPCDS.store s," + + " TPCDS.household_demographics hd" + + " WHERE ss.ss_sold_date_sk = d.d_date_sk AND ss.ss_store_sk = s.s_store_sk" + + " AND ss.ss_hdemo_sk = hd.hd_demo_sk AND d.d_year = 2000 AND hd.hd_vehicle_count >= 0" + + " AND s.s_company_id = 1 GROUP BY ss.ss_ticket_number, ss.ss_customer_sk) dj," + + " TPCDS.customer c" + " WHERE dj.ss_customer_sk = c.c_customer_sk AND dj.cnt BETWEEN 1 AND 5" + + " ORDER BY dj.cnt DESC, c.c_last_name, c.c_first_name, dj.ss_ticket_number LIMIT 20"; + + // TPC-DS Q82: items in a price range with on-hand inventory that were also sold in store. + static final String Q82 = "SELECT i.i_item_id, i.i_item_desc, i.i_current_price" + + " FROM TPCDS.item i, TPCDS.inventory inv, TPCDS.date_dim d, TPCDS.store_sales ss" + + " WHERE i.i_current_price BETWEEN 20 AND 80 AND inv.inv_item_sk = i.i_item_sk" + + " AND d.d_date_sk = inv.inv_date_sk AND inv.inv_quantity_on_hand BETWEEN 100 AND 500" + + " AND i.i_manufact_id IN (1, 2, 3, 4, 5) AND inv.inv_item_sk = ss.ss_item_sk" + + " GROUP BY i.i_item_id, i.i_item_desc, i.i_current_price ORDER BY i.i_item_id LIMIT 100"; + + // TPC-DS Q21: inventory before/after a pivot date by warehouse/item (CASE pivot). + static final String Q21 = "SELECT w.w_warehouse_name, i.i_item_id," + + " SUM(CASE WHEN d.d_date_sk < 12 THEN inv.inv_quantity_on_hand ELSE 0 END) inv_before," + + " SUM(CASE WHEN d.d_date_sk >= 12 THEN inv.inv_quantity_on_hand ELSE 0 END) inv_after" + + " FROM TPCDS.inventory inv, TPCDS.warehouse w, TPCDS.item i, TPCDS.date_dim d" + + " WHERE i.i_item_sk = inv.inv_item_sk AND inv.inv_warehouse_sk = w.w_warehouse_sk" + + " AND inv.inv_date_sk = d.d_date_sk AND i.i_current_price BETWEEN 10 AND 100" + + " AND i.i_manufact_id IN (1, 2, 3) AND w.w_warehouse_sk IN (1, 2)" + + " GROUP BY w.w_warehouse_name, i.i_item_id" + + " ORDER BY w.w_warehouse_name, i.i_item_id LIMIT 100"; + + // TPC-DS Q26: average catalog sale measures by item for a gender/marital/promo segment. + static final String Q26 = "SELECT i.i_item_id, AVG(cs.cs_quantity) agg1," + + " AVG(cs.cs_list_price) agg2, AVG(cs.cs_coupon_amt) agg3, AVG(cs.cs_sales_price) agg4" + + " FROM TPCDS.catalog_sales cs, TPCDS.customer_demographics cd, TPCDS.date_dim d," + + " TPCDS.item i, TPCDS.promotion p" + + " WHERE cs.cs_sold_date_sk = d.d_date_sk AND cs.cs_item_sk = i.i_item_sk" + + " AND cs.cs_bill_cdemo_sk = cd.cd_demo_sk AND cs.cs_promo_sk = p.p_promo_sk" + + " AND cd.cd_gender = 'F' AND cd.cd_marital_status = 'M'" + + " AND (p.p_channel_email = 'N' OR p.p_channel_event = 'N') AND d.d_year = 2000" + + " GROUP BY i.i_item_id ORDER BY i.i_item_id LIMIT 100"; + + private static final String[][] Q03_EXPECTED = + { { "2000", "1002", "brand#1002", "41" }, { "2000", "1000", "brand#1000", "37" }, + { "2000", "1005", "brand#1005", "36" }, { "2000", "1007", "brand#1007", "18" }, + { "2000", "1004", "brand#1004", "5" }, { "2001", "1004", "brand#1004", "36" }, + { "2001", "1002", "brand#1002", "21" }, { "2001", "1007", "brand#1007", "18" }, + { "2001", "1000", "brand#1000", "11" }, { "2001", "1005", "brand#1005", "7" }, }; + private static final String[][] Q07_EXPECTED = { { "ITEM0000000001", "3", "73.5", "16", "57.5" }, + { "ITEM0000000003", "13", "30.5", "2", "27.5" }, { "ITEM0000000005", "7", "37.5", "8", "26.5" }, + { "ITEM0000000007", "8", "72.5", "1", "67.5" }, + { "ITEM0000000009", "14", "92.5", "10", "81.5" }, + { "ITEM0000000011", "2", "106.5", "15", "98.5" }, + { "ITEM0000000013", "5", "66.5", "10", "55.5" }, { "ITEM0000000015", "9", "34.5", "2", "15.5" }, + { "ITEM0000000017", "8", "51.5", "14", "49.5" }, }; + private static final String[][] Q42_EXPECTED = { { "2000", "5", "Jewelry", "748" }, + { "2000", "3", "Electronics", "591" }, { "2000", "1", "Books", "61" }, }; + private static final String[][] Q52_EXPECTED = { { "2000", "1002", "brand#1002", "748" }, + { "2000", "1004", "brand#1004", "591" }, { "2000", "1000", "brand#1000", "61" }, }; + private static final String[][] Q55_EXPECTED = { { "1001", "brand#1001", "456.5" }, + { "1003", "brand#1003", "257.5" }, { "1005", "brand#1005", "82.5" }, }; + private static final String[][] Q43_EXPECTED = + { { "Store 1", "STORE0000000001", "0", "0", "0", "0", "1638", "0", "0" }, + { "Store 5", "STORE0000000005", "1229", "0", "0", "0", "0", "0", "0" }, }; + private static final String[][] Q96_EXPECTED = { { "192" }, }; + private static final String[][] Q19_EXPECTED = + { { "1002", "brand#1002", "1", "manufact#1", "748" }, + { "1004", "brand#1004", "1", "manufact#1", "591" }, + { "1000", "brand#1000", "1", "manufact#1", "61" }, }; + private static final String[][] Q46_EXPECTED = { { "Last10", "First10", "City2", "45", "2338.5" }, + { "Last14", "First14", "City6", "48", "2291.5" }, + { "Last15", "First15", "City0", "29", "2357" }, { "Last18", "First18", "City3", "31", "2724" }, + { "Last2", "First2", "City1", "24", "632" }, { "Last20", "First20", "City5", "45", "3686" }, + { "Last22", "First22", "City1", "35", "2991" }, { "Last26", "First26", "City5", "57", "3988" }, + { "Last27", "First27", "City6", "62", "1133" }, { "Last3", "First3", "City2", "26", "908.5" }, + { "Last30", "First30", "City2", "27", "542" }, { "Last6", "First6", "City5", "42", "1937.5" }, + { "Last8", "First8", "City0", "24", "701.5" }, }; + private static final String[][] Q50_EXPECTED = { { "Store 1", "STORE0000000001", "24", "0", "0" }, + { "Store 3", "STORE0000000003", "24", "0", "0" }, + { "Store 5", "STORE0000000005", "24", "0", "0" }, }; + private static final String[][] Q73_EXPECTED = + { { "Last1", "First1", "222", "1" }, { "Last1", "First1", "245", "1" }, + { "Last1", "First1", "268", "1" }, { "Last10", "First10", "16", "1" }, + { "Last10", "First10", "39", "1" }, { "Last10", "First10", "62", "1" }, + { "Last10", "First10", "85", "1" }, { "Last11", "First11", "5", "1" }, + { "Last11", "First11", "28", "1" }, { "Last11", "First11", "51", "1" }, + { "Last11", "First11", "74", "1" }, { "Last11", "First11", "97", "1" }, + { "Last12", "First12", "17", "1" }, { "Last12", "First12", "40", "1" }, + { "Last12", "First12", "63", "1" }, { "Last12", "First12", "86", "1" }, + { "Last12", "First12", "109", "1" }, { "Last13", "First13", "6", "1" }, + { "Last13", "First13", "29", "1" }, { "Last13", "First13", "52", "1" }, }; + private static final String[][] Q82_EXPECTED = + { { "ITEM0000000010", "Item description 10", "20.99" }, + { "ITEM0000000011", "Item description 11", "21.99" }, + { "ITEM0000000012", "Item description 12", "22.99" }, + { "ITEM0000000013", "Item description 13", "23.99" }, + { "ITEM0000000014", "Item description 14", "24.99" }, + { "ITEM0000000015", "Item description 15", "25.99" }, + { "ITEM0000000016", "Item description 16", "26.99" }, + { "ITEM0000000017", "Item description 17", "27.99" }, + { "ITEM0000000018", "Item description 18", "28.99" }, + { "ITEM0000000019", "Item description 19", "29.99" }, + { "ITEM0000000020", "Item description 20", "30.99" }, + { "ITEM0000000021", "Item description 21", "31.99" }, + { "ITEM0000000022", "Item description 22", "32.99" }, + { "ITEM0000000023", "Item description 23", "33.99" }, + { "ITEM0000000024", "Item description 24", "34.99" }, }; + private static final String[][] Q21_EXPECTED = + { { "Warehouse 1", "ITEM0000000001", "1091", "1203" }, + { "Warehouse 1", "ITEM0000000002", "1053", "1257" }, + { "Warehouse 1", "ITEM0000000003", "1681", "1517" }, + { "Warehouse 1", "ITEM0000000006", "1631", "1011" }, + { "Warehouse 1", "ITEM0000000007", "1237", "1972" }, + { "Warehouse 1", "ITEM0000000008", "1187", "1271" }, + { "Warehouse 1", "ITEM0000000011", "1306", "1307" }, + { "Warehouse 1", "ITEM0000000012", "587", "1511" }, + { "Warehouse 1", "ITEM0000000013", "1212", "1372" }, + { "Warehouse 1", "ITEM0000000016", "1611", "1589" }, + { "Warehouse 1", "ITEM0000000017", "1242", "1589" }, + { "Warehouse 1", "ITEM0000000018", "1012", "1618" }, + { "Warehouse 1", "ITEM0000000021", "1582", "948" }, + { "Warehouse 1", "ITEM0000000022", "1074", "1872" }, + { "Warehouse 1", "ITEM0000000023", "1792", "1537" }, + { "Warehouse 2", "ITEM0000000001", "831", "1683" }, + { "Warehouse 2", "ITEM0000000002", "1462", "899" }, + { "Warehouse 2", "ITEM0000000003", "1873", "1837" }, + { "Warehouse 2", "ITEM0000000006", "1010", "1437" }, + { "Warehouse 2", "ITEM0000000007", "1107", "1515" }, + { "Warehouse 2", "ITEM0000000008", "1255", "1714" }, + { "Warehouse 2", "ITEM0000000011", "1073", "941" }, + { "Warehouse 2", "ITEM0000000012", "1936", "1490" }, + { "Warehouse 2", "ITEM0000000013", "1015", "2189" }, + { "Warehouse 2", "ITEM0000000016", "1331", "1388" }, + { "Warehouse 2", "ITEM0000000017", "1721", "1268" }, + { "Warehouse 2", "ITEM0000000018", "1434", "900" }, + { "Warehouse 2", "ITEM0000000021", "1306", "1858" }, + { "Warehouse 2", "ITEM0000000022", "946", "1806" }, + { "Warehouse 2", "ITEM0000000023", "1159", "1383" }, }; + private static final String[][] Q26_EXPECTED = { { "ITEM0000000003", "2", "109.5", "13", "92.5" }, + { "ITEM0000000006", "8", "77.5", "6", "68.5" }, + { "ITEM0000000009", "11", "76.5", "18", "57.5" }, { "ITEM0000000012", "16", "63.5", "9", "54" }, + { "ITEM0000000015", "6", "32.5", "6", "18.5" }, + { "ITEM0000000018", "18", "62.5", "11", "52.5" }, + { "ITEM0000000021", "18", "9.5", "13", "5.5" }, + { "ITEM0000000024", "17", "77.5", "12", "67.5" }, }; + + /** Label for {@link TPCDSLikeExpectedRegenerator}. */ + public static Map queries() { + Map q = new LinkedHashMap<>(); + q.put("Q03", Q03); + q.put("Q07", Q07); + q.put("Q42", Q42); + q.put("Q52", Q52); + q.put("Q55", Q55); + q.put("Q43", Q43); + q.put("Q96", Q96); + q.put("Q19", Q19); + q.put("Q46", Q46); + q.put("Q50", Q50); + q.put("Q73", Q73); + q.put("Q82", Q82); + q.put("Q21", Q21); + q.put("Q26", Q26); + return q; + } + + @Test + public void testQ03() throws SQLException { + check("Q03", Q03, Q03_EXPECTED, new String[] { "HASH BUILD" }, new String[] { "SS_I" }); + } + + @Test + public void testQ07() throws SQLException { + check("Q07", Q07, Q07_EXPECTED, "HASH BUILD"); + } + + @Test + public void testQ42() throws SQLException { + check("Q42", Q42, Q42_EXPECTED, new String[] { "HASH BUILD" }, new String[] { "SS_I" }); + } + + @Test + public void testQ52() throws SQLException { + check("Q52", Q52, Q52_EXPECTED, new String[] { "HASH BUILD" }, new String[] { "SS_I" }); + } + + @Test + public void testQ55() throws SQLException { + check("Q55", Q55, Q55_EXPECTED, new String[] { "HASH BUILD" }, new String[] { "SS_I" }); + } + + @Test + public void testQ43() throws SQLException { + check("Q43", Q43, Q43_EXPECTED, new String[] { "HASH BUILD" }, new String[] { "SS_I" }); + } + + @Test + public void testQ96() throws SQLException { + check("Q96", Q96, Q96_EXPECTED, "HASH BUILD"); + } + + @Test + public void testQ19() throws SQLException { + check("Q19", Q19, Q19_EXPECTED, new String[] { "HASH BUILD" }, new String[] { "SS_I" }); + } + + @Test + public void testQ46() throws SQLException { + check("Q46", Q46, Q46_EXPECTED, "HASH BUILD"); + } + + @Test + public void testQ50() throws SQLException { + check("Q50", Q50, Q50_EXPECTED, new String[] { "HASH BUILD" }, new String[] { "SS_I" }); + } + + @Test + public void testQ73() throws SQLException { + check("Q73", Q73, Q73_EXPECTED, "HASH BUILD"); + } + + @Test + public void testQ82() throws SQLException { + check("Q82", Q82, Q82_EXPECTED, new String[] { "HASH BUILD" }, new String[] { "INV_I" }); + } + + @Test + public void testQ21() throws SQLException { + check("Q21", Q21, Q21_EXPECTED, new String[] { "HASH BUILD" }, new String[] { "INV_I" }); + } + + @Test + public void testQ26() throws SQLException { + check("Q26", Q26, Q26_EXPECTED, "HASH BUILD"); + } +} 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 8100742e671..495363b3b5a 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 @@ -2552,14 +2552,16 @@ public void testFuncIndexUsage() throws SQLException { 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").serverFirstKeyOnlyProjection(true) .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']").serverFirstKeyOnlyProjection(true).end(); + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("RANGE SCAN").table("IDX").keyRanges(" ['foobar']") + .serverFirstKeyOnlyProjection(true).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").serverFirstKeyOnlyProjection(true) .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']").serverFirstKeyOnlyProjection(true).end(); + .subPlan(0).abstractExplainPlan("PARALLEL INNER-JOIN TABLE 0 /* HASH BUILD RIGHT */") + .scanType("RANGE SCAN").table("IDX").keyRanges(" ['foobar']") + .serverFirstKeyOnlyProjection(true).end(); } finally { conn.close(); } @@ -7215,7 +7217,8 @@ public void testUncoveredPhoenix6984() throws Exception { .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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("SKIP-SCAN-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("RANGE SCAN").table("I").keyRanges(" ['XXX']").serverFirstKeyOnlyProjection(true) .end(); } @@ -7244,7 +7247,8 @@ public void testUncoveredPhoenix6986() throws Exception { .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") + .subPlanCount(1).subPlan(0) + .abstractExplainPlan("SKIP-SCAN-JOIN TABLE 0 /* HASH BUILD RIGHT */") .scanType("RANGE SCAN").table("IDX_PHOENIX_6986").keyRanges(" ['XXX']") .serverFirstKeyOnlyProjection(true).end(); } diff --git a/phoenix-core/src/test/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeExpectedRegenerator.java b/phoenix-core/src/test/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeExpectedRegenerator.java new file mode 100644 index 00000000000..e6f424939c9 --- /dev/null +++ b/phoenix-core/src/test/java/org/apache/phoenix/end2end/tpcds/TPCDSLikeExpectedRegenerator.java @@ -0,0 +1,63 @@ +/* + * 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.end2end.tpcds; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Developer only tool that prints the embedded {@code