diff --git a/django/contrib/postgres/indexes.py b/django/contrib/postgres/indexes.py new file mode 100644 index 00000000000..5382c15e1a3 --- /dev/null +++ b/django/contrib/postgres/indexes.py @@ -0,0 +1,12 @@ +from __future__ import unicode_literals + +from django.db.models import Index + +__all__ = ['GinIndex'] + + +class GinIndex(Index): + suffix = 'gin' + + def create_sql(self, model, schema_editor): + return super(GinIndex, self).create_sql(model, schema_editor, using=' USING gin') diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index 67960a02ac4..aae64e3be79 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -880,6 +880,7 @@ class BaseDatabaseSchemaEditor(object): return sql_create_index % { "table": self.quote_name(model._meta.db_table), "name": self.quote_name(self._create_index_name(model, columns, suffix=suffix)), + "using": "", "columns": ", ".join(self.quote_name(column) for column in columns), "extra": tablespace_sql, } diff --git a/django/db/backends/postgresql/schema.py b/django/db/backends/postgresql/schema.py index 16f20031966..1afb85d4682 100644 --- a/django/db/backends/postgresql/schema.py +++ b/django/db/backends/postgresql/schema.py @@ -11,6 +11,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): sql_delete_sequence = "DROP SEQUENCE IF EXISTS %(sequence)s CASCADE" sql_set_sequence_max = "SELECT setval('%(sequence)s', MAX(%(column)s)) FROM %(table)s" + sql_create_index = "CREATE INDEX %(name)s ON %(table)s%(using)s (%(columns)s)%(extra)s" sql_create_varchar_index = "CREATE INDEX %(name)s ON %(table)s (%(columns)s varchar_pattern_ops)%(extra)s" sql_create_text_index = "CREATE INDEX %(name)s ON %(table)s (%(columns)s text_pattern_ops)%(extra)s" diff --git a/django/db/models/indexes.py b/django/db/models/indexes.py index bb33f8a7585..f70c2e56256 100644 --- a/django/db/models/indexes.py +++ b/django/db/models/indexes.py @@ -42,7 +42,7 @@ class Index(object): self.name = 'D%s' % self.name[1:] return errors - def create_sql(self, model, schema_editor): + def create_sql(self, model, schema_editor, using=''): fields = [model._meta.get_field(field_name) for field_name, order in self.fields_orders] tablespace_sql = schema_editor._get_index_tablespace_sql(model, fields) quote_name = schema_editor.quote_name @@ -54,6 +54,7 @@ class Index(object): 'table': quote_name(model._meta.db_table), 'name': quote_name(self.name), 'columns': ', '.join(columns), + 'using': using, 'extra': tablespace_sql, } diff --git a/docs/ref/contrib/postgres/index.txt b/docs/ref/contrib/postgres/index.txt index d04ed148894..5a212fbdcd4 100644 --- a/docs/ref/contrib/postgres/index.txt +++ b/docs/ref/contrib/postgres/index.txt @@ -35,6 +35,7 @@ release. Some fields require higher versions. fields forms functions + indexes lookups operations search diff --git a/docs/ref/contrib/postgres/indexes.txt b/docs/ref/contrib/postgres/indexes.txt new file mode 100644 index 00000000000..7bf26fb117c --- /dev/null +++ b/docs/ref/contrib/postgres/indexes.txt @@ -0,0 +1,24 @@ +================================= +PostgreSQL specific model indexes +================================= + +.. module:: django.contrib.postgres.indexes + +.. versionadded:: 1.11 + +The following are PostgreSQL specific :doc:`indexes ` +available from the ``django.contrib.postgres.indexes`` module. + +``GinIndex`` +============ + +.. class:: GinIndex() + + Creates a `gin index + `_. + + To use this index, you need to activate the `btree_gin extension + `_ on + PostgreSQL. You can install it using the + :class:`~django.contrib.postgres.operations.BtreeGinExtension` migration + operation. diff --git a/docs/ref/models/indexes.txt b/docs/ref/models/indexes.txt index ac2839bbd43..999513c9d6b 100644 --- a/docs/ref/models/indexes.txt +++ b/docs/ref/models/indexes.txt @@ -56,3 +56,8 @@ case, a descending index is created as a normal index. The name of the index. If ``name`` isn't provided Django will auto-generate a name. For compatibility with different databases, index names cannot be longer than 30 characters and shouldn't start with a number (0-9) or underscore (_). + +.. seealso:: + + For a list of PostgreSQL-specific indexes, see + :mod:`django.contrib.postgres.indexes`. diff --git a/tests/postgres_tests/test_indexes.py b/tests/postgres_tests/test_indexes.py new file mode 100644 index 00000000000..41bdd3beca4 --- /dev/null +++ b/tests/postgres_tests/test_indexes.py @@ -0,0 +1,55 @@ +from django.contrib.postgres.indexes import GinIndex +from django.db import connection + +from . import PostgreSQLTestCase +from .models import IntegerArrayModel + + +class GinIndexTests(PostgreSQLTestCase): + + def test_repr(self): + index = GinIndex(fields=['title']) + self.assertEqual(repr(index), "") + + def test_eq(self): + index = GinIndex(fields=['title']) + same_index = GinIndex(fields=['title']) + another_index = GinIndex(fields=['author']) + self.assertEqual(index, same_index) + self.assertNotEqual(index, another_index) + + def test_name_auto_generation(self): + index = GinIndex(fields=['field']) + index.set_name_with_model(IntegerArrayModel) + self.assertEqual(index.name, 'postgres_te_field_def2f8_gin') + + def test_deconstruction(self): + index = GinIndex(fields=['title'], name='test_title_gin') + 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'}) + + +class SchemaTests(PostgreSQLTestCase): + + def get_indexes(self, table): + """ + Get the indexes on the table using a new cursor. + """ + with connection.cursor() as cursor: + return connection.introspection.get_indexes(cursor, table) + + def test_gin_index(self): + # Ensure the table is there and doesn't have an index. + self.assertNotIn('field', self.get_indexes(IntegerArrayModel._meta.db_table)) + # Add the index + index = GinIndex(fields=['field'], name='integer_array_model_field_gin') + with connection.schema_editor() as editor: + editor.add_index(IntegerArrayModel, index) + self.assertIn('field', self.get_indexes(IntegerArrayModel._meta.db_table)) + self.assertEqual(self.get_indexes(IntegerArrayModel._meta.db_table)['field']['type'], 'gin') + # Drop the index + with connection.schema_editor() as editor: + editor.remove_index(IntegerArrayModel, index) + self.assertNotIn('field', self.get_indexes(IntegerArrayModel._meta.db_table)) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 9622a443a91..5baba26e009 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -1803,6 +1803,7 @@ class SchemaTests(TransactionTestCase): editor.sql_create_index % { "table": editor.quote_name(table), "name": editor.quote_name(constraint_name), + "using": "", "columns": editor.quote_name(column), "extra": "", }