From de42adf4ff6b80e25bbd9e70070146ef15076a83 Mon Sep 17 00:00:00 2001 From: Mads Jensen Date: Tue, 30 May 2017 23:17:32 +0200 Subject: [PATCH] Fixed #27869 -- Added fastupdate and gin_pending_list_limit params to GinIndex. Thanks Tim Graham and Markus Holtermann for review. --- django/contrib/postgres/indexes.py | 22 ++++++++++++ django/db/backends/postgresql/features.py | 4 +++ docs/ref/contrib/postgres/indexes.txt | 16 ++++++++- docs/releases/2.0.txt | 3 ++ tests/postgres_tests/test_indexes.py | 42 +++++++++++++++++++++-- 5 files changed, 84 insertions(+), 3 deletions(-) diff --git a/django/contrib/postgres/indexes.py b/django/contrib/postgres/indexes.py index 9ff66c2a009..7c687987db1 100644 --- a/django/contrib/postgres/indexes.py +++ b/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') diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py index 44c48eb946a..3f6cc7894d1 100644 --- a/django/db/backends/postgresql/features.py +++ b/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 diff --git a/docs/ref/contrib/postgres/indexes.txt b/docs/ref/contrib/postgres/indexes.txt index ce5a50caf3c..e364f8d83e3 100644 --- a/docs/ref/contrib/postgres/indexes.txt +++ b/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 `_. @@ -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. diff --git a/docs/releases/2.0.txt b/docs/releases/2.0.txt index 70a21d3da65..5170e66cbec 100644 --- a/docs/releases/2.0.txt +++ b/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` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/postgres_tests/test_indexes.py b/tests/postgres_tests/test_indexes.py index c3172f3a49a..d866a8b8690 100644 --- a/tests/postgres_tests/test_indexes.py +++ b/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'