This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/master by this push:
new 9477645c97 Revert "PHOENIX-7879 Tests for EXPLAIN text and
ExplainPlanAttributes serialization compatibility (#2495)"
9477645c97 is described below
commit 9477645c97d4176706c1e6f2adc4c14022aca3d1
Author: Andrew Purtell <[email protected]>
AuthorDate: Mon Jun 8 08:47:23 2026 -0700
Revert "PHOENIX-7879 Tests for EXPLAIN text and ExplainPlanAttributes
serialization compatibility (#2495)"
This reverts commit 29fbbbcef084b15ef555d7b9674e92786eb97f88.
---
.../phoenix/compile/ExplainPlanAttributes.java | 13 -
.../compile/RegionLocationsListSerializer.java | 61 --
.../compile/ServerMergeColumnsSerializer.java | 59 --
.../org/apache/phoenix/schema/MetaDataClient.java | 6 +-
.../phoenix/query/explain/ExplainChangeRule.java | 54 --
.../query/explain/ExplainCompatibilityTest.java | 769 ---------------------
.../query/explain/ExplainJsonNormalizer.java | 71 --
.../phoenix/query/explain/ExplainOracle.java | 207 ------
.../query/explain/ExplainTextNormalizer.java | 68 --
9 files changed, 3 insertions(+), 1305 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 f4ed63f245..07f00f2b67 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,8 +17,6 @@
*/
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;
@@ -32,15 +30,6 @@ import org.apache.phoenix.schema.PColumn;
* 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;
@@ -310,12 +299,10 @@ public class ExplainPlanAttributes {
return rhsJoinQueryExplainPlan;
}
- @JsonSerialize(using = ServerMergeColumnsSerializer.class)
public Set<PColumn> getServerMergeColumns() {
return serverMergeColumns;
}
- @JsonSerialize(using = RegionLocationsListSerializer.class)
public List<HRegionLocation> 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
deleted file mode 100644
index 1ae1b77a53..0000000000
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/RegionLocationsListSerializer.java
+++ /dev/null
@@ -1,61 +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.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<HRegionLocation>} 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<List<HRegionLocation>> {
-
- private static final long serialVersionUID = 1L;
-
- @SuppressWarnings("unchecked")
- public RegionLocationsListSerializer() {
- super((Class<List<HRegionLocation>>) (Class<?>) List.class);
- }
-
- @Override
- public void serialize(List<HRegionLocation> 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
deleted file mode 100644
index 988c319bb8..0000000000
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ServerMergeColumnsSerializer.java
+++ /dev/null
@@ -1,59 +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.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<PColumn>} 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<Set<PColumn>> {
-
- private static final long serialVersionUID = 1L;
-
- @SuppressWarnings("unchecked")
- public ServerMergeColumnsSerializer() {
- super((Class<Set<PColumn>>) (Class<?>) Set.class);
- }
-
- @Override
- public void serialize(Set<PColumn> value, JsonGenerator gen,
SerializerProvider provider)
- throws IOException {
- List<String> 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 e70aba774f..22833e2494 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 class MetaDataClient {
/**
* 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/ExplainChangeRule.java
b/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainChangeRule.java
deleted file mode 100644
index a661bdca15..0000000000
---
a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainChangeRule.java
+++ /dev/null
@@ -1,54 +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.explain;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import java.util.List;
-
-/**
- * The {@link ExplainOracle} freezes today's EXPLAIN output as a set of golden
fixtures. Future
- * EXPLAIN-design PRs that intentionally change the output register an {@code
ExplainChangeRule}
- * that rewrites the (frozen) form into the new expected form, so every change
to the grammar is
- * explicit and reviewable.
- */
-public interface ExplainChangeRule {
-
- /**
- * Transform the golden plan steps text for the named case. The default
implementation returns
- * {@code goldenText} unchanged.
- * @param caseId the corpus case identifier (e.g. {@code "pointLookup"})
- * @param goldenText the line-oriented golden as currently expected (already
transformed by any
- * earlier rule in the chain)
- * @return the next-expected golden text (may be a new list or the same
instance)
- */
- default List<String> applyText(String caseId, List<String> goldenText) {
- return goldenText;
- }
-
- /**
- * Transform the golden JSON attributes tree for the named case. The default
implementation
- * returns {@code goldenJson} unchanged.
- * @param caseId the corpus case identifier
- * @param goldenJson the JSON attributes tree as currently expected (already
transformed by any
- * earlier rule in the chain)
- * @return the next-expected JSON tree (may be a mutated input or a new node)
- */
- default JsonNode applyJson(String caseId, JsonNode goldenJson) {
- return goldenJson;
- }
-}
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
deleted file mode 100644
index cd17176286..0000000000
---
a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainCompatibilityTest.java
+++ /dev/null
@@ -1,769 +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.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.ArrayList;
-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.
- * <p>
- * 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 <N>-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 <N>-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 <N>-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 <N>-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 <N>-WAY SKIP SCAN ON 6 RANGES OVER PTSDB"
- + " ['na1','a','2013-01-01'] - ['na3','b','2013-01-02']",
- " SERVER FILTER BY FIRST KEY ONLY"),
- 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 <N>-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 <N>-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 <N>-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 <N>-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 <N>-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 <N>-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 <N>-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 <N>-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 <N>-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 <N>-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 <N>-WAY RANGE SCAN OVER ATABLE
['00D000000000001']", "AND",
- " CLIENT PARALLEL <N>-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 <N>-WAY RANGE SCAN OVER ATABLE
['00D000000000001']",
- " PARALLEL INNER-JOIN TABLE 0", " CLIENT PARALLEL <N>-WAY
FULL SCAN OVER ATABLE",
- " DYNAMIC SERVER FILTER BY A.ORGANIZATION_ID IN
(B.ORGANIZATION_ID)"),
- // HashJoinPlan uses the List<String>-only ExplainPlan constructor,
which installs the
- // default attributes (all-null/empty). Freeze that baseline.
- defaultAttrs());
- }
-
- @Test
- 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 <N>-WAY FULL SCAN OVER ATABLE", "
SKIP-SCAN-JOIN TABLE 0",
- " CLIENT PARALLEL <N>-WAY FULL SCAN OVER ATABLE",
- " SERVER FILTER BY A_INTEGER = 1",
- " SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY
[ORGANIZATION_ID]",
- " DYNAMIC SERVER FILTER BY ATABLE.ORGANIZATION_ID IN ($1.$3)"),
- 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 <N>-WAY RANGE SCAN OVER ATABLE
['00D000000000001']",
- " CLIENT PARALLEL <N>-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 <N>-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 <N>-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 <N>-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 <N>-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 <N>-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 <N>-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 <N>-WAY RANGE SCAN OVER EO_MT_BASE ['tenant42']",
- " SERVER 1 ROW LIMIT", "CLIENT 1 ROW LIMIT"),
- attrs().put("iteratorTypeAndScanSize", "SERIAL
<N>-WAY").put("consistency", "STRONG")
- .put("explainScanType", "RANGE SCAN ").put("tableName", "EO_MT_BASE")
- .put("keyRanges", " ['tenant42']").put("serverRowLimit",
1).put("clientRowLimit", 1));
- }
-
- @Test
- public void testTextNormalizerCollapsesWayCount() {
- assertEquals(Collections.singletonList("CLIENT PARALLEL <N>-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 <N>-CHUNK PARALLEL <N>-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 <N>-CHUNK PARALLEL <N>-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 <N>-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 testTextNormalizerPreservesAllGrammar() {
- List<String> 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 <N>-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 <N>-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 <N>-WAY",
root.get("iteratorTypeAndScanSize").asText());
- assertEquals(0, root.get("numRegionLocationLookups").asInt());
- JsonNode nestedRhs = root.get("rhsJoinQueryExplainPlan");
- assertEquals("PARALLEL <N>-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<PColumn> 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 testIdentityRuleChainPasses() throws Exception {
- ExplainPlan plan = samplePlan("PARALLEL 4-WAY", "RANGE SCAN ");
- List<String> expectedText =
- text("CLIENT PARALLEL <N>-WAY RANGE SCAN OVER T", " SERVER FILTER BY
FIRST KEY ONLY");
- // samplePlan() sets only four attributes on the builder; consistency
stays null.
- ObjectNode expectedJson = defaultAttrs().put("iteratorTypeAndScanSize",
"PARALLEL <N>-WAY")
- .put("explainScanType", "RANGE SCAN ").put("tableName", "T")
- .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY");
- // Identity rule: returns inputs unchanged.
- new ExplainOracle(Collections.singletonList(new ExplainChangeRule() {
- })).verify("identity", plan, expectedText, expectedJson);
- }
-
- @Test
- public void testChangeRuleRewritesText() throws Exception {
- // Today's plan emits "SERVER FILTER BY FIRST KEY ONLY".
- ExplainPlanAttributes todayAttrs = new ExplainPlanAttributesBuilder()
- .setIteratorTypeAndScanSize("PARALLEL 1-WAY").setExplainScanType("FULL
SCAN ")
- .setTableName("T").setServerWhereFilter("SERVER FILTER BY FIRST KEY
ONLY").build();
- ExplainPlan todayPlan = new ExplainPlan(Arrays.asList("CLIENT PARALLEL
1-WAY FULL SCAN OVER T",
- " SERVER FILTER BY FIRST KEY ONLY"), todayAttrs);
- List<String> todayExpectedText =
- text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER T", " SERVER FILTER BY
FIRST KEY ONLY");
- // samplePlan-style builder leaves consistency null and key-ranges unset.
- ObjectNode todayExpectedJson =
defaultAttrs().put("iteratorTypeAndScanSize", "PARALLEL <N>-WAY")
- .put("explainScanType", "FULL SCAN ").put("tableName", "T")
- .put("serverWhereFilter", "SERVER FILTER BY FIRST KEY ONLY");
-
- // Sanity: with no rules, today's plan compares against today's expected.
- new ExplainOracle().verify("today", todayPlan, todayExpectedText,
todayExpectedJson);
-
- // Tomorrow: design renames the marker. Future plan emits the new form;
the embedded baseline
- // stays unchanged ("today's" expected); a rule transforms the baseline
into the new shape and
- // the comparison passes.
- ExplainPlanAttributes futureAttrs = new ExplainPlanAttributesBuilder()
- .setIteratorTypeAndScanSize("PARALLEL 1-WAY").setExplainScanType("FULL
SCAN ")
- .setTableName("T").setServerWhereFilter("SERVER PROJECTION FILTER BY
FIRST KEY ONLY").build();
- ExplainPlan futurePlan = new ExplainPlan(Arrays.asList("CLIENT PARALLEL
1-WAY FULL SCAN OVER T",
- " SERVER PROJECTION FILTER BY FIRST KEY ONLY"), futureAttrs);
-
- ExplainChangeRule rename = new ExplainChangeRule() {
- @Override
- public List<String> applyText(String caseId, List<String> goldenText) {
- List<String> out = new ArrayList<>(goldenText.size());
- for (String s : goldenText) {
- out.add(s.replace("SERVER FILTER BY FIRST KEY ONLY",
- "SERVER PROJECTION FILTER BY FIRST KEY ONLY"));
- }
- return out;
- }
-
- @Override
- public JsonNode applyJson(String caseId, JsonNode goldenJson) {
- if (goldenJson.isObject()) {
- ObjectNode obj = (ObjectNode) goldenJson;
- JsonNode swf = obj.get("serverWhereFilter");
- if (
- swf != null && swf.isTextual() && swf.asText().equals("SERVER
FILTER BY FIRST KEY ONLY")
- ) {
- obj.put("serverWhereFilter", "SERVER PROJECTION FILTER BY FIRST
KEY ONLY");
- }
- }
- return goldenJson;
- }
- };
-
- new ExplainOracle(Collections.singletonList(rename)).verify("today",
futurePlan,
- todayExpectedText, todayExpectedJson);
- }
-
- @Test
- public void testDiffMessageShowsExpectedAndActualForTextMismatch() {
- ExplainPlan plan = samplePlan("PARALLEL 1-WAY", "FULL SCAN ");
- // Caller's "expected" disagrees with what the plan actually emits.
- List<String> divergentExpectedText =
- text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER T", " SERVER FILTER BY
(X = 9)");
- ObjectNode divergentExpectedJson = defaultAttrs()
- .put("iteratorTypeAndScanSize", "PARALLEL
<N>-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<String>
expectedText,
- JsonNode expectedJson) throws Exception {
- verifyQuery(caseId, query, defaultProps(), expectedText, expectedJson);
- }
-
- private void verifyQuery(String caseId, String query, Properties props,
List<String> 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<String> 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<String> text(String... lines) {
- return Arrays.asList(lines);
- }
-
- /**
- * Returns a fresh {@link ObjectNode} populated with the JSON shape that
- * {@link ExplainPlanAttributes#getDefaultExplainPlan()} serializes to
(after normalization): all
- * nullable fields are null, both booleans are false, and {@code
numRegionLocationLookups} is 0.
- * Each test case starts from this baseline and overrides only the fields it
asserts on. Field
- * order is irrelevant — {@link JsonNode#equals(Object)} compares maps, not
order.
- */
- private static ObjectNode defaultAttrs() {
- ObjectNode n = mapper.createObjectNode();
- n.putNull("abstractExplainPlan");
- 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 <N>-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
deleted file mode 100644
index 51144afd20..0000000000
---
a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainJsonNormalizer.java
+++ /dev/null
@@ -1,71 +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.explain;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.NullNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-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) {
- 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("<N>-WAY"));
- }
-
- JsonNode rhs = obj.get("rhsJoinQueryExplainPlan");
- if (rhs != null && rhs.isObject()) {
- normalize(rhs);
- }
-
- 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
deleted file mode 100644
index 42ab8a2876..0000000000
---
a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainOracle.java
+++ /dev/null
@@ -1,207 +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.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.Arrays;
-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.
- * <p>
- * For each case, the test compares the current EXPLAIN output against an
expected baseline supplied
- * by the caller (embedded directly in the test).
- * <p>
- * The {@link ExplainChangeRule} chain is applied to the expected side before
comparison, so a
- * future EXPLAIN change appends one rule transforming the embedded baseline
into its new expected
- * form.
- */
-public final class ExplainOracle {
-
- private final List<ExplainChangeRule> rules;
- private final ExplainTextNormalizer textNormalizer;
- private final ExplainJsonNormalizer jsonNormalizer;
- private final ObjectMapper mapper;
- private final ObjectWriter prettyWriter;
-
- public ExplainOracle() {
- this(Collections.<ExplainChangeRule> emptyList());
- }
-
- public ExplainOracle(List<ExplainChangeRule> rules) {
- this.rules = new ArrayList<>(rules);
- 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<String>
expectedText,
- JsonNode expectedJson) throws IOException {
- List<String> textCurrent = textNormalizer.normalize(plan.getPlanSteps());
- JsonNode jsonCurrent =
serializeNormalized(plan.getPlanStepsAsAttributes());
-
- List<String> textExpected = new ArrayList<>(expectedText);
- JsonNode jsonExpected = expectedJson == null ? null :
expectedJson.deepCopy();
-
- for (ExplainChangeRule rule : rules) {
- textExpected = rule.applyText(caseId, textExpected);
- if (jsonExpected != null) {
- jsonExpected = rule.applyJson(caseId, jsonExpected);
- }
- }
-
- 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<String> normalizeText(List<String> 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<String> expected,
List<String> 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) : "<missing>";
- String a = i < actual.size() ? actual.get(i) : "<missing>";
- 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<String> 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');
- }
-
- /** Assemble an ExplainOracle with the given rule chain. */
- public static ExplainOracle forRules(ExplainChangeRule... rules) {
- return new ExplainOracle(Arrays.asList(rules));
- }
-}
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
deleted file mode 100644
index 62b5c6854d..0000000000
---
a/phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainTextNormalizer.java
+++ /dev/null
@@ -1,68 +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.explain;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Pattern;
-
-/**
- * Elides cluster- and connection-specific details from the {@code
List<String>} returned by
- * {@code ExplainPlan.getPlanSteps()} so the EXPLAIN text can be compared
across environments.
- */
-public final class ExplainTextNormalizer {
-
- // CLIENT 5-CHUNK -> CLIENT <N>-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 <N>-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<String> normalize(List<String> raw) {
- List<String> 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("<N>-CHUNK");
- normalized = WAY_COUNT.matcher(normalized).replaceAll("<N>-WAY");
- normalized = ROWS_BYTES.matcher(normalized).replaceAll("");
- out.add(normalized);
- }
- return out;
- }
-}