From 1c9c2daf83546619f36c7309d46a44fce2806848 Mon Sep 17 00:00:00 2001
From: Matt Westcott <matt@west.co.tt>
Date: Wed, 12 Jan 2022 11:57:03 +0000
Subject: [PATCH] Warn and use fallback backend if fts5 is available but the
 index table is missing

This scenario will happen if sqlite has been upgraded to a version with fts5 support since wagtailsearch migration 0006 was run.
---
 wagtail/search/backends/database/__init__.py  | 25 +++++++++++++++++--
 .../search/backends/database/sqlite/utils.py  | 13 ++++++++++
 2 files changed, 36 insertions(+), 2 deletions(-)

diff --git a/wagtail/search/backends/database/__init__.py b/wagtail/search/backends/database/__init__.py
index 6e181b2b31..255eda268b 100644
--- a/wagtail/search/backends/database/__init__.py
+++ b/wagtail/search/backends/database/__init__.py
@@ -1,6 +1,11 @@
+import warnings
+
 from django.db import connection
 
 
+USE_SQLITE_FTS = None  # True if sqlite FTS is available, False if not, None if untested
+
+
 def SearchBackend(params):
     """
     Returns the appropriate search backend for the current 'default' database system
@@ -12,8 +17,24 @@ def SearchBackend(params):
         from .mysql.mysql import MySQLSearchBackend
         return MySQLSearchBackend(params)
     elif connection.vendor == 'sqlite':
-        from .sqlite.utils import fts5_available
-        if fts5_available():
+        global USE_SQLITE_FTS
+
+        if USE_SQLITE_FTS is None:
+            from .sqlite.utils import fts5_available, fts_table_exists
+            if not fts5_available():
+                USE_SQLITE_FTS = False
+            elif not fts_table_exists():
+                USE_SQLITE_FTS = False
+                warnings.warn(
+                    "The installed SQLite library supports full-text search, but the table for storing "
+                    "searchable content is missing. This probably means SQLite was upgraded after the "
+                    "migration was applied. To enable full-text search, reapply wagtailsearch migration 0006 "
+                    "or create the table manually."
+                )
+            else:
+                USE_SQLITE_FTS = True
+
+        if USE_SQLITE_FTS:
             from .sqlite.sqlite import SQLiteSearchBackend
             return SQLiteSearchBackend(params)
         else:
diff --git a/wagtail/search/backends/database/sqlite/utils.py b/wagtail/search/backends/database/sqlite/utils.py
index fb4e0326f3..cb9a75171c 100644
--- a/wagtail/search/backends/database/sqlite/utils.py
+++ b/wagtail/search/backends/database/sqlite/utils.py
@@ -20,3 +20,16 @@ def fts5_available():
         tmp_db.close()
 
     return True
+
+
+def fts_table_exists():
+    from wagtail.search.models import SQLiteFTSIndexEntry
+
+    try:
+        # ignore result of query; we are only interested in the query failing,
+        # not the presence of index entries
+        SQLiteFTSIndexEntry.objects.exists()
+    except OperationalError:
+        return False
+
+    return True