Skip to content

Commit

Permalink
Implement integration tests, update Expand logic, rename data set.
Browse files Browse the repository at this point in the history
Signed-off-by: currantw <[email protected]>
  • Loading branch information
currantw committed Feb 5, 2025
1 parent 63a7364 commit 4b111c0
Show file tree
Hide file tree
Showing 9 changed files with 327 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public BindingTuple bindingTuples() {

@Override
public Map<String, ExprValue> tupleValue() {
return Collections.unmodifiableMap(valueMap);
return valueMap;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

package org.opensearch.sql.planner.physical;

import static org.opensearch.sql.data.type.ExprCoreType.ARRAY;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
Expand All @@ -14,6 +16,7 @@
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.opensearch.sql.data.model.ExprValue;
import org.opensearch.sql.data.model.ExprValueUtils;
import org.opensearch.sql.expression.ReferenceExpression;
import org.opensearch.sql.utils.PathUtils;

Expand Down Expand Up @@ -42,7 +45,7 @@ public List<PhysicalPlan> getChild() {
@Override
public boolean hasNext() {
while (expandedRows.isEmpty() && input.hasNext()) {
expandedRows = expandExprValue(input.next(), field.getAttr());
expandedRows = expandExprValueAtPath(input.next(), field.getAttr());
}

return !expandedRows.isEmpty();
Expand All @@ -53,23 +56,28 @@ public ExprValue next() {
return expandedRows.removeFirst();
}

/**
* Expands the {@link org.opensearch.sql.data.model.ExprCollectionValue} at the specified path and
* returns the resulting value. If the value is null or missing, the unmodified value is returned.
*/
private static List<ExprValue> expandExprValue(ExprValue rootExprValue, String path) {
/** Expands the {@link ExprValue} at the specified path and returns the resulting value. */
private static List<ExprValue> expandExprValueAtPath(ExprValue rootExprValue, String path) {

if (!PathUtils.containsExprValueAtPath(rootExprValue, path)) {
return List.of();
return new LinkedList<>(Collections.singletonList(rootExprValue));
}

ExprValue targetExprValue = PathUtils.getExprValueAtPath(rootExprValue, path);
if (targetExprValue.isMissing() || targetExprValue.isNull()) {
return List.of();
}
List<ExprValue> expandedExprValues = expandExprValue(targetExprValue);

return targetExprValue.collectionValue().stream()
return expandedExprValues.stream()
.map(v -> PathUtils.setExprValueAtPath(rootExprValue, path, v))
.collect(Collectors.toCollection(LinkedList::new));
}

/** Expands the given {@link ExprValue} and returns the result. */
private static List<ExprValue> expandExprValue(ExprValue exprValue) {
if (exprValue.type().equals(ARRAY)) {
List<ExprValue> values = exprValue.collectionValue();
return values.isEmpty() ? List.of(ExprValueUtils.nullValue()) : values;
}

return List.of(exprValue);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@
package org.opensearch.sql.planner.physical;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;
import static org.opensearch.sql.data.type.ExprCoreType.ARRAY;
import static org.opensearch.sql.data.type.ExprCoreType.INTEGER;
import static org.opensearch.sql.planner.physical.PhysicalPlanDSL.expand;

import java.util.List;
Expand All @@ -24,119 +22,181 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.opensearch.sql.data.model.ExprValue;
import org.opensearch.sql.data.model.ExprValueUtils;
import org.opensearch.sql.exception.ExpressionEvaluationException;
import org.opensearch.sql.expression.DSL;

@ToString
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
@ExtendWith(MockitoExtension.class)
class ExpandOperatorTest extends PhysicalPlanTestBase {
@Mock private PhysicalPlan inputPlan;

// Test constants
private static final Integer integerValue = 0;
private static final Double doubleValue = 0.0;
private static final String stringValue = "value";

private static final ExprValue nullExprValue = ExprValueUtils.nullValue();
private static final ExprValue missingExprValue = ExprValueUtils.missingValue();

// Test variables
@Mock private PhysicalPlan inputPlan;
private ExprValue inputRow;
private List<ExprValue> actualRows;
private List<ExprValue> expectedRows;

@Test
void testArrayEmpty() {
mockInput(
void testArray() {
inputRow =
ExprValueUtils.tupleValue(
Map.of("array_empty", ExprValueUtils.collectionValue(List.of()))));
List<ExprValue> actualRows = execute(expand(inputPlan, DSL.ref("array_empty", ARRAY)));
Map.of("array", ExprValueUtils.collectionValue(List.of(integerValue, doubleValue))));
mockInput(inputRow);

assertTrue(actualRows.isEmpty());
actualRows = execute(expand(inputPlan, DSL.ref("array", ARRAY)));
expectedRows =
List.of(
ExprValueUtils.tupleValue(Map.of("array", integerValue)),
ExprValueUtils.tupleValue(Map.of("array", doubleValue)));

assertEquals(expectedRows, actualRows);
}

@Test
void testArrayNull() {
mockInput(ExprValueUtils.tupleValue(Map.of("array_empty", ExprValueUtils.nullValue())));
List<ExprValue> actualRows = execute(expand(inputPlan, DSL.ref("array_empty", ARRAY)));
void testArrayEmpty() {
ExprValue inputRow =
ExprValueUtils.tupleValue(Map.of("array_empty", ExprValueUtils.collectionValue(List.of())));
mockInput(inputRow);

assertTrue(actualRows.isEmpty());
actualRows = execute(expand(inputPlan, DSL.ref("array_empty", ARRAY)));
expectedRows = List.of(ExprValueUtils.tupleValue(Map.of("array_empty", nullExprValue)));

assertEquals(expectedRows, actualRows);
}

@Test
void testArrayMissing() {
mockInput(ExprValueUtils.tupleValue(Map.of("array_missing", ExprValueUtils.missingValue())));
List<ExprValue> actualRows = execute(expand(inputPlan, DSL.ref("array_missing", ARRAY)));
void testArrayNested() {
ExprValue inputRow =
ExprValueUtils.tupleValue(
Map.of(
"struct",
ExprValueUtils.tupleValue(
Map.of("array", ExprValueUtils.collectionValue(List.of(stringValue))))));
mockInput(inputRow);

assertTrue(actualRows.isEmpty());
actualRows = execute(expand(inputPlan, DSL.ref("struct.array", ARRAY)));
expectedRows =
List.of(
ExprValueUtils.tupleValue(
Map.of("struct", ExprValueUtils.tupleValue(Map.of("array", stringValue)))));

assertEquals(expectedRows, actualRows);
}

@Test
void testArrayUnknown() {
List<ExprValue> actualRows = execute(expand(inputPlan, DSL.ref("array_unknown", ARRAY)));
assertTrue(actualRows.isEmpty());
void testScalar() {
ExprValue inputValue = ExprValueUtils.tupleValue(Map.of("scalar", stringValue));
mockInput(inputValue);

actualRows = execute(expand(inputPlan, DSL.ref("scalar", ARRAY)));
expectedRows = List.of(inputValue);

assertEquals(expectedRows, actualRows);
}

@Test
void testArray() {
mockInput(
ExprValueUtils.tupleValue(
Map.of("array", ExprValueUtils.collectionValue(List.of(integerValue, doubleValue)))));
List<ExprValue> actualRows = execute(expand(inputPlan, DSL.ref("array", ARRAY)));
void testScalarNull() {
ExprValue inputRow = ExprValueUtils.tupleValue(Map.of("scalar_null", nullExprValue));
mockInput(inputRow);

List<ExprValue> expectedRows =
List.of(
ExprValueUtils.tupleValue(Map.of("array", integerValue)),
ExprValueUtils.tupleValue(Map.of("array", doubleValue)));
actualRows = execute(expand(inputPlan, DSL.ref("scalar_null", ARRAY)));
expectedRows = List.of(inputRow);

assertEquals(expectedRows, actualRows);
}

@Test
void testArrayNested() {
mockInput(
void testScalarMissing() {

/** With {@link org.opensearch.sql.data.model.ExprMissingValue} */
inputRow = ExprValueUtils.tupleValue(Map.of());
mockInput(inputRow);

actualRows = execute(expand(inputPlan, DSL.ref("scalar_missing", ARRAY)));
expectedRows = List.of(inputRow);

assertEquals(expectedRows, actualRows);

/** Without {@link org.opensearch.sql.data.model.ExprMissingValue} */
inputRow = ExprValueUtils.tupleValue(Map.of("scalar_missing", missingExprValue));
mockInput(inputRow);

actualRows = execute(expand(inputPlan, DSL.ref("scalar_missing", ARRAY)));
expectedRows = List.of(inputRow);

assertEquals(expectedRows, actualRows);
}

@Test
void testScalarNested() {
ExprValue rowInput =
ExprValueUtils.tupleValue(
Map.of(
"struct",
ExprValueUtils.tupleValue(
Map.of(
"array_nested", ExprValueUtils.collectionValue(List.of(stringValue)))))));
List<ExprValue> actualRows = execute(expand(inputPlan, DSL.ref("struct.array_nested", ARRAY)));
Map.of("struct", ExprValueUtils.tupleValue(Map.of("scalar", stringValue))));
mockInput(rowInput);

List<ExprValue> expectedRows =
actualRows = execute(expand(inputPlan, DSL.ref("struct.scalar", ARRAY)));
expectedRows =
List.of(
ExprValueUtils.tupleValue(
Map.of("struct", ExprValueUtils.tupleValue(Map.of("array_nested", stringValue)))));
Map.of("struct", ExprValueUtils.tupleValue(Map.of("scalar", stringValue)))));

assertEquals(expectedRows, actualRows);
}

@Test
void testPathUnknown() {
actualRows = execute(expand(inputPlan, DSL.ref("unknown", ARRAY)));
expectedRows = List.of();

assertEquals(expectedRows, actualRows);
}

@Test
void testAncestorNull() {
mockInput(ExprValueUtils.tupleValue(Map.of("struct", ExprValueUtils.nullValue())));
ExprValue rowInput = ExprValueUtils.tupleValue(Map.of("struct_null", nullExprValue));
mockInput(rowInput);

List<ExprValue> actualRows = execute(expand(inputPlan, DSL.ref("struct.array_nested", ARRAY)));
assertTrue(actualRows.isEmpty());
actualRows = execute(expand(inputPlan, DSL.ref("struct_null.unreachable", ARRAY)));
expectedRows = List.of(rowInput);

assertEquals(expectedRows, actualRows);
}

@Test
void testAncestorMissing() {
mockInput(ExprValueUtils.tupleValue(Map.of("struct", ExprValueUtils.missingValue())));

List<ExprValue> actualRows = execute(expand(inputPlan, DSL.ref("struct.array_nested", ARRAY)));
assertTrue(actualRows.isEmpty());
/** With {@link org.opensearch.sql.data.model.ExprMissingValue} */
inputRow = ExprValueUtils.tupleValue(Map.of());
mockInput(inputRow);

actualRows = execute(expand(inputPlan, DSL.ref("struct_missing.unreachable", ARRAY)));
expectedRows = List.of(inputRow);

assertEquals(expectedRows, actualRows);

/** Without {@link org.opensearch.sql.data.model.ExprMissingValue} */
inputRow = ExprValueUtils.tupleValue(Map.of("struct_missing", missingExprValue));
mockInput(inputRow);

actualRows = execute(expand(inputPlan, DSL.ref("struct_missing.unreachable", ARRAY)));
expectedRows = List.of(inputRow);

assertEquals(expectedRows, actualRows);
}

@Test
void testAncestorUnknown() {
List<ExprValue> actualRows = execute(expand(inputPlan, DSL.ref("struct.array_nested", ARRAY)));
actualRows = execute(expand(inputPlan, DSL.ref("unknown.unreachable", ARRAY)));
assertTrue(actualRows.isEmpty());
}

@Test
void testInvalidType() {
mockInput(ExprValueUtils.tupleValue(Map.of("integer", integerValue)));

Exception ex =
assertThrows(
ExpressionEvaluationException.class,
() -> execute(expand(inputPlan, DSL.ref("integer", INTEGER))));
assertEquals("invalid to get collectionValue from value of type INTEGER", ex.getMessage());
}

/** Mocks the input plan to return a single row with the given input value. */
private void mockInput(ExprValue mockInputValue) {
when(inputPlan.hasNext()).thenReturn(true, false);
Expand Down
4 changes: 4 additions & 0 deletions doctest/test_data/expand.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{"name": "Seattle", "location": { "state": "Washington", "country": "United States", "coordinates": {"latitude": 47.6061, "longitude": -122.3328}}}
{"name": "Vancouver", "location": { "province": "British Columbia", "country": "Canada", "coordinates": {"latitude": 49.2827, "longitude": -123.1207}}}
{"name": "Null Location", "location": null}
{"name": "Null Coordinates", "location": { "state": "Victoria", "country": "Australia", "coordinates": null}}
34 changes: 34 additions & 0 deletions doctest/test_mapping/expand.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"mappings": {
"properties": {
"name": {
"type": "keyword"
},
"location": {
"type": "object",
"properties": {
"state": {
"type": "keyword"
},
"province": {
"type": "keyword"
},
"country": {
"type": "keyword"
},
"coordinates": {
"type": "object",
"properties": {
"latitude": {
"type": "double"
},
"longitude": {
"type": "double"
}
}
}
}
}
}
}
}
Loading

0 comments on commit 4b111c0

Please sign in to comment.