From 998ea4c6bc702d0f771e2111f63ead3003728895 Mon Sep 17 00:00:00 2001 From: Peng Huo Date: Wed, 22 Jul 2026 15:35:50 +0000 Subject: [PATCH] Anonymize xyseries command and mark it experimental in docs Add visitXyseries to PPLQueryDataAnonymizer so the xyseries stage appears in the anonymized query logged for every PPL request, with pivot literals and options masked. Previously the visitor fell through to visitChildren and the entire xyseries clause was silently dropped from log output. Also flip the xyseries entry in docs/user/ppl/index.md from stable to experimental (since 3.8), matching how other newly-introduced commands are listed. Signed-off-by: Peng Huo --- docs/user/ppl/index.md | 2 +- .../sql/ppl/utils/PPLQueryDataAnonymizer.java | 21 +++++++++++++++++ .../ppl/utils/PPLQueryDataAnonymizerTest.java | 23 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) 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() {