Set time limit of 1000ms on SQL queries

Using the (undocumented in the Python docs) fact that if you return 1 from a
set_progress_handler callback, SQLite will cancel the current query.

Closes #35
This commit is contained in:
Simon Willison 2017-10-24 18:34:54 -07:00
commit 6823b09406

24
app.py
View file

@ -4,6 +4,7 @@ from sanic.exceptions import NotFound
from sanic.views import HTTPMethodView from sanic.views import HTTPMethodView
from sanic_jinja2 import SanicJinja2 from sanic_jinja2 import SanicJinja2
import sqlite3 import sqlite3
from contextlib import contextmanager
from pathlib import Path from pathlib import Path
from functools import wraps from functools import wraps
import urllib.parse import urllib.parse
@ -18,6 +19,7 @@ app_root = Path(__file__).parent
BUILD_METADATA = 'build-metadata.json' BUILD_METADATA = 'build-metadata.json'
DB_GLOBS = ('*.db', '*.sqlite', '*.sqlite3') DB_GLOBS = ('*.db', '*.sqlite', '*.sqlite3')
HASH_BLOCK_SIZE = 1024 * 1024 HASH_BLOCK_SIZE = 1024 * 1024
SQL_TIME_LIMIT_MS = 1000
conns = {} conns = {}
@ -167,7 +169,8 @@ class DatabaseView(BaseView):
def data(self, request, name, hash): def data(self, request, name, hash):
conn = get_conn(name) conn = get_conn(name)
sql = request.args.get('sql') or 'select * from sqlite_master' sql = request.args.get('sql') or 'select * from sqlite_master'
rows = conn.execute(sql) with sqlite_timelimit(conn, SQL_TIME_LIMIT_MS):
rows = conn.execute(sql)
columns = [r[0] for r in rows.description] columns = [r[0] for r in rows.description]
return { return {
'database': name, 'database': name,
@ -192,10 +195,13 @@ class TableView(BaseView):
else: else:
sql = 'select * from "{}" limit 50'.format(table) sql = 'select * from "{}" limit 50'.format(table)
params = [] params = []
rows = conn.execute(sql, params)
with sqlite_timelimit(conn, SQL_TIME_LIMIT_MS):
rows = conn.execute(sql, params)
columns = [r[0] for r in rows.description] columns = [r[0] for r in rows.description]
pks = pks_for_table(conn, table)
rows = list(rows) rows = list(rows)
pks = pks_for_table(conn, table)
info = ensure_build_metadata() info = ensure_build_metadata()
total_rows = info[name]['tables'].get(table) total_rows = info[name]['tables'].get(table)
return { return {
@ -355,6 +361,18 @@ class CustomJSONEncoder(json.JSONEncoder):
return json.JSONEncoder.default(self, obj) return json.JSONEncoder.default(self, obj)
@contextmanager
def sqlite_timelimit(conn, ms):
deadline = time.time() + (ms / 1000)
def handler():
if time.time() >= deadline:
return 1
conn.set_progress_handler(handler, 10000)
yield
conn.set_progress_handler(None, 10000)
if __name__ == '__main__': if __name__ == '__main__':
if '--build' in sys.argv: if '--build' in sys.argv:
ensure_build_metadata(True) ensure_build_metadata(True)