Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public enum CommandType {
public enum Option {
countField,
showCount,
showPerc,
useNull,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3230,6 +3230,39 @@ public RelNode visitRareTopN(RareTopN node, CalcitePlanContext context) {
orderKeys.add(countField);
orderKeys.addAll(tieBreakKeys);

// 3. compute percentage if showperc=true
Boolean showPerc = (Boolean) argumentMap.get(RareTopN.Option.showPerc.name()).getValue();
if (showPerc) {
if (context.relBuilder.peek().getRowType().getFieldNames().contains("percent")) {
throw new IllegalArgumentException(
"Field `percent` already exists in the output. Consider renaming the conflicting"
+ " field.");
}
RexNode totalWindowOver =
Comment thread
AjimelecGonzalez marked this conversation as resolved.
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);

// Round the percent value 2 decimal places
RexNode roundedPerc =
context.relBuilder.call(
SqlStdOperatorTable.ROUND, percValue, context.relBuilder.literal(2));

context.relBuilder.projectPlus(context.relBuilder.alias(roundedPerc, "percent"));
Comment thread
AjimelecGonzalez marked this conversation as resolved.
}

RexNode rowNumberWindowOver =
PlanUtils.makeOver(
context,
Expand All @@ -3242,14 +3275,14 @@ public RelNode visitRareTopN(RareTopN node, CalcitePlanContext context) {
context.relBuilder.projectPlus(
context.relBuilder.alias(rowNumberWindowOver, ROW_NUMBER_COLUMN_FOR_RARE_TOP));

// 3. filter row_number() <= k in each partition
// 4. filter row_number() <= k in each partition
int k = node.getNoOfResults();
context.relBuilder.filter(
context.relBuilder.lessThanOrEqual(
context.relBuilder.field(ROW_NUMBER_COLUMN_FOR_RARE_TOP),
context.relBuilder.literal(k)));

// 4. project final output. the default output is group by list + field list
// 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));
Expand Down
29 changes: 26 additions & 3 deletions docs/user/ppl/cmd/rare.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The `rare` command supports the following parameters.
| --- | --- | --- |
| `<field-list>` | Required | A comma-delimited list of field names. |
| `<by-clause>` | Optional | One or more fields to group the results by. |
| `rare-options` | Optional | Additional options for controlling output: <br> - `showcount`: Whether to create a field in the output containing the frequency count for each combination of values. Default is `true`. <br> - `countfield`: The name of the field that contains the count. Default is `count`. <br> - `usenull`: Whether to output null values. Default is the value of `plugins.ppl.syntax.legacy.preferred`. |
| `rare-options` | Optional | Additional options for controlling output: <br> - `showcount`: Whether to create a field in the output containing the frequency count for each combination of values. Default is `true`. <br> - `countfield`: The name of the field that contains the count. Default is `count`. <br> - `showperc`: Whether to create a field in the output containing the percentage of each value's count relative to the total. Default is `false`. <br> - `usenull`: Whether to output null values. Default is the value of `plugins.ppl.syntax.legacy.preferred`. |

## Example 1: Finding the least common values without showing counts

Expand Down Expand Up @@ -125,7 +125,30 @@ fetched rows / total rows = 4/4
+--------------+-----+
```

## Example 5: Specifying null value handling
## Example 5: Displaying percentages

The following query finds the least common severity levels and shows what percentage each represents:

```ppl
source=otellogs
| rare showperc=true severityText
```

The query returns the following results:

```text
fetched rows / total rows = 4/4
+--------------+-------+---------+
| severityText | count | percent |
|--------------+-------+---------|
| DEBUG | 3 | 15.0 |
| WARN | 4 | 20.0 |
| INFO | 6 | 30.0 |
| ERROR | 7 | 35.0 |
+--------------+-------+---------+
```

## Example 6: Specifying null value handling

The following query uses `usenull=false` to exclude null values:

Expand Down Expand Up @@ -166,4 +189,4 @@ fetched rows / total rows = 4/4
| @opentelemetry/instrumentation-http | 2 |
| null | 16 |
+-----------------------------------------------------------------------------+-------+
```
```
27 changes: 25 additions & 2 deletions docs/user/ppl/cmd/top.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ The `top` command supports the following parameters.
| Parameter | Required/Optional | Description |
| --- | --- | --- |
| `<N>` | Optional | The number of results to return. Default is `10`. |
| `top-options` | Optional | `showcount`: Whether to create a field in the output that represents a count of the tuple of values. Default is `true`.<br>`countfield`: The name of the field that contains the count. Default is `count`.<br>`usenull`: Whether to output `null` values. Default is the value of `plugins.ppl.syntax.legacy.preferred`. |
| `top-options` | Optional | `showcount`: Whether to create a field in the output that represents a count of the tuple of values. Default is `true`.<br>`countfield`: The name of the field that contains the count. Default is `count`.<br>`showperc`: Whether to create a field in the output that represents the percentage of the count relative to the total. Default is `false`.<br>`usenull`: Whether to output `null` values. Default is the value of `plugins.ppl.syntax.legacy.preferred`. |
| `<field-list>` | Required | A comma-delimited list of field names. |
| `<by-clause>` | Optional | One or more fields to group the results by. |

Expand Down Expand Up @@ -139,7 +139,30 @@ fetched rows / total rows = 7/7
+----------------------------------+--------------+
```

## Example 6: Specifying null value handling
## Example 6: Displaying percentages

The following query finds the most common severity levels and shows the percentage each represents:

```ppl
source=otellogs
| top showperc=true severityText
```

The query returns the following results:

```text
fetched rows / total rows = 4/4
+--------------+-------+---------+
| severityText | count | percent |
|--------------+-------+---------|
| ERROR | 7 | 35.0 |
| INFO | 6 | 30.0 |
| WARN | 4 | 20.0 |
| DEBUG | 3 | 15.0 |
+--------------+-------+---------+
```

## Example 7: Specifying null value handling

The following query specifies `usenull=false` to exclude null values:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
package org.opensearch.sql.calcite.remote;

import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_BANK_WITH_NULL_VALUES;
import static org.opensearch.sql.util.MatcherUtils.schema;
import static org.opensearch.sql.util.MatcherUtils.verifyNumOfRows;
import static org.opensearch.sql.util.MatcherUtils.verifySchemaInOrder;
import static org.opensearch.sql.util.MatcherUtils.*;

import java.io.IOException;
import org.json.JSONObject;
Expand Down Expand Up @@ -41,6 +39,43 @@ public void testTopCommandUseNullFalse() throws IOException {
verifyNumOfRows(result, 5);
}

@Test
public void testTopCommandShowPerc() throws IOException {
JSONObject result =
executeQuery(
String.format("source=%s | top showperc=true age", TEST_INDEX_BANK_WITH_NULL_VALUES));
verifySchemaInOrder(
result, schema("age", "int"), schema("count", "bigint"), schema("percent", "double"));
Comment thread
AjimelecGonzalez marked this conversation as resolved.
verifyNumOfRows(result, 6);
verifyDataRows(
result,
rows(36, 2, 28.57),
rows(28, 1, 14.29),
rows(32, 1, 14.29),
rows(33, 1, 14.29),
rows(34, 1, 14.29),
rows(null, 1, 14.29));
}

@Test
public void testTopCommandShowPercWithoutShowCount() throws IOException {
JSONObject result =
executeQuery(
String.format(
"source=%s | top showperc=true showcount=false age",
TEST_INDEX_BANK_WITH_NULL_VALUES));
verifySchemaInOrder(result, schema("age", "int"), schema("percent", "double"));
Comment thread
AjimelecGonzalez marked this conversation as resolved.
verifyNumOfRows(result, 6);
verifyDataRows(
result,
rows(36, 28.57),
rows(28, 14.29),
rows(32, 14.29),
rows(33, 14.29),
rows(34, 14.29),
rows(null, 14.29));
}

@Test
public void testTopCommandLegacyFalse() throws IOException {
withSettings(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,20 @@ public void testTopNWithGroup() throws IOException {
verifyDataRows(result, rows("F", "TX"), rows("M", "MD"));
}
}

@Test
public void testTopWithShowPerc() throws IOException {
JSONObject result =
executeQuery(String.format("source=%s | top showperc=true gender", TEST_INDEX_ACCOUNT));
if (isCalciteEnabled()) {
verifySchemaInOrder(
result,
schema("gender", "string"),
schema("count", "bigint"),
schema("percent", "double"));
Comment thread
AjimelecGonzalez marked this conversation as resolved.
verifyDataRows(result, rows("M", 507, 50.7), rows("F", 493, 49.3));
} else {
verifyDataRows(result, rows("M"), rows("F"));
}
}
}
1 change: 1 addition & 0 deletions ppl/src/main/antlr/OpenSearchPPLLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ UNION: 'UNION';
MAXOUT: 'MAXOUT';
COUNTFIELD: 'COUNTFIELD';
SHOWCOUNT: 'SHOWCOUNT';
SHOWPERC: 'SHOWPERC';
LIMIT: 'LIMIT';
USEOTHER: 'USEOTHER';
OTHERSTR: 'OTHERSTR';
Expand Down
2 changes: 2 additions & 0 deletions ppl/src/main/antlr/OpenSearchPPLParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ rareTopCommand
rareTopOption
: COUNTFIELD EQUAL countField = stringLiteral
| SHOWCOUNT EQUAL showCount = booleanLiteral
| SHOWPERC EQUAL showPerc = booleanLiteral
| USENULL EQUAL useNull = booleanLiteral
;

Expand Down Expand Up @@ -1791,6 +1792,7 @@ searchableKeyWord
| ANOMALY_SCORE_THRESHOLD
| COUNTFIELD
| SHOWCOUNT
| SHOWPERC
| MAXOUT
| PATH
| INPUT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,11 @@ public static List<Argument> getArgumentList(
new Argument(
RareTopN.Option.showCount.name(),
opt.isPresent() ? getArgumentValue(opt.get().showCount) : Literal.TRUE));
opt = ctx.rareTopOption().stream().filter(op -> op.showPerc != null).findFirst();
list.add(
new Argument(
RareTopN.Option.showPerc.name(),
opt.isPresent() ? getArgumentValue(opt.get().showPerc) : Literal.FALSE));
opt = ctx.rareTopOption().stream().filter(op -> op.useNull != null).findFirst();
list.add(
new Argument(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,13 +459,15 @@ public String visitRareTopN(RareTopN node, String context) {
Integer noOfResults = node.getNoOfResults();
String countField = (String) arguments.get(RareTopN.Option.countField.name()).getValue();
Boolean showCount = (Boolean) arguments.get(RareTopN.Option.showCount.name()).getValue();
Boolean showPerc = (Boolean) arguments.get(RareTopN.Option.showPerc.name()).getValue();
Boolean useNull = (Boolean) arguments.get(RareTopN.Option.useNull.name()).getValue();
String fields = visitFieldList(node.getFields());
String group = visitExpressionList(node.getGroupExprList());
String options =
UnresolvedPlanHelper.isCalciteEnabled(settings)
? StringUtils.format(
"countield='%s' showcount=%s usenull=%s ", countField, showCount, useNull)
"countfield='%s' showcount=%s showperc=%s usenull=%s ",
countField, showCount, showPerc, useNull)
: "";
return StringUtils.format(
"%s | %s %d %s%s",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,18 @@ public void testRareCommandWithGroupByShouldPass() {
assertNotEquals(null, tree);
}

@Test
public void testRareCommandWithShowPercShouldPass() {
ParseTree tree = new PPLSyntaxParser().parse("source=t a=1 | rare showperc=true a");
assertNotEquals(null, tree);
}

@Test
public void testRareCommandWithShowPercAndGroupByShouldPass() {
ParseTree tree = new PPLSyntaxParser().parse("source=t | rare showperc=true a by b");
assertNotEquals(null, tree);
}

@Test
public void testTopCommandWithoutNShouldPass() {
ParseTree tree = new PPLSyntaxParser().parse("source=t a=1 | top a");
Expand All @@ -422,6 +434,18 @@ public void testTopCommandWithoutNAndGroupByShouldPass() {
assertNotEquals(null, tree);
}

@Test
public void testTopCommandWithShowPercShouldPass() {
ParseTree tree = new PPLSyntaxParser().parse("source=t | top showperc=true a");
assertNotEquals(null, tree);
}

@Test
public void testTopCommandWithShowPercAndGroupByShouldPass() {
ParseTree tree = new PPLSyntaxParser().parse("source=t | top showperc=true a by b");
assertNotEquals(null, tree);
}

@Test
public void testCanParseMultiMatchRelevanceFunction() {
assertNotEquals(
Expand Down
Loading
Loading