Skip to content

[Feature] showperc with top and rare commands.#5642

Open
AjimelecGonzalez wants to merge 1 commit into
opensearch-project:mainfrom
AjimelecGonzalez:feature/showperc
Open

[Feature] showperc with top and rare commands.#5642
AjimelecGonzalez wants to merge 1 commit into
opensearch-project:mainfrom
AjimelecGonzalez:feature/showperc

Conversation

@AjimelecGonzalez

Copy link
Copy Markdown
Contributor

Description

Add showperc option to the top and rare PPL commands. When showperc=true is specified, a percent column is added to the output showing each value's percentage of the total count within its partition (group-by clause).

The percentage is computed as ROUND(100.0 * count / SUM(count) OVER (PARTITION BY group_fields), 2) and is rounded to 2 decimal places. When used with a by clause, percentages are calculated within each partition (summing to 100% per group).

Syntax

source=index | top showperc=true field_name [by group_field]
source=index | rare showperc=true field_name [by group_field]

Top Example

source=otellogs | top showperc=true severityText

Returns:

severityText count percent
ERROR 7 35.0
INFO 6 30.0
WARN 4 20.0
DEBUG 3 15.0

Rare Example

source=otellogs | rare showperc=true severityText

Returns:

severityText count percent
DEBUG 3 15.0
WARN 4 20.0
INFO 6 30.0
ERROR 7 35.0

Related Issues

Resolves #5530

Check List

  • New functionality includes testing.
  • New functionality has been documented.
  • New functionality has javadoc added.
  • New functionality has a user manual doc added.
  • New PPL command checklist all confirmed.
  • API changes companion pull request created.
  • Commits are signed per the DCO using --signoff or -s.
  • Public documentation issue/PR created.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

(Review updated until commit be841df)

Here are some key observations to aid the review process:

🧪 PR contains tests
🔒 No security concerns identified
✅ No TODO sections
🔀 No multiple PR themes
⚡ Recommended focus areas for review

Division by Zero

When all rows in a partition have been filtered out or the dataset is empty, SUM(count) in the window function returns zero or null, causing division by zero in the percent calculation. This will produce NaN or throw an error depending on the SQL engine's behavior.

RexNode totalWindowOver =
    PlanUtils.makeOver(
        context,
        BuiltinFunctionName.SUM,
        context.relBuilder.field(countFieldName),
        List.of(),
        partitionKeys,
        List.of(),
        WindowFrame.rowsUnbounded());

RexNode hundred = context.relBuilder.literal(new BigDecimal("100.0"));
RexNode countCast =
    context.relBuilder.cast(context.relBuilder.field(countFieldName), SqlTypeName.DOUBLE);
RexNode totalCast = context.relBuilder.cast(totalWindowOver, SqlTypeName.DOUBLE);
RexNode numerator = context.relBuilder.call(SqlStdOperatorTable.MULTIPLY, hundred, countCast);
RexNode percValue = context.relBuilder.call(SqlStdOperatorTable.DIVIDE, numerator, totalCast);
Typo Fixed

The old code had a typo 'countield' instead of 'countfield'. The new code fixes this, but the fix also changes the format string to include showperc. If the old typo was intentional or if this change breaks existing anonymization logic that depends on the old format, it could cause issues.

"countfield='%s' showcount=%s showperc=%s usenull=%s ",
countField, showCount, showPerc, useNull)

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

PR Code Suggestions ✨

Latest suggestions up to be841df

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Prevent division by zero error

Handle potential division by zero when totalCast is zero. If all counts are zero or
the partition is empty, the division will produce undefined results or errors.

core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java [3251-3256]

 RexNode hundred = context.relBuilder.literal(new BigDecimal("100.0"));
 RexNode countCast =
     context.relBuilder.cast(context.relBuilder.field(countFieldName), SqlTypeName.DOUBLE);
 RexNode totalCast = context.relBuilder.cast(totalWindowOver, SqlTypeName.DOUBLE);
 RexNode numerator = context.relBuilder.call(SqlStdOperatorTable.MULTIPLY, hundred, countCast);
-RexNode percValue = context.relBuilder.call(SqlStdOperatorTable.DIVIDE, numerator, totalCast);
+RexNode zero = context.relBuilder.literal(0.0);
+RexNode percValue = context.relBuilder.call(SqlStdOperatorTable.CASE,
+    context.relBuilder.call(SqlStdOperatorTable.EQUALS, totalCast, zero),
+    zero,
+    context.relBuilder.call(SqlStdOperatorTable.DIVIDE, numerator, totalCast));
Suggestion importance[1-10]: 7

__

Why: The suggestion addresses a potential edge case where totalCast could be zero, which would cause division by zero. However, in typical usage with aggregated counts, this scenario is unlikely since the total would only be zero if there are no rows, in which case the percentage calculation wouldn't be reached.

Medium

Previous suggestions

Suggestions up to commit b576e5a
CategorySuggestion                                                                                                                                    Impact
Possible issue
Handle division by zero safely

Add null safety check for division operation. When totalWindowOver evaluates to zero
or null, the division will fail or produce incorrect results. Consider using a CASE
expression to handle zero/null denominators gracefully.

core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java [3251-3256]

 RexNode hundred = context.relBuilder.literal(new BigDecimal("100.0"));
 RexNode countCast =
     context.relBuilder.cast(context.relBuilder.field(countFieldName), SqlTypeName.DOUBLE);
 RexNode totalCast = context.relBuilder.cast(totalWindowOver, SqlTypeName.DOUBLE);
 RexNode numerator = context.relBuilder.call(SqlStdOperatorTable.MULTIPLY, hundred, countCast);
-RexNode percValue = context.relBuilder.call(SqlStdOperatorTable.DIVIDE, numerator, totalCast);
+RexNode zero = context.relBuilder.literal(0.0);
+RexNode isZero = context.relBuilder.equals(totalCast, zero);
+RexNode percValue = context.relBuilder.call(
+    SqlStdOperatorTable.CASE,
+    isZero,
+    zero,
+    context.relBuilder.call(SqlStdOperatorTable.DIVIDE, numerator, totalCast));
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential division by zero issue when totalWindowOver is zero. However, in the context of SUM(COUNT(*)) over a window, this scenario is unlikely in practice since it would require no rows in the partition. The suggested CASE expression adds defensive programming but may be overly cautious for this specific use case.

Medium
General
Improve field conflict error message

The field name conflict check occurs after showPerc is evaluated but before the
percentage calculation. If showPerc is false, this check is skipped, but a
user-defined percent field could still conflict with the internal
row_number_rare_top field or future features. Consider checking for reserved field
names earlier or documenting this limitation.

core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java [3234-3240]

 Boolean showPerc = (Boolean) argumentMap.get(RareTopN.Option.showPerc.name()).getValue();
 if (showPerc) {
-  if (context.relBuilder.peek().getRowType().getFieldNames().contains("percent")) {
+  List<String> fieldNames = context.relBuilder.peek().getRowType().getFieldNames();
+  if (fieldNames.contains("percent")) {
     throw new IllegalArgumentException(
         "Field `percent` already exists in the output. Consider renaming the conflicting"
-            + " field.");
+            + " field or using showperc=false.");
   }
Suggestion importance[1-10]: 3

__

Why: The suggestion only adds "or using showperc=false" to the error message, which provides minimal improvement. The existing error message already clearly indicates the conflict and suggests renaming. The concern about checking reserved field names earlier is valid but not addressed by the improved_code, making this a minor enhancement.

Low
Suggestions up to commit 0039627
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix typo in field name check

The field name check contains a typo: "precent" instead of "percent". This will fail
to detect actual conflicts with the percent field being created, allowing duplicate
field names in the output.

core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java [3236-3240]

-if (context.relBuilder.peek().getRowType().getFieldNames().contains("precent")) {
+if (context.relBuilder.peek().getRowType().getFieldNames().contains("percent")) {
   throw new IllegalArgumentException(
       "Field `percent` already exists in the output. Consider renaming the conflicting"
           + " field.");
 }
Suggestion importance[1-10]: 10

__

Why: This is a critical bug. The code checks for "precent" instead of "percent", which means it will fail to detect actual field name conflicts. This could lead to duplicate field names in the output, causing runtime errors or incorrect results.

High
Suggestions up to commit 0b5b6ed
CategorySuggestion                                                                                                                                    Impact
Possible issue
Exclude count field when needed

When showPerc=true but showCount=false, the countFieldName column is still needed
for percentage calculation but should be excluded from final output. The current
logic only handles showCount and doesn't account for this scenario.

core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java [3280-3285]

 // 5. project final output: group by list + field list, optionally count and percent
 Boolean showCount = (Boolean) argumentMap.get(RareTopN.Option.showCount.name()).getValue();
-if (showCount) {
-  context.relBuilder.projectExcept(context.relBuilder.field(ROW_NUMBER_COLUMN_FOR_RARE_TOP));
+if (showCount || showPerc) {
+  if (showCount) {
+    context.relBuilder.projectExcept(context.relBuilder.field(ROW_NUMBER_COLUMN_FOR_RARE_TOP));
+  } else {
+    // showPerc=true but showCount=false: exclude both row_number and count columns
+    context.relBuilder.projectExcept(
+        context.relBuilder.field(ROW_NUMBER_COLUMN_FOR_RARE_TOP),
+        context.relBuilder.field(countFieldName));
+  }
 } else {
Suggestion importance[1-10]: 9

__

Why: This is a critical bug fix. When showPerc=true and showCount=false, the count field is needed for percentage calculation but should be excluded from the final output. The current code doesn't handle this case, which would result in the count field appearing in the output even when showCount=false. The test case testTopCommandShowPercWithoutShowCount confirms this scenario should be supported.

High
Handle division by zero error

The percentage calculation will fail with division by zero when the total count is
zero (empty result set). Add a null check or conditional logic to handle this edge
case before performing the division operation.

core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java [3253-3278]

 Boolean showPerc = (Boolean) argumentMap.get(RareTopN.Option.showPerc.name()).getValue();
 if (showPerc) {
   RexNode totalWindowOver =
       PlanUtils.makeOver(
           context,
           BuiltinFunctionName.SUM,
           context.relBuilder.field(countFieldName),
           List.of(),
           partitionKeys,
           List.of(),
           WindowFrame.rowsUnbounded());
-  ...
+
+  RexNode hundred = context.relBuilder.literal(new BigDecimal("100.0"));
+  RexNode countCast =
+      context.relBuilder.cast(context.relBuilder.field(countFieldName), SqlTypeName.DOUBLE);
+  RexNode totalCast = context.relBuilder.cast(totalWindowOver, SqlTypeName.DOUBLE);
+  RexNode numerator = context.relBuilder.call(SqlStdOperatorTable.MULTIPLY, hundred, countCast);
+  
+  // Handle division by zero
+  RexNode zero = context.relBuilder.literal(0.0);
+  RexNode isZero = context.relBuilder.equals(totalCast, zero);
+  RexNode percValue = context.relBuilder.call(
+      SqlStdOperatorTable.CASE,
+      isZero,
+      zero,
+      context.relBuilder.call(SqlStdOperatorTable.DIVIDE, numerator, totalCast));
+
+  RexNode roundedPerc =
+      context.relBuilder.call(
+          SqlStdOperatorTable.ROUND, percValue, context.relBuilder.literal(2));
+
   context.relBuilder.projectPlus(context.relBuilder.alias(roundedPerc, "percent"));
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential division by zero issue when calculating percentages. However, in the context of rare and top commands that aggregate data, an empty result set would not reach this code path, making this a less critical edge case. The suggestion is valid but may be overly defensive.

Medium
Suggestions up to commit dae1ecd
CategorySuggestion                                                                                                                                    Impact
Possible issue
Handle division by zero

The percentage calculation doesn't handle the case where totalWindowOver could be
zero, which would result in division by zero. Add a null check or conditional logic
to prevent this error when the total count is zero.

core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java [3265-3270]

 RexNode hundred = context.relBuilder.literal(new BigDecimal("100.0"));
 RexNode countCast =
     context.relBuilder.cast(context.relBuilder.field(countFieldName), SqlTypeName.DOUBLE);
 RexNode totalCast = context.relBuilder.cast(totalWindowOver, SqlTypeName.DOUBLE);
 RexNode numerator = context.relBuilder.call(SqlStdOperatorTable.MULTIPLY, hundred, countCast);
-RexNode percValue = context.relBuilder.call(SqlStdOperatorTable.DIVIDE, numerator, totalCast);
+RexNode zero = context.relBuilder.literal(0.0);
+RexNode isZero = context.relBuilder.equals(totalCast, zero);
+RexNode percValue = context.relBuilder.call(
+    SqlStdOperatorTable.CASE,
+    isZero,
+    zero,
+    context.relBuilder.call(SqlStdOperatorTable.DIVIDE, numerator, totalCast));
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential division by zero issue when totalWindowOver is zero. However, in the context of a SUM window function over counts, this scenario is unlikely in practice since records exist to be counted. The suggestion provides a valid defensive programming approach but may be overly cautious for this specific use case.

Medium
Suggestions up to commit ca7e450
CategorySuggestion                                                                                                                                    Impact
General
Use range-based window frame

The window frame for computing the total should use WindowFrame.rangeUnbounded()
instead of WindowFrame.rowsUnbounded() to ensure consistent behavior with the
expected SQL output. The generated Spark SQL uses RANGE BETWEEN UNBOUNDED PRECEDING
AND UNBOUNDED FOLLOWING, which corresponds to a range-based frame.

core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java [3255-3263]

 RexNode totalWindowOver =
     PlanUtils.makeOver(
         context,
         BuiltinFunctionName.SUM,
         context.relBuilder.field(countFieldName),
         List.of(),
         partitionKeys,
         List.of(),
-        WindowFrame.rowsUnbounded());
+        WindowFrame.rangeUnbounded());
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that WindowFrame.rangeUnbounded() should be used instead of WindowFrame.rowsUnbounded() to match the expected Spark SQL output (RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING). This is a significant correctness issue that affects the semantic behavior of the window function.

Medium
Possible issue
Handle division by zero

Division by zero can occur when totalWindowOver is zero or null, causing runtime
errors. Add a null check or use a CASE expression to handle scenarios where the
total count is zero, returning null or zero for the percentage in such cases.

core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java [3265-3270]

 RexNode hundred = context.relBuilder.literal(new BigDecimal("100.0"));
 RexNode countCast =
     context.relBuilder.cast(context.relBuilder.field(countFieldName), SqlTypeName.DOUBLE);
 RexNode totalCast = context.relBuilder.cast(totalWindowOver, SqlTypeName.DOUBLE);
 RexNode numerator = context.relBuilder.call(SqlStdOperatorTable.MULTIPLY, hundred, countCast);
-RexNode percValue = context.relBuilder.call(SqlStdOperatorTable.DIVIDE, numerator, totalCast);
+RexNode zero = context.relBuilder.literal(0.0);
+RexNode isZero = context.relBuilder.equals(totalCast, zero);
+RexNode percValue = context.relBuilder.call(
+    SqlStdOperatorTable.CASE,
+    isZero,
+    context.relBuilder.literal(null),
+    context.relBuilder.call(SqlStdOperatorTable.DIVIDE, numerator, totalCast));
Suggestion importance[1-10]: 7

__

Why: The suggestion addresses a potential runtime error when totalWindowOver is zero. While this is a valid concern for robustness, the likelihood depends on the data context. Adding null/zero handling would improve code safety, though it may not be critical if the data guarantees non-zero totals.

Medium

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit ca7e450

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit dae1ecd

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 0b5b6ed

@ahkcs ahkcs added the feature label Jul 22, 2026
Comment thread integ-test/src/test/java/org/opensearch/sql/ppl/TopCommandIT.java
@github-actions

Copy link
Copy Markdown
Contributor

PR Code Analyzer ❗

AI-powered 'Code-Diff-Analyzer' found issues on commit 0039627.

PathLineSeverityDescription
core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java3237lowConflict-detection check tests for misspelled field name 'precent' instead of 'percent', so the guard against duplicate output fields will never fire. This renders the protective error message dead code, meaning a user with a pre-existing 'percent' field will get a silently incorrect result rather than the intended error. Appears to be a typo rather than intentional, but worth flagging as an anomaly.

The table above displays the top 10 most important findings.

Total: 1 | Critical: 0 | High: 0 | Medium: 0 | Low: 1


Pull Requests Author(s): Please update your Pull Request according to the report above.

Repository Maintainer(s): You can bypass diff analyzer by adding label skip-diff-analyzer after reviewing the changes carefully, then re-run failed actions. To re-enable the analyzer, remove the label, then re-run all actions.


⚠️ Note: The Code-Diff-Analyzer helps protect against potentially harmful code patterns. Please ensure you have thoroughly reviewed the changes beforehand.

Thanks.

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 0039627

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit b576e5a

* Clean docs
* Fix CalcitePPLRareTopNTest showperc tests
* Calculate percentages before filtering

Signed-off-by: Ajimelec Gonzalez <ajimelec@amazon.com>
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit be841df

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] showperc with top

2 participants