From d7429defe6f8d1dce49976cf49827d18ff6be025 Mon Sep 17 00:00:00 2001
From: Tomasz Rybak <tomasz.rybak@gmail.com>
Date: Sat, 23 Feb 2013 17:07:50 +0100
Subject: [PATCH] Add sqldropindexes to manage

Change patch from https://code.djangoproject.com/ticket/5568
to work on modern Django.
Add special case for MySQL which has different syntax for DROP INDEX.
Add unit tests for the new functionality.
---
 .../management/commands/sqldropindexes.py     | 23 ++++++++++
 django/core/management/sql.py                 |  7 +++
 django/db/backends/creation.py                | 46 +++++++++++++++++++
 django/db/backends/mysql/creation.py          | 26 +++++++++++
 .../regressiontests/bash_completion/tests.py  |  2 +-
 tests/regressiontests/commands_sql/tests.py   |  9 +++-
 6 files changed, 111 insertions(+), 2 deletions(-)
 create mode 100644 django/core/management/commands/sqldropindexes.py

diff --git a/django/core/management/commands/sqldropindexes.py b/django/core/management/commands/sqldropindexes.py
new file mode 100644
index 0000000000..fce77211fb
--- /dev/null
+++ b/django/core/management/commands/sqldropindexes.py
@@ -0,0 +1,23 @@
+from __future__ import unicode_literals
+
+from optparse import make_option
+
+from django.core.management.base import AppCommand
+from django.core.management.sql import sql_destroy_indexes
+from django.db import connections, DEFAULT_DB_ALIAS
+
+class Command(AppCommand):
+    help = "Prints the DROP INDEX SQL statements for the given model module name(s)."
+
+    option_list = AppCommand.option_list + (
+        make_option('--database', action='store', dest='database',
+            default=DEFAULT_DB_ALIAS, help='Nominates a database to print the '
+                'SQL for.  Defaults to the "default" database.'),
+
+    )
+
+    output_transaction = True
+
+    def handle_app(self, app, **options):
+        return '\n'.join(sql_destroy_indexes(app, self.style, connections[options.get('database')]))
+
diff --git a/django/core/management/sql.py b/django/core/management/sql.py
index 66df43e971..ac60ed470c 100644
--- a/django/core/management/sql.py
+++ b/django/core/management/sql.py
@@ -137,6 +137,13 @@ def sql_indexes(app, style, connection):
         output.extend(connection.creation.sql_indexes_for_model(model, style))
     return output
 
+def sql_destroy_indexes(app, style, connection):
+    "Returns a list of the DROP INDEX SQL statements for all models in the given app."
+    output = []
+    for model in models.get_models(app):
+        output.extend(connection.creation.sql_destroy_indexes_for_model(model, style))
+    return output
+
 
 def sql_all(app, style, connection):
     "Returns a list of CREATE TABLE SQL, initial-data inserts, and CREATE INDEX SQL for the given module."
diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py
index 89ff1170dc..77c9e6c9e6 100644
--- a/django/db/backends/creation.py
+++ b/django/db/backends/creation.py
@@ -259,6 +259,52 @@ class BaseDatabaseCreation(object):
         del references_to_delete[model]
         return output
 
+    def sql_destroy_indexes_for_model(self, model, style):
+        """
+        Returns the DROP INDEX SQL statements for a single model.
+        """
+        if not model._meta.managed or model._meta.proxy or model._meta.swapped:
+            return []
+        output = []
+        for f in model._meta.local_fields:
+            output.extend(self.sql_destroy_indexes_for_field(model, f, style))
+        for fs in model._meta.index_together:
+            fields = [model._meta.get_field_by_name(f)[0] for f in fs]
+            output.extend(self.sql_destroy_indexes_for_fields(model, fields, style))
+        return output
+
+    def sql_destroy_indexes_for_field(self, model, f, style):
+        """
+        Return the DROP INDEX SQL statements for a single model field.
+        """
+        if f.db_index and not f.unique:
+            return self.sql_destroy_indexes_for_fields(model, [f], style)
+        else:
+            return []
+
+    def sql_destroy_indexes_for_fields(self, model, fields, style):
+        if len(fields) == 1 and fields[0].db_tablespace:
+            tablespace_sql = self.connection.ops.tablespace_sql(fields[0].db_tablespace)
+        elif model._meta.db_tablespace:
+            tablespace_sql = self.connection.ops.tablespace_sql(model._meta.db_tablespace)
+        else:
+            tablespace_sql = ""
+        if tablespace_sql:
+            tablespace_sql = " " + tablespace_sql
+
+        field_names = []
+        qn = self.connection.ops.quote_name
+        for f in fields:
+            field_names.append(style.SQL_FIELD(qn(f.column)))
+
+        index_name = "%s_%s" % (model._meta.db_table, self._digest([f.name for f in fields]))
+
+        return [
+            style.SQL_KEYWORD("DROP INDEX") + " " +
+            style.SQL_TABLE(qn(truncate_name(index_name, self.connection.ops.max_name_length()))) + " " +
+            ";",
+        ]
+
     def create_test_db(self, verbosity=1, autoclobber=False):
         """
         Creates a test database, prompting the user for confirmation if the
diff --git a/django/db/backends/mysql/creation.py b/django/db/backends/mysql/creation.py
index 8d72d11921..01efe8d35b 100644
--- a/django/db/backends/mysql/creation.py
+++ b/django/db/backends/mysql/creation.py
@@ -41,3 +41,29 @@ class DatabaseCreation(BaseDatabaseCreation):
     def sql_for_inline_foreign_key_references(self, model, field, known_models, style):
         "All inline references are pending under MySQL"
         return [], True
+
+    def sql_destroy_indexes_for_fields(self, model, fields, style):
+        if len(fields) == 1 and fields[0].db_tablespace:
+            tablespace_sql = self.connection.ops.tablespace_sql(fields[0].db_tablespace)
+        elif model._meta.db_tablespace:
+            tablespace_sql = self.connection.ops.tablespace_sql(model._meta.db_tablespace)
+        else:
+            tablespace_sql = ""
+        if tablespace_sql:
+            tablespace_sql = " " + tablespace_sql
+
+        field_names = []
+        qn = self.connection.ops.quote_name
+        for f in fields:
+            field_names.append(style.SQL_FIELD(qn(f.column)))
+
+        index_name = "%s_%s" % (model._meta.db_table, self._digest([f.name for f in fields]))
+
+        from ..util import truncate_name
+
+        return [
+            style.SQL_KEYWORD("DROP INDEX") + " " +
+            style.SQL_TABLE(qn(truncate_name(index_name, self.connection.ops.max_name_length()))) + " " +
+            style.SQL_KEYWORD("ON") + " " +
+            style.SQL_TABLE(qn(model._meta.db_table)) + ";",
+        ]
diff --git a/tests/regressiontests/bash_completion/tests.py b/tests/regressiontests/bash_completion/tests.py
index ed8cedf1ab..4fdb793feb 100644
--- a/tests/regressiontests/bash_completion/tests.py
+++ b/tests/regressiontests/bash_completion/tests.py
@@ -66,7 +66,7 @@ class BashCompletionTests(unittest.TestCase):
         "Subcommands can be autocompleted"
         self._user_input('django-admin.py sql')
         output = self._run_autocomplete()
-        self.assertEqual(output, ['sql sqlall sqlclear sqlcustom sqlflush sqlindexes sqlinitialdata sqlsequencereset'])
+        self.assertEqual(output, ['sql sqlall sqlclear sqlcustom sqldropindexes sqlflush sqlindexes sqlinitialdata sqlsequencereset'])
 
     def test_help(self):
         "No errors, just an empty list if there are no autocomplete options"
diff --git a/tests/regressiontests/commands_sql/tests.py b/tests/regressiontests/commands_sql/tests.py
index 723e5cc3f7..949032ae6f 100644
--- a/tests/regressiontests/commands_sql/tests.py
+++ b/tests/regressiontests/commands_sql/tests.py
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
 
 from django.core.management.color import no_style
 from django.core.management.sql import (sql_create, sql_delete, sql_indexes,
-    sql_all)
+    sql_destroy_indexes, sql_all)
 from django.db import connections, DEFAULT_DB_ALIAS, models
 from django.test import TestCase
 from django.utils import six
@@ -36,6 +36,13 @@ class SQLCommandsTestCase(TestCase):
         self.assertIn(len(output), [1, 2])
         self.assertTrue(output[0].startswith("CREATE INDEX"))
 
+    def test_sql_destroy_indexes(self):
+        app = models.get_app('commands_sql')
+        output = sql_destroy_indexes(app, no_style(), connections[DEFAULT_DB_ALIAS])
+        # PostgreSQL creates two indexes
+        self.assertIn(len(output), [1, 2])
+        self.assertTrue(output[0].startswith("DROP INDEX"))
+
     def test_sql_all(self):
         app = models.get_app('commands_sql')
         output = sql_all(app, no_style(), connections[DEFAULT_DB_ALIAS])