From 6dd4edb1b4f5441c5f543e29395039839c50d10b Mon Sep 17 00:00:00 2001
From: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Date: Sun, 17 Jun 2018 08:51:02 +0200
Subject: [PATCH] Fixed #29496 -- Fixed crash on Oracle when converting a
 non-unique field to primary key.

Thanks Tim Graham for the review.
---
 django/db/backends/base/schema.py   | 16 +++++++++++-----
 django/db/backends/oracle/schema.py |  6 ++++++
 tests/schema/models.py              |  1 +
 tests/schema/tests.py               | 15 ++++++++++++++-
 4 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py
index cf18e7653e..a722e497c3 100644
--- a/django/db/backends/base/schema.py
+++ b/django/db/backends/base/schema.py
@@ -539,7 +539,7 @@ class BaseDatabaseSchemaEditor:
                 fks_dropped.add((old_field.column,))
                 self.execute(self._delete_constraint_sql(self.sql_delete_fk, model, fk_name))
         # Has unique been removed?
-        if old_field.unique and (not new_field.unique or (not old_field.primary_key and new_field.primary_key)):
+        if old_field.unique and (not new_field.unique or self._field_became_primary_key(old_field, new_field)):
             # Find the unique constraint for this field
             constraint_names = self._constraint_names(model, [old_field.column], unique=True, primary_key=False)
             if strict and len(constraint_names) != 1:
@@ -689,9 +689,7 @@ class BaseDatabaseSchemaEditor:
         if old_field.primary_key and not new_field.primary_key:
             self._delete_primary_key(model, strict)
         # Added a unique?
-        if (not old_field.unique and new_field.unique) or (
-            old_field.primary_key and not new_field.primary_key and new_field.unique
-        ):
+        if self._unique_should_be_added(old_field, new_field):
             self.execute(self._create_unique_sql(model, [new_field.column]))
         # Added an index? Add an index if db_index switched to True or a unique
         # constraint will no longer be used in lieu of an index. The following
@@ -710,7 +708,7 @@ class BaseDatabaseSchemaEditor:
         if old_field.primary_key and new_field.primary_key and old_type != new_type:
             rels_to_update.extend(_related_non_m2m_objects(old_field, new_field))
         # Changed to become primary key?
-        if not old_field.primary_key and new_field.primary_key:
+        if self._field_became_primary_key(old_field, new_field):
             # Make the new one
             self.execute(
                 self.sql_create_pk % {
@@ -966,6 +964,14 @@ class BaseDatabaseSchemaEditor:
     def _field_should_be_indexed(self, model, field):
         return field.db_index and not field.unique
 
+    def _field_became_primary_key(self, old_field, new_field):
+        return not old_field.primary_key and new_field.primary_key
+
+    def _unique_should_be_added(self, old_field, new_field):
+        return (not old_field.unique and new_field.unique) or (
+            old_field.primary_key and not new_field.primary_key and new_field.unique
+        )
+
     def _rename_field_sql(self, table, old_field, new_field, new_type):
         return self.sql_rename_column % {
             "table": self.quote_name(table),
diff --git a/django/db/backends/oracle/schema.py b/django/db/backends/oracle/schema.py
index 39c3b7e83d..105d6a4033 100644
--- a/django/db/backends/oracle/schema.py
+++ b/django/db/backends/oracle/schema.py
@@ -144,6 +144,12 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
             return False
         return create_index
 
+    def _unique_should_be_added(self, old_field, new_field):
+        return (
+            super()._unique_should_be_added(old_field, new_field) and
+            not self._field_became_primary_key(old_field, new_field)
+        )
+
     def _is_identity_column(self, table_name, column_name):
         with self.connection.cursor() as cursor:
             cursor.execute("""
diff --git a/tests/schema/models.py b/tests/schema/models.py
index 4512d8bc01..f6bdbd8815 100644
--- a/tests/schema/models.py
+++ b/tests/schema/models.py
@@ -12,6 +12,7 @@ class Author(models.Model):
     name = models.CharField(max_length=255)
     height = models.PositiveIntegerField(null=True, blank=True)
     weight = models.IntegerField(null=True, blank=True)
+    uuid = models.UUIDField(null=True)
 
     class Meta:
         apps = new_apps
diff --git a/tests/schema/tests.py b/tests/schema/tests.py
index e1bf438ca3..f18b68d04d 100644
--- a/tests/schema/tests.py
+++ b/tests/schema/tests.py
@@ -12,7 +12,7 @@ from django.db.models.deletion import CASCADE, PROTECT
 from django.db.models.fields import (
     AutoField, BigAutoField, BigIntegerField, BinaryField, BooleanField,
     CharField, DateField, DateTimeField, IntegerField, PositiveIntegerField,
-    SlugField, TextField, TimeField,
+    SlugField, TextField, TimeField, UUIDField,
 )
 from django.db.models.fields.related import (
     ForeignKey, ForeignObject, ManyToManyField, OneToOneField,
@@ -603,6 +603,19 @@ class SchemaTests(TransactionTestCase):
         with connection.schema_editor() as editor:
             editor.alter_field(Author, old_field, new_field, strict=True)
 
+    def test_alter_not_unique_field_to_primary_key(self):
+        # Create the table.
+        with connection.schema_editor() as editor:
+            editor.create_model(Author)
+        # Change UUIDField to primary key.
+        old_field = Author._meta.get_field('uuid')
+        new_field = UUIDField(primary_key=True)
+        new_field.set_attributes_from_name('uuid')
+        new_field.model = Author
+        with connection.schema_editor() as editor:
+            editor.remove_field(Author, Author._meta.get_field('id'))
+            editor.alter_field(Author, old_field, new_field, strict=True)
+
     def test_alter_text_field(self):
         # Regression for "BLOB/TEXT column 'info' can't have a default value")
         # on MySQL.