Show SQL query when reporting time limit error, closes #1819

This commit is contained in:
Simon Willison 2022-09-26 16:06:01 -07:00
commit 5f9f567acb
4 changed files with 35 additions and 13 deletions

View file

@ -476,7 +476,10 @@ class WriteTask:
class QueryInterrupted(Exception): class QueryInterrupted(Exception):
pass def __init__(self, e, sql, params):
self.e = e
self.sql = sql
self.params = params
class MultipleValues(Exception): class MultipleValues(Exception):

View file

@ -1,10 +1,12 @@
import asyncio import asyncio
import csv import csv
import hashlib import hashlib
import re
import sys import sys
import textwrap
import time import time
import urllib import urllib
from markupsafe import escape
import pint import pint
@ -24,11 +26,9 @@ from datasette.utils import (
path_with_removed_args, path_with_removed_args,
path_with_format, path_with_format,
sqlite3, sqlite3,
HASH_LENGTH,
) )
from datasette.utils.asgi import ( from datasette.utils.asgi import (
AsgiStream, AsgiStream,
Forbidden,
NotFound, NotFound,
Response, Response,
BadRequest, BadRequest,
@ -371,13 +371,18 @@ class DataView(BaseView):
) = response_or_template_contexts ) = response_or_template_contexts
else: else:
data, extra_template_data, templates = response_or_template_contexts data, extra_template_data, templates = response_or_template_contexts
except QueryInterrupted: except QueryInterrupted as ex:
raise DatasetteError( raise DatasetteError(
""" textwrap.dedent(
SQL query took too long. The time limit is controlled by the """
<p>SQL query took too long. The time limit is controlled by the
<a href="https://docs.datasette.io/en/stable/settings.html#sql-time-limit-ms">sql_time_limit_ms</a> <a href="https://docs.datasette.io/en/stable/settings.html#sql-time-limit-ms">sql_time_limit_ms</a>
configuration option. configuration option.</p>
""", <pre>{}</pre>
""".format(
escape(ex.sql)
)
).strip(),
title="SQL Interrupted", title="SQL Interrupted",
status=400, status=400,
message_is_html=True, message_is_html=True,

View file

@ -656,7 +656,17 @@ def test_custom_sql(app_client):
def test_sql_time_limit(app_client_shorter_time_limit): def test_sql_time_limit(app_client_shorter_time_limit):
response = app_client_shorter_time_limit.get("/fixtures.json?sql=select+sleep(0.5)") response = app_client_shorter_time_limit.get("/fixtures.json?sql=select+sleep(0.5)")
assert 400 == response.status assert 400 == response.status
assert "SQL Interrupted" == response.json["title"] assert response.json == {
"ok": False,
"error": (
"<p>SQL query took too long. The time limit is controlled by the\n"
'<a href="https://docs.datasette.io/en/stable/settings.html#sql-time-limit-ms">sql_time_limit_ms</a>\n'
"configuration option.</p>\n"
"<pre>select sleep(0.5)</pre>"
),
"status": 400,
"title": "SQL Interrupted",
}
def test_custom_sql_time_limit(app_client): def test_custom_sql_time_limit(app_client):

View file

@ -168,10 +168,14 @@ def test_disallowed_custom_sql_pragma(app_client):
def test_sql_time_limit(app_client_shorter_time_limit): def test_sql_time_limit(app_client_shorter_time_limit):
response = app_client_shorter_time_limit.get("/fixtures?sql=select+sleep(0.5)") response = app_client_shorter_time_limit.get("/fixtures?sql=select+sleep(0.5)")
assert 400 == response.status assert 400 == response.status
expected_html_fragment = """ expected_html_fragments = [
"""
<a href="https://docs.datasette.io/en/stable/settings.html#sql-time-limit-ms">sql_time_limit_ms</a> <a href="https://docs.datasette.io/en/stable/settings.html#sql-time-limit-ms">sql_time_limit_ms</a>
""".strip() """.strip(),
assert expected_html_fragment in response.text "<pre>select sleep(0.5)</pre>",
]
for expected_html_fragment in expected_html_fragments:
assert expected_html_fragment in response.text
def test_row_page_does_not_truncate(): def test_row_page_does_not_truncate():