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 7f4909a7df PHOENIX-7936 EXPLAIN the user's projection on server-driven
mutation inner plans (#2545)
7f4909a7df is described below
commit 7f4909a7df206f2760152e13c0a8fe316e5a7b48
Author: Andrew Purtell <[email protected]>
AuthorDate: Wed Jun 24 18:56:55 2026 -0700
PHOENIX-7936 EXPLAIN the user's projection on server-driven mutation inner
plans (#2545)
Co-authored-by: Claude Opus 4.8[1m] <[email protected]>
---
.../org/apache/phoenix/compile/DeleteCompiler.java | 4 ++
.../org/apache/phoenix/compile/UpsertCompiler.java | 3 +
.../org/apache/phoenix/iterate/ExplainTable.java | 64 ++++++++++++++++++++--
3 files changed, 65 insertions(+), 6 deletions(-)
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
index a727a0280d..e73ad41dbe 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
@@ -1002,6 +1002,10 @@ public class DeleteCompiler {
planSteps.add(" RETURNING *");
}
planSteps.addAll(queryPlanSteps);
+ // Surface the row-identity projection the scan actually reads so
VERBOSE explain describes
+ // the delete rather than the count.
+ ExplainTable.overrideMutationProject(planSteps, explainPlanAttributes,
newBuilder,
+ dataPlan.getProjector());
if (getContext().isRoot()) {
ExplainTable.populateTopOfPlanAttributes(newBuilder, getContext(),
getTargetRef());
ExplainTable.populateTopOfPlanEstimates(newBuilder, this);
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
index fa06c95a82..ad750d0e07 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
@@ -1261,6 +1261,9 @@ public class UpsertCompiler {
planSteps.add(" RETURNING *");
}
planSteps.addAll(queryPlanSteps);
+ // Surface the user's SELECT projection instead so VERBOSE explain
describes the upsert.
+ ExplainTable.overrideMutationProject(planSteps, explainPlanAttributes,
newBuilder,
+ queryPlan.getProjector());
if (getContext().isRoot()) {
ExplainTable.populateTopOfPlanAttributes(newBuilder, getContext(),
getTargetRef());
ExplainTable.populateTopOfPlanEstimates(newBuilder, this);
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 00e2564ab0..88ccead7f3 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
@@ -604,13 +604,30 @@ public abstract class ExplainTable {
if (!verbose) {
return;
}
- RowProjector projector = getProjector();
- if (projector == null) {
+ List<String> columns = projectedColumnNames(getProjector());
+ if (columns == null) {
return;
}
+ planSteps.add(" PROJECT " + String.join(", ", columns));
+ if (explainPlanAttributesBuilder != null) {
+ explainPlanAttributesBuilder.setServerProject(columns);
+ }
+ }
+
+ /**
+ * Render a {@link RowProjector}'s column list as the display names used by
the VERBOSE
+ * {@code PROJECT} line, falling back to the projector's expression string
when a column projector
+ * has no name.
+ * @return the list of display names, or {@code null} when the projector is
{@code null} or
+ * projects nothing
+ */
+ public static List<String> projectedColumnNames(RowProjector projector) {
+ if (projector == null) {
+ return null;
+ }
List<? extends ColumnProjector> columnProjectors =
projector.getColumnProjectors();
if (columnProjectors == null || columnProjectors.isEmpty()) {
- return;
+ return null;
}
List<String> columns = new ArrayList<>(columnProjectors.size());
for (ColumnProjector columnProjector : columnProjectors) {
@@ -620,10 +637,45 @@ public abstract class ExplainTable {
}
columns.add(name);
}
- planSteps.add(" PROJECT " + String.join(", ", columns));
- if (explainPlanAttributesBuilder != null) {
- explainPlanAttributesBuilder.setServerProject(columns);
+ return columns;
+ }
+
+ /**
+ * Server-side {@code UPSERT SELECT} and {@code DELETE} inner plans are
compiled as an aggregating
+ * {@code SELECT COUNT(1)} whose count is used solely to report how many
rows were touched. That
+ * count is an internal compiler choice. Surfacing {@code PROJECT COUNT(1)}
on the explain output
+ * is misleading because the user asked to upsert/delete a set of
columns/rows, not to count them.
+ * @param planSteps the composed mutation plan steps (mutated in place)
+ * @param innerAttributes the inner aggregate plan's rendered attributes
(source of the count-form
+ * {@code serverProject})
+ * @param builder the mutation plan's attribute builder (its {@code
serverProject} is
+ * overwritten)
+ * @param userProjector the user-facing projection to surface instead of
the count form
+ */
+ public static void overrideMutationProject(List<String> planSteps,
+ ExplainPlanAttributes innerAttributes, ExplainPlanAttributesBuilder
builder,
+ RowProjector userProjector) {
+ if (planSteps == null || innerAttributes == null || builder == null) {
+ return;
+ }
+ List<String> countProject = innerAttributes.getServerProject();
+ if (countProject == null || countProject.isEmpty()) {
+ return;
+ }
+ // Only override the internal COUNT(*)/COUNT(1) aggregate projection used
for mutation row counts.
+ if (countProject.size() != 1 || !countProject.get(0).startsWith("COUNT("))
{
+ return;
+ }
+ List<String> userColumns = projectedColumnNames(userProjector);
+ if (userColumns == null || userColumns.isEmpty()) {
+ return;
+ }
+ String oldLine = " PROJECT " + String.join(", ", countProject);
+ int idx = planSteps.indexOf(oldLine);
+ if (idx >= 0) {
+ planSteps.set(idx, " PROJECT " + String.join(", ", userColumns));
}
+ builder.setServerProject(userColumns);
}
/**