mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Introduce new /$DB/-/query endpoint, soft replaces /$DB?sql=... (#2363)
* Introduce new default /$DB/-/query endpoint * Fix a lot of tests * Update pyodide test to use query endpoint * Link to /fixtures/-/query in a few places * Documentation for QueryView --------- Co-authored-by: Simon Willison <swillison@gmail.com>
This commit is contained in:
parent
56adfff8d2
commit
a23c2aee00
21 changed files with 148 additions and 83 deletions
|
|
@ -159,7 +159,7 @@ async def test_database_page(ds_client):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_custom_sql(ds_client):
|
||||
response = await ds_client.get("/fixtures?sql=.schema")
|
||||
response = await ds_client.get("/fixtures/-/query?sql=.schema")
|
||||
assert response.status_code == 400
|
||||
assert "Statement must be a SELECT" in response.text
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ async def test_invalid_custom_sql(ds_client):
|
|||
@pytest.mark.asyncio
|
||||
async def test_disallowed_custom_sql_pragma(ds_client):
|
||||
response = await ds_client.get(
|
||||
"/fixtures?sql=SELECT+*+FROM+pragma_not_on_allow_list('idx52')"
|
||||
"/fixtures/-/query?sql=SELECT+*+FROM+pragma_not_on_allow_list('idx52')"
|
||||
)
|
||||
assert response.status_code == 400
|
||||
pragmas = ", ".join("pragma_{}()".format(pragma) for pragma in allowed_pragmas)
|
||||
|
|
@ -180,7 +180,9 @@ async def test_disallowed_custom_sql_pragma(ds_client):
|
|||
|
||||
|
||||
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/-/query?sql=select+sleep(0.5)"
|
||||
)
|
||||
assert 400 == response.status
|
||||
expected_html_fragments = [
|
||||
"""
|
||||
|
|
@ -207,7 +209,7 @@ def test_row_page_does_not_truncate():
|
|||
def test_query_page_truncates():
|
||||
with make_app_client(settings={"truncate_cells_html": 5}) as client:
|
||||
response = client.get(
|
||||
"/fixtures?"
|
||||
"/fixtures/-/query?"
|
||||
+ urllib.parse.urlencode(
|
||||
{
|
||||
"sql": "select 'this is longer than 5' as a, 'https://example.com/' as b"
|
||||
|
|
@ -229,7 +231,7 @@ def test_query_page_truncates():
|
|||
[
|
||||
("/", ["index"]),
|
||||
("/fixtures", ["db", "db-fixtures"]),
|
||||
("/fixtures?sql=select+1", ["query", "db-fixtures"]),
|
||||
("/fixtures/-/query?sql=select+1", ["query", "db-fixtures"]),
|
||||
(
|
||||
"/fixtures/simple_primary_key",
|
||||
["table", "db-fixtures", "table-simple_primary_key"],
|
||||
|
|
@ -296,21 +298,24 @@ async def test_row_json_export_link(ds_client):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_query_json_csv_export_links(ds_client):
|
||||
response = await ds_client.get("/fixtures?sql=select+1")
|
||||
response = await ds_client.get("/fixtures/-/query?sql=select+1")
|
||||
assert response.status_code == 200
|
||||
assert '<a href="/fixtures.json?sql=select+1">json</a>' in response.text
|
||||
assert '<a href="/fixtures.csv?sql=select+1&_size=max">CSV</a>' in response.text
|
||||
assert '<a href="/fixtures/-/query.json?sql=select+1">json</a>' in response.text
|
||||
assert (
|
||||
'<a href="/fixtures/-/query.csv?sql=select+1&_size=max">CSV</a>'
|
||||
in response.text
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_query_parameter_form_fields(ds_client):
|
||||
response = await ds_client.get("/fixtures?sql=select+:name")
|
||||
response = await ds_client.get("/fixtures/-/query?sql=select+:name")
|
||||
assert response.status_code == 200
|
||||
assert (
|
||||
'<label for="qp1">name</label> <input type="text" id="qp1" name="name" value="">'
|
||||
in response.text
|
||||
)
|
||||
response2 = await ds_client.get("/fixtures?sql=select+:name&name=hello")
|
||||
response2 = await ds_client.get("/fixtures/-/query?sql=select+:name&name=hello")
|
||||
assert response2.status_code == 200
|
||||
assert (
|
||||
'<label for="qp1">name</label> <input type="text" id="qp1" name="name" value="hello">'
|
||||
|
|
@ -453,7 +458,9 @@ async def test_database_metadata(ds_client):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_database_metadata_with_custom_sql(ds_client):
|
||||
response = await ds_client.get("/fixtures?sql=select+*+from+simple_primary_key")
|
||||
response = await ds_client.get(
|
||||
"/fixtures/-/query?sql=select+*+from+simple_primary_key"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
soup = Soup(response.text, "html.parser")
|
||||
# Page title should be the default
|
||||
|
|
@ -591,7 +598,7 @@ async def test_canned_query_with_custom_metadata(ds_client):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_urlify_custom_queries(ds_client):
|
||||
path = "/fixtures?" + urllib.parse.urlencode(
|
||||
path = "/fixtures/-/query?" + urllib.parse.urlencode(
|
||||
{"sql": "select ('https://twitter.com/' || 'simonw') as user_url;"}
|
||||
)
|
||||
response = await ds_client.get(path)
|
||||
|
|
@ -609,7 +616,7 @@ async def test_urlify_custom_queries(ds_client):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_show_hide_sql_query(ds_client):
|
||||
path = "/fixtures?" + urllib.parse.urlencode(
|
||||
path = "/fixtures/-/query?" + urllib.parse.urlencode(
|
||||
{"sql": "select ('https://twitter.com/' || 'simonw') as user_url;"}
|
||||
)
|
||||
response = await ds_client.get(path)
|
||||
|
|
@ -696,15 +703,15 @@ def test_canned_query_show_hide_metadata_option(
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_binary_data_display_in_query(ds_client):
|
||||
response = await ds_client.get("/fixtures?sql=select+*+from+binary_data")
|
||||
response = await ds_client.get("/fixtures/-/query?sql=select+*+from+binary_data")
|
||||
assert response.status_code == 200
|
||||
table = Soup(response.content, "html.parser").find("table")
|
||||
expected_tds = [
|
||||
[
|
||||
'<td class="col-data"><a class="blob-download" href="/fixtures.blob?sql=select+*+from+binary_data&_blob_column=data&_blob_hash=f3088978da8f9aea479ffc7f631370b968d2e855eeb172bea7f6c7a04262bb6d"><Binary:\xa07\xa0bytes></a></td>'
|
||||
'<td class="col-data"><a class="blob-download" href="/fixtures/-/query.blob?sql=select+*+from+binary_data&_blob_column=data&_blob_hash=f3088978da8f9aea479ffc7f631370b968d2e855eeb172bea7f6c7a04262bb6d"><Binary:\xa07\xa0bytes></a></td>'
|
||||
],
|
||||
[
|
||||
'<td class="col-data"><a class="blob-download" href="/fixtures.blob?sql=select+*+from+binary_data&_blob_column=data&_blob_hash=b835b0483cedb86130b9a2c280880bf5fadc5318ddf8c18d0df5204d40df1724"><Binary:\xa07\xa0bytes></a></td>'
|
||||
'<td class="col-data"><a class="blob-download" href="/fixtures/-/query.blob?sql=select+*+from+binary_data&_blob_column=data&_blob_hash=b835b0483cedb86130b9a2c280880bf5fadc5318ddf8c18d0df5204d40df1724"><Binary:\xa07\xa0bytes></a></td>'
|
||||
],
|
||||
['<td class="col-data">\xa0</td>'],
|
||||
]
|
||||
|
|
@ -719,7 +726,7 @@ async def test_binary_data_display_in_query(ds_client):
|
|||
[
|
||||
("/fixtures/binary_data/1.blob?_blob_column=data", "binary_data-1-data.blob"),
|
||||
(
|
||||
"/fixtures.blob?sql=select+*+from+binary_data&_blob_column=data&_blob_hash=f3088978da8f9aea479ffc7f631370b968d2e855eeb172bea7f6c7a04262bb6d",
|
||||
"/fixtures/-/query.blob?sql=select+*+from+binary_data&_blob_column=data&_blob_hash=f3088978da8f9aea479ffc7f631370b968d2e855eeb172bea7f6c7a04262bb6d",
|
||||
"data-f30889.blob",
|
||||
),
|
||||
],
|
||||
|
|
@ -758,7 +765,7 @@ async def test_blob_download_invalid_messages(ds_client, path, expected_message)
|
|||
@pytest.mark.parametrize(
|
||||
"path",
|
||||
[
|
||||
"/fixtures?sql=select+*+from+[123_starts_with_digits]",
|
||||
"/fixtures/-/query?sql=select+*+from+[123_starts_with_digits]",
|
||||
"/fixtures/123_starts_with_digits",
|
||||
],
|
||||
)
|
||||
|
|
@ -771,7 +778,7 @@ async def test_zero_results(ds_client, path):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_query_error(ds_client):
|
||||
response = await ds_client.get("/fixtures?sql=select+*+from+notatable")
|
||||
response = await ds_client.get("/fixtures/-/query?sql=select+*+from+notatable")
|
||||
html = response.text
|
||||
assert '<p class="message-error">no such table: notatable</p>' in html
|
||||
assert '<textarea id="sql-editor" name="sql" style="height: 3em' in html
|
||||
|
|
@ -811,7 +818,7 @@ def test_debug_context_includes_extra_template_vars():
|
|||
"/fixtures/paginated_view",
|
||||
"/fixtures/facetable",
|
||||
"/fixtures/facetable?_facet=state",
|
||||
"/fixtures?sql=select+1",
|
||||
"/fixtures/-/query?sql=select+1",
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("use_prefix", (True, False))
|
||||
|
|
@ -879,17 +886,17 @@ def test_base_url_affects_metadata_extra_css_urls(app_client_base_url_prefix):
|
|||
[
|
||||
(
|
||||
"/fixtures/neighborhood_search",
|
||||
"/fixtures?sql=%0Aselect+_neighborhood%2C+facet_cities.name%2C+state%0Afrom+facetable%0A++++join+facet_cities%0A++++++++on+facetable._city_id+%3D+facet_cities.id%0Awhere+_neighborhood+like+%27%25%27+%7C%7C+%3Atext+%7C%7C+%27%25%27%0Aorder+by+_neighborhood%3B%0A&text=",
|
||||
"/fixtures/-/query?sql=%0Aselect+_neighborhood%2C+facet_cities.name%2C+state%0Afrom+facetable%0A++++join+facet_cities%0A++++++++on+facetable._city_id+%3D+facet_cities.id%0Awhere+_neighborhood+like+%27%25%27+%7C%7C+%3Atext+%7C%7C+%27%25%27%0Aorder+by+_neighborhood%3B%0A&text=",
|
||||
),
|
||||
(
|
||||
"/fixtures/neighborhood_search?text=ber",
|
||||
"/fixtures?sql=%0Aselect+_neighborhood%2C+facet_cities.name%2C+state%0Afrom+facetable%0A++++join+facet_cities%0A++++++++on+facetable._city_id+%3D+facet_cities.id%0Awhere+_neighborhood+like+%27%25%27+%7C%7C+%3Atext+%7C%7C+%27%25%27%0Aorder+by+_neighborhood%3B%0A&text=ber",
|
||||
"/fixtures/-/query?sql=%0Aselect+_neighborhood%2C+facet_cities.name%2C+state%0Afrom+facetable%0A++++join+facet_cities%0A++++++++on+facetable._city_id+%3D+facet_cities.id%0Awhere+_neighborhood+like+%27%25%27+%7C%7C+%3Atext+%7C%7C+%27%25%27%0Aorder+by+_neighborhood%3B%0A&text=ber",
|
||||
),
|
||||
("/fixtures/pragma_cache_size", None),
|
||||
(
|
||||
# /fixtures/𝐜𝐢𝐭𝐢𝐞𝐬
|
||||
"/fixtures/~F0~9D~90~9C~F0~9D~90~A2~F0~9D~90~AD~F0~9D~90~A2~F0~9D~90~9E~F0~9D~90~AC",
|
||||
"/fixtures?sql=select+id%2C+name+from+facet_cities+order+by+id+limit+1%3B",
|
||||
"/fixtures/-/query?sql=select+id%2C+name+from+facet_cities+order+by+id+limit+1%3B",
|
||||
),
|
||||
("/fixtures/magic_parameters", None),
|
||||
],
|
||||
|
|
@ -960,7 +967,7 @@ async def test_navigation_menu_links(
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_trace_correctly_escaped(ds_client):
|
||||
response = await ds_client.get("/fixtures?sql=select+'<h1>Hello'&_trace=1")
|
||||
response = await ds_client.get("/fixtures/-/query?sql=select+'<h1>Hello'&_trace=1")
|
||||
assert "select '<h1>Hello" not in response.text
|
||||
assert "select '<h1>Hello" in response.text
|
||||
|
||||
|
|
@ -989,8 +996,8 @@ async def test_trace_correctly_escaped(ds_client):
|
|||
),
|
||||
# Custom query page
|
||||
(
|
||||
"/fixtures?sql=select+*+from+facetable",
|
||||
"http://localhost/fixtures.json?sql=select+*+from+facetable",
|
||||
"/fixtures/-/query?sql=select+*+from+facetable",
|
||||
"http://localhost/fixtures/-/query.json?sql=select+*+from+facetable",
|
||||
),
|
||||
# Canned query page
|
||||
(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue