diff --git a/docs/user/ppl/index.md b/docs/user/ppl/index.md index 067d5f524fd..647c4568446 100644 --- a/docs/user/ppl/index.md +++ b/docs/user/ppl/index.md @@ -89,7 +89,7 @@ source=accounts | [nomv command](cmd/nomv.md) | 3.6 | stable (since 3.6) | Converts a multivalue field to a single-value string by joining elements with newlines. | | [mvexpand command](cmd/mvexpand.md) | 3.6 | stable (since 3.6) | Expand a multi-valued field into separate documents (one per value). | | [graphlookup command](cmd/graphlookup.md) | 3.6 | experimental (since 3.6) | Performs recursive graph traversal on a collection using a BFS algorithm.| -| [xyseries command](cmd/xyseries.md) | 3.8 | stable (since 3.8) | Converts row-oriented grouped results into a wide table format suitable for chart visualizations. One field serves as the X axis (row key), one field provides pivot values for generating output column names, and one or more data fields fill the pivoted cells. Only rows matching the explicitly provided pivot values in the `in` clause are included. | +| [xyseries command](cmd/xyseries.md) | 3.8 | experimental (since 3.8) | Converts row-oriented grouped results into a wide table format suitable for chart visualizations. One field serves as the X axis (row key), one field provides pivot values for generating output column names, and one or more data fields fill the pivoted cells. Only rows matching the explicitly provided pivot values in the `in` clause are included. | - [Syntax](cmd/syntax.md) - PPL query structure and command syntax formatting * **Functions** diff --git a/ppl/src/main/java/org/opensearch/sql/ppl/utils/PPLQueryDataAnonymizer.java b/ppl/src/main/java/org/opensearch/sql/ppl/utils/PPLQueryDataAnonymizer.java index 11c47d137e8..398361fcea7 100644 --- a/ppl/src/main/java/org/opensearch/sql/ppl/utils/PPLQueryDataAnonymizer.java +++ b/ppl/src/main/java/org/opensearch/sql/ppl/utils/PPLQueryDataAnonymizer.java @@ -113,6 +113,7 @@ import org.opensearch.sql.ast.tree.UnresolvedPlan; import org.opensearch.sql.ast.tree.Values; import org.opensearch.sql.ast.tree.Window; +import org.opensearch.sql.ast.tree.Xyseries; import org.opensearch.sql.calcite.plan.OpenSearchConstants; import org.opensearch.sql.common.setting.Settings; import org.opensearch.sql.common.utils.StringUtils; @@ -824,6 +825,26 @@ public String visitTranspose(Transpose node, String context) { return anonymized.toString(); } + @Override + public String visitXyseries(Xyseries node, String context) { + String child = node.getChild().get(0).accept(this, context); + StringBuilder command = new StringBuilder(); + command.append(" | xyseries"); + if (node.getSeparator() != null && !": ".equals(node.getSeparator())) { + command.append(" sep=").append(MASK_LITERAL); + } + if (node.getFormat() != null) { + command.append(" format=").append(MASK_LITERAL); + } + command.append(" ").append(visitExpression(node.getXField())); + command.append(" ").append(visitExpression(node.getYNameField())); + command.append(" in (").append(MASK_LITERAL).append(")"); + String dataFields = + node.getYDataFields().stream().map(this::visitExpression).collect(Collectors.joining(",")); + command.append(" ").append(dataFields); + return StringUtils.format("%s%s", child, command.toString()); + } + @Override public String visitAppendCol(AppendCol node, String context) { String child = node.getChild().get(0).accept(this, context); diff --git a/ppl/src/test/java/org/opensearch/sql/ppl/utils/PPLQueryDataAnonymizerTest.java b/ppl/src/test/java/org/opensearch/sql/ppl/utils/PPLQueryDataAnonymizerTest.java index eba4f57112b..63f7dea81d7 100644 --- a/ppl/src/test/java/org/opensearch/sql/ppl/utils/PPLQueryDataAnonymizerTest.java +++ b/ppl/src/test/java/org/opensearch/sql/ppl/utils/PPLQueryDataAnonymizerTest.java @@ -327,6 +327,29 @@ public void testChartCommandOverBy() { anonymize("source=t | chart sum(amount) over gender by age")); } + @Test + public void testXyseriesCommand() { + assertEquals( + "source=table | stats avg(identifier) by identifier,identifier" + + " | xyseries identifier identifier in (***) identifier", + anonymize( + "source=t | stats avg(balance) by gender, state" + + " | xyseries state gender in (\"F\",\"M\") avg_balance")); + } + + @Test + public void testXyseriesCommandWithOptions() { + assertEquals( + "source=table | stats avg(identifier),max(identifier) by identifier,identifier" + + " | xyseries sep=*** format=*** identifier identifier in (***)" + + " identifier,identifier", + anonymize( + "source=t | stats avg(balance) as avg_balance, max(balance) as max_balance" + + " by gender, state" + + " | xyseries sep=\"_\" format=\"$AGG$_$VAL$\" state gender" + + " in (\"F\",\"M\") avg_balance, max_balance")); + } + // todo, sort order is ignored, it doesn't impact the log analysis. @Test public void testSortCommandWithOptions() {