This is an automated email from the ASF dual-hosted git repository.
apurtell pushed a commit to branch PHOENIX-7876-feature
in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/PHOENIX-7876-feature by this
push:
new c06c96fe54 PHOENIX-7902 SERVER ARRAY|JSON|BSON PROJECTION counted
forms in EXPLAIN (#2522)
c06c96fe54 is described below
commit c06c96fe548c1ab4fb2b012643666a6ba891c4a8
Author: Andrew Purtell <[email protected]>
AuthorDate: Thu Jun 11 18:42:28 2026 -0700
PHOENIX-7902 SERVER ARRAY|JSON|BSON PROJECTION counted forms in EXPLAIN
(#2522)
Co-authored-by: Claude Opus 4.8[1m] <[email protected]>
---
.../phoenix/compile/ExplainPlanAttributes.java | 52 ++++++++++++++++-----
.../apache/phoenix/compile/ProjectionCompiler.java | 3 ++
.../apache/phoenix/compile/StatementContext.java | 21 +++++++++
.../org/apache/phoenix/iterate/ExplainTable.java | 53 ++++++++++++++++++----
.../end2end/ProjectArrayElemAfterHashJoinIT.java | 3 +-
.../phoenix/end2end/json/JsonFunctionsIT.java | 20 ++++----
.../apache/phoenix/compile/QueryCompilerTest.java | 14 +++---
.../phoenix/query/explain/ExplainPlanTest.java | 39 ++++++++++++++--
.../phoenix/query/explain/ExplainPlanTestUtil.java | 38 ++++++++++++++--
9 files changed, 198 insertions(+), 45 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 9b9dd8dd8c..45768f6f73 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
@@ -21,7 +21,9 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.apache.hadoop.hbase.HRegionLocation;
import org.apache.hadoop.hbase.client.Consistency;
@@ -41,7 +43,7 @@ import org.apache.phoenix.schema.PColumn;
"indexRejected", "saltBuckets", "regionsPlanned", "scanTimeRangeMin",
"scanTimeRangeMax",
"splitsChunk", "useRoundRobinIterator", "samplingRate", "hexStringRVCOffset",
"iteratorTypeAndScanSize", "scanEstimatedRows", "scanEstimatedSizeInBytes",
"serverWhereFilter",
- "serverDistinctFilter", "serverMergeColumns", "serverArrayElementProjection",
+ "serverDistinctFilter", "serverMergeColumns", "serverParsedProjections",
"serverFirstKeyOnlyProjection", "serverEmptyColumnOnlyProjection",
"serverAggregate",
"serverGroupByLimit", "serverSortedBy", "serverOffset", "serverRowLimit",
"clientFilterBy",
"clientAggregate", "clientDistinctFilter", "clientAfterAggregate",
"clientSortAlgo",
@@ -91,7 +93,7 @@ public class ExplainPlanAttributes {
private final String serverWhereFilter;
private final String serverDistinctFilter;
private final Set<PColumn> serverMergeColumns;
- private final boolean serverArrayElementProjection;
+ private final Map<String, List<String>> serverParsedProjections;
private final boolean serverFirstKeyOnlyProjection;
private final boolean serverEmptyColumnOnlyProjection;
private final String serverAggregate;
@@ -165,7 +167,7 @@ public class ExplainPlanAttributes {
this.serverWhereFilter = null;
this.serverDistinctFilter = null;
this.serverMergeColumns = null;
- this.serverArrayElementProjection = false;
+ this.serverParsedProjections = null;
this.serverFirstKeyOnlyProjection = false;
this.serverEmptyColumnOnlyProjection = false;
this.serverAggregate = null;
@@ -205,7 +207,7 @@ public class ExplainPlanAttributes {
Integer splitsChunk, boolean useRoundRobinIterator, Double samplingRate,
String hexStringRVCOffset, String iteratorTypeAndScanSize, Long
scanEstimatedRows,
Long scanEstimatedSizeInBytes, String serverWhereFilter, String
serverDistinctFilter,
- Set<PColumn> serverMergeColumns, boolean serverArrayElementProjection,
+ Set<PColumn> serverMergeColumns, Map<String, List<String>>
serverParsedProjections,
boolean serverFirstKeyOnlyProjection, boolean
serverEmptyColumnOnlyProjection,
String serverAggregate, Integer serverGroupByLimit, String serverSortedBy,
Integer serverOffset,
Long serverRowLimit, String clientFilterBy, String clientAggregate, String
clientDistinctFilter,
@@ -253,7 +255,7 @@ public class ExplainPlanAttributes {
this.serverWhereFilter = serverWhereFilter;
this.serverDistinctFilter = serverDistinctFilter;
this.serverMergeColumns = serverMergeColumns;
- this.serverArrayElementProjection = serverArrayElementProjection;
+ this.serverParsedProjections =
copyServerParsedProjections(serverParsedProjections);
this.serverFirstKeyOnlyProjection = serverFirstKeyOnlyProjection;
this.serverEmptyColumnOnlyProjection = serverEmptyColumnOnlyProjection;
this.serverAggregate = serverAggregate;
@@ -419,8 +421,20 @@ public class ExplainPlanAttributes {
return serverMergeColumns;
}
- public boolean isServerArrayElementProjection() {
- return serverArrayElementProjection;
+ public Map<String, List<String>> getServerParsedProjections() {
+ return serverParsedProjections;
+ }
+
+ private static Map<String, List<String>>
+ copyServerParsedProjections(Map<String, List<String>> source) {
+ if (source == null || source.isEmpty()) {
+ return null;
+ }
+ Map<String, List<String>> copy = new LinkedHashMap<>();
+ for (Map.Entry<String, List<String>> entry : source.entrySet()) {
+ copy.put(entry.getKey(), Collections.unmodifiableList(new
ArrayList<>(entry.getValue())));
+ }
+ return Collections.unmodifiableMap(copy);
}
public boolean isServerFirstKeyOnlyProjection() {
@@ -574,7 +588,7 @@ public class ExplainPlanAttributes {
private String serverWhereFilter;
private String serverDistinctFilter;
private Set<PColumn> serverMergeColumns;
- private boolean serverArrayElementProjection;
+ private Map<String, List<String>> serverParsedProjections;
private boolean serverFirstKeyOnlyProjection;
private boolean serverEmptyColumnOnlyProjection;
private String serverAggregate;
@@ -644,7 +658,10 @@ public class ExplainPlanAttributes {
this.serverWhereFilter = explainPlanAttributes.getServerWhereFilter();
this.serverDistinctFilter =
explainPlanAttributes.getServerDistinctFilter();
this.serverMergeColumns = explainPlanAttributes.getServerMergeColumns();
- this.serverArrayElementProjection =
explainPlanAttributes.isServerArrayElementProjection();
+ Map<String, List<String>> srcServerParsedProjections =
+ explainPlanAttributes.getServerParsedProjections();
+ this.serverParsedProjections =
+ srcServerParsedProjections == null ? null : new
LinkedHashMap<>(srcServerParsedProjections);
this.serverFirstKeyOnlyProjection =
explainPlanAttributes.isServerFirstKeyOnlyProjection();
this.serverEmptyColumnOnlyProjection =
explainPlanAttributes.isServerEmptyColumnOnlyProjection();
@@ -851,8 +868,19 @@ public class ExplainPlanAttributes {
}
public ExplainPlanAttributesBuilder
- setServerArrayElementProjection(boolean serverArrayElementProjection) {
- this.serverArrayElementProjection = serverArrayElementProjection;
+ setServerParsedProjections(Map<String, List<String>>
serverParsedProjections) {
+ this.serverParsedProjections =
+ serverParsedProjections == null ? null : new
LinkedHashMap<>(serverParsedProjections);
+ return this;
+ }
+
+ public ExplainPlanAttributesBuilder addServerParsedProjection(String label,
+ List<String> details) {
+ if (this.serverParsedProjections == null) {
+ this.serverParsedProjections = new LinkedHashMap<>();
+ }
+ this.serverParsedProjections.put(label,
+ Collections.unmodifiableList(new ArrayList<>(details)));
return this;
}
@@ -1016,7 +1044,7 @@ public class ExplainPlanAttributes {
indexRejected, saltBuckets, regionsPlanned, scanTimeRangeMin,
scanTimeRangeMax, splitsChunk,
useRoundRobinIterator, samplingRate, hexStringRVCOffset,
iteratorTypeAndScanSize,
scanEstimatedRows, scanEstimatedSizeInBytes, serverWhereFilter,
serverDistinctFilter,
- serverMergeColumns, serverArrayElementProjection,
serverFirstKeyOnlyProjection,
+ serverMergeColumns, serverParsedProjections,
serverFirstKeyOnlyProjection,
serverEmptyColumnOnlyProjection, serverAggregate, serverGroupByLimit,
serverSortedBy,
serverOffset, serverRowLimit, clientFilterBy, clientAggregate,
clientDistinctFilter,
clientAfterAggregate, clientSortAlgo, clientSortedBy, clientOffset,
clientRowLimit,
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java
index 4ee8c1f469..138f000e50 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java
@@ -600,6 +600,9 @@ public class ProjectionCompiler {
serverAttributeToKVExpressionMap.get(entry.getKey()));
}
}
+ // Stash the per-type expression buckets on the context so EXPLAIN can
render the per-type
+ // SERVER ARRAY|JSON|BSON PROJECTION clauses (and their per-expression
detail lines).
+ context.setServerParsedProjections(serverAttributeToFuncExpressionMap);
KeyValueSchemaBuilder builder = new KeyValueSchemaBuilder(0);
for (Expression expression : serverParsedKVRefs) {
builder.addField(expression);
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/StatementContext.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/StatementContext.java
index e329b4ee86..d5eac400e1 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/StatementContext.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/StatementContext.java
@@ -31,6 +31,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.util.Pair;
+import org.apache.phoenix.expression.Expression;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixStatement;
import org.apache.phoenix.log.QueryLogger;
@@ -106,6 +107,7 @@ public class StatementContext {
private int derivedTableFlattenCount;
private List<Pair<ParseNode, String>> indexExpressionSubstitutions;
private Set<Pair<String, String>> partialIndexCheckedSet;
+ private Map<String, List<Expression>> serverParsedProjections;
private StatementContext parentContext;
public StatementContext(PhoenixStatement statement) {
@@ -147,6 +149,7 @@ public class StatementContext {
this.derivedTableFlattenCount = context.derivedTableFlattenCount;
this.indexExpressionSubstitutions = context.indexExpressionSubstitutions;
this.partialIndexCheckedSet = context.partialIndexCheckedSet;
+ this.serverParsedProjections = context.serverParsedProjections;
this.parentContext = context.parentContext;
}
@@ -214,6 +217,7 @@ public class StatementContext {
this.derivedTableFlattenCount = 0;
this.indexExpressionSubstitutions = new ArrayList<>();
this.partialIndexCheckedSet = Sets.newHashSet();
+ this.serverParsedProjections = null;
this.parentContext = null;
}
@@ -506,6 +510,7 @@ public class StatementContext {
this.derivedTableFlattenCount = source.derivedTableFlattenCount;
this.indexExpressionSubstitutions = source.indexExpressionSubstitutions;
this.partialIndexCheckedSet = source.partialIndexCheckedSet;
+ this.serverParsedProjections = source.serverParsedProjections;
}
public void incrementDerivedTableFlattenCount() {
@@ -534,6 +539,22 @@ public class StatementContext {
return partialIndexCheckedSet.add(new Pair<>(tableName, indexName));
}
+ /**
+ * Server-evaluated parsed projection expressions, keyed by the scan
attribute they were
+ * serialized into ({@code _SpecificArrayIndex}, {@code _JsonValueFunction},
+ * {@code _JsonQueryFunction}, {@code _BsonValueFunction}). Populated by
+ * {@link ProjectionCompiler} when at least one JSON/BSON/array path
expression is pushed to the
+ * server, and consumed by {@code ExplainTable} to render the per-type
{@code SERVER * PROJECTION}
+ * clauses. {@code null} when no server-side parsed projection compile
occurred.
+ */
+ public Map<String, List<Expression>> getServerParsedProjections() {
+ return serverParsedProjections;
+ }
+
+ public void setServerParsedProjections(Map<String, List<Expression>>
serverParsedProjections) {
+ this.serverParsedProjections = serverParsedProjections;
+ }
+
public StatementContext getParentContext() {
return parentContext;
}
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 28318ea2c6..62b8ed556f 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
@@ -26,6 +26,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.apache.hadoop.hbase.HRegionLocation;
import org.apache.hadoop.hbase.client.Consistency;
@@ -44,6 +45,7 @@ import org.apache.phoenix.compile.ScanRanges;
import org.apache.phoenix.compile.StatementContext;
import org.apache.phoenix.compile.StatementPlan;
import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
+import org.apache.phoenix.expression.Expression;
import org.apache.phoenix.filter.BooleanExpressionFilter;
import org.apache.phoenix.filter.DistinctPrefixFilter;
import org.apache.phoenix.filter.EmptyColumnOnlyFilter;
@@ -513,17 +515,50 @@ public abstract class ExplainTable {
}
getRegionLocations(planSteps, explainPlanAttributesBuilder,
regionLocations);
groupBy.explain(planSteps, groupByLimit, explainPlanAttributesBuilder);
- if
(scan.getAttribute(BaseScannerRegionObserverConstants.SPECIFIC_ARRAY_INDEX) !=
null) {
- planSteps.add(" SERVER ARRAY ELEMENT PROJECTION");
- if (explainPlanAttributesBuilder != null) {
- explainPlanAttributesBuilder.setServerArrayElementProjection(true);
+ emitServerProjection(planSteps, explainPlanAttributesBuilder, "ARRAY",
+
Collections.singletonList(BaseScannerRegionObserverConstants.SPECIFIC_ARRAY_INDEX));
+ emitServerProjection(planSteps, explainPlanAttributesBuilder, "JSON",
+ Arrays.asList(BaseScannerRegionObserverConstants.JSON_VALUE_FUNCTION,
+ BaseScannerRegionObserverConstants.JSON_QUERY_FUNCTION));
+ emitServerProjection(planSteps, explainPlanAttributesBuilder, "BSON",
+
Collections.singletonList(BaseScannerRegionObserverConstants.BSON_VALUE_FUNCTION));
+ }
+
+ /**
+ * Emit a {@code SERVER <label> PROJECTION <count>} clause plus one indented
detail line per
+ * server evaluated path expression of the given type. The expressions are
read from
+ * {@link StatementContext#getServerParsedProjections()}, gathering the
buckets named by
+ * {@code attributeKeys} in order.
+ * @param planSteps plan step lines to append to
+ * @param explainPlanAttributesBuilder attributes builder to populate, or
{@code null}
+ * @param label the type label ({@code ARRAY}, {@code
JSON}, {@code BSON})
+ * @param attributeKeys the scan attribute bucket keys
contributing to this type
+ */
+ private void emitServerProjection(List<String> planSteps,
+ ExplainPlanAttributesBuilder explainPlanAttributesBuilder, String label,
+ List<String> attributeKeys) {
+ Map<String, List<Expression>> serverParsedProjections =
context.getServerParsedProjections();
+ if (serverParsedProjections == null) {
+ return;
+ }
+ List<String> details = new ArrayList<>();
+ for (String attributeKey : attributeKeys) {
+ List<Expression> expressions = serverParsedProjections.get(attributeKey);
+ if (expressions != null) {
+ for (Expression expression : expressions) {
+ details.add(expression.toString());
+ }
}
}
- if (
-
scan.getAttribute(BaseScannerRegionObserverConstants.JSON_VALUE_FUNCTION) !=
null
- ||
scan.getAttribute(BaseScannerRegionObserverConstants.JSON_QUERY_FUNCTION) !=
null
- ) {
- planSteps.add(" SERVER JSON FUNCTION PROJECTION");
+ if (details.isEmpty()) {
+ return;
+ }
+ planSteps.add(" SERVER " + label + " PROJECTION " + details.size());
+ for (String detail : details) {
+ planSteps.add(" " + detail);
+ }
+ if (explainPlanAttributesBuilder != null) {
+ explainPlanAttributesBuilder.addServerParsedProjection(label, details);
}
}
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 0b882f3a16..18899bd340 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
@@ -110,11 +110,10 @@ public class ProjectArrayElemAfterHashJoinIT extends
ParallelStatsDisabledIT {
private void verifyExplain(Connection conn, String table, boolean fullArray,
boolean hashJoin)
throws Exception {
-
String query = getQuery(table, fullArray, hashJoin);
ExplainPlanAttributes attributes = getExplainAttributes(conn, query);
if (!fullArray) {
- assertPlan(attributes).serverArrayElementProjection(true);
+ assertPlan(attributes).serverParsedProjectionCount("ARRAY", 4);
}
assertPlan(attributes).subPlanCount(hashJoin ? 1 : 0);
}
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 f452aacace..d33debd7b7 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
@@ -113,7 +113,7 @@ public class JsonFunctionsIT extends
ParallelStatsDisabledIT {
// Check here for the JSON server side projection
rs = conn.createStatement().executeQuery("EXPLAIN " + query);
- assertTrue(QueryUtil.getExplainPlan(rs).contains(" SERVER JSON
FUNCTION PROJECTION"));
+ assertTrue(QueryUtil.getExplainPlan(rs).contains(" SERVER JSON
PROJECTION "));
}
}
@@ -542,7 +542,7 @@ public class JsonFunctionsIT extends
ParallelStatsDisabledIT {
String query = String.format(queryTemplate, "AndersenFamily");
// check if the explain plan indicates server side execution
ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + query);
- assertFalse(QueryUtil.getExplainPlan(rs).contains(" SERVER JSON
FUNCTION PROJECTION"));
+ assertFalse(QueryUtil.getExplainPlan(rs).contains(" SERVER JSON
PROJECTION "));
}
}
@@ -568,8 +568,8 @@ public class JsonFunctionsIT extends
ParallelStatsDisabledIT {
// Since we are using complete array and json col, no server side
execution
ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + query);
String explainPlan = QueryUtil.getExplainPlan(rs);
- assertFalse(explainPlan.contains(" SERVER JSON FUNCTION PROJECTION"));
- assertPlan(conn, query).serverArrayElementProjection(false)
+ assertFalse(explainPlan.contains(" SERVER JSON PROJECTION "));
+ assertPlan(conn, query).serverParsedProjectionsNone()
.indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
rs = conn.createStatement().executeQuery(query);
assertTrue(rs.next());
@@ -583,8 +583,8 @@ public class JsonFunctionsIT extends
ParallelStatsDisabledIT {
+ " WHERE JSON_VALUE(jsoncol, '$.name') = 'AndersenFamily'";
rs = conn.createStatement().executeQuery("EXPLAIN " + query);
explainPlan = QueryUtil.getExplainPlan(rs);
- assertTrue(explainPlan.contains(" SERVER JSON FUNCTION PROJECTION"));
- assertPlan(conn, query).serverArrayElementProjection(true)
+ assertTrue(explainPlan.contains(" SERVER JSON PROJECTION "));
+ assertPlan(conn, query).serverParsedProjectionCount("ARRAY", 1)
.indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
// only Array optimization and not Json
@@ -592,8 +592,8 @@ public class JsonFunctionsIT extends
ParallelStatsDisabledIT {
+ " WHERE JSON_VALUE(jsoncol, '$.name') = 'AndersenFamily'";
rs = conn.createStatement().executeQuery("EXPLAIN " + query);
explainPlan = QueryUtil.getExplainPlan(rs);
- assertFalse(explainPlan.contains(" SERVER JSON FUNCTION PROJECTION"));
- assertPlan(conn, query).serverArrayElementProjection(true)
+ assertFalse(explainPlan.contains(" SERVER JSON PROJECTION "));
+ assertPlan(conn, query).serverParsedProjectionCount("ARRAY", 1)
.indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
// only Json optimization and not Array Index
@@ -601,8 +601,8 @@ public class JsonFunctionsIT extends
ParallelStatsDisabledIT {
+ " WHERE JSON_VALUE(jsoncol, '$.name') = 'AndersenFamily'";
rs = conn.createStatement().executeQuery("EXPLAIN " + query);
explainPlan = QueryUtil.getExplainPlan(rs);
- assertTrue(explainPlan.contains(" SERVER JSON FUNCTION PROJECTION"));
- assertPlan(conn, query).serverArrayElementProjection(false)
+ assertTrue(explainPlan.contains(" SERVER JSON PROJECTION "));
+ assertPlan(conn, query).serverParsedProjectionCount("ARRAY", 0)
.indexRule(OptimizerReasons.RULE_DATA_TABLE).indexRejectedNone();
}
}
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 d806e615f1..ec714f23e2 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
@@ -2202,7 +2202,8 @@ public class QueryCompilerTest extends
BaseConnectionlessQueryTest {
Connection conn = DriverManager.getConnection(getUrl());
try {
conn.createStatement().execute("CREATE TABLE t(a INTEGER PRIMARY KEY,
arr INTEGER ARRAY)");
- assertPlan(conn, "SELECT arr[1] from
t").serverArrayElementProjection(true);
+ assertPlan(conn, "SELECT arr[1] from t").serverParsedProjections("ARRAY",
+ "ARRAY_ELEM(ARR, 1)");
} finally {
conn.createStatement().execute("DROP TABLE IF EXISTS t");
conn.close();
@@ -2214,7 +2215,7 @@ public class QueryCompilerTest extends
BaseConnectionlessQueryTest {
Connection conn = DriverManager.getConnection(getUrl());
try {
conn.createStatement().execute("CREATE TABLE t(a INTEGER PRIMARY KEY,
arr INTEGER ARRAY)");
- assertPlan(conn, "SELECT arr, arr[1] from
t").serverArrayElementProjection(false);
+ assertPlan(conn, "SELECT arr, arr[1] from
t").serverParsedProjectionsNone();
} finally {
conn.createStatement().execute("DROP TABLE IF EXISTS t");
conn.close();
@@ -2227,7 +2228,8 @@ public class QueryCompilerTest extends
BaseConnectionlessQueryTest {
try {
conn.createStatement()
.execute("CREATE TABLE t(a INTEGER PRIMARY KEY, arr INTEGER ARRAY,
arr2 VARCHAR ARRAY)");
- assertPlan(conn, "SELECT arr, arr[1], arr2[1] from
t").serverArrayElementProjection(true);
+ assertPlan(conn, "SELECT arr, arr[1], arr2[1] from
t").serverParsedProjections("ARRAY",
+ "ARRAY_ELEM(ARR2, 1)");
} finally {
conn.createStatement().execute("DROP TABLE IF EXISTS t");
conn.close();
@@ -2242,7 +2244,7 @@ public class QueryCompilerTest extends
BaseConnectionlessQueryTest {
.execute("CREATE TABLE t (p INTEGER PRIMARY KEY, arr1 INTEGER ARRAY,
arr2 INTEGER ARRAY)");
assertPlan(conn,
"SELECT arr1, arr1[1], ARRAY_APPEND(ARRAY_APPEND(arr1, arr2[2]),
arr2[1]), p from t")
- .serverArrayElementProjection(true);
+ .serverParsedProjectionCount("ARRAY", 2);
} finally {
conn.createStatement().execute("DROP TABLE IF EXISTS t");
conn.close();
@@ -2302,7 +2304,7 @@ public class QueryCompilerTest extends
BaseConnectionlessQueryTest {
.execute("CREATE TABLE t (p INTEGER PRIMARY KEY, arr1 INTEGER ARRAY,
arr2 INTEGER ARRAY)");
assertPlan(conn,
"SELECT arr1, arr1[1], ARRAY_ELEM(ARRAY_APPEND(arr1, arr2[1]), 1), p,
arr2[2] from t")
- .serverArrayElementProjection(true);
+ .serverParsedProjectionCount("ARRAY", 2);
} finally {
conn.createStatement().execute("DROP TABLE IF EXISTS t");
conn.close();
@@ -2314,7 +2316,7 @@ public class QueryCompilerTest extends
BaseConnectionlessQueryTest {
Connection conn = DriverManager.getConnection(getUrl());
try {
conn.createStatement().execute("CREATE TABLE t(arr INTEGER ARRAY PRIMARY
KEY)");
- assertPlan(conn, "SELECT arr[1] from
t").serverArrayElementProjection(false);
+ assertPlan(conn, "SELECT arr[1] from t").serverParsedProjectionsNone();
} finally {
conn.createStatement().execute("DROP TABLE IF EXISTS t");
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 cd4503bf3f..99ad4f17e7 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
@@ -76,6 +76,8 @@ public class ExplainPlanTest extends
BaseConnectionlessQueryTest {
private static final String MT_BASE = "EO_MT_BASE";
private static final String MT_VIEW = "EO_MT_VIEW";
private static final String TENANT_ID = "tenant42";
+ private static final String JSON_TBL = "EO_JSON";
+ private static final String BSON_TBL = "EO_BSON";
private static ExplainOracle oracle;
private static ObjectMapper mapper;
@@ -89,6 +91,10 @@ public class ExplainPlanTest extends
BaseConnectionlessQueryTest {
conn.createStatement().execute("CREATE TABLE IF NOT EXISTS " + SALTED
+ " (k VARCHAR NOT NULL PRIMARY KEY, v INTEGER) SALT_BUCKETS=4");
conn.createStatement().execute("CREATE SEQUENCE IF NOT EXISTS " + SEQ);
+ conn.createStatement().execute("CREATE TABLE IF NOT EXISTS " + JSON_TBL
+ + " (pk VARCHAR NOT NULL PRIMARY KEY, jsoncol JSON)");
+ conn.createStatement().execute("CREATE TABLE IF NOT EXISTS " + BSON_TBL
+ + " (pk VARCHAR NOT NULL PRIMARY KEY, payload BSON)");
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"
@@ -283,10 +289,37 @@ public class ExplainPlanTest extends
BaseConnectionlessQueryTest {
@Test
public void testArrayElementProjection() throws Exception {
+ ObjectNode arrayBucket = mapper.createObjectNode();
+ arrayBucket.set("ARRAY",
mapper.createArrayNode().add("ARRAY_ELEM(A_STRING_ARRAY, 1)"));
verifyQuery("arrayElementProjection", "SELECT a_string_array[1] FROM
table_with_array",
text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER TABLE_WITH_ARRAY", "
INDEX TABLE_WITH_ARRAY",
- " REGIONS PLANNED <N>", " SERVER ARRAY ELEMENT PROJECTION"),
- scanAttrs("FULL SCAN ", "TABLE_WITH_ARRAY",
"").put("serverArrayElementProjection", true));
+ " REGIONS PLANNED <N>", " SERVER ARRAY PROJECTION 1",
+ " ARRAY_ELEM(A_STRING_ARRAY, 1)"),
+ scanAttrs("FULL SCAN ", "TABLE_WITH_ARRAY",
"").set("serverParsedProjections", arrayBucket));
+ }
+
+ @Test
+ public void testJsonFunctionProjection() throws Exception {
+ ObjectNode jsonBucket = mapper.createObjectNode();
+ jsonBucket.set("JSON", mapper.createArrayNode().add("JSON_VALUE(JSONCOL,
'$.type')"));
+ verifyQuery("jsonFunctionProjection", "SELECT JSON_VALUE(jsoncol,
'$.type') FROM " + JSON_TBL,
+ text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER " + JSON_TBL, " INDEX "
+ JSON_TBL,
+ " REGIONS PLANNED <N>", " SERVER JSON PROJECTION 1",
+ " JSON_VALUE(JSONCOL, '$.type')"),
+ scanAttrs("FULL SCAN ", JSON_TBL, "").set("serverParsedProjections",
jsonBucket));
+ }
+
+ @Test
+ public void testBsonValueProjection() throws Exception {
+ ObjectNode bsonBucket = mapper.createObjectNode();
+ bsonBucket.set("BSON",
+ mapper.createArrayNode().add("BSON_VALUE(PAYLOAD, 'user.id', 'VARCHAR',
)"));
+ verifyQuery("bsonValueProjection",
+ "SELECT BSON_VALUE(payload, 'user.id', 'VARCHAR') FROM " + BSON_TBL,
+ text("CLIENT PARALLEL <N>-WAY FULL SCAN OVER " + BSON_TBL, " INDEX "
+ BSON_TBL,
+ " REGIONS PLANNED <N>", " SERVER BSON PROJECTION 1",
+ " BSON_VALUE(PAYLOAD, 'user.id', 'VARCHAR', )"),
+ scanAttrs("FULL SCAN ", BSON_TBL, "").set("serverParsedProjections",
bsonBucket));
}
@Test
@@ -1406,7 +1439,7 @@ public class ExplainPlanTest extends
BaseConnectionlessQueryTest {
n.putNull("serverDistinctFilter");
n.putNull("serverOffset");
n.putNull("serverRowLimit");
- n.put("serverArrayElementProjection", false);
+ n.putNull("serverParsedProjections");
n.put("serverFirstKeyOnlyProjection", false);
n.put("serverEmptyColumnOnlyProjection", false);
n.putNull("serverAggregate");
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 62e32025f0..fffc655160 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
@@ -27,6 +27,7 @@ import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import org.apache.phoenix.compile.ExplainPlan;
import org.apache.phoenix.compile.ExplainPlanAttributes;
import org.apache.phoenix.compile.QueryPlan;
@@ -404,9 +405,40 @@ public final class ExplainPlanTestUtil {
return this;
}
- public ExplainPlanAssert serverArrayElementProjection(boolean expected) {
- assertEquals(at("serverArrayElementProjection"), expected,
- attributes.isServerArrayElementProjection());
+ /** Assert the entire server-parsed-projection map matches {@code
expected}. */
+ public ExplainPlanAssert serverParsedProjections(Map<String, List<String>>
expected) {
+ assertEquals(at("serverParsedProjections"), expected,
+ attributes.getServerParsedProjections());
+ return this;
+ }
+
+ /**
+ * Assert that the named bucket ({@code ARRAY}, {@code JSON}, {@code
BSON}) holds exactly the
+ * listed per-expression renderings, in order.
+ */
+ public ExplainPlanAssert serverParsedProjections(String label, String...
expected) {
+ Map<String, List<String>> actual =
attributes.getServerParsedProjections();
+ assertNotNull(at("serverParsedProjections") + " must not be null",
actual);
+ List<String> bucket = actual.get(label);
+ assertNotNull(at("serverParsedProjections[" + label + "]") + " must not
be null", bucket);
+ assertEquals(at("serverParsedProjections[" + label + "]"),
Arrays.asList(expected), bucket);
+ return this;
+ }
+
+ /** Assert that no server-parsed projections were disclosed (null or
empty). */
+ public ExplainPlanAssert serverParsedProjectionsNone() {
+ Map<String, List<String>> actual =
attributes.getServerParsedProjections();
+ assertTrue(at("serverParsedProjections") + " expected none but was " +
actual,
+ actual == null || actual.isEmpty());
+ return this;
+ }
+
+ /** Assert the number of expressions in the named bucket. */
+ public ExplainPlanAssert serverParsedProjectionCount(String label, int
expected) {
+ Map<String, List<String>> actual =
attributes.getServerParsedProjections();
+ List<String> bucket = actual == null ? null : actual.get(label);
+ int actualCount = bucket == null ? 0 : bucket.size();
+ assertEquals(at("serverParsedProjections[" + label + "].size"),
expected, actualCount);
return this;
}