diff --git a/docs/python-api.rst b/docs/python-api.rst index 5d3409d..d29133e 100644 --- a/docs/python-api.rst +++ b/docs/python-api.rst @@ -1213,6 +1213,21 @@ To remove the FTS tables and triggers you created, use the ``disable_fts()`` tab dogs.disable_fts() +Rebuilding a full-text search table +=================================== + +You can rebuild a table using the ``table.rebuild_fts()`` method. This is useful for if the table configuration changes or the indexed data has become corrupted in some way. + +.. code-block:: python + + dogs.rebuild_fts() + +This method can be called on a table that has been configured for full-text search - ``dogs`` in this instance - or directly on a ``_fts`` table: + +.. code-block:: python + + db["dogs_fts"].rebuild_fts() + Optimizing a full-text search table =================================== diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 0a22a3a..e077231 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -905,6 +905,17 @@ class Table(Queryable): for trigger_name in trigger_names: self.db.execute("DROP TRIGGER IF EXISTS [{}]".format(trigger_name)) + def rebuild_fts(self): + fts_table = self.detect_fts() + if fts_table is None: + # Assume this is itself an FTS table + fts_table = self.name + self.db.execute( + "INSERT INTO [{table}]([{table}]) VALUES('rebuild');".format( + table=fts_table + ) + ) + def detect_fts(self): "Detect if table has a corresponding FTS virtual table and return it" sql = ( diff --git a/tests/test_fts.py b/tests/test_fts.py index defb886..e0b5475 100644 --- a/tests/test_fts.py +++ b/tests/test_fts.py @@ -1,4 +1,5 @@ import pytest +from sqlite_utils.utils import sqlite3 search_records = [ { @@ -173,3 +174,28 @@ def test_disable_fts(fresh_db, create_triggers): ).fetchone()[0] ) assert ["searchable"] == fresh_db.table_names() + + +@pytest.mark.parametrize("table_to_fix", ["searchable", "searchable_fts"]) +def test_rebuild_fts(fresh_db, table_to_fix): + table = fresh_db["searchable"] + table.insert(search_records[0]) + table.enable_fts(["text", "country"]) + # Run a search + assert [("tanuki are running tricksters", "Japan", "foo")] == table.search("tanuki") + # Delete from searchable_fts_data + fresh_db["searchable_fts_data"].delete_where() + # This should have broken the index + with pytest.raises(sqlite3.DatabaseError): + table.search("tanuki") + # Running rebuild_fts() should fix it + fresh_db[table_to_fix].rebuild_fts() + assert [("tanuki are running tricksters", "Japan", "foo")] == table.search("tanuki") + + +@pytest.mark.parametrize("invalid_table", ["does_not_exist", "not_searchable"]) +def test_rebuild_fts_invalid(fresh_db, invalid_table): + fresh_db["not_searchable"].insert({"foo": "bar"}) + # Raise OperationalError on invalid table + with pytest.raises(sqlite3.OperationalError): + fresh_db[invalid_table].rebuild_fts()