Skip to content

Commit 7003abb

Browse files
committed
Implement a minimal viable version of expand
Signed-off-by: Yuanchun Shen <yuanchu@amazon.com>
1 parent ae55697 commit 7003abb

5 files changed

Lines changed: 76 additions & 81 deletions

File tree

core/src/main/java/org/opensearch/sql/analysis/Analyzer.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.opensearch.sql.ast.tree.CloseCursor;
5151
import org.opensearch.sql.ast.tree.Dedupe;
5252
import org.opensearch.sql.ast.tree.Eval;
53+
import org.opensearch.sql.ast.tree.Expand;
5354
import org.opensearch.sql.ast.tree.FetchCursor;
5455
import org.opensearch.sql.ast.tree.FillNull;
5556
import org.opensearch.sql.ast.tree.Filter;
@@ -623,6 +624,12 @@ public LogicalPlan visitML(ML node, AnalysisContext context) {
623624
return new LogicalML(child, node.getArguments());
624625
}
625626

627+
@Override
628+
public LogicalPlan visitExpand(Expand expand, AnalysisContext context) {
629+
throw new UnsupportedOperationException(
630+
"Expand is supported only when " + CALCITE_ENGINE_ENABLED.getKeyValue() + "=true");
631+
}
632+
626633
/** Build {@link LogicalTrendline} for Trendline command. */
627634
@Override
628635
public LogicalPlan visitTrendline(Trendline node, AnalysisContext context) {

core/src/main/java/org/opensearch/sql/ast/tree/Expand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public Expand attach(UnresolvedPlan child) {
3030

3131
@Override
3232
public List<UnresolvedPlan> getChild() {
33-
return ImmutableList.of(child);
33+
return this.child == null ? ImmutableList.of() : ImmutableList.of(this.child);
3434
}
3535

3636
@Override

core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -840,44 +840,45 @@ public RelNode visitExpand(Expand expand, CalcitePlanContext context) {
840840
// 1. Visit Children
841841
visitChildren(expand, context);
842842

843-
var relBuilder = context.relBuilder;
843+
RelBuilder relBuilder = context.relBuilder;
844844

845845
// 2. Get the field to expand
846846
Field arrayField = expand.getField();
847-
// 3. Unnest the array field
848-
// Analyze the array field to get its RexNode
849847
RexInputRef arrayFieldRex = (RexInputRef) rexVisitor.analyze(arrayField, context);
850-
// Push the original table to the RelBuilder stack
851-
// No alias is provided in the expand command, so we remove the original array field,
852-
// then replace it with the unnest result.
853-
// relBuilder.projectExcept(arrayFieldRex);
854848

855-
// Capture the outer row in a CorrelationId
849+
// 3. Capture the outer row in a CorrelationId
856850
Holder<RexCorrelVariable> correlVariable = Holder.empty();
857851
relBuilder.variable(correlVariable::set);
858852

859-
// Push a copy of the original table to the RelBuilder stack as right
860-
// side of the join.
853+
// 4. Push a copy of the original table to the RelBuilder stack as right
854+
// side of the correlate (join).
861855
relBuilder.push(relBuilder.peek());
862856
RexNode correlArrayField =
863857
relBuilder.field(
864858
context.rexBuilder.makeCorrel(relBuilder.peek().getRowType(), correlVariable.get().id),
865859
arrayFieldRex.getIndex());
866860

867-
// Filter rows where the array field is the same as the left side
861+
// 5. Filter rows where the array field is the same as the left side
868862
// TODO: This is not a standard way to use correlate and uncollect together.
869-
// Correct it in the future.
863+
// A filter should not be necessary. Correct it in the future.
870864
RexNode filterCondition = relBuilder.equals(correlArrayField, arrayFieldRex);
871865
relBuilder.filter(filterCondition);
866+
867+
// 6. Project only the array field for the uncollect operation
872868
relBuilder.project(List.of(correlArrayField), List.of(arrayField.getField().toString()));
873869

874-
// Alias is not supported in expand yet, we pass in an empty list
870+
// 7. Expand the array field using uncollect
875871
relBuilder.uncollect(List.of(), false);
876872

873+
// 8. Perform a nested-loop join (correlate) between the original table and the expanded
874+
// array field.
877875
// The last parameter has to refer to the array to be expanded on the left side. It will
878876
// be used by the right side to correlate with the left side.
879877
relBuilder.correlate(JoinRelType.INNER, correlVariable.get().id, List.of(arrayFieldRex));
880878

879+
// 8. Remove the original array field from the output. No alias is currently supported in the
880+
// expand command, so it can be safely deleted. Its name is re-used for the expanded element.
881+
relBuilder.projectExcept(arrayFieldRex);
881882
return relBuilder.peek();
882883
}
883884
}

integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteExpandCommandIT.java

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,65 @@
77

88
package org.opensearch.sql.calcite.remote;
99

10-
import org.opensearch.sql.ppl.ExpandCommandIT;
10+
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_ARRAY;
11+
import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_NESTED_SIMPLE;
12+
import static org.opensearch.sql.util.MatcherUtils.schema;
13+
import static org.opensearch.sql.util.MatcherUtils.verifyNumOfRows;
14+
import static org.opensearch.sql.util.MatcherUtils.verifySchema;
1115

12-
public class CalciteExpandCommandIT extends ExpandCommandIT {
16+
import org.json.JSONObject;
17+
import org.junit.Ignore;
18+
import org.junit.jupiter.api.Test;
19+
import org.opensearch.sql.ppl.PPLIntegTestCase;
20+
21+
public class CalciteExpandCommandIT extends PPLIntegTestCase {
1322
@Override
1423
public void init() throws Exception {
1524
super.init();
25+
loadIndex(Index.NESTED_SIMPLE);
26+
loadIndex(Index.ARRAY);
1627
enableCalcite();
1728
disallowCalciteFallback();
1829
}
30+
31+
@Test
32+
public void testExpandOnNested() throws Exception {
33+
JSONObject response =
34+
executeQuery(String.format("source=%s | expand address", TEST_INDEX_NESTED_SIMPLE));
35+
verifySchema(
36+
response,
37+
schema("name", "string"),
38+
schema("age", "bigint"),
39+
schema("id", "bigint"),
40+
schema("address", "struct"));
41+
verifyNumOfRows(response, 11);
42+
}
43+
44+
// TODO: confirm if expand on array (instead of nested) will be supported.
45+
// In Opensearch, a string field can store either a single string or an array of strings.
46+
// This makes it difficult to implement expand on array.
47+
@Ignore
48+
@Test
49+
public void testExpandOnArray() throws Exception {
50+
JSONObject response =
51+
executeQuery(String.format("source=%s | expand strings", TEST_INDEX_ARRAY));
52+
verifySchema(response, schema("numbers", "array"), schema("strings", "string"));
53+
verifyNumOfRows(response, 5);
54+
}
55+
56+
// TODO: confirm if expand with alias will be supported
57+
@Ignore
58+
@Test
59+
public void testExpandWithAlias() throws Exception {
60+
JSONObject response =
61+
executeQuery(String.format("source=%s | expand address as addr", TEST_INDEX_NESTED_SIMPLE));
62+
verifySchema(
63+
response,
64+
schema("name", "string"),
65+
schema("age", "integer"),
66+
schema("id", "integer"),
67+
schema("address", "array"),
68+
schema("addr", "struct"));
69+
verifyNumOfRows(response, 11);
70+
}
1971
}

integ-test/src/test/java/org/opensearch/sql/ppl/ExpandCommandIT.java

Lines changed: 0 additions & 65 deletions
This file was deleted.

0 commit comments

Comments
 (0)