diff --git a/django/db/models/indexes.py b/django/db/models/indexes.py index e3f94f0bba..bb33f8a758 100644 --- a/django/db/models/indexes.py +++ b/django/db/models/indexes.py @@ -17,6 +17,11 @@ class Index(object): if not fields: raise ValueError('At least one field is required to define an index.') self.fields = fields + # A list of 2-tuple with the field name and ordering ('' or 'DESC'). + self.fields_orders = [ + (field_name[1:], 'DESC') if field_name.startswith('-') else (field_name, '') + for field_name in self.fields + ] self.name = name or '' if self.name: errors = self.check_name() @@ -38,15 +43,17 @@ class Index(object): return errors def create_sql(self, model, schema_editor): - fields = [model._meta.get_field(field) for field in self.fields] + 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) - columns = [field.column for field in fields] - quote_name = schema_editor.quote_name + columns = [ + ('%s %s' % (quote_name(field.column), order)).strip() + for field, (field_name, order) in zip(fields, self.fields_orders) + ] return schema_editor.sql_create_index % { 'table': quote_name(model._meta.db_table), 'name': quote_name(self.name), - 'columns': ', '.join(quote_name(column) for column in columns), + 'columns': ', '.join(columns), 'extra': tablespace_sql, } @@ -82,8 +89,12 @@ class Index(object): fit its size by truncating the excess length. """ table_name = model._meta.db_table - column_names = [model._meta.get_field(field).column for field in self.fields] - hash_data = [table_name] + column_names + [self.suffix] + column_names = [model._meta.get_field(field_name).column for field_name, order in self.fields_orders] + column_names_with_order = [ + (('-%s' if order else '%s') % column_name) + for column_name, (field_name, order) in zip(column_names, self.fields_orders) + ] + hash_data = [table_name] + column_names_with_order + [self.suffix] self.name = '%s_%s_%s' % ( table_name[:11], column_names[0][:7], diff --git a/docs/ref/models/indexes.txt b/docs/ref/models/indexes.txt index b63e86517c..ac2839bbd4 100644 --- a/docs/ref/models/indexes.txt +++ b/docs/ref/models/indexes.txt @@ -34,6 +34,20 @@ options`_. A list of the name of the fields on which the index is desired. +By default, indexes are created with an ascending order for each column. To +define an index with a descending order for a column, add a hyphen before the +field's name. + +For example ``Index(fields=['headline', '-pub_date'])`` would create SQL with +``(headline, pub_date DESC)``. Index ordering isn't supported on MySQL. In that +case, a descending index is created as a normal index. + +.. admonition:: Support for column ordering on SQLite + + Column ordering is supported on SQLite 3.3.0+ and only for some database + file formats. Refer to the `SQLite docs + `_ for specifics. + ``name`` -------- diff --git a/tests/model_indexes/tests.py b/tests/model_indexes/tests.py index b3f5d04dc1..52db494905 100644 --- a/tests/model_indexes/tests.py +++ b/tests/model_indexes/tests.py @@ -46,6 +46,11 @@ class IndexesTests(TestCase): index.set_name_with_model(Book) self.assertEqual(index.name, 'model_index_author_0f5565_idx') + # '-' for DESC columns should be accounted for in the index name. + index = models.Index(fields=['-author']) + index.set_name_with_model(Book) + self.assertEqual(index.name, 'model_index_author_708765_idx') + # fields may be truncated in the name. db_column is used for naming. long_field_index = models.Index(fields=['pages']) long_field_index.set_name_with_model(Book) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 6bb4aa6f8f..9622a443a9 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -156,6 +156,12 @@ class SchemaTests(TransactionTestCase): counts['indexes'] += 1 return counts + def assertIndexOrder(self, table, index, order): + constraints = self.get_constraints(table) + self.assertIn(index, constraints) + index_orders = constraints[index]['orders'] + self.assertTrue(all([(val == expected) for val, expected in zip(index_orders, order)])) + # Tests def test_creation_deletion(self): """ @@ -1597,6 +1603,24 @@ class SchemaTests(TransactionTestCase): editor.remove_index(Author, index) self.assertNotIn('name', self.get_indexes(Author._meta.db_table)) + def test_order_index(self): + """ + Indexes defined with ordering (ASC/DESC) defined on column + """ + with connection.schema_editor() as editor: + editor.create_model(Author) + # The table doesn't have an index + self.assertNotIn('title', self.get_indexes(Author._meta.db_table)) + index_name = 'author_name_idx' + # Add the index + index = Index(fields=['name', '-weight'], name=index_name) + with connection.schema_editor() as editor: + editor.add_index(Author, index) + if connection.features.supports_index_column_ordering: + if connection.features.uppercases_column_names: + index_name = index_name.upper() + self.assertIndexOrder(Author._meta.db_table, index_name, ['ASC', 'DESC']) + def test_indexes(self): """ Tests creation/altering of indexes