55
66package org .opensearch .sql .api ;
77
8+ import lombok .RequiredArgsConstructor ;
89import org .antlr .v4 .runtime .tree .ParseTree ;
910import org .apache .calcite .rel .RelCollation ;
1011import org .apache .calcite .rel .RelCollations ;
1112import org .apache .calcite .rel .RelNode ;
13+ import org .apache .calcite .rel .RelRoot ;
1214import org .apache .calcite .rel .core .Sort ;
1315import org .apache .calcite .rel .logical .LogicalSort ;
16+ import org .apache .calcite .sql .SqlNode ;
17+ import org .apache .calcite .tools .Frameworks ;
18+ import org .apache .calcite .tools .Planner ;
1419import org .opensearch .sql .ast .statement .Query ;
1520import org .opensearch .sql .ast .statement .Statement ;
1621import org .opensearch .sql .ast .tree .UnresolvedPlan ;
1722import org .opensearch .sql .calcite .CalciteRelNodeVisitor ;
18- import org .opensearch .sql .common .antlr .Parser ;
1923import org .opensearch .sql .common .antlr .SyntaxCheckException ;
2024import org .opensearch .sql .executor .QueryType ;
2125import org .opensearch .sql .ppl .antlr .PPLSyntaxParser ;
2832 * such as Spark or command-line tools, abstracting away Calcite internals.
2933 */
3034public class UnifiedQueryPlanner {
31- /** The parser instance responsible for converting query text into a parse tree. */
32- private final Parser parser ;
3335
34- /** Unified query context containing CalcitePlanContext with all configuration. */
35- private final UnifiedQueryContext context ;
36-
37- /** AST-to-RelNode visitor that builds logical plans from the parsed AST. */
38- private final CalciteRelNodeVisitor relNodeVisitor =
39- new CalciteRelNodeVisitor (new EmptyDataSourceService ());
36+ /** Planning strategy selected at construction time based on query type. */
37+ private final PlanningStrategy strategy ;
4038
4139 /**
4240 * Constructs a UnifiedQueryPlanner with a unified query context.
4341 *
4442 * @param context the unified query context containing CalcitePlanContext
4543 */
4644 public UnifiedQueryPlanner (UnifiedQueryContext context ) {
47- this .parser = buildQueryParser (context .getPlanContext ().queryType );
48- this .context = context ;
45+ this .strategy =
46+ context .getPlanContext ().queryType == QueryType .SQL
47+ ? new CalciteNativeStrategy (context )
48+ : new CustomVisitorStrategy (context );
4949 }
5050
5151 /**
5252 * Parses and analyzes a query string into a Calcite logical plan (RelNode). TODO: Generate
5353 * optimal physical plan to fully unify query execution and leverage Calcite's optimizer.
5454 *
55- * @param query the raw query string in PPL or other supported syntax
55+ * @param query the raw query string in PPL or SQL syntax
5656 * @return a logical plan representing the query
5757 */
5858 public RelNode plan (String query ) {
5959 try {
60- return preserveCollation ( analyze ( parse ( query )) );
60+ return strategy . plan ( query );
6161 } catch (SyntaxCheckException e ) {
6262 // Re-throw syntax error without wrapping
6363 throw e ;
@@ -66,38 +66,63 @@ public RelNode plan(String query) {
6666 }
6767 }
6868
69- private Parser buildQueryParser (QueryType queryType ) {
70- if (queryType == QueryType .PPL ) {
71- return new PPLSyntaxParser ();
72- }
73- throw new IllegalArgumentException ("Unsupported query type: " + queryType );
69+ /** Strategy interface for language-specific planning logic. */
70+ private interface PlanningStrategy {
71+ RelNode plan (String query ) throws Exception ;
7472 }
7573
76- private UnresolvedPlan parse (String query ) {
77- ParseTree cst = parser .parse (query );
78- AstStatementBuilder astStmtBuilder =
79- new AstStatementBuilder (
80- new AstBuilder (query , context .getSettings ()),
81- AstStatementBuilder .StatementBuilderContext .builder ().build ());
82- Statement statement = cst .accept (astStmtBuilder );
74+ /** ANSI SQL planning using Calcite's native SqlParser → SqlValidator → SqlToRelConverter. */
75+ @ RequiredArgsConstructor
76+ private static class CalciteNativeStrategy implements PlanningStrategy {
77+ private final UnifiedQueryContext context ;
8378
84- if (statement instanceof Query ) {
85- return ((Query ) statement ).getPlan ();
79+ @ Override
80+ public RelNode plan (String query ) throws Exception {
81+ try (Planner planner = Frameworks .getPlanner (context .getPlanContext ().config )) {
82+ SqlNode parsed = planner .parse (query );
83+ SqlNode validated = planner .validate (parsed );
84+ RelRoot relRoot = planner .rel (validated );
85+ return relRoot .project ();
86+ }
8687 }
87- throw new UnsupportedOperationException (
88- "Only query statements are supported but got " + statement .getClass ().getSimpleName ());
8988 }
9089
91- private RelNode analyze (UnresolvedPlan ast ) {
92- return relNodeVisitor .analyze (ast , context .getPlanContext ());
93- }
90+ /** AST-based planning via ANTLR parser → UnresolvedPlan → CalciteRelNodeVisitor. */
91+ @ RequiredArgsConstructor
92+ private static class CustomVisitorStrategy implements PlanningStrategy {
93+ private final UnifiedQueryContext context ;
94+ private final PPLSyntaxParser parser = new PPLSyntaxParser ();
95+ private final CalciteRelNodeVisitor relNodeVisitor =
96+ new CalciteRelNodeVisitor (new EmptyDataSourceService ());
97+
98+ @ Override
99+ public RelNode plan (String query ) {
100+ UnresolvedPlan ast = parse (query );
101+ RelNode logical = relNodeVisitor .analyze (ast , context .getPlanContext ());
102+ return preserveCollation (logical );
103+ }
104+
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+ }
94119
95- private RelNode preserveCollation (RelNode logical ) {
96- RelNode calcitePlan = logical ;
97- RelCollation collation = logical .getTraitSet ().getCollation ();
98- if (!(logical instanceof Sort ) && collation != RelCollations .EMPTY ) {
99- calcitePlan = LogicalSort .create (logical , collation , null , null );
120+ private RelNode preserveCollation (RelNode logical ) {
121+ RelCollation collation = logical .getTraitSet ().getCollation ();
122+ if (!(logical instanceof Sort ) && collation != RelCollations .EMPTY ) {
123+ return LogicalSort .create (logical , collation , null , null );
124+ }
125+ return logical ;
100126 }
101- return calcitePlan ;
102127 }
103128}
0 commit comments