From 71c93cb39c2383c0d408cb2431f63ae59f4d5f2e Mon Sep 17 00:00:00 2001
From: Jake Howard <jake.howard@torchbox.com>
Date: Wed, 30 Oct 2024 16:27:29 +0000
Subject: [PATCH] Remove non-unsert support for Postgres indexing (#12509)

PostgreSQL 9.4 support was dropped with the release of Django 3.0.
---
 CHANGELOG.txt                                 |  1 +
 docs/releases/6.4.md                          |  1 +
 .../backends/database/postgres/postgres.py    | 73 +++----------------
 wagtail/search/tests/test_postgres_backend.py | 14 ----
 4 files changed, 14 insertions(+), 75 deletions(-)

diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 5585e5faec..e4667d0103 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -29,6 +29,7 @@ Changelog
  * Maintenance: Clean up JS comments throughout codebase to be aligned to JSDoc where practical (LB (Ben) Johnston)
  * Maintenance: Upgrade Node tooling to active LTS version 22 (LB (Ben) Johnston)
  * Maintenance: Remove defunct oEmbed providers (Rahul Samant)
+ * Maintenance: Remove obsolete non-upsert-based code for Postgres search indexing (Jake Howard)
 
 
 6.3 LTS (01.11.2024)
diff --git a/docs/releases/6.4.md b/docs/releases/6.4.md
index 96fa840bf3..004c9297b1 100644
--- a/docs/releases/6.4.md
+++ b/docs/releases/6.4.md
@@ -49,6 +49,7 @@ depth: 1
  * Clean up JS comments throughout codebase to be aligned to JSDoc where practical (LB (Ben) Johnston)
  * Upgrade Node tooling to active LTS version 22 (LB (Ben) Johnston)
  * Remove defunct oEmbed providers (Rahul Samant)
+ * Remove obsolete non-upsert-based code for Postgres search indexing (Jake Howard)
 
 
 ## Upgrade considerations - changes affecting all projects
diff --git a/wagtail/search/backends/database/postgres/postgres.py b/wagtail/search/backends/database/postgres/postgres.py
index 4d04313e1f..ec58dd0bd3 100644
--- a/wagtail/search/backends/database/postgres/postgres.py
+++ b/wagtail/search/backends/database/postgres/postgres.py
@@ -173,9 +173,6 @@ class Index:
                 "You must select a PostgreSQL database " "to use PostgreSQL search."
             )
 
-        # Whether to allow adding items via the faster upsert method available in Postgres >=9.5
-        self._enable_upsert = self.connection.pg_version >= 90500
-
         self.entries = IndexEntry._default_manager.using(self.db_alias)
 
     def add_model(self, model):
@@ -237,7 +234,18 @@ class Index:
     def add_item(self, obj):
         self.add_items(obj._meta.model, [obj])
 
-    def add_items_upsert(self, content_type_pk, indexers):
+    def add_items(self, model, objs):
+        search_fields = model.get_search_fields()
+        if not search_fields:
+            return
+
+        indexers = [ObjectIndexer(obj, self.backend) for obj in objs]
+
+        # TODO: Delete unindexed objects while dealing with proxy models.
+        if not indexers:
+            return
+
+        content_type_pk = get_content_type_pk(model)
         compiler = InsertQuery(IndexEntry).get_compiler(connection=self.connection)
         title_sql = []
         autocomplete_sql = []
@@ -295,63 +303,6 @@ class Index:
 
         self._refresh_title_norms()
 
-    def add_items_update_then_create(self, content_type_pk, indexers):
-        ids_and_data = {}
-        for indexer in indexers:
-            ids_and_data[indexer.id] = (
-                indexer.title,
-                indexer.autocomplete,
-                indexer.body,
-            )
-
-        index_entries_for_ct = self.entries.filter(content_type_id=content_type_pk)
-        indexed_ids = frozenset(
-            index_entries_for_ct.filter(object_id__in=ids_and_data.keys()).values_list(
-                "object_id", flat=True
-            )
-        )
-        for indexed_id in indexed_ids:
-            title, autocomplete, body = ids_and_data[indexed_id]
-            index_entries_for_ct.filter(object_id=indexed_id).update(
-                title=title, autocomplete=autocomplete, body=body
-            )
-
-        to_be_created = []
-        for object_id in ids_and_data.keys():
-            if object_id not in indexed_ids:
-                title, autocomplete, body = ids_and_data[object_id]
-                to_be_created.append(
-                    IndexEntry(
-                        content_type_id=content_type_pk,
-                        object_id=object_id,
-                        title=title,
-                        autocomplete=autocomplete,
-                        body=body,
-                    )
-                )
-
-        self.entries.bulk_create(to_be_created)
-
-        self._refresh_title_norms()
-
-    def add_items(self, model, objs):
-        search_fields = model.get_search_fields()
-        if not search_fields:
-            return
-
-        indexers = [ObjectIndexer(obj, self.backend) for obj in objs]
-
-        # TODO: Delete unindexed objects while dealing with proxy models.
-        if indexers:
-            content_type_pk = get_content_type_pk(model)
-
-            update_method = (
-                self.add_items_upsert
-                if self._enable_upsert
-                else self.add_items_update_then_create
-            )
-            update_method(content_type_pk, indexers)
-
     def delete_item(self, item):
         item.index_entries.all()._raw_delete(using=self.db_alias)
 
diff --git a/wagtail/search/tests/test_postgres_backend.py b/wagtail/search/tests/test_postgres_backend.py
index a66a289d00..22552f34e6 100644
--- a/wagtail/search/tests/test_postgres_backend.py
+++ b/wagtail/search/tests/test_postgres_backend.py
@@ -160,20 +160,6 @@ class TestPostgresSearchBackend(BackendTests, TestCase):
         results = self.backend.autocomplete("first <-> second", models.Book)
         self.assertUnsortedListEqual([r.title for r in results], [])
 
-    def test_index_without_upsert(self):
-        # Test the add_items code path for Postgres 9.4, where upsert is not available
-        self.backend.reset_index()
-
-        index = self.backend.get_index_for_model(models.Book)
-        index._enable_upsert = False
-        index.add_items(models.Book, models.Book.objects.all())
-
-        results = self.backend.search("JavaScript", models.Book)
-        self.assertUnsortedListEqual(
-            [r.title for r in results],
-            ["JavaScript: The good parts", "JavaScript: The Definitive Guide"],
-        )
-
 
 @unittest.skipUnless(
     connection.vendor == "postgresql", "The current database is not PostgreSQL"