Commits

Stephan Jaensch committed 5c70eac

Oracle: properly delete CHECK constraints during alter column. Add testcase and comments to the generic backend.

Comments (0)

Files changed (3)

south/db/generic.py

             field.column = name
 
         if not ignore_constraints:
-            # Drop all check constraints. TODO: Add the right ones back.
+            # Drop all check constraints. Note that constraints will be added back
+            # with self.alter_string_set_type and self.alter_string_drop_null.
             if self.has_check_constraints:
                 check_constraints = self._constraints_affecting_columns(table_name, [name], "CHECK")
                 for constraint in check_constraints:

south/db/oracle.py

     alter_string_set_default =  'ALTER TABLE %(table_name)s MODIFY %(column)s DEFAULT %(default)s;'
     add_column_string =         'ALTER TABLE %s ADD %s;'
     delete_column_string =      'ALTER TABLE %s DROP COLUMN %s;'
+    add_constraint_string =     'ALTER TABLE %(table_name)s ADD CONSTRAINT %(constraint)s %(clause)s'
 
     allows_combined_alters = False
     
         qn_col = self.quote_name(name)
 
         # First, change the type
+        # This will actually also add any CHECK constraints needed,
+        # since e.g. 'type' for a BooleanField is 'NUMBER(1) CHECK (%(qn_column)s IN (0,1))'
         params = {
             'table_name':qn,
             'column': qn_col,
 
         sqls.append(self.alter_string_set_default % params)
 
-        #UNIQUE constraint
+        # 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])
 
-        #CHECK constraint is not handled
+        # drop CHECK constraints. Make sure this is executed before the ALTER TABLE statements
+        # generated above, since those statements recreate the constraints we delete here.
+        check_constraints = self._constraints_affecting_columns(table_name, [name], "CHECK")
+        for constraint in check_constraints:
+            self.execute(self.delete_check_sql % {
+                'table': self.quote_name(table_name),
+                'constraint': self.quote_name(constraint),
+            })
 
         for sql in sqls:
             try:

south/tests/db.py

         db.add_column("test_add_unique_fk", "mock2", models.OneToOneField(db.mock_model('Mock', 'mock'), null=True))
         
         db.delete_table("test_add_unique_fk")
-
+        
+    def test_column_constraint(self):
+        """
+        Tests that the value constraint of PositiveIntegerField is enforced on
+        the database level.
+        """
+        db.create_table("test_column_constraint", [
+            ('spam', models.PositiveIntegerField()),
+        ])
+        db.execute_deferred_sql()
+        
+        # Make sure we can't insert negative values
+        db.commit_transaction()
+        db.start_transaction()
+        try:
+            db.execute("INSERT INTO test_column_constraint VALUES (-42)")
+            self.fail("Could insert a negative value into a PositiveIntegerField.")
+        except:
+            pass
+        db.rollback_transaction()
+        
+        # remove constraint
+        db.alter_column("test_column_constraint", "spam", models.IntegerField())
+        # make sure the insertion works now
+        db.execute('INSERT INTO test_column_constraint VALUES (-42)')
+        db.execute('DELETE FROM test_column_constraint')
+        
+        # add it back again
+        db.alter_column("test_column_constraint", "spam", models.PositiveIntegerField())
+        # it should fail again
+        db.start_transaction()
+        try:
+            db.execute("INSERT INTO test_column_constraint VALUES (-42)")
+            self.fail("Could insert a negative value after changing an IntegerField to a PositiveIntegerField.")
+        except:
+            pass
+        db.rollback_transaction()
+        
+        db.delete_table("test_column_constraint")
+        db.start_transaction()
+        
 class TestCacheGeneric(unittest.TestCase):
     base_ops_cls = generic.DatabaseOperations
     def setUp(self):