From b9a670b22799a44fe7d3467d1d21949f9f717593 Mon Sep 17 00:00:00 2001
From: Markus Holtermann <info@markusholtermann.eu>
Date: Tue, 23 Sep 2014 23:17:00 +0200
Subject: [PATCH] Fixed #23426 -- Don't require double percent sign in RunSQL
 without parameters

---
 django/db/migrations/operations/special.py |  4 ++--
 docs/ref/migration-operations.txt          |  5 +++++
 docs/releases/1.7.1.txt                    |  3 +++
 tests/migrations/test_operations.py        | 18 +++++++++++++++---
 4 files changed, 25 insertions(+), 5 deletions(-)

diff --git a/django/db/migrations/operations/special.py b/django/db/migrations/operations/special.py
index fe5f00a012..bfe418034c 100644
--- a/django/db/migrations/operations/special.py
+++ b/django/db/migrations/operations/special.py
@@ -66,14 +66,14 @@ class RunSQL(Operation):
     def database_forwards(self, app_label, schema_editor, from_state, to_state):
         statements = schema_editor.connection.ops.prepare_sql_script(self.sql)
         for statement in statements:
-            schema_editor.execute(statement)
+            schema_editor.execute(statement, params=None)
 
     def database_backwards(self, app_label, schema_editor, from_state, to_state):
         if self.reverse_sql is None:
             raise NotImplementedError("You cannot reverse this operation")
         statements = schema_editor.connection.ops.prepare_sql_script(self.reverse_sql)
         for statement in statements:
-            schema_editor.execute(statement)
+            schema_editor.execute(statement, params=None)
 
     def describe(self):
         return "Raw SQL operation"
diff --git a/docs/ref/migration-operations.txt b/docs/ref/migration-operations.txt
index a2da17f1bf..c55ad0afb8 100644
--- a/docs/ref/migration-operations.txt
+++ b/docs/ref/migration-operations.txt
@@ -192,6 +192,11 @@ operation here so that the autodetector still has an up-to-date state of the
 model (otherwise, when you next run ``makemigrations``, it won't see any
 operation that adds that field and so will try to run it again).
 
+.. versionchanged:: 1.7.1
+
+    If you want to include literal percent signs in the query you don't need to
+    double them anymore.
+
 .. _sqlparse: https://pypi.python.org/pypi/sqlparse
 
 RunPython
diff --git a/docs/releases/1.7.1.txt b/docs/releases/1.7.1.txt
index 8d8af5c7f2..2467da13bd 100644
--- a/docs/releases/1.7.1.txt
+++ b/docs/releases/1.7.1.txt
@@ -67,3 +67,6 @@ Bugfixes
   :meth:`~django.contrib.admin.InlineModelAdmin.get_min_num()` hooks to
   :class:`~django.contrib.contenttypes.admin.GenericInlineModelAdmin`
   (:ticket:`23539`).
+
+* Made ``migrations.RunSQL`` no longer require percent sign escaping. This is
+  now consistent with ``cursor.execute()`` (:ticket:`23426`).
diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py
index bddb09f9e7..208f7e24ba 100644
--- a/tests/migrations/test_operations.py
+++ b/tests/migrations/test_operations.py
@@ -1156,10 +1156,18 @@ class OperationTests(OperationTestBase):
         # Create the operation
         operation = migrations.RunSQL(
             # Use a multi-line string with a comment to test splitting on SQLite and MySQL respectively
-            "CREATE TABLE i_love_ponies (id int, special_thing int);\n"
-            "INSERT INTO i_love_ponies (id, special_thing) VALUES (1, 42); -- this is magic!\n"
-            "INSERT INTO i_love_ponies (id, special_thing) VALUES (2, 51);\n",
+            "CREATE TABLE i_love_ponies (id int, special_thing varchar(15));\n"
+            "INSERT INTO i_love_ponies (id, special_thing) VALUES (1, 'i love ponies'); -- this is magic!\n"
+            "INSERT INTO i_love_ponies (id, special_thing) VALUES (2, 'i love django');\n"
+            "UPDATE i_love_ponies SET special_thing = 'Ponies' WHERE special_thing LIKE '%%ponies';"
+            "UPDATE i_love_ponies SET special_thing = 'Django' WHERE special_thing LIKE '%django';",
+
+            # Run delete queries to test for parameter substitution failure
+            # reported in #23426
+            "DELETE FROM i_love_ponies WHERE special_thing LIKE '%Django%';"
+            "DELETE FROM i_love_ponies WHERE special_thing LIKE '%%Ponies%%';"
             "DROP TABLE i_love_ponies",
+
             state_operations=[migrations.CreateModel("SomethingElse", [("id", models.AutoField(primary_key=True))])],
         )
         self.assertEqual(operation.describe(), "Raw SQL operation")
@@ -1177,6 +1185,10 @@ class OperationTests(OperationTestBase):
         with connection.cursor() as cursor:
             cursor.execute("SELECT COUNT(*) FROM i_love_ponies")
             self.assertEqual(cursor.fetchall()[0][0], 2)
+            cursor.execute("SELECT COUNT(*) FROM i_love_ponies WHERE special_thing = 'Django'")
+            self.assertEqual(cursor.fetchall()[0][0], 1)
+            cursor.execute("SELECT COUNT(*) FROM i_love_ponies WHERE special_thing = 'Ponies'")
+            self.assertEqual(cursor.fetchall()[0][0], 1)
         # And test reversal
         self.assertTrue(operation.reversible)
         with connection.schema_editor() as editor: