Fix SQL explain API rejecting ?format=json parameter - #5607
Fix SQL explain API rejecting ?format=json parameter#5607gingeekrishna wants to merge 3 commits into
Conversation
isSupportedExplainFormat() only accepted "simple", "standard", "extended", and "cost" — excluding "json". Before OpenSearch 3.0, ?format=json was valid for the explain endpoint, so existing workflows that pass this parameter now receive: "Failed to create executor due to unknown response format: json" The explain endpoint already returns JSON unconditionally regardless of the format parameter, so accepting "json" is a pure backward- compatibility restoration with no behavioral change. Fixes opensearch-project#4373 Signed-off-by: Radhakrishnan Pachyappan <[email protected]>
PR Reviewer Guide 🔍(Review updated until commit 26ce5c5)Here are some key observations to aid the review process:
|
|
The |
|
|
||
| private boolean isSupportedExplainFormat() { | ||
| return Stream.of("simple", "standard", "extended", "cost").anyMatch(format::equalsIgnoreCase); | ||
| // "json" is accepted for backward compatibility: the explain endpoint always returns JSON |
There was a problem hiding this comment.
Could you add yaml IT to verify and this may be moved there as context? Ref: https://github.com/opensearch-project/sql/blob/main/integ-test/src/yamlRestTest/resources/rest-api-spec/test/issues/5702.yml
There was a problem hiding this comment.
Thanks for asking for this - it turned up something important. While putting together an integration test I traced the actual request flow and found the original fix (adding "json" to isSupportedExplainFormat()) doesn't actually fix the bug: that method is in SQLQueryRequest (the V2 engine), but RestSqlAction#prepareRequest calls SqlRequestParam.getFormat(request.params()) unconditionally, for every request, before SQLQueryRequest is ever constructed. That call throws for any format outside jdbc/csv/raw/table - "json" included - which is exactly the "Failed to create executor due to unknown response format: json" error from #4373. So the original fix was never reached; the request was already failing a layer up.
Pushed 2a4d6a4c0, which adds the real fix in RestSqlAction (falls back instead of throwing when the rejected value is "json" on an explain request), and kept the original SQLQueryRequest change since it still matters once that's fixed (makes the V2 engine handle it natively instead of always falling back to legacy).
For the test: there's no YAML REST-spec coverage for any SQL endpoint yet (only ppl/ppl.explain/ppl.grammar/query.settings action specs exist), so introducing a new sql.explain action spec felt like a lot of new surface for one regression test. Added 26ce5c514 instead, an integration test in legacy/ExplainIT.java (SQL's existing IT pattern) that hits POST /_plugins/_sql/_explain?format=json for real. It's the test that would have actually caught this - my original unit test on isSupportedExplainFormat() alone gave false confidence since it never touched RestSqlAction at all. Let me know if you'd still prefer a YAML test / new action spec added instead.
The original fix in this PR only touched SQLQueryRequest#isSupportedExplainFormat() (the V2/sql-module engine's own validation), but that code is never reached for this bug: RestSqlAction#prepareRequest calls SqlRequestParam.getFormat(params) unconditionally, for every request, before SQLQueryRequest is even constructed. That call throws IllegalArgumentException for any format outside the legacy Format enum (jdbc/csv/raw/table) - "json" included - which is exactly the "Failed to create executor due to unknown response format: json" error from opensearch-project#4373. So format=json on /_explain was failing before either engine ever saw the request. Add RestSqlAction#resolveFormat, which falls back to a default Format when parsing fails AND the request is an explain request AND the rejected value is "json" - explain's response body is JSON regardless of this parameter (executeSqlRequest's explain branch calls queryAction.explain().explain() directly, never through a Format-specific executor), so ignoring it here changes nothing else about the response. The earlier SQLQueryRequest#isSupportedExplainFormat() change stays: once a request past this point, it makes the V2 engine treat format=json as supported so it handles the explain natively instead of always falling back to the legacy engine. Signed-off-by: Radhakrishnan P <[email protected]>
Per review, cover this with an integration test rather than only a unit test of SQLQueryRequest#isSupportedExplainFormat() in isolation - that unit test alone gave false confidence, since the real bug was in RestSqlAction, a layer up, which the unit test never exercised. There's no YAML REST-spec test coverage for any SQL endpoint yet (only ppl/ppl.explain/ppl.grammar/query.settings action specs exist under integ-test/src/yamlRestTest/resources/rest-api-spec/api/), so this follows the existing Java IT pattern used for the rest of legacy/ExplainIT.java instead of introducing a new sql.explain action spec for a single test. Signed-off-by: Radhakrishnan P <[email protected]>
|
Persistent review updated to latest commit 26ce5c5 |
PR Code Suggestions ✨Explore these optional code suggestions:
|
Description
Fixes #4373
Problem: Since OpenSearch 3.0, calling
POST /_plugins/_sql/_explain?format=jsonreturns:{ "error": { "reason": "Invalid SQL query", "details": "Failed to create executor due to unknown response format: json", "type": "IllegalArgumentException" }, "status": 400 }Root cause (corrected): My first attempt at this fix (still included below) only added
"json"toisSupportedExplainFormat()inSQLQueryRequest.java, the V2/sql-module engine's own validation. That turned out to be insufficient - that method is never reached for this bug.RestSqlAction#prepareRequestcallsSqlRequestParam.getFormat(request.params())unconditionally, for every request, beforeSQLQueryRequestis even constructed. That call throwsIllegalArgumentExceptionfor any format outside the legacyFormatenum (jdbc/csv/raw/table) -"json"included - which is exactly the error above. So the request was failing before either engine (legacy or V2) ever got a chance to look at it.Fix:
RestSqlAction#resolveFormat(new): whenSqlRequestParam.getFormatrejects the value AND the request is an explain request AND the rejected value is"json", fall back to a defaultFormatinstead of throwing. The explain response body is JSON regardless of this parameter (executeSqlRequest's explain branch callsqueryAction.explain().explain()directly, never through aFormat-specific executor), so ignoring it here changes nothing else about the response.SQLQueryRequest#isSupportedExplainFormat()(kept from the original attempt): once a request gets past (1), this makes the V2 engine treatformat=jsonas a supported explain format, so it handles the explain natively instead of always falling back to the legacy engine.Changes
legacy/src/main/java/.../plugin/RestSqlAction.java— addresolveFormat(), the actual fix for the reported crashsql/src/main/java/.../SQLQueryRequest.java— add"json"toisSupportedExplainFormat()(keeps the V2 engine as the primary path once the format-parsing crash is fixed)sql/src/test/java/.../SQLQueryRequestTest.java— unit test forisSupportedExplainFormat()integ-test/.../legacy/ExplainIT.java— integration test hitting the real/_explain?format=jsonendpoint end-to-end (per review - a unit test ofisSupportedExplainFormat()alone doesn't exerciseRestSqlAction, so it wouldn't have caught that the real bug was one layer up)Testing
SQLQueryRequestTest: unit coverage for the V2 engine's format validation.ExplainIT#testExplainAcceptsJsonFormatForBackwardCompatibility: hitsPOST /_plugins/_sql/_explain?format=jsonfor real and asserts a 200 with noerrorkey - this is the test that actually exercises the code path the bug lived in.Note: I don't have a Java/Gradle toolchain available in my current environment, so I couldn't run these locally - would appreciate CI confirming.