diff --git a/datasette/database.py b/datasette/database.py index 554f9fbf..b74f02bb 100644 --- a/datasette/database.py +++ b/datasette/database.py @@ -578,10 +578,22 @@ class Database: SELECT name FROM fts3_shadow_tables ) SELECT name FROM final ORDER BY 1 - """ ) ] + # Also hide any FTS tables that have a content= argument + hidden_tables += [ + x[0] + for x in await self.execute( + """ + SELECT name + FROM sqlite_master + WHERE sql LIKE '%VIRTUAL TABLE%' + AND sql LIKE '%USING FTS%' + AND sql LIKE '%content=%' + """ + ) + ] has_spatialite = await self.execute_fn(detect_spatialite) if has_spatialite: diff --git a/tests/test_api.py b/tests/test_api.py index 44d5113a..84b33a09 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -389,29 +389,6 @@ async def test_database_page(ds_client): }, "private": False, }, - { - "name": "searchable_fts", - "columns": [ - "text1", - "text2", - "name with . and spaces", - ] - + ( - [ - "searchable_fts", - "docid", - "__langid", - ] - if supports_table_xinfo() - else [] - ), - "primary_keys": [], - "count": 2, - "hidden": False, - "fts_table": "searchable_fts", - "foreign_keys": {"incoming": [], "outgoing": []}, - "private": False, - }, { "name": "searchable_tags", "columns": ["searchable_id", "tag"], @@ -538,6 +515,31 @@ async def test_database_page(ds_client): "foreign_keys": {"incoming": [], "outgoing": []}, "private": False, }, + { + "columns": Either( + [ + "text1", + "text2", + "name with . and spaces", + "searchable_fts", + "docid", + "__langid", + ], + # Get tests to pass on SQLite 3.25 as well + [ + "text1", + "text2", + "name with . and spaces", + ], + ), + "count": 2, + "foreign_keys": {"incoming": [], "outgoing": []}, + "fts_table": "searchable_fts", + "hidden": True, + "name": "searchable_fts", + "primary_keys": [], + "private": False, + }, { "name": "searchable_fts_docsize", "columns": ["docid", "size"], @@ -1198,3 +1200,12 @@ async def test_upgrade_metadata(metadata, expected_config, expected_metadata): assert response.json() == expected_config response2 = await ds.client.get("/-/metadata.json") assert response2.json() == expected_metadata + + +class Either: + def __init__(self, a, b): + self.a = a + self.b = b + + def __eq__(self, other): + return other == self.a or other == self.b diff --git a/tests/test_html.py b/tests/test_html.py index 085fa037..6c838549 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -41,14 +41,13 @@ def test_homepage(app_client_two_attached_databases): assert "extra database" == h2.text.strip() counts_p, links_p = h2.find_all_next("p")[:2] assert ( - "4 rows in 2 tables, 3 rows in 3 hidden tables, 1 view" == counts_p.text.strip() + "2 rows in 1 table, 5 rows in 4 hidden tables, 1 view" == counts_p.text.strip() ) # We should only show visible, not hidden tables here: table_links = [ {"href": a["href"], "text": a.text.strip()} for a in links_p.find_all("a") ] assert [ - {"href": r"/extra+database/searchable_fts", "text": "searchable_fts"}, {"href": r"/extra+database/searchable", "text": "searchable"}, {"href": r"/extra+database/searchable_view", "text": "searchable_view"}, ] == table_links diff --git a/tests/test_internals_database.py b/tests/test_internals_database.py index eeaf8e9a..89a17047 100644 --- a/tests/test_internals_database.py +++ b/tests/test_internals_database.py @@ -722,6 +722,25 @@ async def test_hidden_tables(app_client): "r_rowid", ] + # A fts virtual table with a content table should be hidden too + await db.execute("create virtual table f2_fts using fts5(a, content='f')") + assert await db.hidden_table_names() == [ + "_hideme", + "f2_fts_config", + "f2_fts_data", + "f2_fts_docsize", + "f2_fts_idx", + "f_config", + "f_content", + "f_data", + "f_docsize", + "f_idx", + "r_node", + "r_parent", + "r_rowid", + "f2_fts", + ] + @pytest.mark.asyncio async def test_replace_database(tmpdir):