public
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
Fixed mysql change_column_default to not make the column always nullable.

Also added change_column_null to both mysql and sqlite to keep the api features 
closer to postgresql.

[#617 state:resolved]
Tarmo Tänav (author)
Sun Jul 13 23:42:20 -0700 2008
jeremy (committer)
Mon Jul 14 12:42:01 -0700 2008
commit  07578ac85585d3c64d4d38d4892fd31582c7c473
tree    b1a3bfc9964f22609ade326af1303af3b8a46ded
parent  8f72bc92e20b1242272714f253e23b256761ec1a
...
1
2
 
 
3
4
5
...
1
2
3
4
5
6
7
0
@@ -1,5 +1,7 @@
0
 *Edge*
0
 
0
+* change_column_default preserves the not-null constraint.  #617 [Tarmo Tänav]
0
+
0
 * Fixed that create database statements would always include "DEFAULT NULL" (Nick Sieger) [#334]
0
 
0
 * Add :accessible option to associations for allowing (opt-in) mass assignment. #474. [David Dollar] Example :
...
437
438
439
440
 
 
 
 
 
 
 
 
 
 
441
442
 
443
444
445
 
 
446
447
448
449
450
451
 
 
 
 
 
452
453
454
...
460
461
462
 
463
464
465
...
536
537
538
 
 
 
 
 
 
 
539
540
541
...
437
438
439
 
440
441
442
443
444
445
446
447
448
449
450
 
451
452
453
454
455
456
457
 
 
 
 
 
458
459
460
461
462
463
464
465
...
471
472
473
474
475
476
477
...
548
549
550
551
552
553
554
555
556
557
558
559
560
0
@@ -437,18 +437,29 @@ module ActiveRecord
0
       end
0
 
0
       def change_column_default(table_name, column_name, default) #:nodoc:
0
-        current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
0
+        column = column_for(table_name, column_name)
0
+        change_column table_name, column_name, column.sql_type, :default => default
0
+      end
0
+
0
+      def change_column_null(table_name, column_name, null, default = nil)
0
+        column = column_for(table_name, column_name)
0
+
0
+        unless null || default.nil?
0
+          execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
0
+        end
0
 
0
-        execute("ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{current_type} DEFAULT #{quote(default)}")
0
+        change_column table_name, column_name, column.sql_type, :null => null
0
       end
0
 
0
       def change_column(table_name, column_name, type, options = {}) #:nodoc:
0
+        column = column_for(table_name, column_name)
0
+
0
         unless options_include_default?(options)
0
-          if column = columns(table_name).find { |c| c.name == column_name.to_s }
0
-            options[:default] = column.default
0
-          else
0
-            raise "No such column: #{table_name}.#{column_name}"
0
-          end
0
+          options[:default] = column.default
0
+        end
0
+
0
+        unless options.has_key?(:null)
0
+          options[:null] = column.null
0
         end
0
 
0
         change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
0
@@ -460,6 +471,7 @@ module ActiveRecord
0
         options = {}
0
         if column = columns(table_name).find { |c| c.name == column_name.to_s }
0
           options[:default] = column.default
0
+          options[:null] = column.null
0
         else
0
           raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
0
         end
0
@@ -536,6 +548,13 @@ module ActiveRecord
0
         def version
0
           @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
0
         end
0
+
0
+        def column_for(table_name, column_name)
0
+          unless column = columns(table_name).find { |c| c.name == column_name.to_s }
0
+            raise "No such column: #{table_name}.#{column_name}"
0
+          end
0
+          column
0
+        end
0
     end
0
   end
0
 end
...
238
239
240
 
 
 
 
 
 
 
 
 
241
242
243
...
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
0
@@ -238,6 +238,15 @@ module ActiveRecord
0
         end
0
       end
0
 
0
+      def change_column_null(table_name, column_name, null, default = nil)
0
+        unless null || default.nil?
0
+          execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
0
+        end
0
+        alter_table(table_name) do |definition|
0
+          definition[column_name].null = null
0
+        end
0
+      end
0
+
0
       def change_column(table_name, column_name, type, options = {}) #:nodoc:
0
         alter_table(table_name) do |definition|
0
           include_default = options_include_default?(options)
...
703
704
705
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
706
707
708
...
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
0
@@ -703,6 +703,55 @@ if ActiveRecord::Base.connection.supports_migrations?
0
       Person.connection.drop_table :testings rescue nil
0
     end
0
 
0
+    def test_keeping_default_and_notnull_constaint_on_change
0
+      Person.connection.create_table :testings do |t|
0
+        t.column :title, :string
0
+      end
0
+      person_klass = Class.new(Person)
0
+      person_klass.set_table_name 'testings'
0
+
0
+      person_klass.connection.add_column "testings", "wealth", :integer, :null => false, :default => 99
0
+      person_klass.reset_column_information
0
+      assert_equal 99, person_klass.columns_hash["wealth"].default
0
+      assert_equal false, person_klass.columns_hash["wealth"].null
0
+      assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")}
0
+
0
+      # change column default to see that column doesn't lose its not null definition
0
+      person_klass.connection.change_column_default "testings", "wealth", 100
0
+      person_klass.reset_column_information
0
+      assert_equal 100, person_klass.columns_hash["wealth"].default
0
+      assert_equal false, person_klass.columns_hash["wealth"].null
0
+
0
+      # rename column to see that column doesn't lose its not null and/or default definition
0
+      person_klass.connection.rename_column "testings", "wealth", "money"
0
+      person_klass.reset_column_information
0
+      assert_nil person_klass.columns_hash["wealth"]
0
+      assert_equal 100, person_klass.columns_hash["money"].default
0
+      assert_equal false, person_klass.columns_hash["money"].null
0
+
0
+      # change column
0
+      person_klass.connection.change_column "testings", "money", :integer, :null => false, :default => 1000
0
+      person_klass.reset_column_information
0
+      assert_equal 1000, person_klass.columns_hash["money"].default
0
+      assert_equal false, person_klass.columns_hash["money"].null
0
+
0
+      # change column, make it nullable and clear default
0
+      person_klass.connection.change_column "testings", "money", :integer, :null => true, :default => nil
0
+      person_klass.reset_column_information
0
+      assert_nil person_klass.columns_hash["money"].default
0
+      assert_equal true, person_klass.columns_hash["money"].null
0
+
0
+      # change_column_null, make it not nullable and set null values to a default value
0
+      person_klass.connection.execute('UPDATE testings SET money = NULL')
0
+      person_klass.connection.change_column_null "testings", "money", false, 2000
0
+      person_klass.reset_column_information
0
+      assert_nil person_klass.columns_hash["money"].default
0
+      assert_equal false, person_klass.columns_hash["money"].null
0
+      assert_equal [2000], Person.connection.select_values("SELECT money FROM testings").map { |s| s.to_i }.sort
0
+    ensure
0
+      Person.connection.drop_table :testings rescue nil
0
+    end
0
+
0
     def test_change_column_default_to_null
0
       Person.connection.change_column_default "people", "first_name", nil
0
       Person.reset_column_information

Comments