2020import org .opensearch .core .action .ActionListener ;
2121import org .opensearch .sql .api .UnifiedQueryContext ;
2222import org .opensearch .sql .api .UnifiedQueryPlanner ;
23+ import org .opensearch .sql .api .parser .PPLQueryParser ;
24+ import org .opensearch .sql .ast .tree .Relation ;
25+ import org .opensearch .sql .ast .tree .UnresolvedPlan ;
2326import org .opensearch .sql .calcite .CalcitePlanContext ;
2427import org .opensearch .sql .calcite .plan .rel .LogicalSystemLimit ;
2528import org .opensearch .sql .common .response .ResponseListener ;
29+ import org .opensearch .sql .common .setting .Settings ;
2630import org .opensearch .sql .executor .ExecutionEngine .QueryResponse ;
2731import org .opensearch .sql .executor .QueryType ;
2832import org .opensearch .sql .executor .analytics .AnalyticsExecutionEngine ;
2933import org .opensearch .sql .executor .analytics .QueryPlanExecutor ;
3034import org .opensearch .sql .lang .LangSpec ;
31- import org .opensearch .sql .plugin .rest .analytics .stub .StubIndexDetector ;
3235import org .opensearch .sql .plugin .rest .analytics .stub .StubSchemaProvider ;
3336import org .opensearch .sql .plugin .transport .TransportPPLQueryResponse ;
3437import org .opensearch .sql .ppl .domain .PPLQueryRequest ;
@@ -49,30 +52,59 @@ public class RestUnifiedQueryAction {
4952
5053 private final AnalyticsExecutionEngine analyticsEngine ;
5154 private final NodeClient client ;
55+ private final PPLQueryParser pplParser ;
5256
53- public RestUnifiedQueryAction (NodeClient client , QueryPlanExecutor planExecutor ) {
57+ public RestUnifiedQueryAction (
58+ NodeClient client , QueryPlanExecutor planExecutor , Settings settings ) {
5459 this .client = client ;
5560 this .analyticsEngine = new AnalyticsExecutionEngine (planExecutor );
61+ this .pplParser = new PPLQueryParser (settings );
5662 }
5763
5864 /**
59- * Check if the query targets an analytics engine index. Delegates to {@link StubIndexDetector}
60- * which will be replaced by UnifiedQueryParser and index settings when available .
65+ * Check if the query targets an analytics engine index (e.g., Parquet-backed). Uses {@link
66+ * PPLQueryParser} to parse the query and extract the index name from the AST .
6167 */
62- public static boolean isAnalyticsIndex (String query ) {
63- return StubIndexDetector .isAnalyticsIndex (query );
68+ public boolean isAnalyticsIndex (String query ) {
69+ if (query == null || query .isEmpty ()) {
70+ return false ;
71+ }
72+ try {
73+ String indexName = extractIndexName (query );
74+ if (indexName == null ) {
75+ return false ;
76+ }
77+ int lastDot = indexName .lastIndexOf ('.' );
78+ String tableName = lastDot >= 0 ? indexName .substring (lastDot + 1 ) : indexName ;
79+ return tableName .startsWith ("parquet_" );
80+ } catch (Exception e ) {
81+ return false ;
82+ }
83+ }
84+
85+ /** Extract the source index name by parsing the PPL AST and finding the Relation node. */
86+ private String extractIndexName (String query ) {
87+ UnresolvedPlan plan = pplParser .parse (query );
88+ Relation relation = findRelation (plan );
89+ return relation != null ? relation .getTableQualifiedName ().toString () : null ;
90+ }
91+
92+ /** Walk the AST to find the Relation (table scan) node. */
93+ private static Relation findRelation (UnresolvedPlan plan ) {
94+ if (plan instanceof Relation ) {
95+ return (Relation ) plan ;
96+ }
97+ for (var child : plan .getChild ()) {
98+ if (child instanceof UnresolvedPlan unresolvedChild ) {
99+ Relation found = findRelation (unresolvedChild );
100+ if (found != null ) {
101+ return found ;
102+ }
103+ }
104+ }
105+ return null ;
64106 }
65107
66- /**
67- * Execute a query through the unified query pipeline on the sql-worker thread pool. Called from
68- * {@code TransportPPLQueryAction} which handles PPL enabled check, metrics, request ID, and
69- * profiling cleanup.
70- *
71- * @param query the query string
72- * @param queryType SQL or PPL
73- * @param pplRequest the original PPL request
74- * @param listener the transport action listener
75- */
76108 /** Execute a query through the unified query pipeline on the sql-worker thread pool. */
77109 public void execute (
78110 String query ,
@@ -90,7 +122,7 @@ public void execute(
90122 /**
91123 * Explain a query through the unified query pipeline on the sql-worker thread pool. Returns
92124 * ExplainResponse via ResponseListener so the caller (TransportPPLQueryAction) can format it
93- * using its own createExplainResponseListener, reusing the existing format-aware logic .
125+ * using its own createExplainResponseListener.
94126 */
95127 public void explain (
96128 String query ,
@@ -111,14 +143,14 @@ private void doExecute(
111143 PPLQueryRequest pplRequest ,
112144 ActionListener <TransportPPLQueryResponse > listener ) {
113145 try {
114- long startTime = System .nanoTime ();
115146 AbstractSchema schema = StubSchemaProvider .buildSchema ();
116147
117148 try (UnifiedQueryContext context =
118149 UnifiedQueryContext .builder ()
119150 .language (queryType )
120151 .catalog (SCHEMA_NAME , schema )
121152 .defaultNamespace (SCHEMA_NAME )
153+ .profiling (pplRequest .profile ())
122154 .build ()) {
123155
124156 UnifiedQueryPlanner planner = new UnifiedQueryPlanner (context );
@@ -127,14 +159,7 @@ private void doExecute(
127159 CalcitePlanContext planContext = context .getPlanContext ();
128160 plan = addQuerySizeLimit (plan , planContext );
129161
130- long planTime = System .nanoTime ();
131- LOG .info (
132- "[unified] Planning completed in {}ms for {} query" ,
133- (planTime - startTime ) / 1_000_000 ,
134- queryType );
135-
136- analyticsEngine .execute (
137- plan , planContext , createQueryListener (queryType , planTime , listener ));
162+ analyticsEngine .execute (plan , planContext , createQueryListener (queryType , listener ));
138163 }
139164 } catch (Exception e ) {
140165 listener .onFailure (e );
@@ -147,14 +172,14 @@ private void doExplain(
147172 PPLQueryRequest pplRequest ,
148173 ResponseListener <ExplainResponse > listener ) {
149174 try {
150- long startTime = System .nanoTime ();
151175 AbstractSchema schema = StubSchemaProvider .buildSchema ();
152176
153177 try (UnifiedQueryContext context =
154178 UnifiedQueryContext .builder ()
155179 .language (queryType )
156180 .catalog (SCHEMA_NAME , schema )
157181 .defaultNamespace (SCHEMA_NAME )
182+ .profiling (pplRequest .profile ())
158183 .build ()) {
159184
160185 UnifiedQueryPlanner planner = new UnifiedQueryPlanner (context );
@@ -163,23 +188,13 @@ private void doExplain(
163188 CalcitePlanContext planContext = context .getPlanContext ();
164189 plan = addQuerySizeLimit (plan , planContext );
165190
166- long planTime = System .nanoTime ();
167- LOG .info (
168- "[unified] Planning completed in {}ms for {} query" ,
169- (planTime - startTime ) / 1_000_000 ,
170- queryType );
171-
172191 analyticsEngine .explain (plan , pplRequest .mode (), planContext , listener );
173192 }
174193 } catch (Exception e ) {
175194 listener .onFailure (e );
176195 }
177196 }
178197
179- /**
180- * Add a system-level query size limit to the plan. This ensures the analytics engine enforces the
181- * limit during execution rather than returning all rows for post-processing truncation.
182- */
183198 private static RelNode addQuerySizeLimit (RelNode plan , CalcitePlanContext context ) {
184199 return LogicalSystemLimit .create (
185200 LogicalSystemLimit .SystemLimitType .QUERY_SIZE_LIMIT ,
@@ -188,18 +203,11 @@ private static RelNode addQuerySizeLimit(RelNode plan, CalcitePlanContext contex
188203 }
189204
190205 private ResponseListener <QueryResponse > createQueryListener (
191- QueryType queryType ,
192- long planEndTime ,
193- ActionListener <TransportPPLQueryResponse > transportListener ) {
206+ QueryType queryType , ActionListener <TransportPPLQueryResponse > transportListener ) {
194207 ResponseFormatter <QueryResult > formatter = new JdbcResponseFormatter (PRETTY );
195208 return new ResponseListener <QueryResponse >() {
196209 @ Override
197210 public void onResponse (QueryResponse response ) {
198- long execTime = System .nanoTime ();
199- LOG .info (
200- "[unified] Execution completed in {}ms, {} rows returned" ,
201- (execTime - planEndTime ) / 1_000_000 ,
202- response .getResults ().size ());
203211 LangSpec langSpec = queryType == QueryType .PPL ? PPL_SPEC : LangSpec .SQL_SPEC ;
204212 String result =
205213 formatter .format (
@@ -215,11 +223,6 @@ public void onFailure(Exception e) {
215223 };
216224 }
217225
218- /**
219- * Capture current thread context and restore it on the worker thread. Ensures security context
220- * (user identity, permissions) is propagated. Same pattern as {@link
221- * org.opensearch.sql.opensearch.executor.OpenSearchQueryManager#withCurrentContext}.
222- */
223226 private static Runnable withCurrentContext (final Runnable task ) {
224227 final Map <String , String > currentContext = ThreadContext .getImmutableContext ();
225228 return () -> {
0 commit comments