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);
   }
 
   /**

Reply via email to