Copilot commented on code in PR #2526:
URL: https://github.com/apache/phoenix/pull/2526#discussion_r3407509407


##########
phoenix-core/src/test/java/org/apache/phoenix/query/explain/ExplainPlanTest.java:
##########
@@ -973,11 +974,176 @@ public void testIndexRejectedRendered() throws Exception 
{
       stmt.execute("CREATE LOCAL INDEX " + idx + " ON " + base + " (c1)");
       String query =
         "SELECT c1, max(rowkey), max(c2) FROM " + base + " WHERE rowkey <= 'z' 
GROUP BY c1";
+      // The structured indexRejected attribute is populated regardless of 
EXPLAIN mode.
       ExplainPlanTestUtil.assertPlan(conn, query).indexName(base)
         
.indexRule(OptimizerReasons.RULE_MORE_BOUND_PK_COLUMNS).indexRejectedCount(1)
         .indexRejected(0, idx, OptimizerReasons.REASON_NO_PK_PREFIX_BOUND);
       assertPlanContainsLine(conn, query, "    INDEX " + base + "  /* more 
bound PK columns */");
-      assertPlanContainsLine(conn, query, "    /* !INDEX " + idx + " -- no PK 
prefix bound */");
+      // The !INDEX rejection comment text is VERBOSE-only.
+      assertNoPlanLineContains(conn, query, "!INDEX");
+      List<String> verboseSteps =
+        ExplainPlanTestUtil.getPlanSteps(conn, query, ExplainOptions.VERBOSE);
+      assertTrue(
+        "expected VERBOSE plan to contain the !INDEX rejection comment but was 
" + verboseSteps,
+        verboseSteps.contains("    /* !INDEX " + idx + " -- no PK prefix bound 
*/"));
+    }
+  }
+
+  @Test
+  public void testVerboseProjectLine() throws Exception {
+    try (Connection conn = DriverManager.getConnection(getUrl(), 
defaultProps());
+      java.sql.Statement stmt = conn.createStatement()) {
+      String t = generateUniqueName();
+      stmt
+        .execute("CREATE TABLE " + t + " (k VARCHAR PRIMARY KEY, a VARCHAR, b 
VARCHAR, c VARCHAR)");
+      String query = "SELECT a, b FROM " + t;
+      ExplainPlanTestUtil.assertPlanWithVerbose(conn, 
query).serverProject("A", "B");
+      List<String> verboseSteps =
+        ExplainPlanTestUtil.getPlanSteps(conn, query, ExplainOptions.VERBOSE);
+      assertTrue("expected VERBOSE plan to contain the PROJECT line but was " 
+ verboseSteps,
+        verboseSteps.contains("    PROJECT A, B"));
+      // Plain EXPLAIN carries no PROJECT line and no serverProject attribute.
+      ExplainPlanTestUtil.assertPlan(conn, query).serverProjectNone();
+      assertNoPlanLineContains(conn, query, "PROJECT ");
+    }
+  }
+
+  @Test
+  public void testVerboseServerFilterWhereFanout() throws Exception {
+    try (Connection conn = DriverManager.getConnection(getUrl(), 
defaultProps());
+      java.sql.Statement stmt = conn.createStatement()) {
+      String t = generateUniqueName();
+      stmt
+        .execute("CREATE TABLE " + t + " (k VARCHAR PRIMARY KEY, a VARCHAR, b 
VARCHAR, c VARCHAR)");
+      String query = "SELECT a FROM " + t + " WHERE b = 'x' AND c = 'y'";
+      ExplainPlanTestUtil.assertPlanWithVerbose(conn, 
query).serverFilterCount(2)
+        .serverFilterOrigin(0, "WHERE").serverFilterPathTest(0, 
null).serverFilterOrigin(1, "WHERE")
+        .serverFilterPathTest(1, null);

Review Comment:
   This chained assertion line exceeds the project's 100-character Checkstyle 
`LineLength` limit, which will fail the build. Please wrap the fluent calls 
onto separate lines.



##########
phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java:
##########
@@ -562,6 +585,165 @@ private void emitServerProjection(List<String> planSteps,
     }
   }
 
+  /** Emit the VERBOSE-only {@code PROJECT <cols>} line and populate {@code 
serverProject}. */
+  private void emitProject(List<String> planSteps,
+    ExplainPlanAttributesBuilder explainPlanAttributesBuilder, boolean 
verbose) {
+    if (!verbose) {
+      return;
+    }
+    RowProjector projector = getProjector();
+    if (projector == null) {
+      return;
+    }
+    List<? extends ColumnProjector> columnProjectors = 
projector.getColumnProjectors();
+    if (columnProjectors == null || columnProjectors.isEmpty()) {
+      return;
+    }
+    List<String> columns = new ArrayList<>(columnProjectors.size());
+    for (ColumnProjector columnProjector : columnProjectors) {
+      String name = columnProjector.getName();
+      if (name == null || name.isEmpty()) {
+        name = String.valueOf(columnProjector.getExpression());
+      }
+      columns.add(name);
+    }
+    planSteps.add("    PROJECT " + String.join(", ", columns));
+    if (explainPlanAttributesBuilder != null) {
+      explainPlanAttributesBuilder.setServerProject(columns);
+    }
+  }
+
+  /**
+   * Emit the VERBOSE-only ignored-hint comments, one {@code /*- HINT(args) -- 
reason *}{@code /}
+   * line per hint the planner intentionally ignored.
+   */
+  private void emitIgnoredHints(List<String> planSteps,
+    ExplainPlanAttributesBuilder explainPlanAttributesBuilder, boolean 
verbose) {
+    if (!verbose) {
+      return;
+    }
+    Map<Hint, String> ignoredHints = context.getIgnoredHints();
+    if (ignoredHints == null || ignoredHints.isEmpty()) {
+      return;
+    }
+    for (Map.Entry<Hint, String> entry : ignoredHints.entrySet()) {
+      Hint ignoredHint = entry.getKey();
+      String args = hint == null ? null : hint.getHint(ignoredHint);
+      String rendered = ignoredHint.name() + (args == null ? "" : args);
+      planSteps.add("    /*- " + rendered + " -- " + entry.getValue() + " */");
+      if (explainPlanAttributesBuilder != null) {
+        explainPlanAttributesBuilder.addIgnoredHint(ignoredHint.name(), 
entry.getValue());
+      }
+    }
+  }
+
+  /**
+   * Emit the VERBOSE-only per-predicate {@code SERVER FILTER BY <expr>  -- 
<origin>} lines and
+   * populate {@code serverFilters}. When the top-level filter is an {@link 
AndExpression} whose
+   * children all carry origin tags, one line is emitted per child. Otherwise 
a single line carries
+   * the comma-separated union of origins.
+   */
+  private void emitServerFilters(List<String> planSteps,
+    ExplainPlanAttributesBuilder explainPlanAttributesBuilder, Expression 
whereExpression,
+    String whereFilterStr) {
+    List<ServerFilter> filters = renderVerboseFilters(context, 
whereExpression, whereFilterStr,
+      "    SERVER FILTER BY", planSteps);
+    if (explainPlanAttributesBuilder != null) {
+      explainPlanAttributesBuilder.setServerFilters(filters);
+    }
+  }
+
+  /**
+   * Render the VERBOSE per-predicate filter breakdown. Each emitted line is 
appended to
+   * {@code planSteps}.
+   * @param context           the statement context carrying the 
predicate-origin tags.
+   * @param filterExpression  the residual filter expression (may be {@code 
null} when only the
+   *                          rendered string is available, e.g. an 
index-serialized filter).
+   * @param combinedFilterStr the combined filter string used for the 
single-line fallback.
+   * @param linePrefix        the full leading token including any 
indentation, e.g.
+   *                          {@code "    SERVER FILTER BY"} or {@code "CLIENT 
FILTER BY"}.
+   * @param planSteps         the plan-steps list to append rendered lines to.
+   * @return the structured filters in render order (never {@code null}; never 
empty).
+   */
+  public static List<ServerFilter> renderVerboseFilters(StatementContext 
context,
+    Expression filterExpression, String combinedFilterStr, String linePrefix,
+    List<String> planSteps) {
+    List<ServerFilter> filters = new ArrayList<>();
+    if (filterExpression instanceof AndExpression) {
+      List<Expression> children = filterExpression.getChildren();
+      boolean allTagged = !children.isEmpty();
+      for (Expression child : children) {
+        if (context.getPredicateOrigins(child).isEmpty()) {
+          allTagged = false;
+          break;
+        }
+      }
+      if (allTagged) {
+        for (Expression child : children) {
+          filters.add(buildServerFilter(context, "(" + child + ")", child));
+        }
+      }
+    }
+    if (filters.isEmpty()) {
+      filters.add(buildServerFilter(context, combinedFilterStr, 
filterExpression));
+    }
+    for (ServerFilter filter : filters) {
+      StringBuilder line = new StringBuilder(linePrefix).append(" 
").append(filter.getExpr());
+      String comment = renderOriginComment(filter);
+      if (comment != null) {
+        line.append("  -- ").append(comment);
+      }
+      planSteps.add(line.toString());
+    }
+    return filters;
+  }
+
+  private static ServerFilter buildServerFilter(StatementContext context, 
String exprStr,
+    Expression expression) {
+    List<String> origins =
+      expression == null ? null : new 
ArrayList<>(context.getPredicateOrigins(expression));
+    String pathTestSubtag = detectPathTestSubtag(expression);
+    return new ServerFilter(exprStr, origins, pathTestSubtag);
+  }

Review Comment:
   `renderVerboseFilters` falls back to a single combined line when the 
top-level filter is an `AndExpression` but not all children are tagged. In that 
fallback path, `buildServerFilter` only reads origin tags from the parent 
expression, so the origin attribution comment can be empty even when some child 
predicates were tagged. This contradicts the method Javadoc/PR intent (“union 
of origins”) and can hide useful attribution in VERBOSE output.
   
   Consider unioning origin tags from conjunct children when the parent has no 
tags (or when falling back), so the combined line includes the expected origin 
set.



##########
phoenix-core-client/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java:
##########
@@ -581,6 +619,59 @@ public static ExplainPlanAttributes 
getDefaultExplainPlan() {
     return EXPLAIN_PLAN_INSTANCE;
   }
 
+  /** A single VERBOSE-mode server filter predicate. */
+  @JsonPropertyOrder({ "expr", "origin", "pathTestSubtag" })
+  public static class ServerFilter {

Review Comment:
   The new `ExplainPlanAttributes.ServerFilter` type is used for both 
`serverFilters` and `clientFilters`. Naming it `ServerFilter` is misleading in 
client contexts and makes the API harder to understand for callers/JSON 
consumers.
   
   Consider renaming it to something neutral like 
`FilterPredicate`/`ExplainFilter` and using that for both fields.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to