Commits

Andrew Godwin committed 232e0b5 Merge

Merge commit

  • Participants
  • Parent commits ac2dfa3, c5ff4ec

Comments (0)

Files changed (3)

south/db/generic.py

                         'table': self.quote_name(table_name),
                         'constraint': self.quote_name(constraint),
                     })
+                    
+            # Drop or add UNIQUE constraint
+            unique_constraint = list(self._constraints_affecting_columns(table_name, [name], "UNIQUE"))
+            if field.unique and not unique_constraint:
+                self.create_unique(table_name, [name])
+            elif not field.unique and unique_constraint:
+                self.delete_unique(table_name, [name])
         
             # Drop all foreign key constraints
             try:

south/db/oracle.py

 
     allows_combined_alters = False
     
-    constraits_dict = {
-        'PRIMARY KEY': 'P',
-        'UNIQUE': 'U',
-        'CHECK': 'C',
-        'REFERENCES': 'R'
+    constraints_dict = {
+        'P': 'PRIMARY KEY',
+        'U': 'UNIQUE',
+        'C': 'CHECK',
+        'R': 'FOREIGN KEY'
     }
 
     def adj_column_sql(self, col):
 
     def check_meta(self, table_name):
         return table_name in [ m._meta.db_table for m in models.get_models() ] #caching provided by Django
+    
+    def normalize_name(self, name):
+        """
+        Get the properly shortened and uppercased identifier as returned by quote_name(), but without the actual quotes.
+        """
+        nn = self.quote_name(name)
+        if nn[0] == '"' and nn[-1] == '"':
+            nn = nn[1:-1]
+        return nn
 
     @generic.invalidate_table_constraints
     def create_table(self, table_name, fields): 
         sqls.append(self.alter_string_set_default % params)
 
         #UNIQUE constraint
-        unique_constraint = list(self._constraints_affecting_columns(qn, [qn_col]))
-
+        unique_constraint = list(self._constraints_affecting_columns(table_name, [name], 'UNIQUE'))
         if field.unique and not unique_constraint:
-            self.create_unique(qn, [qn_col])
+            self.create_unique(table_name, [name])
         elif not field.unique and unique_constraint:
-            self.delete_unique(qn, [qn_col])
+            self.delete_unique(table_name, [name])
 
         #CHECK constraint is not handled
 
     def delete_column(self, table_name, name):
         return super(DatabaseOperations, self).delete_column(self.quote_name(table_name), name)
 
+    def lookup_constraint(self, db_name, table_name, column_name=None):
+        if column_name:
+            # Column names in the constraint cache come from the database,
+            # make sure we use the properly shortened/uppercased version
+            # for lookup.
+            column_name = self.normalize_name(column_name)
+        return super(DatabaseOperations, self).lookup_constraint(db_name, table_name, column_name)
+
+    def _constraints_affecting_columns(self, table_name, columns, type="UNIQUE"):
+        if columns:
+            columns = [self.normalize_name(c) for c in columns]
+        return super(DatabaseOperations, self)._constraints_affecting_columns(table_name, columns, type)
+
     def _field_sanity(self, field):
         """
         This particular override stops us sending DEFAULTs for BooleanField.
             field.default = int(field.to_python(field.get_default()))
         return field
 
-
-
     def _fill_constraint_cache(self, db_name, table_name):
-        qn = self.quote_name
         self._constraint_cache.setdefault(db_name, {}) 
         self._constraint_cache[db_name][table_name] = {} 
 
                  user_constraints.table_name = user_cons_columns.table_name AND 
                  user_constraints.constraint_name = user_cons_columns.constraint_name
             WHERE user_constraints.table_name = '%s'
-        """ % (qn(table_name)))
+        """ % self.normalize_name(table_name))
 
         for constraint, column, kind in rows:
             self._constraint_cache[db_name][table_name].setdefault(column, set())
-            self._constraint_cache[db_name][table_name][column].add((kind, constraint))
+            self._constraint_cache[db_name][table_name][column].add((self.constraints_dict[kind], constraint))
         return

south/tests/db.py

         db.execute("INSERT INTO test_pki (id, new_pkey, eggs) VALUES (1, 2, 3)")
         db.execute("INSERT INTO test_pki (id, new_pkey, eggs) VALUES (1, 3, 4)")
         db.delete_table("test_pki")
-        
     
     def test_add_columns(self):
         """
         db.delete_unique("test_unique", ["spam", "eggs", "ham_id"])
         db.start_transaction()
     
+    def test_alter_unique(self):
+        """
+        Tests that unique constraints are properly created and deleted when
+        altering columns.
+        """
+        db.create_table("test_alter_unique", [
+            ('spam', models.IntegerField()),
+            ('eggs', models.IntegerField(unique=True)),
+        ])
+        db.execute_deferred_sql()
+        
+        # Make sure the unique constraint is created
+        db.execute('INSERT INTO test_alter_unique VALUES (0, 42)')
+        db.commit_transaction()
+        db.start_transaction()
+        try:
+            db.execute("INSERT INTO test_alter_unique VALUES (1, 42)")
+            self.fail("Could insert the same integer twice into a field with unique=True.")
+        except:
+            pass
+        db.rollback_transaction()
+        
+        # remove constraint
+        db.alter_column("test_alter_unique", "eggs", models.IntegerField())
+        # make sure the insertion works now
+        db.execute('INSERT INTO test_alter_unique VALUES (1, 42)')
+        
+        # add it back again
+        db.execute('DELETE FROM test_alter_unique WHERE spam=1')
+        db.alter_column("test_alter_unique", "eggs", models.IntegerField(unique=True))
+        # it should fail again
+        db.start_transaction()
+        try:
+            db.execute("INSERT INTO test_alter_unique VALUES (1, 42)")
+            self.fail("Unique constraint not created during alter_column()")
+        except:
+            pass
+        db.rollback_transaction()
+        
+        # Delete the unique index/constraint
+        if db.backend_name != "sqlite3":
+            db.delete_unique("test_alter_unique", ["eggs"])
+        db.delete_table("test_alter_unique")
+        db.start_transaction()
+
     def test_capitalised_constraints(self):
         """
         Under PostgreSQL at least, capitalised constraints must be quoted.