Compare commits

...

1 Commits

Author SHA1 Message Date
Maxime Beauchemin
59ddd52789 fix(sqllab): Set explicit Content-Type headers to prevent HTTP 406 errors
Fixes #36072 where SQL Lab queries with WHERE clauses failed with
"Database error: Not acceptable" in Superset v4.1+.

Root cause: Flask 2.3+ (upgraded in v4.1.0) has stricter content
negotiation that could return HTTP 406 when Content-Type headers
aren't explicitly set, particularly with ENABLE_PROXY_FIX or certain
Accept header configurations.

Changes:
- Add explicit Content-Type headers to /api/v1/sqllab/execute/ and
  /api/v1/sqllab/results/ endpoints
- Improve error handling with try-except blocks for result fetching
  and JSON serialization
- Add targeted integration test for WHERE clause queries

The fix ensures Flask 2.3+ doesn't attempt content negotiation that
could fail, while maintaining backward compatibility.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 10:47:44 -08:00
2 changed files with 63 additions and 9 deletions

View File

@@ -337,16 +337,32 @@ class SqlLabRestApi(BaseSupersetApi):
params = kwargs["rison"]
key = params.get("key")
rows = params.get("rows")
result = SqlExecutionResultsCommand(key=key, rows=rows).run()
try:
result = SqlExecutionResultsCommand(key=key, rows=rows).run()
except Exception as ex:
logger.exception("Error fetching query results for key=%s", key)
return self.response_500(message=str(ex))
# Using pessimistic json serialization since some database drivers can return
# unserializeable types at times
payload = json.dumps(
result,
default=json.pessimistic_json_iso_dttm_ser,
ignore_nan=True,
)
return json_success(payload, 200)
try:
payload = json.dumps(
result,
default=json.pessimistic_json_iso_dttm_ser,
ignore_nan=True,
)
except Exception as ex:
logger.exception("Error serializing query results for key=%s", key)
return self.response_500(message="Unable to serialize query results")
# Use json_success with explicit Content-Type to ensure Flask 2.3+ correctly
# handles the response and doesn't trigger HTTP 406 errors due to content
# negotiation issues with Accept headers or proxy configurations
response = json_success(payload, 200)
# Explicitly set Content-Type as a safeguard against content negotiation issues
response.headers["Content-Type"] = "application/json; charset=utf-8"
return response
@expose("/execute/", methods=("POST",))
@protect()
@@ -410,8 +426,11 @@ class SqlLabRestApi(BaseSupersetApi):
if command_result["status"] == SqlJsonExecutionStatus.QUERY_IS_RUNNING
else 200
)
# return the execution result without special encoding
return json_success(command_result["payload"], response_status)
# Return the execution result without special encoding
# Set explicit Content-Type to prevent Flask 2.3+ content negotiation issues
response = json_success(command_result["payload"], response_status)
response.headers["Content-Type"] = "application/json; charset=utf-8"
return response
except SqlLabException as ex:
payload = {"errors": [ex.to_dict()]}

View File

@@ -126,6 +126,41 @@ class TestSqlLab(SupersetTestCase):
"engine_name": engine_name,
}
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
def test_sql_json_where_clause_content_type(self):
"""
Test that queries with WHERE clauses return proper Content-Type headers.
This test addresses issue #36072 where Flask 2.3+ content negotiation
could cause HTTP 406 errors for queries with WHERE clauses, particularly
when using ENABLE_PROXY_FIX or certain Accept header configurations.
"""
self.login(ADMIN_USERNAME)
# Test query with WHERE clause
resp = self.client.post(
"/api/v1/sqllab/execute/",
json={
"database_id": self.get_database_by_name("examples").id,
"sql": "SELECT * FROM birth_names WHERE name = 'John' LIMIT 5",
"client_id": "test_where_1",
},
)
# Verify response is successful
assert resp.status_code in (200, 202), f"Expected 200/202, got {resp.status_code}"
# Verify Content-Type header is explicitly set to prevent 406 errors
assert "application/json" in resp.headers.get("Content-Type", "")
# Verify response body is valid JSON
data = resp.json
assert isinstance(data, dict)
# If query ran synchronously (200), verify it has data
if resp.status_code == 200:
assert "data" in data or "query_id" in data
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
def test_sql_json_dml_disallowed(self):
self.login(ADMIN_USERNAME)