mirror of https://github.com/django/django.git
Fixed #28126 -- Added GistIndex to contrib.postgres.
Thanks to Marc Tamlyn for the initial patch.
This commit is contained in:
parent
66657eb01f
commit
f4135783ad
|
@ -1,16 +1,19 @@
|
|||
from django.db.models import Index
|
||||
|
||||
__all__ = ['BrinIndex', 'GinIndex']
|
||||
__all__ = ['BrinIndex', 'GinIndex', 'GistIndex']
|
||||
|
||||
|
||||
class BrinIndex(Index):
|
||||
suffix = 'brin'
|
||||
class MaxLengthMixin:
|
||||
# Allow an index name longer than 30 characters since the suffix is 4
|
||||
# characters (usual limit is 3). Since this index can only be used on
|
||||
# PostgreSQL, the 30 character limit for cross-database compatibility isn't
|
||||
# applicable.
|
||||
max_name_length = 31
|
||||
|
||||
|
||||
class BrinIndex(MaxLengthMixin, Index):
|
||||
suffix = 'brin'
|
||||
|
||||
def __init__(self, *, pages_per_range=None, **kwargs):
|
||||
if pages_per_range is not None and pages_per_range <= 0:
|
||||
raise ValueError('pages_per_range must be None or a positive integer')
|
||||
|
@ -58,3 +61,31 @@ class GinIndex(Index):
|
|||
if with_params:
|
||||
statement.parts['extra'] = 'WITH ({}) {}'.format(', '.join(with_params), statement.parts['extra'])
|
||||
return statement
|
||||
|
||||
|
||||
class GistIndex(MaxLengthMixin, Index):
|
||||
suffix = 'gist'
|
||||
|
||||
def __init__(self, *, buffering=None, fillfactor=None, **kwargs):
|
||||
self.buffering = buffering
|
||||
self.fillfactor = fillfactor
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def deconstruct(self):
|
||||
path, args, kwargs = super().deconstruct()
|
||||
if self.buffering is not None:
|
||||
kwargs['buffering'] = self.buffering
|
||||
if self.fillfactor is not None:
|
||||
kwargs['fillfactor'] = self.fillfactor
|
||||
return path, args, kwargs
|
||||
|
||||
def create_sql(self, model, schema_editor):
|
||||
statement = super().create_sql(model, schema_editor, using=' USING gist')
|
||||
with_params = []
|
||||
if self.buffering is not None:
|
||||
with_params.append('buffering = {}'.format('on' if self.buffering else 'off'))
|
||||
if self.fillfactor is not None:
|
||||
with_params.append('fillfactor = %s' % self.fillfactor)
|
||||
if with_params:
|
||||
statement.parts['extra'] = 'WITH ({}) {}'.format(', '.join(with_params), statement.parts['extra'])
|
||||
return statement
|
||||
|
|
|
@ -33,6 +33,12 @@ class BtreeGinExtension(CreateExtension):
|
|||
self.name = 'btree_gin'
|
||||
|
||||
|
||||
class BtreeGistExtension(CreateExtension):
|
||||
|
||||
def __init__(self):
|
||||
self.name = 'btree_gist'
|
||||
|
||||
|
||||
class CITextExtension(CreateExtension):
|
||||
|
||||
def __init__(self):
|
||||
|
|
|
@ -48,3 +48,35 @@ available from the ``django.contrib.postgres.indexes`` module.
|
|||
.. versionchanged:: 2.0
|
||||
|
||||
The ``fastupdate`` and ``gin_pending_list_limit`` parameters were added.
|
||||
|
||||
``GistIndex``
|
||||
=============
|
||||
|
||||
.. class:: GistIndex(buffering=None, fillfactor=None, **options)
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Creates a `GiST index
|
||||
<https://www.postgresql.org/docs/current/static/gist.html>`_. These indexes
|
||||
are automatically created on spatial fields with :attr:`spatial_index=True
|
||||
<django.contrib.gis.db.models.BaseSpatialField.spatial_index>`. They're
|
||||
also useful on other types, such as
|
||||
:class:`~django.contrib.postgres.fields.HStoreField` or the :ref:`range
|
||||
fields <range-fields>`.
|
||||
|
||||
To use this index on data types not in the built-in `gist operator classes
|
||||
<https://www.postgresql.org/docs/current/static/gist-builtin-opclasses.html>`_,
|
||||
you need to activate the `btree_gist extension
|
||||
<https://www.postgresql.org/docs/current/static/btree-gist.html>`_ on
|
||||
PostgreSQL. You can install it using the
|
||||
:class:`~django.contrib.postgres.operations.BtreeGistExtension` migration
|
||||
operation.
|
||||
|
||||
Set the ``buffering`` parameter to ``True`` or ``False`` to manually enable
|
||||
or disable `buffering build`_ of the index.
|
||||
|
||||
Provide an integer value from 10 to 100 to the fillfactor_ parameter to
|
||||
tune how packed the index pages will be. PostgreSQL's default is 90.
|
||||
|
||||
.. _buffering build: https://www.postgresql.org/docs/current/static/gist-implementation.html#GIST-BUFFERING-BUILD
|
||||
.. _fillfactor: https://www.postgresql.org/docs/current/static/sql-createindex.html#SQL-CREATEINDEX-STORAGE-PARAMETERS
|
||||
|
|
|
@ -58,6 +58,15 @@ run the query ``CREATE EXTENSION IF NOT EXISTS hstore;``.
|
|||
|
||||
Install the ``btree_gin`` extension.
|
||||
|
||||
``BtreeGistExtension``
|
||||
======================
|
||||
|
||||
.. class:: BtreeGistExtension()
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Install the ``btree_gist`` extension.
|
||||
|
||||
``CITextExtension``
|
||||
===================
|
||||
|
||||
|
|
|
@ -117,6 +117,12 @@ Minor features
|
|||
* :class:`django.contrib.postgres.indexes.GinIndex` now supports the
|
||||
``fastupdate`` and ``gin_pending_list_limit`` parameters.
|
||||
|
||||
* The new :class:`~django.contrib.postgres.indexes.GistIndex` class allows
|
||||
creating ``GiST`` indexes in the database. The new
|
||||
:class:`~django.contrib.postgres.operations.BtreeGistExtension` migration
|
||||
operation installs the ``btree_gist`` extension to add support for operator
|
||||
classes that aren't built-in.
|
||||
|
||||
:mod:`django.contrib.redirects`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -4,11 +4,13 @@ from django.db import migrations
|
|||
|
||||
try:
|
||||
from django.contrib.postgres.operations import (
|
||||
BtreeGinExtension, CITextExtension, CreateExtension, CryptoExtension,
|
||||
HStoreExtension, TrigramExtension, UnaccentExtension,
|
||||
BtreeGinExtension, BtreeGistExtension, CITextExtension,
|
||||
CreateExtension, CryptoExtension, HStoreExtension, TrigramExtension,
|
||||
UnaccentExtension,
|
||||
)
|
||||
except ImportError:
|
||||
BtreeGinExtension = mock.Mock()
|
||||
BtreeGistExtension = mock.Mock()
|
||||
CITextExtension = mock.Mock()
|
||||
CreateExtension = mock.Mock()
|
||||
CryptoExtension = mock.Mock()
|
||||
|
@ -21,6 +23,7 @@ class Migration(migrations.Migration):
|
|||
|
||||
operations = [
|
||||
BtreeGinExtension(),
|
||||
BtreeGistExtension(),
|
||||
CITextExtension(),
|
||||
# Ensure CreateExtension quotes extension names by creating one with a
|
||||
# dash in its name.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.contrib.postgres.indexes import BrinIndex, GinIndex
|
||||
from django.contrib.postgres.indexes import BrinIndex, GinIndex, GistIndex
|
||||
from django.db import connection
|
||||
from django.test import skipUnlessDBFeature
|
||||
|
||||
|
@ -92,6 +92,46 @@ class GinIndexTests(PostgreSQLTestCase):
|
|||
self.assertEqual(kwargs, {'fields': ['title'], 'name': 'test_title_gin'})
|
||||
|
||||
|
||||
class GistIndexTests(PostgreSQLTestCase):
|
||||
|
||||
def test_suffix(self):
|
||||
self.assertEqual(GistIndex.suffix, 'gist')
|
||||
|
||||
def test_eq(self):
|
||||
index = GistIndex(fields=['title'], fillfactor=64)
|
||||
same_index = GistIndex(fields=['title'], fillfactor=64)
|
||||
another_index = GistIndex(fields=['author'], buffering=True)
|
||||
self.assertEqual(index, same_index)
|
||||
self.assertNotEqual(index, another_index)
|
||||
|
||||
def test_name_auto_generation(self):
|
||||
index = GistIndex(fields=['field'])
|
||||
index.set_name_with_model(CharFieldModel)
|
||||
self.assertEqual(index.name, 'postgres_te_field_1e0206_gist')
|
||||
|
||||
def test_deconstruction(self):
|
||||
index = GistIndex(fields=['title'], name='test_title_gist', buffering=False, fillfactor=80)
|
||||
path, args, kwargs = index.deconstruct()
|
||||
self.assertEqual(path, 'django.contrib.postgres.indexes.GistIndex')
|
||||
self.assertEqual(args, ())
|
||||
self.assertEqual(
|
||||
kwargs,
|
||||
{
|
||||
'fields': ['title'],
|
||||
'name': 'test_title_gist',
|
||||
'buffering': False,
|
||||
'fillfactor': 80,
|
||||
}
|
||||
)
|
||||
|
||||
def test_deconstruction_no_customization(self):
|
||||
index = GistIndex(fields=['title'], name='test_title_gist')
|
||||
path, args, kwargs = index.deconstruct()
|
||||
self.assertEqual(path, 'django.contrib.postgres.indexes.GistIndex')
|
||||
self.assertEqual(args, ())
|
||||
self.assertEqual(kwargs, {'fields': ['title'], 'name': 'test_title_gist'})
|
||||
|
||||
|
||||
class SchemaTests(PostgreSQLTestCase):
|
||||
|
||||
def get_constraints(self, table):
|
||||
|
@ -154,3 +194,31 @@ class SchemaTests(PostgreSQLTestCase):
|
|||
with connection.schema_editor() as editor:
|
||||
editor.remove_index(CharFieldModel, index)
|
||||
self.assertNotIn(index_name, self.get_constraints(CharFieldModel._meta.db_table))
|
||||
|
||||
def test_gist_index(self):
|
||||
# Ensure the table is there and doesn't have an index.
|
||||
self.assertNotIn('field', self.get_constraints(CharFieldModel._meta.db_table))
|
||||
# Add the index.
|
||||
index_name = 'char_field_model_field_gist'
|
||||
index = GistIndex(fields=['field'], name=index_name)
|
||||
with connection.schema_editor() as editor:
|
||||
editor.add_index(CharFieldModel, index)
|
||||
constraints = self.get_constraints(CharFieldModel._meta.db_table)
|
||||
# The index was added.
|
||||
self.assertEqual(constraints[index_name]['type'], GistIndex.suffix)
|
||||
# Drop the index.
|
||||
with connection.schema_editor() as editor:
|
||||
editor.remove_index(CharFieldModel, index)
|
||||
self.assertNotIn(index_name, self.get_constraints(CharFieldModel._meta.db_table))
|
||||
|
||||
def test_gist_parameters(self):
|
||||
index_name = 'integer_array_gist_buffering'
|
||||
index = GistIndex(fields=['field'], name=index_name, buffering=True, fillfactor=80)
|
||||
with connection.schema_editor() as editor:
|
||||
editor.add_index(CharFieldModel, index)
|
||||
constraints = self.get_constraints(CharFieldModel._meta.db_table)
|
||||
self.assertEqual(constraints[index_name]['type'], GistIndex.suffix)
|
||||
self.assertEqual(constraints[index_name]['options'], ['buffering=on', 'fillfactor=80'])
|
||||
with connection.schema_editor() as editor:
|
||||
editor.remove_index(CharFieldModel, index)
|
||||
self.assertNotIn(index_name, self.get_constraints(CharFieldModel._meta.db_table))
|
||||
|
|
Loading…
Reference in New Issue