-
Notifications
You must be signed in to change notification settings - Fork 190
Integrate SQL REST endpoint with analytics engine path #5317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
ahkcs
merged 10 commits into
feature/mustang-ppl-integration
from
pr/mustang-sql-integration
Apr 8, 2026
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
5c25195
Integrate SQL REST endpoint with analytics engine path
ahkcs 1f8ee7c
Use queryType to branch index extraction instead of instanceof
ahkcs b4f2500
Use Optional for extractIndexName return type
ahkcs b95d48d
Unify execute and explain methods for both PPL and SQL paths
ahkcs f5d7d15
Reuse base class explainQuery() in AnalyticsSQLExplainIT
ahkcs d65305a
Reuse base class executeQuery() in AnalyticsSQLIT
ahkcs aacb418
Remove no-arg constructor and null check for analyticsRouter
ahkcs 617cbed
Extract V2 engine delegation into delegateToV2Engine method
ahkcs 543e32c
Refactor SQL table extraction to use SqlBasicVisitor pattern
ahkcs 7125fb6
Move SqlBasicVisitor to import header
ahkcs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
integ-test/src/test/java/org/opensearch/sql/sql/AnalyticsSQLExplainIT.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| /* | ||
| * Copyright OpenSearch Contributors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package org.opensearch.sql.sql; | ||
|
|
||
| import static org.opensearch.sql.legacy.TestUtils.isIndexExist; | ||
| import static org.opensearch.sql.util.MatcherUtils.assertJsonEqualsIgnoreId; | ||
|
|
||
| import com.google.common.io.Resources; | ||
| import java.io.IOException; | ||
| import java.net.URI; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Paths; | ||
| import org.junit.Test; | ||
| import org.opensearch.client.Request; | ||
| import org.opensearch.sql.legacy.SQLIntegTestCase; | ||
|
|
||
| /** | ||
| * Explain integration tests for SQL queries routed through the analytics engine path (Project | ||
| * Analytics engine). Validates that SQL queries targeting "parquet_*" indices produce correct | ||
| * logical plans via the _plugins/_sql/_explain endpoint. | ||
| * | ||
| * <p>Expected output files are in resources/expectedOutput/analytics_sql/. Each test compares the | ||
| * explain JSON output against its expected file. | ||
| */ | ||
| @SuppressWarnings("deprecation") // assertJsonEqualsIgnoreId is correct for JSON explain response | ||
| public class AnalyticsSQLExplainIT extends SQLIntegTestCase { | ||
|
|
||
| @Override | ||
| protected void init() throws Exception { | ||
| if (!isIndexExist(client(), "parquet_logs")) { | ||
| Request request = new Request("PUT", "/parquet_logs"); | ||
| request.setJsonEntity( | ||
| "{" | ||
| + "\"mappings\": {" | ||
| + " \"properties\": {" | ||
| + " \"ts\": {\"type\": \"date\"}," | ||
| + " \"status\": {\"type\": \"integer\"}," | ||
| + " \"message\": {\"type\": \"keyword\"}," | ||
| + " \"ip_addr\": {\"type\": \"keyword\"}" | ||
| + " }" | ||
| + "}" | ||
| + "}"); | ||
| client().performRequest(request); | ||
| } | ||
| } | ||
|
|
||
| private static String loadExpectedJson(String fileName) { | ||
| return loadFromFile("expectedOutput/analytics_sql/" + fileName); | ||
| } | ||
|
|
||
| private static String loadFromFile(String filename) { | ||
| try { | ||
| URI uri = Resources.getResource(filename).toURI(); | ||
| return new String(Files.readAllBytes(Paths.get(uri))); | ||
| } catch (Exception e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testExplainSelectStar() throws IOException { | ||
| assertJsonEqualsIgnoreId( | ||
| loadExpectedJson("explain_select_star.json"), explainQuery("SELECT * FROM parquet_logs")); | ||
| } | ||
|
|
||
| @Test | ||
| public void testExplainSelectColumns() throws IOException { | ||
| assertJsonEqualsIgnoreId( | ||
| loadExpectedJson("explain_select_columns.json"), | ||
| explainQuery("SELECT ts, status FROM parquet_logs")); | ||
| } | ||
|
|
||
| @Test | ||
| public void testExplainSelectWithWhere() throws IOException { | ||
| assertJsonEqualsIgnoreId( | ||
| loadExpectedJson("explain_select_where.json"), | ||
| explainQuery("SELECT ts, message FROM parquet_logs WHERE status = 200")); | ||
| } | ||
| } | ||
90 changes: 90 additions & 0 deletions
90
integ-test/src/test/java/org/opensearch/sql/sql/AnalyticsSQLIT.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| /* | ||
| * Copyright OpenSearch Contributors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package org.opensearch.sql.sql; | ||
|
|
||
| import static org.opensearch.sql.legacy.TestUtils.isIndexExist; | ||
| import static org.opensearch.sql.util.MatcherUtils.rows; | ||
| import static org.opensearch.sql.util.MatcherUtils.schema; | ||
| import static org.opensearch.sql.util.MatcherUtils.verifyDataRows; | ||
| import static org.opensearch.sql.util.MatcherUtils.verifySchema; | ||
|
|
||
| import java.io.IOException; | ||
| import org.json.JSONObject; | ||
| import org.junit.Test; | ||
| import org.opensearch.client.Request; | ||
| import org.opensearch.client.ResponseException; | ||
| import org.opensearch.sql.legacy.SQLIntegTestCase; | ||
|
|
||
| /** | ||
| * Integration tests for SQL queries routed through the analytics engine path. Queries targeting | ||
| * "parquet_*" indices are routed to {@code RestUnifiedQueryAction} which uses {@code | ||
| * AnalyticsExecutionEngine} with a stub {@code QueryPlanExecutor}. | ||
| * | ||
| * <p>The stub executor returns rows in a fixed order [ts, status, message, ip_addr] regardless of | ||
| * the plan. The schema from OpenSearchSchemaBuilder is alphabetical [ip_addr, message, status, ts]. | ||
| * AnalyticsExecutionEngine maps values by position, so the data values appear mismatched. This is | ||
| * expected; the real analytics engine will evaluate the plan correctly. | ||
| */ | ||
| public class AnalyticsSQLIT extends SQLIntegTestCase { | ||
|
|
||
| @Override | ||
| protected void init() throws Exception { | ||
| createParquetLogsIndex(); | ||
| } | ||
|
|
||
| private void createParquetLogsIndex() throws IOException { | ||
| if (isIndexExist(client(), "parquet_logs")) { | ||
| return; | ||
| } | ||
| Request request = new Request("PUT", "/parquet_logs"); | ||
| request.setJsonEntity( | ||
| "{" | ||
| + "\"mappings\": {" | ||
| + " \"properties\": {" | ||
| + " \"ts\": {\"type\": \"date\"}," | ||
| + " \"status\": {\"type\": \"integer\"}," | ||
| + " \"message\": {\"type\": \"keyword\"}," | ||
| + " \"ip_addr\": {\"type\": \"keyword\"}" | ||
| + " }" | ||
| + "}" | ||
| + "}"); | ||
| client().performRequest(request); | ||
| } | ||
|
|
||
| @Test | ||
| public void testSelectStarSchemaAndData() throws IOException { | ||
| JSONObject result = executeQuery("SELECT * FROM parquet_logs"); | ||
| verifySchema( | ||
| result, | ||
| schema("ip_addr", "string"), | ||
| schema("message", "string"), | ||
| schema("status", "integer"), | ||
| schema("ts", "timestamp")); | ||
| // Stub returns [ts, status, message, ip_addr] per row, mapped by position to | ||
| // [ip_addr, message, status, ts] schema. Values appear mismatched — expected with stub. | ||
| verifyDataRows( | ||
| result, | ||
| rows("2024-01-15 10:30:00", 200, "Request completed", "192.168.1.1"), | ||
| rows("2024-01-15 10:31:00", 200, "Health check OK", "192.168.1.2"), | ||
| rows("2024-01-15 10:32:00", 500, "Internal server error", "192.168.1.3")); | ||
| } | ||
|
|
||
| @Test | ||
| public void testSelectSpecificColumns() throws IOException { | ||
| JSONObject result = executeQuery("SELECT status, message FROM parquet_logs"); | ||
| verifySchema(result, schema("status", "integer"), schema("message", "string")); | ||
| verifyDataRows( | ||
| result, | ||
| rows("2024-01-15 10:30:00", 200), | ||
| rows("2024-01-15 10:31:00", 200), | ||
| rows("2024-01-15 10:32:00", 500)); | ||
| } | ||
|
|
||
| @Test(expected = ResponseException.class) | ||
| public void testSyntaxError() throws IOException { | ||
| executeQuery("SELEC * FROM parquet_logs"); | ||
| } | ||
| } |
5 changes: 5 additions & 0 deletions
5
integ-test/src/test/resources/expectedOutput/analytics_sql/explain_select_columns.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "calcite": { | ||
| "logical": "LogicalSystemLimit(fetch=[10000], type=[QUERY_SIZE_LIMIT])\n LogicalProject(ts=[$3], status=[$2])\n LogicalTableScan(table=[[opensearch, parquet_logs]])\n" | ||
| } | ||
| } |
5 changes: 5 additions & 0 deletions
5
integ-test/src/test/resources/expectedOutput/analytics_sql/explain_select_star.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "calcite": { | ||
| "logical": "LogicalSystemLimit(fetch=[10000], type=[QUERY_SIZE_LIMIT])\n LogicalProject(ip_addr=[$0], message=[$1], status=[$2], ts=[$3])\n LogicalTableScan(table=[[opensearch, parquet_logs]])\n" | ||
| } | ||
| } |
5 changes: 5 additions & 0 deletions
5
integ-test/src/test/resources/expectedOutput/analytics_sql/explain_select_where.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "calcite": { | ||
| "logical": "LogicalSystemLimit(fetch=[10000], type=[QUERY_SIZE_LIMIT])\n LogicalProject(ts=[$3], message=[$1])\n LogicalFilter(condition=[=($2, 200)])\n LogicalTableScan(table=[[opensearch, parquet_logs]])\n" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you also check EXPLAIN statement? It's supported by Calcite SQL, e.g.,
EXPLAIN PLAN FOR ...Ref: https://calcite.apache.org/docs/reference.htmlThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Investigated —
EXPLAIN PLAN FORis parsed by Calcite as aSqlExplainnode, butSqlToRelConverter.convertQueryRecursive()doesn't handle it (throwsAssertionError("not a query: ...") in the default case). Calcite treats EXPLAIN as a meta-statement handled at the JDBC layer (CalcitePrepareImpl), not at theSqlToRelConverterlevel.To support it, we'd need to detect SqlExplain in
CalciteNativeStrategy.plan(), unwrap the inner query, plan it, and return the plan as a formatted result. However, the resulting plan would be identical to what/_plugins/_sql/_explainalready returns for the same query — the only difference is the response format (EXPLAIN PLAN FORreturns the plan as a query result row,/_explainreturns it as a JSON explain response)Do we want to support EXPLAIN statement?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error log: