Skip to content

Commit a602e30

Browse files
committed
Pulled out analyzers to opensearch module
Signed-off-by: Guian Gumpac <[email protected]>
1 parent 3b818c0 commit a602e30

File tree

9 files changed

+219
-101
lines changed

9 files changed

+219
-101
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,19 @@ public Analyzer(
132132
this.repository = repository;
133133
}
134134

135+
public Analyzer(
136+
ExpressionAnalyzer expressionAnalyzer,
137+
SelectExpressionAnalyzer selectExpressionAnalyzer,
138+
NamedExpressionAnalyzer namedExpressionAnalyzer,
139+
DataSourceService dataSourceService,
140+
BuiltinFunctionRepository repository) {
141+
this.expressionAnalyzer = expressionAnalyzer;
142+
this.dataSourceService = dataSourceService;
143+
this.selectExpressionAnalyzer = selectExpressionAnalyzer;
144+
this.namedExpressionAnalyzer = namedExpressionAnalyzer;
145+
this.repository = repository;
146+
}
147+
135148
public LogicalPlan analyze(UnresolvedPlan unresolved, AnalysisContext context) {
136149
return unresolved.accept(this, context);
137150
}

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

Lines changed: 0 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,6 @@ public Expression visitAggregateFunction(AggregateFunction node, AnalysisContext
177177
}
178178
}
179179

180-
@Override
181-
public Expression visitRelevanceFieldList(RelevanceFieldList node, AnalysisContext context) {
182-
return new LiteralExpression(
183-
ExprValueUtils.tupleValue(ImmutableMap.copyOf(node.getFieldList())));
184-
}
185-
186180
@Override
187181
public Expression visitFunction(Function node, AnalysisContext context) {
188182
FunctionName functionName = FunctionName.of(node.getFuncName());
@@ -220,66 +214,6 @@ public Expression visitHighlightFunction(HighlightFunction node, AnalysisContext
220214
return new HighlightExpression(expr);
221215
}
222216

223-
/**
224-
* visitScoreFunction removes the score function from the AST and replaces it with the child
225-
* relevance function node. If the optional boost variable is provided, the boost argument of the
226-
* relevance function is combined.
227-
*
228-
* @param node score function node
229-
* @param context analysis context for the query
230-
* @return resolved relevance function
231-
*/
232-
public Expression visitScoreFunction(ScoreFunction node, AnalysisContext context) {
233-
Literal boostArg = node.getRelevanceFieldWeight();
234-
if (!boostArg.getType().equals(DataType.DOUBLE)) {
235-
throw new SemanticCheckException(
236-
String.format(
237-
"Expected boost type '%s' but got '%s'",
238-
DataType.DOUBLE.name(), boostArg.getType().name()));
239-
}
240-
Double thisBoostValue = ((Double) boostArg.getValue());
241-
242-
// update the existing unresolved expression to add a boost argument if it doesn't exist
243-
// OR multiply the existing boost argument
244-
Function relevanceQueryUnresolvedExpr = (Function) node.getRelevanceQuery();
245-
List<UnresolvedExpression> relevanceFuncArgs = relevanceQueryUnresolvedExpr.getFuncArgs();
246-
247-
boolean doesFunctionContainBoostArgument = false;
248-
List<UnresolvedExpression> updatedFuncArgs = new ArrayList<>();
249-
for (UnresolvedExpression expr : relevanceFuncArgs) {
250-
String argumentName = ((UnresolvedArgument) expr).getArgName();
251-
if (argumentName.equalsIgnoreCase("boost")) {
252-
doesFunctionContainBoostArgument = true;
253-
Literal boostArgLiteral = (Literal) ((UnresolvedArgument) expr).getValue();
254-
Double boostValue =
255-
Double.parseDouble((String) boostArgLiteral.getValue()) * thisBoostValue;
256-
UnresolvedArgument newBoostArg =
257-
new UnresolvedArgument(
258-
argumentName, new Literal(boostValue.toString(), DataType.STRING));
259-
updatedFuncArgs.add(newBoostArg);
260-
} else {
261-
updatedFuncArgs.add(expr);
262-
}
263-
}
264-
265-
// since nothing was found, add an argument
266-
if (!doesFunctionContainBoostArgument) {
267-
UnresolvedArgument newBoostArg =
268-
new UnresolvedArgument(
269-
"boost", new Literal(Double.toString(thisBoostValue), DataType.STRING));
270-
updatedFuncArgs.add(newBoostArg);
271-
}
272-
273-
// create a new function expression with boost argument and resolve it
274-
Function updatedRelevanceQueryUnresolvedExpr =
275-
new Function(relevanceQueryUnresolvedExpr.getFuncName(), updatedFuncArgs);
276-
OpenSearchFunctions.OpenSearchFunction relevanceQueryExpr =
277-
(OpenSearchFunctions.OpenSearchFunction)
278-
updatedRelevanceQueryUnresolvedExpr.accept(this, context);
279-
relevanceQueryExpr.setScoreTracked(true);
280-
return relevanceQueryExpr;
281-
}
282-
283217
@Override
284218
public Expression visitIn(In node, AnalysisContext context) {
285219
return visitIn(node.getField(), node.getValueList(), context);

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

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
@RequiredArgsConstructor
3636
public class SelectExpressionAnalyzer
3737
extends AbstractNodeVisitor<List<NamedExpression>, AnalysisContext> {
38-
private final ExpressionAnalyzer expressionAnalyzer;
38+
protected final ExpressionAnalyzer expressionAnalyzer;
3939

4040
private ExpressionReferenceOptimizer optimizer;
4141

@@ -59,11 +59,6 @@ public List<NamedExpression> visitField(Field node, AnalysisContext context) {
5959

6060
@Override
6161
public List<NamedExpression> visitAlias(Alias node, AnalysisContext context) {
62-
// Expand all nested fields if used in SELECT clause
63-
if (node.getDelegated() instanceof NestedAllTupleFields) {
64-
return node.getDelegated().accept(this, context);
65-
}
66-
6762
Expression expr = referenceIfSymbolDefined(node, context);
6863
return Collections.singletonList(
6964
DSL.named(unqualifiedNameIfFieldOnly(node, context), expr, node.getAlias()));
@@ -82,7 +77,7 @@ public List<NamedExpression> visitAlias(Alias node, AnalysisContext context) {
8277
* groupExpr))
8378
* </ol>
8479
*/
85-
private Expression referenceIfSymbolDefined(Alias expr, AnalysisContext context) {
80+
protected Expression referenceIfSymbolDefined(Alias expr, AnalysisContext context) {
8681
UnresolvedExpression delegatedExpr = expr.getDelegated();
8782

8883
// Pass named expression because expression like window function loses full name
@@ -105,30 +100,6 @@ public List<NamedExpression> visitAllFields(AllFields node, AnalysisContext cont
105100
.collect(Collectors.toList());
106101
}
107102

108-
@Override
109-
public List<NamedExpression> visitNestedAllTupleFields(
110-
NestedAllTupleFields node, AnalysisContext context) {
111-
TypeEnvironment environment = context.peek();
112-
Map<String, ExprType> lookupAllTupleFields =
113-
environment.lookupAllTupleFields(Namespace.FIELD_NAME);
114-
environment.resolve(new Symbol(Namespace.FIELD_NAME, node.getPath()));
115-
116-
// Match all fields with same path as used in nested function.
117-
Pattern p = Pattern.compile(node.getPath() + "\\.[^\\.]+$");
118-
return lookupAllTupleFields.entrySet().stream()
119-
.filter(field -> p.matcher(field.getKey()).find())
120-
.map(
121-
entry -> {
122-
Expression nestedFunc =
123-
new Function(
124-
"nested",
125-
List.of(new QualifiedName(List.of(entry.getKey().split("\\.")))))
126-
.accept(expressionAnalyzer, context);
127-
return DSL.named("nested(" + entry.getKey() + ")", nestedFunc);
128-
})
129-
.collect(Collectors.toList());
130-
}
131-
132103
/**
133104
* Get unqualified name if select item is just a field. For example, suppose an index named
134105
* "accounts", return "age" for "SELECT accounts.age". But do nothing for expression in "SELECT
@@ -137,7 +108,7 @@ public List<NamedExpression> visitNestedAllTupleFields(
137108
* this. Otherwise, what unqualified() returns will override Alias's name as NamedExpression's
138109
* name even though the QualifiedName doesn't have qualifier.
139110
*/
140-
private String unqualifiedNameIfFieldOnly(Alias node, AnalysisContext context) {
111+
protected String unqualifiedNameIfFieldOnly(Alias node, AnalysisContext context) {
141112
UnresolvedExpression selectItem = node.getDelegated();
142113
if (selectItem instanceof QualifiedName) {
143114
QualifierAnalyzer qualifierAnalyzer = new QualifierAnalyzer(context);

core/src/main/java/org/opensearch/sql/storage/StorageEngine.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
import java.util.Collection;
99
import java.util.Collections;
1010
import org.opensearch.sql.DataSourceSchemaName;
11+
import org.opensearch.sql.analysis.Analyzer;
12+
import org.opensearch.sql.analysis.ExpressionAnalyzer;
13+
import org.opensearch.sql.datasource.DataSourceService;
14+
import org.opensearch.sql.expression.function.BuiltinFunctionRepository;
1115
import org.opensearch.sql.expression.function.FunctionResolver;
1216

1317
/** Storage engine for different storage to provide data access API implementation. */
@@ -24,4 +28,8 @@ public interface StorageEngine {
2428
default Collection<FunctionResolver> getFunctions() {
2529
return Collections.emptyList();
2630
}
31+
32+
default Analyzer getAnalyzer(DataSourceService dataSourceService, BuiltinFunctionRepository repository) {
33+
return new Analyzer(new ExpressionAnalyzer(repository), dataSourceService, repository);
34+
}
2735
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.opensearch.sql.opensearch.analysis;
2+
3+
import org.opensearch.sql.analysis.Analyzer;
4+
import org.opensearch.sql.analysis.ExpressionAnalyzer;
5+
import org.opensearch.sql.analysis.NamedExpressionAnalyzer;
6+
import org.opensearch.sql.datasource.DataSourceService;
7+
import org.opensearch.sql.expression.function.BuiltinFunctionRepository;
8+
9+
public class OpenSearchAnalyzer extends Analyzer {
10+
/**
11+
* Constructor.
12+
*
13+
* @param dataSourceService
14+
* @param repository
15+
*/
16+
public OpenSearchAnalyzer(DataSourceService dataSourceService,
17+
BuiltinFunctionRepository repository) {
18+
super(
19+
new OpenSearchExpressionAnalyzer(repository),
20+
new OpenSearchSelectExpressionAnalyzer(new OpenSearchExpressionAnalyzer(repository)),
21+
new NamedExpressionAnalyzer(new OpenSearchExpressionAnalyzer(repository)),
22+
dataSourceService,
23+
repository);
24+
}
25+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package org.opensearch.sql.opensearch.analysis;
2+
3+
import com.google.common.collect.ImmutableMap;
4+
import org.opensearch.sql.analysis.AnalysisContext;
5+
import org.opensearch.sql.analysis.ExpressionAnalyzer;
6+
import org.opensearch.sql.ast.expression.DataType;
7+
import org.opensearch.sql.ast.expression.Function;
8+
import org.opensearch.sql.ast.expression.Literal;
9+
import org.opensearch.sql.ast.expression.RelevanceFieldList;
10+
import org.opensearch.sql.ast.expression.ScoreFunction;
11+
import org.opensearch.sql.ast.expression.UnresolvedArgument;
12+
import org.opensearch.sql.ast.expression.UnresolvedExpression;
13+
import org.opensearch.sql.data.model.ExprValueUtils;
14+
import org.opensearch.sql.exception.SemanticCheckException;
15+
import org.opensearch.sql.expression.Expression;
16+
import org.opensearch.sql.expression.LiteralExpression;
17+
import org.opensearch.sql.expression.function.BuiltinFunctionRepository;
18+
import org.opensearch.sql.expression.function.OpenSearchFunctions;
19+
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
23+
public class OpenSearchExpressionAnalyzer extends ExpressionAnalyzer {
24+
public OpenSearchExpressionAnalyzer(BuiltinFunctionRepository repository) {
25+
super(repository);
26+
}
27+
28+
/**
29+
* visitScoreFunction removes the score function from the AST and replaces it with the child
30+
* relevance function node. If the optional boost variable is provided, the boost argument of the
31+
* relevance function is combined.
32+
*
33+
* @param node score function node
34+
* @param context analysis context for the query
35+
* @return resolved relevance function
36+
*/
37+
public Expression visitScoreFunction(ScoreFunction node, AnalysisContext context) {
38+
Literal boostArg = node.getRelevanceFieldWeight();
39+
if (!boostArg.getType().equals(DataType.DOUBLE)) {
40+
throw new SemanticCheckException(
41+
String.format(
42+
"Expected boost type '%s' but got '%s'",
43+
DataType.DOUBLE.name(), boostArg.getType().name()));
44+
}
45+
Double thisBoostValue = ((Double) boostArg.getValue());
46+
47+
// update the existing unresolved expression to add a boost argument if it doesn't exist
48+
// OR multiply the existing boost argument
49+
Function relevanceQueryUnresolvedExpr = (Function) node.getRelevanceQuery();
50+
List<UnresolvedExpression> relevanceFuncArgs = relevanceQueryUnresolvedExpr.getFuncArgs();
51+
52+
boolean doesFunctionContainBoostArgument = false;
53+
List<UnresolvedExpression> updatedFuncArgs = new ArrayList<>();
54+
for (UnresolvedExpression expr : relevanceFuncArgs) {
55+
String argumentName = ((UnresolvedArgument) expr).getArgName();
56+
if (argumentName.equalsIgnoreCase("boost")) {
57+
doesFunctionContainBoostArgument = true;
58+
Literal boostArgLiteral = (Literal) ((UnresolvedArgument) expr).getValue();
59+
Double boostValue =
60+
Double.parseDouble((String) boostArgLiteral.getValue()) * thisBoostValue;
61+
UnresolvedArgument newBoostArg =
62+
new UnresolvedArgument(
63+
argumentName, new Literal(boostValue.toString(), DataType.STRING));
64+
updatedFuncArgs.add(newBoostArg);
65+
} else {
66+
updatedFuncArgs.add(expr);
67+
}
68+
}
69+
70+
// since nothing was found, add an argument
71+
if (!doesFunctionContainBoostArgument) {
72+
UnresolvedArgument newBoostArg =
73+
new UnresolvedArgument(
74+
"boost", new Literal(Double.toString(thisBoostValue), DataType.STRING));
75+
updatedFuncArgs.add(newBoostArg);
76+
}
77+
78+
// create a new function expression with boost argument and resolve it
79+
Function updatedRelevanceQueryUnresolvedExpr =
80+
new Function(relevanceQueryUnresolvedExpr.getFuncName(), updatedFuncArgs);
81+
OpenSearchFunctions.OpenSearchFunction relevanceQueryExpr =
82+
(OpenSearchFunctions.OpenSearchFunction)
83+
updatedRelevanceQueryUnresolvedExpr.accept(this, context);
84+
relevanceQueryExpr.setScoreTracked(true);
85+
return relevanceQueryExpr;
86+
}
87+
88+
@Override
89+
public Expression visitRelevanceFieldList(RelevanceFieldList node, AnalysisContext context) {
90+
return new LiteralExpression(
91+
ExprValueUtils.tupleValue(ImmutableMap.copyOf(node.getFieldList())));
92+
}
93+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package org.opensearch.sql.opensearch.analysis;
2+
3+
import org.opensearch.sql.analysis.AnalysisContext;
4+
import org.opensearch.sql.analysis.ExpressionAnalyzer;
5+
import org.opensearch.sql.analysis.SelectExpressionAnalyzer;
6+
import org.opensearch.sql.analysis.TypeEnvironment;
7+
import org.opensearch.sql.analysis.symbol.Namespace;
8+
import org.opensearch.sql.analysis.symbol.Symbol;
9+
import org.opensearch.sql.ast.expression.Alias;
10+
import org.opensearch.sql.ast.expression.Function;
11+
import org.opensearch.sql.ast.expression.NestedAllTupleFields;
12+
import org.opensearch.sql.ast.expression.QualifiedName;
13+
import org.opensearch.sql.data.type.ExprType;
14+
import org.opensearch.sql.expression.DSL;
15+
import org.opensearch.sql.expression.Expression;
16+
import org.opensearch.sql.expression.NamedExpression;
17+
18+
import java.util.Collections;
19+
import java.util.List;
20+
import java.util.Map;
21+
import java.util.regex.Pattern;
22+
import java.util.stream.Collectors;
23+
24+
public class OpenSearchSelectExpressionAnalyzer extends SelectExpressionAnalyzer {
25+
public OpenSearchSelectExpressionAnalyzer(ExpressionAnalyzer expressionAnalyzer) {
26+
super(expressionAnalyzer);
27+
}
28+
29+
@Override
30+
public List<NamedExpression> visitAlias(Alias node, AnalysisContext context) {
31+
// Expand all nested fields if used in SELECT clause
32+
if (node.getDelegated() instanceof NestedAllTupleFields) {
33+
return node.getDelegated().accept(this, context);
34+
}
35+
36+
Expression expr = referenceIfSymbolDefined(node, context);
37+
return Collections.singletonList(
38+
DSL.named(unqualifiedNameIfFieldOnly(node, context), expr, node.getAlias()));
39+
}
40+
41+
42+
@Override
43+
public List<NamedExpression> visitNestedAllTupleFields(
44+
NestedAllTupleFields node, AnalysisContext context) {
45+
TypeEnvironment environment = context.peek();
46+
Map<String, ExprType> lookupAllTupleFields =
47+
environment.lookupAllTupleFields(Namespace.FIELD_NAME);
48+
environment.resolve(new Symbol(Namespace.FIELD_NAME, node.getPath()));
49+
50+
// Match all fields with same path as used in nested function.
51+
Pattern p = Pattern.compile(node.getPath() + "\\.[^\\.]+$");
52+
return lookupAllTupleFields.entrySet().stream()
53+
.filter(field -> p.matcher(field.getKey()).find())
54+
.map(
55+
entry -> {
56+
Expression nestedFunc =
57+
new Function(
58+
"nested",
59+
List.of(new QualifiedName(List.of(entry.getKey().split("\\.")))))
60+
.accept(expressionAnalyzer, context);
61+
return DSL.named("nested(" + entry.getKey() + ")", nestedFunc);
62+
})
63+
.collect(Collectors.toList());
64+
}
65+
}

0 commit comments

Comments
 (0)