Commits

Anonymous committed 4620c51

Fix and test for #83 - CHECK constraints not being dropped when needed. Also tidied up DatabaseOperations constants a bit.

Comments (0)

Files changed (3)

south/db/generic.py

     # We assume the generic DB can handle DDL transactions. MySQL wil change this.
     has_ddl_transactions = True
 
+    alter_string_set_type = 'ALTER COLUMN %(column)s TYPE %(type)s'
+    alter_string_set_null = 'ALTER COLUMN %(column)s DROP NOT NULL'
+    alter_string_drop_null = 'ALTER COLUMN %(column)s SET NOT NULL'
+    has_check_constraints = True
+    delete_check_sql = 'ALTER TABLE %(table)s DROP CONSTRAINT %(constraint)s'
+    allows_combined_alters = True
+    add_column_string = 'ALTER TABLE %s ADD COLUMN %s;'
+    delete_unique_sql = "ALTER TABLE %s DROP CONSTRAINT %s"
+    delete_foreign_key_sql = 'ALTER TABLE %s DROP CONSTRAINT %s'
+    supports_foreign_keys = True
+    max_index_name_length = 63
+    drop_index_string = 'DROP INDEX %(index_name)s'
+    delete_column_string = 'ALTER TABLE %s DROP COLUMN %s CASCADE;'
+    create_primary_key_string = "ALTER TABLE %(table)s ADD CONSTRAINT %(constraint)s PRIMARY KEY (%(columns)s)"
+    drop_primary_key_string = "ALTER TABLE %(table)s DROP CONSTRAINT %(constraint)s"
+
     def __init__(self):
         self.debug = False
         self.deferred_sql = []
         self.execute('DELETE FROM %s;' % params)
 
     
-    add_column_string = 'ALTER TABLE %s ADD COLUMN %s;'
 
     def add_column(self, table_name, name, field, keep_default=True):
         """
             if not keep_default and field.default:
                 field.default = NOT_PROVIDED
                 self.alter_column(table_name, name, field, explicit_name=False)
-
     
-    alter_string_set_type = 'ALTER COLUMN %(column)s TYPE %(type)s'
-    alter_string_set_null = 'ALTER COLUMN %(column)s DROP NOT NULL'
-    alter_string_drop_null = 'ALTER COLUMN %(column)s SET NOT NULL'
-    allows_combined_alters = True
 
     def alter_column(self, table_name, name, field, explicit_name=True):
         """
         field.set_attributes_from_name(name)
         if not explicit_name:
             name = field.column
+        
+        # Drop all check constraints. TODO: Add the right ones back.
+        if self.has_check_constraints:
+            check_constraints = self._constraints_affecting_columns(table_name, [name], "CHECK")
+            for constraint in check_constraints:
+                self.execute(self.delete_check_sql % {'table':table_name, 'constraint': constraint})
 
         # First, change the type
         params = {
             sqls.append((self.alter_string_set_null % params, []))
         else:
             sqls.append((self.alter_string_drop_null % params, []))
-
-
+        
         # TODO: Unique
 
         if self.allows_combined_alters:
         Gets the names of the constraints affecting the given columns.
         """
         columns = set(columns)
+        
+        if type == "CHECK":
+            ifsc_table = "constraint_column_usage"
+        else:
+            ifsc_table = "key_column_usage"
+        
         # First, load all constraint->col mappings for this table.
         rows = self.execute("""
             SELECT kc.constraint_name, kc.column_name
-            FROM information_schema.key_column_usage AS kc
+            FROM information_schema.%s AS kc
             JOIN information_schema.table_constraints AS c ON
                 kc.table_schema = c.table_schema AND
                 kc.table_name = c.table_name AND
                 kc.constraint_name = c.constraint_name
             WHERE
-                kc.table_schema = %s AND
-                kc.table_name = %s AND
-                c.constraint_type = %s
-        """, ['public', table_name, type])
+                kc.table_schema = %%s AND
+                kc.table_name = %%s AND
+                c.constraint_type = %%s
+        """ % ifsc_table, ['public', table_name, type])
         # Load into a dict
         mapping = {}
         for constraint, column in rows:
         return name
     
     
-    delete_unique_sql = "ALTER TABLE %s DROP CONSTRAINT %s"
     
     def delete_unique(self, table_name, columns):
         """
             return sql % sqlparams
         else:
             return None
-
-
-    supports_foreign_keys = True
+    
 
     def foreign_key_sql(self, from_table_name, from_column_name, to_table_name, to_column_name):
         """
             qn(to_column_name),
             connection.ops.deferrable_sql() # Django knows this
         )
-    
 
-    delete_foreign_key_sql = 'ALTER TABLE %s DROP CONSTRAINT %s'
 
     def delete_foreign_key(self, table_name, column):
         "Drop a foreign key constraint"
     
     drop_foreign_key = alias('delete_foreign_key')
 
-
-    max_index_name_length = 63
     
     def create_index_name(self, table_name, column_names):
         """
         self.execute(sql)
 
 
-    drop_index_string = 'DROP INDEX %(index_name)s'
-
     def delete_index(self, table_name, column_names, db_tablespace=''):
         """
         Deletes an index created with create_index.
         self.execute(sql)
 
     drop_index = alias('delete_index')
-
     
-    delete_column_string = 'ALTER TABLE %s DROP COLUMN %s CASCADE;'
 
     def delete_column(self, table_name, name):
         """
         raise NotImplementedError("rename_column has no generic SQL syntax")
 
     
-    drop_primary_key_string = "ALTER TABLE %(table)s DROP CONSTRAINT %(constraint)s"
-    
     def drop_primary_key(self, table_name):
         """
         Drops the old primary key.
 
     delete_primary_key = alias('drop_primary_key')
 
-
-    create_primary_key_string = "ALTER TABLE %(table)s ADD CONSTRAINT %(constraint)s PRIMARY KEY (%(columns)s)"
     
     def create_primary_key(self, table_name, columns):
         """

south/db/mysql.py

     drop_primary_key_string = "ALTER TABLE %(table)s DROP PRIMARY KEY"
     allows_combined_alters = False
     has_ddl_transactions = False
+    has_check_constraints = False
     delete_unique_sql = "ALTER TABLE %s DROP INDEX %s"
     
     

south/tests/db.py

         db.rollback_transaction()
         db.delete_table("test4")
     
+    def test_alter_constraints(self):
+        """
+        Tests that going from a PostiveIntegerField to an IntegerField drops
+        the constraint on the database.
+        """
+        db.create_table("test_alterc", [
+            ('num', models.PositiveIntegerField()),
+        ])
+        # Add in some test values
+        db.execute("INSERT INTO test_alterc (num) VALUES (1), (2)")
+        # Ensure that adding a negative number is bad
+        db.start_transaction()
+        try:
+            db.execute("INSERT INTO test_alterc (num) VALUES (-3)")
+        except:
+            db.rollback_transaction()
+        else:
+            self.fail("Could insert a negative integer into a PositiveIntegerField.")
+        # Alter it to a normal IntegerField
+        db.alter_column("test_alterc", "num", models.IntegerField())
+        # It should now work
+        db.execute("INSERT INTO test_alterc (num) VALUES (-3)")
+        db.delete_table("test_alterc")
+    
     def test_unique(self):
         """
         Tests creating/deleting unique constraints.
         """
+        db.create_table("test_unique2", [
+            ('id', models.AutoField(primary_key=True)),
+        ])
         db.create_table("test_unique", [
             ('spam', models.BooleanField(default=False)),
             ('eggs', models.IntegerField()),
+            ('ham', models.ForeignKey(db.mock_model('Unique2', 'test_unique2'))),
         ])
         # Add a constraint
         db.create_unique("test_unique", ["spam"])
         db.create_unique("test_unique", ["spam"])
         db.start_transaction()
         # Test it works
-        db.execute("INSERT INTO test_unique (spam, eggs) VALUES (true, 0), (false, 1)")
+        db.execute("INSERT INTO test_unique2 (id) VALUES (1), (2)")
+        db.execute("INSERT INTO test_unique (spam, eggs, ham_id) VALUES (true, 0, 1), (false, 1, 2)")
         try:
-            db.execute("INSERT INTO test_unique (spam, eggs) VALUES (true, 2)")
+            db.execute("INSERT INTO test_unique (spam, eggs, ham_id) VALUES (true, 2, 1)")
         except:
             db.rollback_transaction()
         else:
         db.create_unique("test_unique", ["eggs"])
         db.start_transaction()
         # Test similarly
-        db.execute("INSERT INTO test_unique (spam, eggs) VALUES (true, 0), (false, 1)")
+        db.execute("INSERT INTO test_unique (spam, eggs, ham_id) VALUES (true, 0, 1), (false, 1, 2)")
         try:
-            db.execute("INSERT INTO test_unique (spam, eggs) VALUES (true, 1)")
+            db.execute("INSERT INTO test_unique (spam, eggs, ham_id) VALUES (true, 1, 1)")
         except:
             db.rollback_transaction()
         else:
         # Drop those, test combined constraints
         db.delete_unique("test_unique", ["eggs"])
         db.execute("DELETE FROM test_unique")
-        db.create_unique("test_unique", ["spam", "eggs"])
+        db.create_unique("test_unique", ["spam", "eggs", "ham_id"])
         db.start_transaction()
         # Test similarly
-        db.execute("INSERT INTO test_unique (spam, eggs) VALUES (true, 0), (false, 1), (true, 1)")
+        db.execute("INSERT INTO test_unique (spam, eggs, ham_id) VALUES (true, 0, 1), (false, 1, 1)")
         try:
-            db.execute("INSERT INTO test_unique (spam, eggs) VALUES (true, 0)")
+            db.execute("INSERT INTO test_unique (spam, eggs, ham_id) VALUES (true, 0, 1)")
         except:
             db.rollback_transaction()
         else:
             self.fail("Could insert non-unique pair.")
-        
+        db.delete_unique("test_unique", ["spam", "eggs", "ham_id"])
         
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.