Browse Source

Fixed #20888 -- Added support for column order in class-based indexes.

Akshesh 8 years ago
parent
commit
311a8e8d50
4 changed files with 60 additions and 6 deletions
  1. 17 6
      django/db/models/indexes.py
  2. 14 0
      docs/ref/models/indexes.txt
  3. 5 0
      tests/model_indexes/tests.py
  4. 24 0
      tests/schema/tests.py

+ 17 - 6
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],

+ 14 - 0
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
+    <https://www.sqlite.org/lang_createindex.html>`_ for specifics.
+
 ``name``
 --------
 

+ 5 - 0
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)

+ 24 - 0
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