Skip to content

Commit e50604c

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 e50604c

4 files changed

Lines changed: 127 additions & 2 deletions

File tree

docs/user/interfaces/endpoint.rst

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,58 @@ 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+
Expected output (trimmed)::
298+
299+
{
300+
"profile": {
301+
"summary": {
302+
"total_time_ms": 33.34
303+
},
304+
"phases": {
305+
"analyze": { "time_ms": 8.68 },
306+
"optimize": { "time_ms": 18.2 },
307+
"execute": { "time_ms": 4.87 },
308+
"format": { "time_ms": 0.05 }
309+
},
310+
"plan": {
311+
"node": "EnumerableCalc",
312+
"time_ms": 4.82,
313+
"rows": 2,
314+
"children": [
315+
{ "node": "CalciteEnumerableIndexScan", "time_ms": 4.12, "rows": 2 }
316+
]
317+
}
318+
}
319+
}
320+
269321
Fetch Size (PPL) [Experimental]
270322
================================
271323

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.isProfileEnabled(),
275275
new ActionListener<>() {
276276
@Override
277277
public void onResponse(TransportPPLQueryResponse response) {

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@
2626
@RequiredArgsConstructor
2727
public class SQLQueryRequest {
2828
private static final String QUERY_FIELD_CURSOR = "cursor";
29+
private static final String QUERY_PARAMS_PROFILE = "profile";
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, QUERY_PARAMS_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";
@@ -118,6 +119,14 @@ public boolean isExplainRequest() {
118119
return path.endsWith("/_explain");
119120
}
120121

122+
/** Check if profiling should run for this request. */
123+
public boolean isProfileEnabled() {
124+
return jsonContent != null
125+
&& jsonContent.optBoolean(QUERY_PARAMS_PROFILE, false)
126+
&& !isExplainRequest()
127+
&& Format.JDBC.getFormatName().equalsIgnoreCase(format);
128+
}
129+
121130
public boolean isCursorCloseRequest() {
122131
return path.endsWith("/close");
123132
}

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,70 @@ 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_disable_profile_when_no_json_content() {
298+
SQLQueryRequest request =
299+
new SQLQueryRequest(null, "SELECT 1", "_plugins/_sql", Map.of(), null);
300+
assertFalse(request.isProfileEnabled());
301+
}
302+
303+
@Test
304+
public void should_disable_profile_when_profile_false() {
305+
SQLQueryRequest request =
306+
new SQLQueryRequest(
307+
new JSONObject("{\"query\": \"SELECT 1\"}"),
308+
"SELECT 1",
309+
"_plugins/_sql",
310+
Map.of(),
311+
null);
312+
assertFalse(request.isProfileEnabled());
313+
}
314+
315+
@Test
316+
public void should_enable_profile_for_jdbc_query_with_profile_true() {
317+
SQLQueryRequest request =
318+
new SQLQueryRequest(
319+
new JSONObject("{\"query\": \"SELECT 1\", \"profile\": true}"),
320+
"SELECT 1",
321+
"_plugins/_sql",
322+
Map.of(),
323+
null);
324+
assertTrue(request.isProfileEnabled());
325+
}
326+
327+
@Test
328+
public void should_disable_profile_on_explain_path() {
329+
SQLQueryRequest request =
330+
new SQLQueryRequest(
331+
new JSONObject("{\"query\": \"SELECT 1\", \"profile\": true}"),
332+
"SELECT 1",
333+
"_plugins/_sql/_explain",
334+
Map.of(),
335+
null);
336+
assertFalse(request.isProfileEnabled());
337+
}
338+
339+
@Test
340+
public void should_disable_profile_for_non_jdbc_format() {
341+
SQLQueryRequest request =
342+
new SQLQueryRequest(
343+
new JSONObject("{\"query\": \"SELECT 1\", \"profile\": true}"),
344+
"SELECT 1",
345+
"_plugins/_sql",
346+
Map.of("format", "csv"),
347+
null);
348+
assertFalse(request.isProfileEnabled());
349+
}
350+
287351
/** SQL query request build helper to improve test data setup readability. */
288352
private static class SQLQueryRequestBuilder {
289353
private String jsonContent;

0 commit comments

Comments
 (0)