Skip to content

Commit 65baa2a

Browse files
authored
Implement spath command with field resolution (#5028)
* Implement spath command with field resolution Signed-off-by: Tomoyuki Morita <moritato@amazon.com> * Fix for test failure Signed-off-by: Tomoyuki Morita <moritato@amazon.com> * Another test fix Signed-off-by: Tomoyuki Morita <moritato@amazon.com> * Fix test failure Signed-off-by: Tomoyuki Morita <moritato@amazon.com> * Address comments Signed-off-by: Tomoyuki Morita <moritato@amazon.com> * Address comment Signed-off-by: Tomoyuki Morita <moritato@amazon.com> * Address comments Signed-off-by: Tomoyuki Morita <moritato@amazon.com> * Address comment Signed-off-by: Tomoyuki Morita <moritato@amazon.com> * Add test case, etc. Signed-off-by: Tomoyuki Morita <moritato@amazon.com> * Quick fix Signed-off-by: Tomoyuki Morita <moritato@amazon.com> * Fix javadoc and test Signed-off-by: Tomoyuki Morita <moritato@amazon.com> * Fix DebugUtils Signed-off-by: Tomoyuki Morita <moritato@amazon.com> * Minor fix Signed-off-by: Tomoyuki Morita <moritato@amazon.com> * Update doc Signed-off-by: Tomoyuki Morita <moritato@amazon.com> --------- Signed-off-by: Tomoyuki Morita <moritato@amazon.com> Signed-off-by: Tomoyuki MORITA <moritato@amazon.com>
1 parent 389971f commit 65baa2a

39 files changed

Lines changed: 2991 additions & 168 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.common.utils;
7+
8+
import java.util.Collection;
9+
import java.util.Map;
10+
import java.util.stream.Collectors;
11+
import org.apache.logging.log4j.LogManager;
12+
import org.apache.logging.log4j.Logger;
13+
14+
/**
15+
* Utility class for debugging operations. This class is only for debugging purpose, and not
16+
* intended to be used in production code.
17+
*/
18+
public class DebugUtils {
19+
// Update this to true while you are debugging. (Safe guard to avoid usage in production code. )
20+
private static final boolean IS_DEBUG = false;
21+
private static final Logger logger = LogManager.getLogger(DebugUtils.class);
22+
23+
public static <T> T debug(T obj, String message) {
24+
verifyDebug();
25+
print("### %s: %s (at %s)", message, stringify(obj), getCalledFrom(1));
26+
return obj;
27+
}
28+
29+
public static <T> T debug(T obj) {
30+
verifyDebug();
31+
print("### %s (at %s)", stringify(obj), getCalledFrom(1));
32+
return obj;
33+
}
34+
35+
private static void verifyDebug() {
36+
if (!IS_DEBUG) {
37+
throw new RuntimeException("DebugUtils can be used only for local debugging.");
38+
}
39+
}
40+
41+
private static void print(String format, Object... args) {
42+
logger.info(String.format(format, args));
43+
}
44+
45+
private static String getCalledFrom(int pos) {
46+
RuntimeException e = new RuntimeException();
47+
StackTraceElement item = e.getStackTrace()[pos + 1];
48+
return item.getClassName() + "." + item.getMethodName() + ":" + item.getLineNumber();
49+
}
50+
51+
private static String stringify(Collection<?> items) {
52+
if (items == null) {
53+
return "null";
54+
}
55+
56+
if (items.isEmpty()) {
57+
return "()";
58+
}
59+
60+
String result = items.stream().map(i -> stringify(i)).collect(Collectors.joining(","));
61+
62+
return "(" + result + ")";
63+
}
64+
65+
private static String stringify(Map<?, ?> map) {
66+
if (map == null) {
67+
return "[[null]]";
68+
}
69+
70+
if (map.isEmpty()) {
71+
return "[[EMPTY]]";
72+
}
73+
74+
String result =
75+
map.entrySet().stream()
76+
.map(entry -> entry.getKey() + ": " + stringify(entry.getValue()))
77+
.collect(Collectors.joining(","));
78+
return "{" + result + "}";
79+
}
80+
81+
private static String stringify(Object obj) {
82+
if (obj instanceof Collection) {
83+
return stringify((Collection) obj);
84+
} else if (obj instanceof Map) {
85+
return stringify((Map) obj);
86+
}
87+
return String.valueOf(obj);
88+
}
89+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.common.utils;
7+
8+
import static org.junit.Assert.assertThrows;
9+
10+
import org.junit.Test;
11+
12+
public class DebugUtilsTest {
13+
14+
@Test
15+
public void testDebugThrowsRuntimeException() {
16+
assertThrows(RuntimeException.class, () -> DebugUtils.debug("test", "test message"));
17+
}
18+
19+
@Test
20+
public void testDebugWithoutMessageThrowsRuntimeException() {
21+
assertThrows(RuntimeException.class, () -> DebugUtils.debug("test"));
22+
}
23+
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
import org.opensearch.sql.ast.tree.Values;
105105
import org.opensearch.sql.ast.tree.Window;
106106
import org.opensearch.sql.common.antlr.SyntaxCheckException;
107+
import org.opensearch.sql.common.patterns.PatternUtils;
107108
import org.opensearch.sql.data.model.ExprMissingValue;
108109
import org.opensearch.sql.data.type.ExprCoreType;
109110
import org.opensearch.sql.datasource.DataSourceService;
@@ -953,10 +954,10 @@ private Aggregation analyzePatternsAgg(Patterns node) {
953954
List<UnresolvedExpression> aggExprs =
954955
Stream.of(
955956
new Alias(
956-
"pattern_count",
957+
PatternUtils.PATTERN_COUNT,
957958
new AggregateFunction(BuiltinFunctionName.COUNT.name(), AllFields.of())),
958959
new Alias(
959-
"sample_logs",
960+
PatternUtils.SAMPLE_LOGS,
960961
new AggregateFunction(
961962
BuiltinFunctionName.TAKE.name(),
962963
node.getSourceField(),
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.ast;
7+
8+
import lombok.experimental.UtilityClass;
9+
import org.opensearch.sql.ast.expression.Let;
10+
import org.opensearch.sql.ast.expression.subquery.SubqueryExpression;
11+
12+
/** Utility class for AST node operations shared among visitor classes. */
13+
@UtilityClass
14+
public class AstNodeUtils {
15+
16+
/**
17+
* Checks if an AST node contains a subquery expression.
18+
*
19+
* @param expr The AST node to check
20+
* @return true if the node or any of its children contains a subquery expression
21+
*/
22+
public static boolean containsSubqueryExpression(Node expr) {
23+
if (expr == null) {
24+
return false;
25+
}
26+
if (expr instanceof SubqueryExpression) {
27+
return true;
28+
}
29+
if (expr instanceof Let l) {
30+
return containsSubqueryExpression(l.getExpression());
31+
}
32+
for (Node child : expr.getChild()) {
33+
if (containsSubqueryExpression(child)) {
34+
return true;
35+
}
36+
}
37+
return false;
38+
}
39+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.ast.analysis;
7+
8+
import java.util.ArrayDeque;
9+
import java.util.Deque;
10+
import java.util.IdentityHashMap;
11+
import java.util.Map;
12+
import java.util.Set;
13+
import java.util.stream.Collectors;
14+
import lombok.Getter;
15+
import org.opensearch.sql.ast.tree.Relation;
16+
import org.opensearch.sql.ast.tree.UnresolvedPlan;
17+
18+
/** Context for field resolution using stack-based traversal. */
19+
public class FieldResolutionContext {
20+
21+
@Getter private final Map<UnresolvedPlan, FieldResolutionResult> results;
22+
private final Deque<FieldResolutionResult> requirementsStack;
23+
24+
public FieldResolutionContext() {
25+
this.results = new IdentityHashMap<>();
26+
this.requirementsStack = new ArrayDeque<>();
27+
this.requirementsStack.push(new FieldResolutionResult(Set.of(), "*"));
28+
}
29+
30+
public void pushRequirements(FieldResolutionResult result) {
31+
requirementsStack.push(result);
32+
}
33+
34+
public FieldResolutionResult popRequirements() {
35+
return requirementsStack.pop();
36+
}
37+
38+
public FieldResolutionResult getCurrentRequirements() {
39+
if (requirementsStack.isEmpty()) {
40+
throw new RuntimeException("empty stack");
41+
} else {
42+
return requirementsStack.peek();
43+
}
44+
}
45+
46+
public void setResult(UnresolvedPlan relation, FieldResolutionResult result) {
47+
results.put(relation, result);
48+
}
49+
50+
public Set<Relation> getRelations() {
51+
return results.keySet().stream()
52+
.filter(k -> k instanceof Relation)
53+
.map(k -> (Relation) k)
54+
.collect(Collectors.toSet());
55+
}
56+
57+
public static String mergeWildcardPatterns(Set<String> patterns) {
58+
if (patterns == null || patterns.isEmpty()) {
59+
return null;
60+
}
61+
if (patterns.size() == 1) {
62+
return patterns.iterator().next();
63+
}
64+
return String.join(" | ", patterns.stream().sorted().toList());
65+
}
66+
67+
@Override
68+
public String toString() {
69+
return "FieldResolutionContext{relationResults=" + results + "}";
70+
}
71+
}

0 commit comments

Comments
 (0)