Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
6 changes: 6 additions & 0 deletions core/src/main/java/org/opensearch/sql/analysis/Analyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
import org.opensearch.sql.ast.tree.StreamWindow;
import org.opensearch.sql.ast.tree.SubqueryAlias;
import org.opensearch.sql.ast.tree.TableFunction;
import org.opensearch.sql.ast.tree.Transpose;
import org.opensearch.sql.ast.tree.Trendline;
import org.opensearch.sql.ast.tree.UnresolvedPlan;
import org.opensearch.sql.ast.tree.Values;
Expand Down Expand Up @@ -703,6 +704,11 @@ public LogicalPlan visitML(ML node, AnalysisContext context) {
return new LogicalML(child, node.getArguments());
}

@Override
public LogicalPlan visitTranspose(Transpose node, AnalysisContext context) {
throw getOnlyForCalciteException("Transpose");
}

@Override
public LogicalPlan visitBin(Bin node, AnalysisContext context) {
throw getOnlyForCalciteException("Bin");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
import org.opensearch.sql.ast.tree.StreamWindow;
import org.opensearch.sql.ast.tree.SubqueryAlias;
import org.opensearch.sql.ast.tree.TableFunction;
import org.opensearch.sql.ast.tree.Transpose;
import org.opensearch.sql.ast.tree.Trendline;
import org.opensearch.sql.ast.tree.Values;
import org.opensearch.sql.ast.tree.Window;
Expand Down Expand Up @@ -282,6 +283,10 @@ public T visitReverse(Reverse node, C context) {
return visitChildren(node, context);
}

public T visitTranspose(Transpose node, C context) {
return visitChildren(node, context);
}

public T visitChart(Chart node, C context) {
return visitChildren(node, context);
}
Expand Down
66 changes: 66 additions & 0 deletions core/src/main/java/org/opensearch/sql/ast/tree/Transpose.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.ast.tree;

import com.google.common.collect.ImmutableList;
import java.util.List;
import lombok.*;
import org.opensearch.sql.ast.AbstractNodeVisitor;
import org.opensearch.sql.ast.expression.Argument;
import org.opensearch.sql.common.utils.StringUtils;

/** AST node represent Transpose operation. */
@Getter
@Setter
@ToString
@EqualsAndHashCode(callSuper = false)
@RequiredArgsConstructor
public class Transpose extends UnresolvedPlan {
private final @NonNull java.util.Map<String, Argument> arguments;
private UnresolvedPlan child;
private static final int MAX_LIMIT_TRANSPOSE = 10000;

public Integer getMaxRows() {
Integer maxRows = 5;
if (arguments.containsKey("number") && arguments.get("number").getValue() != null) {
try {
maxRows = Integer.parseInt(arguments.get("number").getValue().toString());
} catch (NumberFormatException e) {
// log warning and use default
maxRows = 5;
}
}
if (maxRows > MAX_LIMIT_TRANSPOSE) {
throw new IllegalArgumentException(
StringUtils.format("Maximum limit to transpose is %s", MAX_LIMIT_TRANSPOSE));
}
return maxRows;
}

public String getColumnName() {
String columnName = "column";
if (arguments.containsKey("columnName") && arguments.get("columnName").getValue() != null) {
columnName = arguments.get("columnName").getValue().toString();
}
return columnName;
}

@Override
public Transpose attach(UnresolvedPlan child) {
this.child = child;
return this;
}

@Override
public List<UnresolvedPlan> getChild() {
return this.child == null ? ImmutableList.of() : ImmutableList.of(this.child);
}

@Override
public <T, C> T accept(AbstractNodeVisitor<T, C> nodeVisitor, C context) {
return nodeVisitor.visitTranspose(this, context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFamily;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexCorrelVariable;
import org.apache.calcite.rex.RexInputRef;
Expand Down Expand Up @@ -696,6 +697,64 @@ public RelNode visitReverse(
return context.relBuilder.peek();
}

@Override
public RelNode visitTranspose(
org.opensearch.sql.ast.tree.Transpose node, CalcitePlanContext context) {

visitChildren(node, context);

int maxRows =
Optional.ofNullable(node.getMaxRows())
.filter(r -> r > 0)
.orElseThrow(() -> new IllegalArgumentException("maxRows must be positive"));

String columnName = node.getColumnName();
List<String> fieldNames = context.relBuilder.peek().getRowType().getFieldNames();

RelBuilder b = context.relBuilder;
RexBuilder rx = context.rexBuilder;
RelDataType varchar = rx.getTypeFactory().createSqlType(SqlTypeName.VARCHAR);

// Step 1: ROW_NUMBER
b.projectPlus(
b.aggregateCall(SqlStdOperatorTable.ROW_NUMBER)
.over()
.rowsTo(RexWindowBounds.CURRENT_ROW)
.as(PlanUtils.ROW_NUMBER_COLUMN_FOR_TRANSPOSE));

// Step 2: UNPIVOT
b.unpivot(
false,
ImmutableList.of("value"),
ImmutableList.of(columnName),
fieldNames.stream()
.map(
f ->
Map.entry(
ImmutableList.of(rx.makeLiteral(f)),
ImmutableList.of((RexNode) rx.makeCast(varchar, b.field(f), true))))
.collect(Collectors.toList()));

// Step 3: PIVOT
b.pivot(
b.groupKey(b.field(columnName)),
ImmutableList.of(b.max(b.field("value"))),
ImmutableList.of(b.field(PlanUtils.ROW_NUMBER_COLUMN_FOR_TRANSPOSE)),
IntStream.rangeClosed(1, maxRows)
.mapToObj(i -> Map.entry("row " + i, ImmutableList.of((RexNode) b.literal(i))))
.collect(Collectors.toList()));

// Step 4: RENAME
List<String> cleanNames = new ArrayList<>();
cleanNames.add(columnName);
for (int i = 1; i <= maxRows; i++) {
cleanNames.add("row " + i);
}
b.rename(cleanNames);

return b.peek();
}

@Override
public RelNode visitBin(Bin node, CalcitePlanContext context) {
visitChildren(node, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public interface PlanUtils {
String ROW_NUMBER_COLUMN_FOR_SUBSEARCH = "_row_number_subsearch_";
String ROW_NUMBER_COLUMN_FOR_STREAMSTATS = "__stream_seq__";
String ROW_NUMBER_COLUMN_FOR_CHART = "_row_number_chart_";
String ROW_NUMBER_COLUMN_FOR_TRANSPOSE = "_row_number_transpose_";

static SpanUnit intervalUnitToSpanUnit(IntervalUnit unit) {
return switch (unit) {
Expand Down
1 change: 1 addition & 0 deletions docs/category.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"user/ppl/cmd/timechart.md",
"user/ppl/cmd/top.md",
"user/ppl/cmd/trendline.md",
"user/ppl/cmd/transpose.md",
"user/ppl/cmd/where.md",
"user/ppl/functions/aggregations.md",
"user/ppl/functions/collection.md",
Expand Down
92 changes: 92 additions & 0 deletions docs/user/ppl/cmd/transpose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# transpose

## Description

The `transpose` command outputs the requested number of rows as columns, effectively transposing each result row into a corresponding column of field values.

## Syntax

transpose [int] [column_name=<string>]

* number-of-rows: optional. The number of rows to transform into columns. Default value is 5. Maximum allowed is 10000.
* column_name: optional. The name of the first column to use when transposing rows. This column holds the field names.


## Example 1: Transpose results

This example shows transposing wihtout any parameters. It transforms 5 rows into columns as default is 5.

```ppl
source=accounts
| head 5
| fields account_number, firstname, lastname, balance
| transpose
```

Expected output:

```text
fetched rows / total rows = 4/4
+----------------+-------+--------+---------+-------+-------+
| column | row 1 | row 2 | row 3 | row 4 | row 5 |
|----------------+-------+--------+---------+-------+-------|
| account_number | 1 | 6 | 13 | 18 | null |
| firstname | Amber | Hattie | Nanette | Dale | null |
| balance | 39225 | 5686 | 32838 | 4180 | null |
| lastname | Duke | Bond | Bates | Adams | null |
+----------------+-------+--------+---------+-------+-------+
```

## Example 2: Tranpose results up to a provided number of rows.

This example shows transposing wihtout any parameters. It transforms 4 rows into columns as default is 5.

```ppl
source=accounts
| head 5
| fields account_number, firstname, lastname, balance
| transpose 4
```

Expected output:

```text
fetched rows / total rows = 4/4
+----------------+-------+--------+---------+-------+
| column | row 1 | row 2 | row 3 | row 4 |
|----------------+-------+--------+---------+-------|
| account_number | 1 | 6 | 13 | 18 |
| firstname | Amber | Hattie | Nanette | Dale |
| balance | 39225 | 5686 | 32838 | 4180 |
| lastname | Duke | Bond | Bates | Adams |
+----------------+-------+--------+---------+-------+
```

## Example 2: Tranpose results up to a provided number of rows and first column with specified column name.

This example shows transposing wihtout any parameters. It transforms 4 rows into columns as default is 5.

```ppl
source=accounts
| head 5
| fields account_number, firstname, lastname, balance
| transpose 4 column_name='column_names'
```

Expected output:

```text
fetched rows / total rows = 4/4
+----------------+-------+--------+---------+-------+
| column_names | row 1 | row 2 | row 3 | row 4 |
|----------------+-------+--------+---------+-------|
| account_number | 1 | 6 | 13 | 18 |
| firstname | Amber | Hattie | Nanette | Dale |
| balance | 39225 | 5686 | 32838 | 4180 |
| lastname | Duke | Bond | Bates | Adams |
+----------------+-------+--------+---------+-------+
```

## Limitations

The `transpose` command transforms up to a number of rows specified and if not enough rows found, it shows those transposed rows as null columns.
7 changes: 4 additions & 3 deletions docs/user/ppl/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@ source=accounts
| [describe command](cmd/describe.md) | 2.1 | stable (since 2.1) | Query the metadata of an index. |
| [explain command](cmd/explain.md) | 3.1 | stable (since 3.1) | Explain the plan of query. |
| [show datasources command](cmd/showdatasources.md) | 2.4 | stable (since 2.4) | Query datasources configured in the PPL engine. |
| [addtotals command](cmd/addtotals.md) | 3.4 | stable (since 3.4) | Adds row and column values and appends a totals column and row. |
| [addcoltotals command](cmd/addcoltotals.md) | 3.4 | stable (since 3.4) | Adds column values and appends a totals row. |

| [addtotals command](cmd/addtotals.md) | 3.5 | stable (since 3.5) | Adds row and column values and appends a totals column and row. |
| [addcoltotals command](cmd/addcoltotals.md) | 3.5 | stable (since 3.5) | Adds column values and appends a totals row. |
| [transpose command](cmd/transpose.md) | 3.5 | stable (since 3.5) | Transpose rows to columns. |

- [Syntax](cmd/syntax.md) - PPL query structure and command syntax formatting
* **Functions**
- [Aggregation Functions](functions/aggregations.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
CalciteTextFunctionIT.class,
CalciteTopCommandIT.class,
CalciteTrendlineCommandIT.class,
CalciteTransposeCommandIT.class,
CalciteVisualizationFormatIT.class,
CalciteWhereCommandIT.class,
CalcitePPLTpchIT.class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2021,6 +2021,18 @@ public void testaddColTotalsExplain() throws IOException {
+ "| addcoltotals balance age label='GrandTotal'"));
}

@Test
public void testTransposeExplain() throws IOException {
enabledOnlyWhenPushdownIsEnabled();
String expected = loadExpectedPlan("explain_transpose.yaml");
assertYamlEqualsIgnoreId(
expected,
explainQueryYaml(
"source=opensearch-sql_test_index_account"
+ "| head 5 "
+ "| transpose 4 column_name='column_names'"));
}

public void testComplexDedup() throws IOException {
enabledOnlyWhenPushdownIsEnabled();
String expected = loadExpectedPlan("explain_dedup_complex1.yaml");
Expand Down
Loading
Loading