Browse Source

Fixed #27869 -- Added fastupdate and gin_pending_list_limit params to GinIndex.

Thanks Tim Graham and Markus Holtermann for review.
Mads Jensen 7 năm trước cách đây
mục cha
commit
de42adf4ff

+ 22 - 0
django/contrib/postgres/indexes.py

@@ -33,5 +33,27 @@ class BrinIndex(Index):
 class GinIndex(Index):
     suffix = 'gin'
 
+    def __init__(self, fields=[], name=None, fastupdate=None, gin_pending_list_limit=None):
+        self.fastupdate = fastupdate
+        self.gin_pending_list_limit = gin_pending_list_limit
+        super().__init__(fields, name)
+
+    def deconstruct(self):
+        path, args, kwargs = super().deconstruct()
+        kwargs['fastupdate'] = self.fastupdate
+        kwargs['gin_pending_list_limit'] = self.gin_pending_list_limit
+        return path, args, kwargs
+
+    def get_sql_create_template_values(self, model, schema_editor, using):
+        parameters = super().get_sql_create_template_values(model, schema_editor, using=' USING gin')
+        with_params = []
+        if self.gin_pending_list_limit is not None:
+            with_params.append('gin_pending_list_limit = %d' % self.gin_pending_list_limit)
+        if self.fastupdate is not None:
+            with_params.append('fastupdate = {}'.format('on' if self.fastupdate else 'off'))
+        if with_params:
+            parameters['extra'] = 'WITH ({}) {}'.format(', '.join(with_params), parameters['extra'])
+        return parameters
+
     def create_sql(self, model, schema_editor):
         return super().create_sql(model, schema_editor, using=' USING gin')

+ 4 - 0
django/db/backends/postgresql/features.py

@@ -48,3 +48,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
     @cached_property
     def has_jsonb_agg(self):
         return self.connection.pg_version >= 90500
+
+    @cached_property
+    def has_gin_pending_list_limit(self):
+        return self.connection.pg_version >= 90500

+ 15 - 1
docs/ref/contrib/postgres/indexes.txt

@@ -22,7 +22,7 @@ available from the ``django.contrib.postgres.indexes`` module.
 ``GinIndex``
 ============
 
-.. class:: GinIndex()
+.. class:: GinIndex(fields=[], name=None, fastupdate=None, gin_pending_list_limit=None)
 
     Creates a `gin index
     <https://www.postgresql.org/docs/current/static/gin.html>`_.
@@ -34,3 +34,17 @@ available from the ``django.contrib.postgres.indexes`` module.
     PostgreSQL. You can install it using the
     :class:`~django.contrib.postgres.operations.BtreeGinExtension` migration
     operation.
+
+    Set the ``fastupdate`` parameter to ``False`` to disable the `GIN Fast
+    Update Technique`_ that's enabled by default in PostgreSQL.
+
+    Provide an integer number of bytes to the gin_pending_list_limit_ parameter
+    to tune the maximum size of the GIN pending list which is used when
+    ``fastupdate`` is enabled. This parameter requires PostgreSQL ≥ 9.5.
+
+    .. _GIN Fast Update Technique: https://www.postgresql.org/docs/current/static/gin-implementation.html#GIN-FAST-UPDATE
+    .. _gin_pending_list_limit: https://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-GIN-PENDING-LIST-LIMIT
+
+    .. versionchanged:: 2.0
+
+        The ``fastupdate`` and ``gin_pending_list_limit`` parameters were added.

+ 3 - 0
docs/releases/2.0.txt

@@ -108,6 +108,9 @@ Minor features
   :class:`~django.contrib.postgres.operations.CryptoExtension` migration
   operation.
 
+* :class:`django.contrib.postgres.indexes.GinIndex` now supports the
+  ``fast_update`` and ``gin_pending_list_limit`` parameters.
+
 :mod:`django.contrib.redirects`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

+ 40 - 2
tests/postgres_tests/test_indexes.py

@@ -65,11 +65,24 @@ class GinIndexTests(PostgreSQLTestCase):
         self.assertEqual(index.name, 'postgres_te_field_def2f8_gin')
 
     def test_deconstruction(self):
-        index = GinIndex(fields=['title'], name='test_title_gin')
+        index = GinIndex(
+            fields=['title'],
+            name='test_title_gin',
+            fastupdate=True,
+            gin_pending_list_limit=128,
+        )
         path, args, kwargs = index.deconstruct()
         self.assertEqual(path, 'django.contrib.postgres.indexes.GinIndex')
         self.assertEqual(args, ())
-        self.assertEqual(kwargs, {'fields': ['title'], 'name': 'test_title_gin'})
+        self.assertEqual(
+            kwargs,
+            {
+                'fields': ['title'],
+                'name': 'test_title_gin',
+                'fastupdate': True,
+                'gin_pending_list_limit': 128,
+            }
+        )
 
 
 class SchemaTests(PostgreSQLTestCase):
@@ -97,6 +110,31 @@ class SchemaTests(PostgreSQLTestCase):
             editor.remove_index(IntegerArrayModel, index)
         self.assertNotIn(index_name, self.get_constraints(IntegerArrayModel._meta.db_table))
 
+    def test_gin_fastupdate(self):
+        index_name = 'integer_array_gin_fastupdate'
+        index = GinIndex(fields=['field'], name=index_name, fastupdate=False)
+        with connection.schema_editor() as editor:
+            editor.add_index(IntegerArrayModel, index)
+        constraints = self.get_constraints(IntegerArrayModel._meta.db_table)
+        self.assertEqual(constraints[index_name]['type'], 'gin')
+        self.assertEqual(constraints[index_name]['options'], ['fastupdate=off'])
+        with connection.schema_editor() as editor:
+            editor.remove_index(IntegerArrayModel, index)
+        self.assertNotIn(index_name, self.get_constraints(IntegerArrayModel._meta.db_table))
+
+    @skipUnlessDBFeature('has_gin_pending_list_limit')
+    def test_gin_parameters(self):
+        index_name = 'integer_array_gin_params'
+        index = GinIndex(fields=['field'], name=index_name, fastupdate=True, gin_pending_list_limit=64)
+        with connection.schema_editor() as editor:
+            editor.add_index(IntegerArrayModel, index)
+        constraints = self.get_constraints(IntegerArrayModel._meta.db_table)
+        self.assertEqual(constraints[index_name]['type'], 'gin')
+        self.assertEqual(constraints[index_name]['options'], ['gin_pending_list_limit=64', 'fastupdate=on'])
+        with connection.schema_editor() as editor:
+            editor.remove_index(IntegerArrayModel, index)
+        self.assertNotIn(index_name, self.get_constraints(IntegerArrayModel._meta.db_table))
+
     @skipUnlessDBFeature('has_brin_index_support')
     def test_brin_index(self):
         index_name = 'char_field_model_field_brin'