forked from opensearch-project/sql
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: currantw <[email protected]>
- Loading branch information
Showing
4 changed files
with
279 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
core/src/main/java/org/opensearch/sql/utils/PathUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.sql.utils; | ||
|
||
import static org.opensearch.sql.data.type.ExprCoreType.STRUCT; | ||
|
||
import java.util.Arrays; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.regex.Pattern; | ||
import lombok.experimental.UtilityClass; | ||
import org.opensearch.sql.data.model.ExprTupleValue; | ||
import org.opensearch.sql.data.model.ExprValue; | ||
import org.opensearch.sql.exception.SemanticCheckException; | ||
|
||
@UtilityClass | ||
public class PathUtils { | ||
|
||
private final Pattern PATH_SEPARATOR_PATTERN = Pattern.compile(".", Pattern.LITERAL); | ||
|
||
/** Returns true if a value exists at the specified path within the given root value. */ | ||
public boolean containsExprValueAtPath(ExprValue root, String path) { | ||
List<String> pathComponents = splitPath(path); | ||
return containsExprValueForPathComponents(root, pathComponents); | ||
} | ||
|
||
/** | ||
* Returns the {@link ExprValue} at the specified path within the given root value. Returns {@code | ||
* null} if the root value does not contain the path - see {@link | ||
* PathUtils#containsExprValueAtPath}. | ||
*/ | ||
public ExprValue getExprValueAtPath(ExprValue root, String path) { | ||
List<String> pathComponents = splitPath(path); | ||
|
||
if (!containsExprValueForPathComponents(root, pathComponents)) { | ||
return null; | ||
} | ||
|
||
return getExprValueForPathComponents(root, pathComponents); | ||
} | ||
|
||
/** | ||
* Sets the {@link ExprValue} at the specified path within the given root value and returns the | ||
* result. Throws {@link SemanticCheckException} if the root value does not contain the path - see | ||
* {@link PathUtils#containsExprValueAtPath}. | ||
*/ | ||
public ExprValue setExprValueAtPath(ExprValue root, String path, ExprValue newValue) { | ||
List<String> pathComponents = splitPath(path); | ||
|
||
if (!containsExprValueForPathComponents(root, pathComponents)) { | ||
throw new SemanticCheckException(String.format("Field path '%s' does not exist.", path)); | ||
} | ||
|
||
return setExprValueForPathComponents(root, pathComponents, newValue); | ||
} | ||
|
||
/** Helper method for {@link PathUtils#containsExprValueAtPath}. */ | ||
private boolean containsExprValueForPathComponents(ExprValue root, List<String> pathComponents) { | ||
|
||
if (pathComponents.isEmpty()) { | ||
return true; | ||
} | ||
|
||
if (!root.type().equals(STRUCT)) { | ||
return false; | ||
} | ||
|
||
String currentPathComponent = pathComponents.getFirst(); | ||
List<String> remainingPathComponents = pathComponents.subList(1, pathComponents.size()); | ||
|
||
Map<String, ExprValue> exprValueMap = root.tupleValue(); | ||
if (!exprValueMap.containsKey(currentPathComponent)) { | ||
return false; | ||
} | ||
|
||
return containsExprValueForPathComponents( | ||
exprValueMap.get(currentPathComponent), remainingPathComponents); | ||
} | ||
|
||
/** Helper method for {@link PathUtils#getExprValueAtPath}. */ | ||
private ExprValue getExprValueForPathComponents(ExprValue root, List<String> pathComponents) { | ||
|
||
if (pathComponents.isEmpty()) { | ||
return root; | ||
} | ||
|
||
String currentPathComponent = pathComponents.getFirst(); | ||
List<String> remainingPathComponents = pathComponents.subList(1, pathComponents.size()); | ||
|
||
Map<String, ExprValue> exprValueMap = root.tupleValue(); | ||
return getExprValueForPathComponents( | ||
exprValueMap.get(currentPathComponent), remainingPathComponents); | ||
} | ||
|
||
/** Helper method for {@link PathUtils#setExprValueAtPath}. */ | ||
private ExprValue setExprValueForPathComponents( | ||
ExprValue root, List<String> pathComponents, ExprValue newValue) { | ||
|
||
if (pathComponents.isEmpty()) { | ||
return newValue; | ||
} | ||
|
||
String currentPathComponent = pathComponents.getFirst(); | ||
List<String> remainingPathComponents = pathComponents.subList(1, pathComponents.size()); | ||
|
||
Map<String, ExprValue> exprValueMap = new HashMap<>(root.tupleValue()); | ||
exprValueMap.put( | ||
currentPathComponent, | ||
setExprValueForPathComponents( | ||
exprValueMap.get(currentPathComponent), remainingPathComponents, newValue)); | ||
|
||
return ExprTupleValue.fromExprValueMap(exprValueMap); | ||
} | ||
|
||
/** Splits the given path and returns the corresponding components. */ | ||
private List<String> splitPath(String path) { | ||
return Arrays.asList(PATH_SEPARATOR_PATTERN.split(path)); | ||
} | ||
} |
134 changes: 134 additions & 0 deletions
134
core/src/test/java/org/opensearch/sql/utils/PathUtilsTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.sql.utils; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertFalse; | ||
import static org.junit.jupiter.api.Assertions.assertNull; | ||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
import java.util.Map; | ||
import lombok.ToString; | ||
import org.junit.jupiter.api.Test; | ||
import org.opensearch.sql.data.model.ExprValue; | ||
import org.opensearch.sql.data.model.ExprValueUtils; | ||
import org.opensearch.sql.exception.SemanticCheckException; | ||
|
||
@ToString | ||
class PathUtilsTest { | ||
|
||
// Test values | ||
private final ExprValue value = ExprValueUtils.integerValue(0); | ||
private final ExprValue newValue = ExprValueUtils.stringValue("value"); | ||
private final ExprValue nullValue = ExprValueUtils.nullValue(); | ||
private final ExprValue missingValue = ExprValueUtils.missingValue(); | ||
|
||
private final ExprValue struct1Value = ExprValueUtils.tupleValue(Map.of("field", value)); | ||
private final ExprValue struct2Value = | ||
ExprValueUtils.tupleValue( | ||
Map.of("struct1", ExprValueUtils.tupleValue(Map.of("field", value)))); | ||
|
||
private final ExprValue input = | ||
ExprValueUtils.tupleValue( | ||
Map.ofEntries( | ||
Map.entry("field", value), | ||
Map.entry("struct1", struct1Value), | ||
Map.entry("struct2", struct2Value), | ||
Map.entry("struct_null", nullValue), | ||
Map.entry("struct_missing", missingValue))); | ||
|
||
@Test | ||
void testContainsExprValueForPathComponents() { | ||
assertTrue(PathUtils.containsExprValueAtPath(input, "field")); | ||
assertTrue(PathUtils.containsExprValueAtPath(input, "struct1.field")); | ||
assertTrue(PathUtils.containsExprValueAtPath(input, "struct2.struct1.field")); | ||
|
||
assertFalse(PathUtils.containsExprValueAtPath(input, "field_invalid")); | ||
assertFalse(PathUtils.containsExprValueAtPath(input, "struct_null.field")); | ||
assertFalse(PathUtils.containsExprValueAtPath(input, "struct_missing.field")); | ||
assertFalse(PathUtils.containsExprValueAtPath(input, "field.field")); | ||
} | ||
|
||
@Test | ||
void testGetExprValueForPathComponents() { | ||
assertEquals(value, PathUtils.getExprValueAtPath(input, "field")); | ||
assertEquals(value, PathUtils.getExprValueAtPath(input, "struct1.field")); | ||
assertEquals(value, PathUtils.getExprValueAtPath(input, "struct2.struct1.field")); | ||
|
||
assertNull(PathUtils.getExprValueAtPath(input, "field_invalid")); | ||
assertNull(PathUtils.getExprValueAtPath(input, "struct_null.field")); | ||
assertNull(PathUtils.getExprValueAtPath(input, "struct_missing.field")); | ||
assertNull(PathUtils.getExprValueAtPath(input, "field.field")); | ||
} | ||
|
||
@Test | ||
void testSetExprValueForPathComponents() { | ||
ExprValue expected; | ||
ExprValue actual; | ||
|
||
expected = | ||
ExprValueUtils.tupleValue( | ||
Map.ofEntries( | ||
Map.entry("field", newValue), | ||
Map.entry("struct1", struct1Value), | ||
Map.entry("struct2", struct2Value), | ||
Map.entry("struct_null", nullValue), | ||
Map.entry("struct_missing", missingValue))); | ||
actual = PathUtils.setExprValueAtPath(input, "field", newValue); | ||
assertEquals(expected, actual); | ||
|
||
expected = | ||
ExprValueUtils.tupleValue( | ||
Map.ofEntries( | ||
Map.entry("field", value), | ||
Map.entry("struct1", ExprValueUtils.tupleValue(Map.of("field", newValue))), | ||
Map.entry("struct2", struct2Value), | ||
Map.entry("struct_null", nullValue), | ||
Map.entry("struct_missing", missingValue))); | ||
actual = PathUtils.setExprValueAtPath(input, "struct1.field", newValue); | ||
assertEquals(expected, actual); | ||
|
||
expected = | ||
ExprValueUtils.tupleValue( | ||
Map.ofEntries( | ||
Map.entry("field", value), | ||
Map.entry("struct1", struct1Value), | ||
Map.entry( | ||
"struct2", | ||
ExprValueUtils.tupleValue( | ||
Map.of("struct1", ExprValueUtils.tupleValue(Map.of("field", newValue))))), | ||
Map.entry("struct_null", nullValue), | ||
Map.entry("struct_missing", missingValue))); | ||
assertEquals(expected, PathUtils.setExprValueAtPath(input, "struct2.struct1.field", newValue)); | ||
|
||
Exception ex; | ||
|
||
ex = | ||
assertThrows( | ||
SemanticCheckException.class, | ||
() -> PathUtils.setExprValueAtPath(input, "field_invalid", newValue)); | ||
assertEquals("Field path 'field_invalid' does not exist.", ex.getMessage()); | ||
|
||
ex = | ||
assertThrows( | ||
SemanticCheckException.class, | ||
() -> PathUtils.setExprValueAtPath(input, "struct_null.field_invalid", newValue)); | ||
assertEquals("Field path 'struct_null.field_invalid' does not exist.", ex.getMessage()); | ||
|
||
ex = | ||
assertThrows( | ||
SemanticCheckException.class, | ||
() -> PathUtils.setExprValueAtPath(input, "struct_missing.field_invalid", newValue)); | ||
assertEquals("Field path 'struct_missing.field_invalid' does not exist.", ex.getMessage()); | ||
|
||
ex = | ||
assertThrows( | ||
SemanticCheckException.class, | ||
() -> PathUtils.setExprValueAtPath(input, "field.field_invalid", newValue)); | ||
assertEquals("Field path 'field.field_invalid' does not exist.", ex.getMessage()); | ||
} | ||
} |