Skip to content

Commit af22570

Browse files
committed
fix(issues): Replace 'Internal' Error message on Postgres query timeout errors
Handle Postgres statement timeout errors on issue search which previously displayed 'Internal Error' but will now show more appropriate message explaining the query took too long and to possibly revise it Closes: #80166
1 parent 22e51b3 commit af22570

File tree

2 files changed

+42
-2
lines changed

2 files changed

+42
-2
lines changed

src/sentry/api/utils.py

+10
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
from datetime import timedelta
1010
from typing import Any, Literal, overload
1111

12+
import psycopg2.errorcodes
1213
import sentry_sdk
1314
from django.conf import settings
15+
from django.db.utils import OperationalError
1416
from django.http import HttpRequest
1517
from django.utils import timezone
1618
from rest_framework.exceptions import APIException, ParseError, Throttled
@@ -423,6 +425,14 @@ def handle_query_errors() -> Generator[None]:
423425
else:
424426
sentry_sdk.capture_exception(error)
425427
raise APIException(detail=message)
428+
except OperationalError as error:
429+
if hasattr(error, "pgcode") and error.pgcode == psycopg2.errorcodes.QUERY_CANCELED:
430+
sentry_sdk.set_tag("query.error_reason", "Postgres statement timeout")
431+
raise Throttled(
432+
detail="Query timeout. Please try with a smaller date range or fewer conditions."
433+
)
434+
# Let other OperationalErrors propagate as normal
435+
raise
426436

427437

428438
def update_snuba_params_with_timestamp(

tests/sentry/api/test_utils.py

+32-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
import unittest
33
from unittest.mock import MagicMock, patch
44

5+
import psycopg2
56
import pytest
7+
from django.db import OperationalError
68
from django.utils import timezone
79
from rest_framework.exceptions import APIException
810
from sentry_sdk import Scope
@@ -168,13 +170,12 @@ class FooBarError(Exception):
168170
pass
169171

170172

171-
class HandleQueryErrorsTest:
173+
class HandleQueryErrorsTest(APITestCase):
172174
@patch("sentry.api.utils.ParseError")
173175
def test_handle_query_errors(self, mock_parse_error):
174176
exceptions = [
175177
DatasetSelectionError,
176178
IncompatibleMetricsQuery,
177-
InvalidParams,
178179
InvalidSearchQuery,
179180
QueryConnectionFailed,
180181
QueryExecutionError,
@@ -198,6 +199,35 @@ def test_handle_query_errors(self, mock_parse_error):
198199
except Exception as e:
199200
assert isinstance(e, (FooBarError, APIException))
200201

202+
@patch("sentry.api.utils.Throttled")
203+
def test_handle_postgres_timeout(self, mock_throttled):
204+
class TimeoutError(OperationalError):
205+
pgcode = psycopg2.errorcodes.QUERY_CANCELED
206+
207+
mock_throttled.return_value = FooBarError()
208+
209+
try:
210+
with handle_query_errors():
211+
raise TimeoutError()
212+
except Exception as e:
213+
assert isinstance(e, FooBarError)
214+
mock_throttled.assert_called_once_with(
215+
detail="Query timeout. Please try with a smaller date range or fewer conditions."
216+
)
217+
218+
@patch("sentry.api.utils.ParseError")
219+
def test_handle_other_operational_error(self, mock_parse_error):
220+
class OtherError(OperationalError):
221+
# No pgcode attribute
222+
pass
223+
224+
try:
225+
with handle_query_errors():
226+
raise OtherError()
227+
except Exception as e:
228+
assert isinstance(e, OtherError) # Should propagate original error
229+
mock_parse_error.assert_not_called()
230+
201231

202232
class ClampDateRangeTest(unittest.TestCase):
203233
def test_no_clamp_if_range_under_max(self):

0 commit comments

Comments
 (0)