Skip to content

Commit 0676f32

Browse files
committed
Add UnifiedQueryParser with language-specific implementations
Extract parsing logic from UnifiedQueryPlanner into a UnifiedQueryParser interface with language-specific implementations: PPLQueryParser (returns UnresolvedPlan) and CalciteSqlQueryParser (returns SqlNode). UnifiedQueryContext owns the parser instance, created eagerly by the builder which has direct access to query type and future SQL config. Each implementation receives only its required dependencies: PPLQueryParser takes Settings, CalciteSqlQueryParser takes FrameworkConfig. UnifiedQueryPlanner.CustomVisitorStrategy now obtains the parser from the context via the interface type. Signed-off-by: Chen Dai <daichen@amazon.com>
1 parent 71541b1 commit 0676f32

File tree

6 files changed

+177
-30
lines changed

6 files changed

+177
-30
lines changed

api/src/main/java/org/opensearch/sql/api/UnifiedQueryContext.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import java.util.List;
1414
import java.util.Map;
1515
import java.util.Objects;
16-
import lombok.Value;
16+
import lombok.Getter;
1717
import org.apache.calcite.avatica.util.Casing;
1818
import org.apache.calcite.jdbc.CalciteSchema;
1919
import org.apache.calcite.plan.RelTraitDef;
@@ -24,6 +24,9 @@
2424
import org.apache.calcite.tools.FrameworkConfig;
2525
import org.apache.calcite.tools.Frameworks;
2626
import org.apache.calcite.tools.Programs;
27+
import org.opensearch.sql.api.parser.CalciteSqlQueryParser;
28+
import org.opensearch.sql.api.parser.PPLQueryParser;
29+
import org.opensearch.sql.api.parser.UnifiedQueryParser;
2730
import org.opensearch.sql.calcite.CalcitePlanContext;
2831
import org.opensearch.sql.calcite.SysLimit;
2932
import org.opensearch.sql.common.setting.Settings;
@@ -34,14 +37,24 @@
3437
* centralizes configuration for catalog schemas, query type, execution limits, and other settings,
3538
* enabling consistent behavior across all unified query operations.
3639
*/
37-
@Value
40+
@Getter
3841
public class UnifiedQueryContext implements AutoCloseable {
3942

4043
/** CalcitePlanContext containing Calcite framework configuration and query type. */
41-
CalcitePlanContext planContext;
44+
private final CalcitePlanContext planContext;
4245

4346
/** Settings containing execution limits and feature flags used by parsers and planners. */
44-
Settings settings;
47+
private final Settings settings;
48+
49+
/** Query parser created eagerly from this context's configuration. */
50+
private final UnifiedQueryParser<?> parser;
51+
52+
private UnifiedQueryContext(
53+
CalcitePlanContext planContext, Settings settings, UnifiedQueryParser<?> parser) {
54+
this.planContext = planContext;
55+
this.settings = settings;
56+
this.parser = parser;
57+
}
4558

4659
/**
4760
* Closes the underlying resource managed by this context.
@@ -152,7 +165,14 @@ public UnifiedQueryContext build() {
152165
CalcitePlanContext planContext =
153166
CalcitePlanContext.create(
154167
buildFrameworkConfig(), SysLimit.fromSettings(settings), queryType);
155-
return new UnifiedQueryContext(planContext, settings);
168+
return new UnifiedQueryContext(planContext, settings, createParser(planContext, settings));
169+
}
170+
171+
private UnifiedQueryParser<?> createParser(CalcitePlanContext planContext, Settings settings) {
172+
return switch (queryType) {
173+
case PPL -> new PPLQueryParser(settings);
174+
case SQL -> new CalciteSqlQueryParser(planContext);
175+
};
156176
}
157177

158178
private Settings buildSettings() {

api/src/main/java/org/opensearch/sql/api/UnifiedQueryPlanner.java

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
package org.opensearch.sql.api;
77

88
import lombok.RequiredArgsConstructor;
9-
import org.antlr.v4.runtime.tree.ParseTree;
109
import org.apache.calcite.rel.RelCollation;
1110
import org.apache.calcite.rel.RelCollations;
1211
import org.apache.calcite.rel.RelNode;
@@ -16,15 +15,11 @@
1615
import org.apache.calcite.sql.SqlNode;
1716
import org.apache.calcite.tools.Frameworks;
1817
import org.apache.calcite.tools.Planner;
19-
import org.opensearch.sql.ast.statement.Query;
20-
import org.opensearch.sql.ast.statement.Statement;
18+
import org.opensearch.sql.api.parser.UnifiedQueryParser;
2119
import org.opensearch.sql.ast.tree.UnresolvedPlan;
2220
import org.opensearch.sql.calcite.CalciteRelNodeVisitor;
2321
import org.opensearch.sql.common.antlr.SyntaxCheckException;
2422
import org.opensearch.sql.executor.QueryType;
25-
import org.opensearch.sql.ppl.antlr.PPLSyntaxParser;
26-
import org.opensearch.sql.ppl.parser.AstBuilder;
27-
import org.opensearch.sql.ppl.parser.AstStatementBuilder;
2823

2924
/**
3025
* {@code UnifiedQueryPlanner} provides a high-level API for parsing and analyzing queries using the
@@ -87,36 +82,26 @@ public RelNode plan(String query) throws Exception {
8782
}
8883
}
8984

90-
/** AST-based planning via ANTLR parser → UnresolvedPlan → CalciteRelNodeVisitor. */
91-
@RequiredArgsConstructor
85+
/** AST-based planning via context-owned parser → UnresolvedPlan → CalciteRelNodeVisitor. */
9286
private static class CustomVisitorStrategy implements PlanningStrategy {
9387
private final UnifiedQueryContext context;
94-
private final PPLSyntaxParser parser = new PPLSyntaxParser();
88+
private final UnifiedQueryParser<UnresolvedPlan> parser;
9589
private final CalciteRelNodeVisitor relNodeVisitor =
9690
new CalciteRelNodeVisitor(new EmptyDataSourceService());
9791

92+
@SuppressWarnings("unchecked")
93+
CustomVisitorStrategy(UnifiedQueryContext context) {
94+
this.context = context;
95+
this.parser = (UnifiedQueryParser<UnresolvedPlan>) context.getParser();
96+
}
97+
9898
@Override
9999
public RelNode plan(String query) {
100-
UnresolvedPlan ast = parse(query);
100+
UnresolvedPlan ast = parser.parse(query);
101101
RelNode logical = relNodeVisitor.analyze(ast, context.getPlanContext());
102102
return preserveCollation(logical);
103103
}
104104

105-
private UnresolvedPlan parse(String query) {
106-
ParseTree cst = parser.parse(query);
107-
AstStatementBuilder astStmtBuilder =
108-
new AstStatementBuilder(
109-
new AstBuilder(query, context.getSettings()),
110-
AstStatementBuilder.StatementBuilderContext.builder().build());
111-
Statement statement = cst.accept(astStmtBuilder);
112-
113-
if (statement instanceof Query) {
114-
return ((Query) statement).getPlan();
115-
}
116-
throw new UnsupportedOperationException(
117-
"Only query statements are supported but got " + statement.getClass().getSimpleName());
118-
}
119-
120105
private RelNode preserveCollation(RelNode logical) {
121106
RelCollation collation = logical.getTraitSet().getCollation();
122107
if (!(logical instanceof Sort) && collation != RelCollations.EMPTY) {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.api.parser;
7+
8+
import lombok.RequiredArgsConstructor;
9+
import org.apache.calcite.sql.SqlNode;
10+
import org.apache.calcite.sql.parser.SqlParseException;
11+
import org.apache.calcite.sql.parser.SqlParser;
12+
import org.opensearch.sql.calcite.CalcitePlanContext;
13+
import org.opensearch.sql.common.antlr.SyntaxCheckException;
14+
15+
/** Calcite SQL query parser that produces {@link SqlNode} as the native parse result. */
16+
@RequiredArgsConstructor
17+
public class CalciteSqlQueryParser implements UnifiedQueryParser<SqlNode> {
18+
19+
/** Calcite plan context providing parser configuration (e.g., case sensitivity, conformance). */
20+
private final CalcitePlanContext planContext;
21+
22+
@Override
23+
public SqlNode parse(String query) {
24+
try {
25+
return SqlParser.create(query, planContext.config.getParserConfig()).parseQuery();
26+
} catch (SqlParseException e) {
27+
throw new SyntaxCheckException("Failed to parse SQL query: " + e.getMessage());
28+
}
29+
}
30+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.api.parser;
7+
8+
import lombok.RequiredArgsConstructor;
9+
import org.antlr.v4.runtime.tree.ParseTree;
10+
import org.opensearch.sql.ast.statement.Query;
11+
import org.opensearch.sql.ast.statement.Statement;
12+
import org.opensearch.sql.ast.tree.UnresolvedPlan;
13+
import org.opensearch.sql.common.setting.Settings;
14+
import org.opensearch.sql.ppl.antlr.PPLSyntaxParser;
15+
import org.opensearch.sql.ppl.parser.AstBuilder;
16+
import org.opensearch.sql.ppl.parser.AstStatementBuilder;
17+
18+
/** PPL query parser that produces {@link UnresolvedPlan} as the native parse result. */
19+
@RequiredArgsConstructor
20+
public class PPLQueryParser implements UnifiedQueryParser<UnresolvedPlan> {
21+
22+
/** Settings containing execution limits and feature flags used by AST builders. */
23+
private final Settings settings;
24+
25+
/** Reusable ANTLR-based PPL syntax parser. Stateless and thread-safe. */
26+
private final PPLSyntaxParser syntaxParser = new PPLSyntaxParser();
27+
28+
@Override
29+
public UnresolvedPlan parse(String query) {
30+
ParseTree cst = syntaxParser.parse(query);
31+
AstStatementBuilder astStmtBuilder =
32+
new AstStatementBuilder(
33+
new AstBuilder(query, settings),
34+
AstStatementBuilder.StatementBuilderContext.builder().build());
35+
Statement statement = cst.accept(astStmtBuilder);
36+
37+
if (statement instanceof Query) {
38+
return ((Query) statement).getPlan();
39+
}
40+
throw new UnsupportedOperationException(
41+
"Only query statements are supported but got " + statement.getClass().getSimpleName());
42+
}
43+
}
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.api.parser;
7+
8+
/**
9+
* Language-neutral query parser interface. Returns the native parse result for the language (e.g.,
10+
* {@code UnresolvedPlan} for PPL, {@code SqlNode} for Calcite SQL).
11+
*
12+
* @param <R> the native parse result type for this language
13+
*/
14+
public interface UnifiedQueryParser<R> {
15+
16+
/**
17+
* Parses the query and returns the native parse result.
18+
*
19+
* @param query the raw query string
20+
* @return the native parse result
21+
*/
22+
R parse(String query);
23+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.api.parser;
7+
8+
import static org.junit.Assert.assertNotNull;
9+
import static org.junit.Assert.assertThrows;
10+
11+
import org.apache.calcite.sql.SqlNode;
12+
import org.junit.Test;
13+
import org.opensearch.sql.api.UnifiedQueryTestBase;
14+
import org.opensearch.sql.ast.tree.UnresolvedPlan;
15+
import org.opensearch.sql.common.antlr.SyntaxCheckException;
16+
import org.opensearch.sql.executor.QueryType;
17+
18+
public class UnifiedQueryParserTest extends UnifiedQueryTestBase {
19+
20+
@Test
21+
public void testPplParseReturnsUnresolvedPlan() {
22+
UnresolvedPlan ast = (UnresolvedPlan) context.getParser().parse("source = catalog.employees");
23+
assertNotNull(ast);
24+
}
25+
26+
@Test
27+
public void testPplParseSyntaxErrorThrows() {
28+
assertThrows(SyntaxCheckException.class, () -> context.getParser().parse("not a valid query"));
29+
}
30+
31+
@Test
32+
public void testSqlParseReturnsSqlNode() throws Exception {
33+
try (var sqlContext = buildContext(QueryType.SQL)) {
34+
SqlNode node = (SqlNode) sqlContext.getParser().parse("SELECT * FROM my_index");
35+
assertNotNull(node);
36+
}
37+
}
38+
39+
@Test
40+
public void testSqlParseSyntaxErrorThrows() throws Exception {
41+
try (var sqlContext = buildContext(QueryType.SQL)) {
42+
assertThrows(
43+
SyntaxCheckException.class, () -> sqlContext.getParser().parse("NOT VALID SQL !!!"));
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)