Added ?_through= table argument, closes #355

Also added much more interesting many-to-many fixtures - roadside attractions!
This commit is contained in:
Simon Willison 2019-05-22 22:44:34 -07:00
commit 172da009d8
4 changed files with 305 additions and 98 deletions

View file

@ -1,5 +1,6 @@
import urllib import urllib
import itertools import itertools
import json
import jinja2 import jinja2
from sanic.exceptions import NotFound from sanic.exceptions import NotFound
@ -17,6 +18,7 @@ from datasette.utils import (
escape_sqlite, escape_sqlite,
filters_should_redirect, filters_should_redirect,
get_all_foreign_keys, get_all_foreign_keys,
get_outbound_foreign_keys,
is_url, is_url,
path_from_row_pks, path_from_row_pks,
path_with_added_args, path_with_added_args,
@ -289,6 +291,41 @@ class TableView(RowTableShared):
for text in request.args["_where"] for text in request.args["_where"]
] ]
# Support for ?_through={table, column, value}
extra_human_descriptions = []
if "_through" in request.args:
for through in request.args["_through"]:
through_data = json.loads(through)
through_table = through_data["table"]
other_column = through_data["column"]
value = through_data["value"]
outgoing_foreign_keys = await self.ds.execute_against_connection_in_thread(
database,
lambda conn: get_outbound_foreign_keys(conn, through_table),
)
try:
fk_to_us = [
fk for fk in outgoing_foreign_keys if fk["other_table"] == table
][0]
except IndexError:
raise DatasetteError(
"Invalid _through - could not find corresponding foreign key"
)
param = "p{}".format(len(params))
where_clauses.append(
"{our_pk} in (select {our_column} from {through_table} where {other_column} = :{param})".format(
through_table=escape_sqlite(through_table),
our_pk=escape_sqlite(fk_to_us["other_column"]),
our_column=escape_sqlite(fk_to_us["column"]),
other_column=escape_sqlite(other_column),
param=param,
)
)
params[param] = value
extra_human_descriptions.append(
'{}.{} = "{}"'.format(through_table, other_column, value)
)
# _search support: # _search support:
fts_table = special_args.get("_fts_table") fts_table = special_args.get("_fts_table")
fts_table = fts_table or table_metadata.get("fts_table") fts_table = fts_table or table_metadata.get("fts_table")
@ -299,7 +336,6 @@ class TableView(RowTableShared):
search_args = dict( search_args = dict(
pair for pair in special_args.items() if pair[0].startswith("_search") pair for pair in special_args.items() if pair[0].startswith("_search")
) )
search_descriptions = []
search = "" search = ""
if fts_table and search_args: if fts_table and search_args:
if "_search" in search_args: if "_search" in search_args:
@ -310,7 +346,7 @@ class TableView(RowTableShared):
fts_table=escape_sqlite(fts_table), fts_pk=escape_sqlite(fts_pk) fts_table=escape_sqlite(fts_table), fts_pk=escape_sqlite(fts_pk)
) )
) )
search_descriptions.append('search matches "{}"'.format(search)) extra_human_descriptions.append('search matches "{}"'.format(search))
params["search"] = search params["search"] = search
else: else:
# More complex: search against specific columns # More complex: search against specific columns
@ -328,7 +364,7 @@ class TableView(RowTableShared):
i=i, i=i,
) )
) )
search_descriptions.append( extra_human_descriptions.append(
'search column "{}" matches "{}"'.format( 'search column "{}" matches "{}"'.format(
search_col, search_text search_col, search_text
) )
@ -637,7 +673,9 @@ class TableView(RowTableShared):
suggested_facets.extend(await facet.suggest()) suggested_facets.extend(await facet.suggest())
# human_description_en combines filters AND search, if provided # human_description_en combines filters AND search, if provided
human_description_en = filters.human_description_en(extra=search_descriptions) human_description_en = filters.human_description_en(
extra=extra_human_descriptions
)
if sort or sort_desc: if sort or sort_desc:
sorted_by = "sorted by {}{}".format( sorted_by = "sorted by {}{}".format(

View file

@ -293,6 +293,32 @@ Special table arguments
* `facetable?_where=neighborhood like "%c%"&_where=city_id=3 <https://latest.datasette.io/fixtures/facetable?_where=neighborhood%20like%20%22%c%%22&_where=city_id=3>`__ * `facetable?_where=neighborhood like "%c%"&_where=city_id=3 <https://latest.datasette.io/fixtures/facetable?_where=neighborhood%20like%20%22%c%%22&_where=city_id=3>`__
* `facetable?_where=city_id in (select id from facet_cities where name != "Detroit") <https://latest.datasette.io/fixtures/facetable?_where=city_id%20in%20(select%20id%20from%20facet_cities%20where%20name%20!=%20%22Detroit%22)>`__ * `facetable?_where=city_id in (select id from facet_cities where name != "Detroit") <https://latest.datasette.io/fixtures/facetable?_where=city_id%20in%20(select%20id%20from%20facet_cities%20where%20name%20!=%20%22Detroit%22)>`__
``?_through={json}``
This can be used to filter rows via a join against another table.
The JSON parameter must include three keys: ``table``, ``column`` and ``value``.
``table`` must be a table that the current table is related to via a foreign key relationship.
``column`` must be a column in that other table.
``value`` is the value that you want to match against.
For example, to filter ``roadside_attractions`` to just show the attractions that have a characteristic of "museum", you would construct this JSON::
{
"table": "roadside_attraction_characteristics",
"column": "characteristic_id",
"value": "1"
}
As a URL, that looks like this:
``?_through={%22table%22:%22roadside_attraction_characteristics%22,%22column%22:%22characteristic_id%22,%22value%22:%221%22}``
Here's `an example <https://latest.datasette.io/fixtures/roadside_attractions?_through={%22table%22:%22roadside_attraction_characteristics%22,%22column%22:%22characteristic_id%22,%22value%22:%221%22}>`__.
``?_group_count=COLUMN`` ``?_group_count=COLUMN``
Executes a SQL query that returns a count of the number of rows matching Executes a SQL query that returns a count of the number of rows matching
each unique value in that column, with the most common ordered first. each unique value in that column, with the most common ordered first.

View file

@ -551,6 +551,63 @@ CREATE TABLE binary_data (
data BLOB data BLOB
); );
-- Many 2 Many demo: roadside attractions!
CREATE TABLE roadside_attractions (
pk integer primary key,
name text,
address text,
latitude real,
longitude real
);
INSERT INTO roadside_attractions VALUES (
1, "The Mystery Spot", "465 Mystery Spot Road, Santa Cruz, CA 95065",
37.0167, -122.0024
);
INSERT INTO roadside_attractions VALUES (
2, "Winchester Mystery House", "525 South Winchester Boulevard, San Jose, CA 95128",
37.3184, -121.9511
);
INSERT INTO roadside_attractions VALUES (
3, "Burlingame Museum of PEZ Memorabilia", "214 California Drive, Burlingame, CA 94010",
37.5793, -122.3442
);
INSERT INTO roadside_attractions VALUES (
4, "Bigfoot Discovery Museum", "5497 Highway 9, Felton, CA 95018",
37.0414, -122.0725
);
CREATE TABLE attraction_characteristic (
pk integer primary key,
name text
);
INSERT INTO attraction_characteristic VALUES (
1, "Museum"
);
INSERT INTO attraction_characteristic VALUES (
2, "Paranormal"
);
CREATE TABLE roadside_attraction_characteristics (
attraction_id INTEGER REFERENCES roadside_attractions(pk),
characteristic_id INTEGER REFERENCES attraction_characteristic(pk)
);
INSERT INTO roadside_attraction_characteristics VALUES (
1, 2
);
INSERT INTO roadside_attraction_characteristics VALUES (
2, 2
);
INSERT INTO roadside_attraction_characteristics VALUES (
4, 2
);
INSERT INTO roadside_attraction_characteristics VALUES (
3, 1
);
INSERT INTO roadside_attraction_characteristics VALUES (
4, 1
);
INSERT INTO simple_primary_key VALUES (1, 'hello'); INSERT INTO simple_primary_key VALUES (1, 'hello');
INSERT INTO simple_primary_key VALUES (2, 'world'); INSERT INTO simple_primary_key VALUES (2, 'world');
INSERT INTO simple_primary_key VALUES (3, ''); INSERT INTO simple_primary_key VALUES (3, '');

View file

@ -25,7 +25,7 @@ def test_homepage(app_client):
assert response.json.keys() == {"fixtures": 0}.keys() assert response.json.keys() == {"fixtures": 0}.keys()
d = response.json["fixtures"] d = response.json["fixtures"]
assert d["name"] == "fixtures" assert d["name"] == "fixtures"
assert d["tables_count"] == 21 assert d["tables_count"] == 24
assert len(d["tables_and_views_truncated"]) == 5 assert len(d["tables_and_views_truncated"]) == 5
assert d["tables_and_views_more"] is True assert d["tables_and_views_more"] is True
# 4 hidden FTS tables + no_primary_key (hidden in metadata) # 4 hidden FTS tables + no_primary_key (hidden in metadata)
@ -44,9 +44,9 @@ def test_homepage_sort_by_relationships(app_client):
assert [ assert [
"simple_primary_key", "simple_primary_key",
"complex_foreign_keys", "complex_foreign_keys",
"roadside_attraction_characteristics",
"searchable_tags", "searchable_tags",
"foreign_key_references", "foreign_key_references",
"facetable",
] == tables ] == tables
@ -56,115 +56,134 @@ def test_database_page(app_client):
assert "fixtures" == data["database"] assert "fixtures" == data["database"]
assert [ assert [
{ {
"columns": ["content"],
"name": "123_starts_with_digits", "name": "123_starts_with_digits",
"columns": ["content"],
"primary_keys": [],
"count": 0, "count": 0,
"hidden": False, "hidden": False,
"foreign_keys": {"incoming": [], "outgoing": []},
"fts_table": None, "fts_table": None,
"primary_keys": [], "foreign_keys": {"incoming": [], "outgoing": []},
}, },
{ {
"columns": ["pk", "content"],
"name": "Table With Space In Name", "name": "Table With Space In Name",
"columns": ["pk", "content"],
"primary_keys": ["pk"],
"count": 0, "count": 0,
"hidden": False, "hidden": False,
"foreign_keys": {"incoming": [], "outgoing": []},
"fts_table": None, "fts_table": None,
"foreign_keys": {"incoming": [], "outgoing": []},
},
{
"name": "attraction_characteristic",
"columns": ["pk", "name"],
"primary_keys": ["pk"], "primary_keys": ["pk"],
}, "count": 2,
{
"columns": ["data"],
"count": 1,
"foreign_keys": {"incoming": [], "outgoing": []},
"fts_table": None,
"hidden": False,
"name": "binary_data",
"primary_keys": [],
},
{
"columns": ["pk", "f1", "f2", "f3"],
"name": "complex_foreign_keys",
"count": 1,
"foreign_keys": {
"incoming": [],
"outgoing": [
{
"column": "f3",
"other_column": "id",
"other_table": "simple_primary_key",
},
{
"column": "f2",
"other_column": "id",
"other_table": "simple_primary_key",
},
{
"column": "f1",
"other_column": "id",
"other_table": "simple_primary_key",
},
],
},
"hidden": False, "hidden": False,
"fts_table": None, "fts_table": None,
"primary_keys": ["pk"],
},
{
"columns": ["pk1", "pk2", "content"],
"name": "compound_primary_key",
"count": 1,
"hidden": False,
"foreign_keys": {"incoming": [], "outgoing": []},
"fts_table": None,
"primary_keys": ["pk1", "pk2"],
},
{
"columns": ["pk1", "pk2", "pk3", "content"],
"name": "compound_three_primary_keys",
"count": 1001,
"hidden": False,
"foreign_keys": {"incoming": [], "outgoing": []},
"fts_table": None,
"primary_keys": ["pk1", "pk2", "pk3"],
},
{
"columns": ["pk", "foreign_key_with_custom_label"],
"name": "custom_foreign_key_label",
"count": 1,
"hidden": False,
"foreign_keys": {
"incoming": [],
"outgoing": [
{
"column": "foreign_key_with_custom_label",
"other_column": "id",
"other_table": "primary_key_multiple_columns_explicit_label",
}
],
},
"fts_table": None,
"primary_keys": ["pk"],
},
{
"columns": ["id", "name"],
"name": "facet_cities",
"count": 4,
"foreign_keys": { "foreign_keys": {
"incoming": [ "incoming": [
{ {
"column": "id", "other_table": "roadside_attraction_characteristics",
"other_column": "city_id", "column": "pk",
"other_table": "facetable", "other_column": "characteristic_id",
} }
], ],
"outgoing": [], "outgoing": [],
}, },
"fts_table": None,
"hidden": False,
"primary_keys": ["id"],
}, },
{ {
"name": "binary_data",
"columns": ["data"],
"primary_keys": [],
"count": 1,
"hidden": False,
"fts_table": None,
"foreign_keys": {"incoming": [], "outgoing": []},
},
{
"name": "complex_foreign_keys",
"columns": ["pk", "f1", "f2", "f3"],
"primary_keys": ["pk"],
"count": 1,
"hidden": False,
"fts_table": None,
"foreign_keys": {
"incoming": [],
"outgoing": [
{
"other_table": "simple_primary_key",
"column": "f3",
"other_column": "id",
},
{
"other_table": "simple_primary_key",
"column": "f2",
"other_column": "id",
},
{
"other_table": "simple_primary_key",
"column": "f1",
"other_column": "id",
},
],
},
},
{
"name": "compound_primary_key",
"columns": ["pk1", "pk2", "content"],
"primary_keys": ["pk1", "pk2"],
"count": 1,
"hidden": False,
"fts_table": None,
"foreign_keys": {"incoming": [], "outgoing": []},
},
{
"name": "compound_three_primary_keys",
"columns": ["pk1", "pk2", "pk3", "content"],
"primary_keys": ["pk1", "pk2", "pk3"],
"count": 1001,
"hidden": False,
"fts_table": None,
"foreign_keys": {"incoming": [], "outgoing": []},
},
{
"name": "custom_foreign_key_label",
"columns": ["pk", "foreign_key_with_custom_label"],
"primary_keys": ["pk"],
"count": 1,
"hidden": False,
"fts_table": None,
"foreign_keys": {
"incoming": [],
"outgoing": [
{
"other_table": "primary_key_multiple_columns_explicit_label",
"column": "foreign_key_with_custom_label",
"other_column": "id",
}
],
},
},
{
"name": "facet_cities",
"columns": ["id", "name"],
"primary_keys": ["id"],
"count": 4,
"hidden": False,
"fts_table": None,
"foreign_keys": {
"incoming": [
{
"other_table": "facetable",
"column": "id",
"other_column": "city_id",
}
],
"outgoing": [],
},
},
{
"name": "facetable",
"columns": [ "columns": [
"pk", "pk",
"created", "created",
@ -175,94 +194,137 @@ def test_database_page(app_client):
"neighborhood", "neighborhood",
"tags", "tags",
], ],
"name": "facetable", "primary_keys": ["pk"],
"count": 15, "count": 15,
"hidden": False,
"fts_table": None,
"foreign_keys": { "foreign_keys": {
"incoming": [], "incoming": [],
"outgoing": [ "outgoing": [
{ {
"other_table": "facet_cities",
"column": "city_id", "column": "city_id",
"other_column": "id", "other_column": "id",
"other_table": "facet_cities",
} }
], ],
}, },
"fts_table": None,
"hidden": False,
"primary_keys": ["pk"],
}, },
{ {
"columns": ["pk", "foreign_key_with_label", "foreign_key_with_no_label"],
"name": "foreign_key_references", "name": "foreign_key_references",
"columns": ["pk", "foreign_key_with_label", "foreign_key_with_no_label"],
"primary_keys": ["pk"],
"count": 1, "count": 1,
"hidden": False, "hidden": False,
"fts_table": None,
"foreign_keys": { "foreign_keys": {
"incoming": [], "incoming": [],
"outgoing": [ "outgoing": [
{ {
"other_table": "primary_key_multiple_columns",
"column": "foreign_key_with_no_label", "column": "foreign_key_with_no_label",
"other_column": "id", "other_column": "id",
"other_table": "primary_key_multiple_columns",
}, },
{ {
"other_table": "simple_primary_key",
"column": "foreign_key_with_label", "column": "foreign_key_with_label",
"other_column": "id", "other_column": "id",
"other_table": "simple_primary_key",
}, },
], ],
}, },
"fts_table": None,
"primary_keys": ["pk"],
}, },
{ {
"name": "infinity", "name": "infinity",
"columns": ["value"], "columns": ["value"],
"count": 3,
"primary_keys": [], "primary_keys": [],
"count": 3,
"hidden": False, "hidden": False,
"fts_table": None, "fts_table": None,
"foreign_keys": {"incoming": [], "outgoing": []}, "foreign_keys": {"incoming": [], "outgoing": []},
}, },
{ {
"columns": ["id", "content", "content2"],
"name": "primary_key_multiple_columns", "name": "primary_key_multiple_columns",
"columns": ["id", "content", "content2"],
"primary_keys": ["id"],
"count": 1, "count": 1,
"hidden": False,
"fts_table": None,
"foreign_keys": { "foreign_keys": {
"incoming": [ "incoming": [
{ {
"other_table": "foreign_key_references",
"column": "id", "column": "id",
"other_column": "foreign_key_with_no_label", "other_column": "foreign_key_with_no_label",
"other_table": "foreign_key_references",
} }
], ],
"outgoing": [], "outgoing": [],
}, },
"hidden": False,
"fts_table": None,
"primary_keys": ["id"],
}, },
{ {
"columns": ["id", "content", "content2"],
"name": "primary_key_multiple_columns_explicit_label", "name": "primary_key_multiple_columns_explicit_label",
"columns": ["id", "content", "content2"],
"primary_keys": ["id"],
"count": 1, "count": 1,
"hidden": False,
"fts_table": None,
"foreign_keys": { "foreign_keys": {
"incoming": [ "incoming": [
{ {
"other_table": "custom_foreign_key_label",
"column": "id", "column": "id",
"other_column": "foreign_key_with_custom_label", "other_column": "foreign_key_with_custom_label",
"other_table": "custom_foreign_key_label",
} }
], ],
"outgoing": [], "outgoing": [],
}, },
"hidden": False,
"fts_table": None,
"primary_keys": ["id"],
}, },
{ {
"columns": ["pk", "text1", "text2", "name with . and spaces"], "name": "roadside_attraction_characteristics",
"columns": ["attraction_id", "characteristic_id"],
"primary_keys": [],
"count": 5,
"hidden": False,
"fts_table": None,
"foreign_keys": {
"incoming": [],
"outgoing": [
{
"other_table": "attraction_characteristic",
"column": "characteristic_id",
"other_column": "pk",
},
{
"other_table": "roadside_attractions",
"column": "attraction_id",
"other_column": "pk",
},
],
},
},
{
"name": "roadside_attractions",
"columns": ["pk", "name", "address", "latitude", "longitude"],
"primary_keys": ["pk"],
"count": 4,
"hidden": False,
"fts_table": None,
"foreign_keys": {
"incoming": [
{
"other_table": "roadside_attraction_characteristics",
"column": "pk",
"other_column": "attraction_id",
}
],
"outgoing": [],
},
},
{
"name": "searchable", "name": "searchable",
"columns": ["pk", "text1", "text2", "name with . and spaces"],
"primary_keys": ["pk"],
"count": 2, "count": 2,
"hidden": False,
"fts_table": "searchable_fts",
"foreign_keys": { "foreign_keys": {
"incoming": [ "incoming": [
{ {
@ -273,9 +335,6 @@ def test_database_page(app_client):
], ],
"outgoing": [], "outgoing": [],
}, },
"fts_table": "searchable_fts",
"hidden": False,
"primary_keys": ["pk"],
}, },
{ {
"name": "searchable_tags", "name": "searchable_tags",
@ -297,48 +356,49 @@ def test_database_page(app_client):
}, },
}, },
{ {
"columns": ["group", "having", "and", "json"],
"name": "select", "name": "select",
"columns": ["group", "having", "and", "json"],
"primary_keys": [],
"count": 1, "count": 1,
"hidden": False, "hidden": False,
"foreign_keys": {"incoming": [], "outgoing": []},
"fts_table": None, "fts_table": None,
"primary_keys": [], "foreign_keys": {"incoming": [], "outgoing": []},
}, },
{ {
"columns": ["id", "content"],
"name": "simple_primary_key", "name": "simple_primary_key",
"columns": ["id", "content"],
"primary_keys": ["id"],
"count": 4, "count": 4,
"hidden": False, "hidden": False,
"fts_table": None,
"foreign_keys": { "foreign_keys": {
"incoming": [ "incoming": [
{ {
"other_table": "foreign_key_references",
"column": "id", "column": "id",
"other_column": "foreign_key_with_label", "other_column": "foreign_key_with_label",
"other_table": "foreign_key_references",
}, },
{ {
"other_table": "complex_foreign_keys",
"column": "id", "column": "id",
"other_column": "f3", "other_column": "f3",
"other_table": "complex_foreign_keys",
}, },
{ {
"other_table": "complex_foreign_keys",
"column": "id", "column": "id",
"other_column": "f2", "other_column": "f2",
"other_table": "complex_foreign_keys",
}, },
{ {
"other_table": "complex_foreign_keys",
"column": "id", "column": "id",
"other_column": "f1", "other_column": "f1",
"other_table": "complex_foreign_keys",
}, },
], ],
"outgoing": [], "outgoing": [],
}, },
"fts_table": None,
"primary_keys": ["id"],
}, },
{ {
"name": "sortable",
"columns": [ "columns": [
"pk1", "pk1",
"pk2", "pk2",
@ -348,21 +408,20 @@ def test_database_page(app_client):
"sortable_with_nulls_2", "sortable_with_nulls_2",
"text", "text",
], ],
"name": "sortable", "primary_keys": ["pk1", "pk2"],
"count": 201, "count": 201,
"hidden": False, "hidden": False,
"foreign_keys": {"incoming": [], "outgoing": []},
"fts_table": None, "fts_table": None,
"primary_keys": ["pk1", "pk2"], "foreign_keys": {"incoming": [], "outgoing": []},
}, },
{ {
"columns": ["pk", "content"],
"name": "table/with/slashes.csv", "name": "table/with/slashes.csv",
"columns": ["pk", "content"],
"primary_keys": ["pk"],
"count": 1, "count": 1,
"hidden": False, "hidden": False,
"foreign_keys": {"incoming": [], "outgoing": []},
"fts_table": None, "fts_table": None,
"primary_keys": ["pk"], "foreign_keys": {"incoming": [], "outgoing": []},
}, },
{ {
"name": "tags", "name": "tags",
@ -383,33 +442,34 @@ def test_database_page(app_client):
}, },
}, },
{ {
"columns": ["pk", "distance", "frequency"],
"name": "units", "name": "units",
"columns": ["pk", "distance", "frequency"],
"primary_keys": ["pk"],
"count": 3, "count": 3,
"hidden": False, "hidden": False,
"foreign_keys": {"incoming": [], "outgoing": []},
"fts_table": None, "fts_table": None,
"primary_keys": ["pk"], "foreign_keys": {"incoming": [], "outgoing": []},
}, },
{ {
"columns": ["content", "a", "b", "c"],
"name": "no_primary_key", "name": "no_primary_key",
"columns": ["content", "a", "b", "c"],
"primary_keys": [],
"count": 201, "count": 201,
"hidden": True, "hidden": True,
"foreign_keys": {"incoming": [], "outgoing": []},
"fts_table": None, "fts_table": None,
"primary_keys": [],
},
{
"columns": ["text1", "text2", "name with . and spaces", "content"],
"count": 2,
"foreign_keys": {"incoming": [], "outgoing": []}, "foreign_keys": {"incoming": [], "outgoing": []},
"fts_table": "searchable_fts",
"hidden": True,
"name": "searchable_fts",
"primary_keys": [],
}, },
{ {
"name": "searchable_fts",
"columns": ["text1", "text2", "name with . and spaces", "content"],
"primary_keys": [],
"count": 2,
"hidden": True,
"fts_table": "searchable_fts",
"foreign_keys": {"incoming": [], "outgoing": []},
},
{
"name": "searchable_fts_content",
"columns": [ "columns": [
"docid", "docid",
"c0text1", "c0text1",
@ -417,14 +477,14 @@ def test_database_page(app_client):
"c2name with . and spaces", "c2name with . and spaces",
"c3content", "c3content",
], ],
"count": 2,
"foreign_keys": {"incoming": [], "outgoing": []},
"fts_table": None,
"hidden": True,
"name": "searchable_fts_content",
"primary_keys": ["docid"], "primary_keys": ["docid"],
"count": 2,
"hidden": True,
"fts_table": None,
"foreign_keys": {"incoming": [], "outgoing": []},
}, },
{ {
"name": "searchable_fts_segdir",
"columns": [ "columns": [
"level", "level",
"idx", "idx",
@ -433,21 +493,20 @@ def test_database_page(app_client):
"end_block", "end_block",
"root", "root",
], ],
"count": 1,
"foreign_keys": {"incoming": [], "outgoing": []},
"fts_table": None,
"hidden": True,
"name": "searchable_fts_segdir",
"primary_keys": ["level", "idx"], "primary_keys": ["level", "idx"],
"count": 1,
"hidden": True,
"fts_table": None,
"foreign_keys": {"incoming": [], "outgoing": []},
}, },
{ {
"columns": ["blockid", "block"],
"count": 0,
"foreign_keys": {"incoming": [], "outgoing": []},
"fts_table": None,
"hidden": True,
"name": "searchable_fts_segments", "name": "searchable_fts_segments",
"columns": ["blockid", "block"],
"primary_keys": ["blockid"], "primary_keys": ["blockid"],
"count": 0,
"hidden": True,
"fts_table": None,
"foreign_keys": {"incoming": [], "outgoing": []},
}, },
] == data["tables"] ] == data["tables"]
@ -981,6 +1040,33 @@ def test_table_filter_extra_where_disabled_if_no_sql_allowed():
assert "_where= is not allowed" == response.json["error"] assert "_where= is not allowed" == response.json["error"]
def test_table_through(app_client):
# Just the museums:
response = app_client.get(
'/fixtures/roadside_attractions.json?_through={"table":"roadside_attraction_characteristics","column":"characteristic_id","value":"1"}'
)
assert [
[
3,
"Burlingame Museum of PEZ Memorabilia",
"214 California Drive, Burlingame, CA 94010",
37.5793,
-122.3442,
],
[
4,
"Bigfoot Discovery Museum",
"5497 Highway 9, Felton, CA 95018",
37.0414,
-122.0725,
],
] == response.json["rows"]
assert (
'where roadside_attraction_characteristics.characteristic_id = "1"'
== response.json["human_description_en"]
)
def test_max_returned_rows(app_client): def test_max_returned_rows(app_client):
response = app_client.get("/fixtures.json?sql=select+content+from+no_primary_key") response = app_client.get("/fixtures.json?sql=select+content+from+no_primary_key")
data = response.json data = response.json