Skip to content

Commit

Permalink
New way of deriving named parameters using explain, refs #1421
Browse files Browse the repository at this point in the history
  • Loading branch information
simonw committed Aug 9, 2021
1 parent ad90a72 commit fc48468
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 2 deletions.
12 changes: 12 additions & 0 deletions datasette/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1076,3 +1076,15 @@ def method(self, *args, **kwargs):

class StartupError(Exception):
pass


_re_named_parameter = re.compile(":([a-zA-Z0-9_]+)")

async def derive_named_parameters(db, sql):
explain = 'explain {}'.format(sql.strip().rstrip(";"))
possible_params = _re_named_parameter.findall(sql)
try:
results = await db.execute(explain, {p: None for p in possible_params})
return [row["p4"].lstrip(":") for row in results if row["opcode"] == "Variable"]
except sqlite3.DatabaseError:
return []
1 change: 0 additions & 1 deletion datasette/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ async def view(request, send):

class DataView(BaseView):
name = ""
re_named_parameter = re.compile(":([a-zA-Z0-9_]+)")

async def options(self, request, *args, **kwargs):
r = Response.text("ok")
Expand Down
5 changes: 4 additions & 1 deletion datasette/views/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from datasette.utils import (
await_me_maybe,
check_visibility,
derive_named_parameters,
to_css_class,
validate_sql_select,
is_url,
Expand Down Expand Up @@ -223,7 +224,9 @@ async def data(
await self.check_permission(request, "execute-sql", database)

# Extract any :named parameters
named_parameters = named_parameters or self.re_named_parameter.findall(sql)
named_parameters = named_parameters or await derive_named_parameters(
self.ds.get_database(database), sql
)
named_parameter_values = {
named_parameter: params.get(named_parameter) or ""
for named_parameter in named_parameters
Expand Down
15 changes: 15 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,3 +626,18 @@ def test_parse_metadata(content, expected):
utils.parse_metadata(content)
else:
assert utils.parse_metadata(content) == expected


@pytest.mark.asyncio
@pytest.mark.parametrize("sql,expected", (
("select 1", []),
("select 1 + :one", ["one"]),
("select 1 + :one + :two", ["one", "two"]),
("select 'bob' || '0:00' || :cat", ["cat"]),
("select this is invalid", []),
))
async def test_derive_named_parameters(sql, expected):
ds = Datasette([], memory=True)
db = ds.get_database("_memory")
params = await utils.derive_named_parameters(db, sql)
assert params == expected

0 comments on commit fc48468

Please sign in to comment.