Skip to content

Commit 6ae2365

Browse files
committed
feat(sql): add profile support to analytics path
Add a `profile` boolean field to SQLQueryRequest and thread it through the SQL REST handler to the analytics router. Mirrors PPL's existing profile pattern: parsed from the JSON body's `profile` key, gated by the same rules (only honored for non-explain JDBC requests on the analytics engine path). Replaces the hardcoded `false` in SQLPlugin#createSqlAnalyticsRouter so analytics-engine queries now honor the request's profile flag. The V2 SQL engine path is unchanged; profile is silently ignored when the request does not route to the analytics engine. Refs: opensearch-project#5317 Signed-off-by: Chen Dai <daichen@amazon.com>
1 parent 37090ba commit 6ae2365

5 files changed

Lines changed: 120 additions & 3 deletions

File tree

docs/user/interfaces/endpoint.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,46 @@ Result set::
266266
"status": 200
267267
}
268268

269+
Profile (Experimental)
270+
======================
271+
272+
Description
273+
-----------
274+
275+
Profiling captures per-stage timings (in milliseconds) for SQL query
276+
execution. To enable profiling, set ``"profile": true`` in the request
277+
body alongside ``"query"``.
278+
279+
.. note::
280+
The ``profile`` parameter only takes effect when the query runs on
281+
the Analytics Engine. In all other cases the flag is silently ignored.
282+
283+
Profile output is returned only for regular query execution (not
284+
``_explain``) and only with the default ``format=jdbc``.
285+
286+
Example
287+
-------
288+
289+
Request::
290+
291+
POST /_plugins/_sql
292+
{
293+
"query": "SELECT customer_id, SUM(amount) FROM orders GROUP BY customer_id",
294+
"profile": true
295+
}
296+
297+
Response::
298+
299+
{
300+
"schema": [...],
301+
"datarows": [...],
302+
"profile": {
303+
"summary": { "total_time_ms": 42 },
304+
"phases": { "analyze": 3, "optimize": 5, "execute": 30, "format": 4 },
305+
"plan": { ... }
306+
}
307+
}
308+
269309
Fetch Size (PPL) [Experimental]
270310
================================
271311

legacy/src/main/java/org/opensearch/sql/legacy/plugin/RestSqlAction.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,21 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli
145145

146146
Format format = SqlRequestParam.getFormat(request.params());
147147

148+
// Compute profile flag mirroring PPL's gating logic
149+
boolean profileRequested =
150+
sqlRequest.getJsonContent() != null
151+
&& sqlRequest.getJsonContent().optBoolean("profile", false);
152+
boolean enableProfile =
153+
profileRequested && isProfileSupported(request.path(), format, sqlRequest.getSql());
154+
148155
SQLQueryRequest newSqlRequest =
149156
new SQLQueryRequest(
150157
sqlRequest.getJsonContent(),
151158
sqlRequest.getSql(),
152159
request.path(),
153160
request.params(),
154-
sqlRequest.cursor());
161+
sqlRequest.cursor(),
162+
enableProfile);
155163

156164
// Route to analytics engine for non-Lucene (e.g., Parquet-backed) indices.
157165
// The router returns true and sends the response directly if it handled the request.
@@ -366,4 +374,18 @@ private static ColumnTypeProvider performAnalysis(String sql) {
366374
return new ColumnTypeProvider();
367375
}
368376
}
377+
378+
private static final String DEFAULT_RESPONSE_FORMAT = "jdbc";
379+
380+
private static boolean isProfileSupported(String path, Format format, String query) {
381+
boolean explainPath = isExplainRequest(path);
382+
boolean explainQuery = query != null && query.trim().toLowerCase().startsWith("explain");
383+
boolean isJdbcFormat =
384+
format != null && DEFAULT_RESPONSE_FORMAT.equalsIgnoreCase(format.getFormatName());
385+
return !explainPath && !explainQuery && isJdbcFormat;
386+
}
387+
388+
private static boolean isExplainRequest(String path) {
389+
return path != null && path.endsWith("/_explain");
390+
}
369391
}

plugin/src/main/java/org/opensearch/sql/plugin/SQLPlugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ public void onFailure(Exception e) {
271271
unifiedQueryHandler.execute(
272272
sqlRequest.getQuery(),
273273
QueryType.SQL,
274-
false,
274+
sqlRequest.profile(),
275275
new ActionListener<>() {
276276
@Override
277277
public void onResponse(TransportPPLQueryResponse response) {

sql/src/main/java/org/opensearch/sql/sql/domain/SQLQueryRequest.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import lombok.EqualsAndHashCode;
1616
import lombok.Getter;
1717
import lombok.RequiredArgsConstructor;
18+
import lombok.Setter;
1819
import lombok.ToString;
1920
import lombok.experimental.Accessors;
2021
import org.json.JSONObject;
@@ -27,7 +28,7 @@
2728
public class SQLQueryRequest {
2829
private static final String QUERY_FIELD_CURSOR = "cursor";
2930
private static final Set<String> SUPPORTED_FIELDS =
30-
Set.of("query", "fetch_size", "parameters", QUERY_FIELD_CURSOR);
31+
Set.of("query", "fetch_size", "parameters", QUERY_FIELD_CURSOR, "profile");
3132
private static final String QUERY_PARAMS_FORMAT = "format";
3233
private static final String QUERY_PARAMS_SANITIZE = "sanitize";
3334
private static final String QUERY_PARAMS_PRETTY = "pretty";
@@ -55,6 +56,11 @@ public class SQLQueryRequest {
5556
@Accessors(fluent = true)
5657
private boolean pretty = false;
5758

59+
@Setter
60+
@Getter
61+
@Accessors(fluent = true)
62+
private boolean profile = false;
63+
5864
private String cursor;
5965

6066
/** Constructor of SQLQueryRequest that passes request params. */
@@ -64,6 +70,17 @@ public SQLQueryRequest(
6470
String path,
6571
Map<String, String> params,
6672
String cursor) {
73+
this(jsonContent, query, path, params, cursor, false);
74+
}
75+
76+
/** Constructor of SQLQueryRequest that passes request params and profile flag. */
77+
public SQLQueryRequest(
78+
JSONObject jsonContent,
79+
String query,
80+
String path,
81+
Map<String, String> params,
82+
String cursor,
83+
boolean profile) {
6784
this.jsonContent = jsonContent;
6885
this.query = query;
6986
this.path = path;
@@ -72,6 +89,7 @@ public SQLQueryRequest(
7289
this.sanitize = shouldSanitize(params);
7390
this.pretty = shouldPretty(params);
7491
this.cursor = cursor;
92+
this.profile = profile;
7593
}
7694

7795
/**

sql/src/test/java/org/opensearch/sql/sql/domain/SQLQueryRequestTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,43 @@ public void should_support_raw_format() {
284284
assertTrue(csvRequest.isSupported());
285285
}
286286

287+
@Test
288+
public void should_support_query_with_profile_field() {
289+
SQLQueryRequest request =
290+
SQLQueryRequestBuilder.request("SELECT 1")
291+
.jsonContent("{\"query\": \"SELECT 1\", \"profile\": true}")
292+
.build();
293+
assertTrue(request.isSupported());
294+
}
295+
296+
@Test
297+
public void should_have_profile_false_by_default() {
298+
SQLQueryRequest request = SQLQueryRequestBuilder.request("SELECT 1").build();
299+
assertFalse(request.profile());
300+
}
301+
302+
@Test
303+
public void should_set_profile_via_constructor() {
304+
SQLQueryRequest request =
305+
new SQLQueryRequest(
306+
new JSONObject("{\"query\": \"SELECT 1\", \"profile\": true}"),
307+
"SELECT 1",
308+
"_plugins/_sql",
309+
Map.of(),
310+
null,
311+
true);
312+
assertTrue(request.profile());
313+
}
314+
315+
@Test
316+
public void should_support_query_and_profile_fields_only() {
317+
SQLQueryRequest request =
318+
SQLQueryRequestBuilder.request("SELECT 1")
319+
.jsonContent("{\"query\": \"SELECT 1\", \"profile\": true}")
320+
.build();
321+
assertTrue(request.isSupported());
322+
}
323+
287324
/** SQL query request build helper to improve test data setup readability. */
288325
private static class SQLQueryRequestBuilder {
289326
private String jsonContent;

0 commit comments

Comments
 (0)