diff --git a/datasette/app.py b/datasette/app.py
index 8a3442b5..9f2b54c2 100644
--- a/datasette/app.py
+++ b/datasette/app.py
@@ -12,6 +12,7 @@ import urllib.parse
from concurrent import futures
from pathlib import Path
+from markupsafe import Markup
import pluggy
from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader
from sanic import Sanic, response
@@ -461,6 +462,7 @@ class Datasette:
@app.exception(Exception)
def on_exception(request, exception):
title = None
+ help = None
if isinstance(exception, NotFound):
status = 404
info = {}
@@ -473,6 +475,8 @@ class Datasette:
status = exception.status
info = exception.error_dict
message = exception.message
+ if exception.messagge_is_html:
+ message = Markup(message)
title = exception.title
else:
status = 500
diff --git a/datasette/views/base.py b/datasette/views/base.py
index 0aff72bc..d8aa8a14 100644
--- a/datasette/views/base.py
+++ b/datasette/views/base.py
@@ -27,11 +27,12 @@ HASH_LENGTH = 7
class DatasetteError(Exception):
- def __init__(self, message, title=None, error_dict=None, status=500, template=None):
+ def __init__(self, message, title=None, error_dict=None, status=500, template=None, messagge_is_html=False):
self.message = message
self.title = title
self.error_dict = error_dict or {}
self.status = status
+ self.messagge_is_html = messagge_is_html
class RenderMixin(HTTPMethodView):
@@ -154,7 +155,11 @@ class BaseView(RenderMixin):
else:
data, extra_template_data, templates = response_or_template_contexts
except InterruptedError as e:
- raise DatasetteError(str(e), title="SQL Interrupted", status=400)
+ raise DatasetteError("""
+ SQL query took too long. The time limit is controlled by the
+ sql_time_limit_ms
+ configuration option.
+ """, title="SQL Interrupted", status=400, messagge_is_html=True)
except (sqlite3.OperationalError, InvalidSql) as e:
raise DatasetteError(str(e), title="Invalid SQL", status=400)
diff --git a/tests/test_html.py b/tests/test_html.py
index b333362f..bec186c3 100644
--- a/tests/test_html.py
+++ b/tests/test_html.py
@@ -1,10 +1,11 @@
from bs4 import BeautifulSoup as Soup
-from .fixtures import app_client
+from .fixtures import app_client, app_client_shorter_time_limit
import pytest
import re
import urllib.parse
pytest.fixture(scope='session')(app_client)
+pytest.fixture(scope='session')(app_client_shorter_time_limit)
def test_homepage(app_client):
@@ -29,6 +30,18 @@ def test_invalid_custom_sql(app_client):
assert 'Statement must be a SELECT' in response.text
+def test_sql_time_limit(app_client_shorter_time_limit):
+ response = app_client_shorter_time_limit.get(
+ '/test_tables?sql=select+sleep(0.5)',
+ gather_request=False
+ )
+ assert 400 == response.status
+ expected_html_fragment = """
+ sql_time_limit_ms
+ """.strip()
+ assert expected_html_fragment in response.text
+
+
def test_view(app_client):
response = app_client.get('/test_tables/simple_view', gather_request=False)
assert response.status == 200