Changeset 9244
- Timestamp:
- 04/09/08 16:20:15 (8 months ago)
- Files:
-
- trunk/activerecord/CHANGELOG (modified) (1 diff)
- trunk/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb (modified) (1 diff)
- trunk/activerecord/lib/active_record/migration.rb (modified) (9 diffs)
- trunk/activerecord/lib/active_record/schema_dumper.rb (modified) (2 diffs)
- trunk/activerecord/lib/active_record/schema.rb (modified) (2 diffs)
- trunk/activerecord/test/cases/ar_schema_test.rb (modified) (1 diff)
- trunk/activerecord/test/cases/migration_test.rb (modified) (5 diffs)
- trunk/activerecord/test/cases/schema_dumper_test.rb (modified) (3 diffs)
- trunk/activerecord/test/migrations/interleaved (added)
- trunk/activerecord/test/migrations/interleaved/pass_1 (added)
- trunk/activerecord/test/migrations/interleaved/pass_1/3_innocent_jointable.rb (added)
- trunk/activerecord/test/migrations/interleaved/pass_2 (added)
- trunk/activerecord/test/migrations/interleaved/pass_2/1_people_have_last_names.rb (added)
- trunk/activerecord/test/migrations/interleaved/pass_2/3_innocent_jointable.rb (added)
- trunk/activerecord/test/migrations/interleaved/pass_3 (added)
- trunk/activerecord/test/migrations/interleaved/pass_3/1_people_have_last_names.rb (added)
- trunk/activerecord/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb (added)
- trunk/activerecord/test/migrations/interleaved/pass_3/3_innocent_jointable.rb (added)
- trunk/activerecord/test/schema/sybase.drop.sql (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/activerecord/CHANGELOG
r9243 r9244 1 1 *SVN* 2 3 * Add support for interleaving migrations by storing which migrations have run in the new schema_migrations table. Closes #11493 [jordi] 2 4 3 5 * ActiveRecord::Base#sum defaults to 0 if no rows are returned. Closes #11550 [kamal] trunk/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
r9226 r9244 233 233 # Should not be called normally, but this operation is non-destructive. 234 234 # The migrations module handles this automatically. 235 def initialize_schema_information(current_version=0) 236 begin 237 execute "CREATE TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version #{type_to_sql(:string)})" 238 execute "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES(#{current_version})" 239 rescue ActiveRecord::StatementInvalid 240 # Schema has been initialized, make sure version is a string 241 version_column = columns(:schema_info).detect { |c| c.name == "version" } 242 243 # can't just alter the table, since SQLite can't deal 244 unless version_column.type == :string 245 version = ActiveRecord::Migrator.current_version 246 execute "DROP TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)}" 247 initialize_schema_information(version) 235 def initialize_schema_migrations_table 236 sm_table = ActiveRecord::Migrator.schema_migrations_table_name 237 238 unless tables.detect { |t| t == sm_table } 239 create_table(sm_table, :id => false) do |schema_migrations_table| 240 schema_migrations_table.column :version, :string, :null => false 248 241 end 249 end 250 end 251 252 def dump_schema_information #:nodoc: 253 begin 254 if (current_schema = ActiveRecord::Migrator.current_version) > 0 255 return "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES (#{current_schema})" 242 add_index sm_table, :version, :unique => true, 243 :name => 'unique_schema_migrations' 244 245 # Backwards-compatibility: if we find schema_info, assume we've 246 # migrated up to that point: 247 si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix 248 249 if tables.detect { |t| t == si_table } 250 251 old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i 252 assume_migrated_upto_version(old_version) 253 drop_table(si_table) 256 254 end 257 rescue ActiveRecord::StatementInvalid 258 # No Schema Info 259 end 260 end 261 255 end 256 end 257 258 def assume_migrated_upto_version(version) 259 sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name) 260 migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i) 261 versions = Dir['db/migrate/[0-9]*_*.rb'].map do |filename| 262 filename.split('/').last.split('_').first.to_i 263 end 264 265 execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" unless migrated.include?(version.to_i) 266 (versions - migrated).select { |v| v < version.to_i }.each do |v| 267 execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" 268 end 269 end 262 270 263 271 def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc: trunk/activerecord/lib/active_record/migration.rb
r9126 r9244 124 124 # To run migrations against the currently configured database, use 125 125 # <tt>rake db:migrate</tt>. This will update the database by running all of the 126 # pending migrations, creating the <tt>schema_info</tt> table if missing. 126 # pending migrations, creating the <tt>schema_migrations</tt> table 127 # (see "About the schema_migrations table" section below) if missing. 127 128 # 128 129 # To roll the database back to a previous migration version, use … … 217 218 # The phrase "Updating salaries..." would then be printed, along with the 218 219 # benchmark for the block when the block completes. 220 # 221 # == About the schema_migrations table 222 # 223 # Rails versions 2.0 and prior used to create a table called 224 # <tt>schema_info</tt> when using migrations. This table contained the 225 # version of the schema as of the last applied migration. 226 # 227 # Starting with Rails 2.1, the <tt>schema_info</tt> table is 228 # (automatically) replaced by the <tt>schema_migrations</tt> table, which 229 # contains the version numbers of all the migrations applied. 230 # 231 # As a result, it is now possible to add migration files that are numbered 232 # lower than the current schema version: when migrating up, those 233 # never-applied "interleaved" migrations will be automatically applied, and 234 # when migrating down, never-applied "interleaved" migrations will be skipped. 219 235 class Migration 220 236 @@verbose = true … … 316 332 def migrate(migrations_path, target_version = nil) 317 333 case 318 when target_version.nil?, current_version < target_version 319 up(migrations_path, target_version) 320 when current_version > target_version 321 down(migrations_path, target_version) 322 when current_version == target_version 323 return # You're on the right version 324 end 325 end 326 334 when target_version.nil? then up(migrations_path, target_version) 335 when current_version > target_version then down(migrations_path, target_version) 336 else up(migrations_path, target_version) 337 end 338 end 339 327 340 def rollback(migrations_path, steps=1) 328 341 migrator = self.new(:down, migrations_path) … … 347 360 end 348 361 349 def schema_ info_table_name350 Base.table_name_prefix + "schema_info"+ Base.table_name_suffix362 def schema_migrations_table_name 363 Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix 351 364 end 352 365 353 366 def current_version 354 Base.connection.select_value("SELECT version FROM #{schema_info_table_name}").to_i 367 Base.connection.select_values( 368 "SELECT version FROM #{schema_migrations_table_name}").map(&:to_i).max || 0 355 369 end 356 370 … … 363 377 def initialize(direction, migrations_path, target_version = nil) 364 378 raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations? 365 Base.connection.initialize_schema_ information379 Base.connection.initialize_schema_migrations_table 366 380 @direction, @migrations_path, @target_version = direction, migrations_path, target_version 367 381 end … … 384 398 current = migrations.detect { |m| m.version == current_version } 385 399 target = migrations.detect { |m| m.version == @target_version } 386 400 387 401 if target.nil? && !@target_version.nil? && @target_version > 0 388 402 raise UnknownMigrationVersionError.new(@target_version) 389 403 end 390 404 391 start = migrations.index(current) || 0392 finish = migrations.index(target) || migrations.size - 1 405 start = up? ? 0 : (migrations.index(current) || 0) 406 finish = migrations.index(target) || migrations.size - 1 393 407 runnable = migrations[start..finish] 394 395 # skip the current migration if we're heading upwards396 runnable.shift if up? && runnable.first == current397 408 398 409 # skip the last migration if we're headed down, but not ALL the way down … … 401 412 runnable.each do |migration| 402 413 Base.logger.info "Migrating to #{migration} (#{migration.version})" 403 migration.migrate(@direction) 404 set_schema_version_after_migrating(migration) 414 415 # On our way up, we skip migrating the ones we've already migrated 416 # On our way down, we skip reverting the ones we've never migrated 417 next if up? && migrated.include?(migration.version.to_i) 418 419 if down? && !migrated.include?(migration.version.to_i) 420 migration.announce 'never migrated, skipping'; migration.write 421 else 422 migration.migrate(@direction) 423 record_version_state_after_migrating(migration.version) 424 end 405 425 end 406 426 end … … 413 433 version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first 414 434 415 raise IllegalMigrationNameError.new(f ) unless version435 raise IllegalMigrationNameError.new(file) unless version 416 436 version = version.to_i 417 437 … … 434 454 435 455 def pending_migrations 436 migrations.select { |m| m.version > current_version } 456 already_migrated = migrated 457 migrations.reject { |m| already_migrated.include?(m.version.to_i) } 458 end 459 460 def migrated 461 sm_table = self.class.schema_migrations_table_name 462 Base.connection.select_values("SELECT version FROM #{sm_table}").map(&:to_i).sort 437 463 end 438 464 439 465 private 440 def set_schema_version_after_migrating(migration)441 version = migration.version442 466 def record_version_state_after_migrating(version) 467 sm_table = self.class.schema_migrations_table_name 468 443 469 if down? 444 after = migrations[migrations.index(migration) + 1] 445 version = after ? after.version : 0 446 end 447 448 Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{version}") 470 Base.connection.update("DELETE FROM #{sm_table} WHERE version = '#{version}'") 471 else 472 Base.connection.insert("INSERT INTO #{sm_table} (version) VALUES ('#{version}')") 473 end 449 474 end 450 475 trunk/activerecord/lib/active_record/schema_dumper.rb
r8124 r9244 31 31 @connection = connection 32 32 @types = @connection.native_database_types 33 @ info = @connection.select_one("SELECT * FROM schema_info")rescue nil33 @version = Migrator::current_version rescue nil 34 34 end 35 35 36 36 def header(stream) 37 define_params = @ info ? ":version => #{@info['version']}" : ""37 define_params = @version ? ":version => #{@version}" : "" 38 38 39 39 stream.puts <<HEADER … … 60 60 def tables(stream) 61 61 @connection.tables.sort.each do |tbl| 62 next if [ "schema_info", ignore_tables].flatten.any? do |ignored|62 next if ['schema_migrations', ignore_tables].flatten.any? do |ignored| 63 63 case ignored 64 64 when String; tbl == ignored trunk/activerecord/lib/active_record/schema.rb
r8089 r9244 35 35 # 36 36 # The +info+ hash is optional, and if given is used to define metadata 37 # about the current schema ( likethe schema's version):37 # about the current schema (currently, only the schema's version): 38 38 # 39 # ActiveRecord::Schema.define(:version => 15) do39 # ActiveRecord::Schema.define(:version => 20380119000001) do 40 40 # ... 41 41 # end … … 43 43 instance_eval(&block) 44 44 45 unless info.empty? 46 initialize_schema_information 47 cols = columns('schema_info') 48 49 info = info.map do |k,v| 50 v = Base.connection.quote(v, cols.detect { |c| c.name == k.to_s }) 51 "#{k} = #{v}" 52 end 53 54 Base.connection.update "UPDATE #{Migrator.schema_info_table_name} SET #{info.join(", ")}" 45 unless info[:version].blank? 46 initialize_schema_migrations_table 47 assume_migrated_upto_version info[:version] 55 48 end 56 49 end trunk/activerecord/test/cases/ar_schema_test.rb
r8681 r9244 26 26 27 27 assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } 28 assert_nothing_raised { @connection.select_all "SELECT * FROM schema_ info" }29 assert_equal 7, @connection.select_one("SELECT version FROM schema_info")['version'].to_i28 assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" } 29 assert_equal 7, ActiveRecord::Migrator::current_version 30 30 end 31 31 end trunk/activerecord/test/cases/migration_test.rb
r9175 r9244 8 8 require MIGRATIONS_ROOT + "/valid/2_we_need_reminders" 9 9 require MIGRATIONS_ROOT + "/decimal/1_give_me_big_numbers" 10 require MIGRATIONS_ROOT + "/interleaved/pass_3/2_i_raise_on_down" 10 11 11 12 if ActiveRecord::Base.connection.supports_migrations? … … 35 36 36 37 def teardown 37 ActiveRecord::Base.connection.initialize_schema_ information38 ActiveRecord::Base.connection. update "UPDATE #{ActiveRecord::Migrator.schema_info_table_name} SET version = 0"38 ActiveRecord::Base.connection.initialize_schema_migrations_table 39 ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}" 39 40 40 41 %w(reminders people_reminders prefix_reminders_suffix).each do |table| … … 780 781 end 781 782 783 def test_finds_migrations 784 migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid").migrations 785 [['1', 'people_have_last_names'], 786 ['2', 'we_need_reminders'], 787 ['3', 'innocent_jointable']].each_with_index do |pair, i| 788 migrations[i].version == pair.first 789 migrations[1].name == pair.last 790 end 791 end 792 793 def test_finds_pending_migrations 794 ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2", 1) 795 migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/interleaved/pass_2").pending_migrations 796 assert_equal 1, migrations.size 797 migrations[0].version == '3' 798 migrations[0].name == 'innocent_jointable' 799 end 800 801 def test_migrator_interleaved_migrations 802 ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_1") 803 804 assert_nothing_raised do 805 ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2") 806 end 807 808 Person.reset_column_information 809 assert Person.column_methods_hash.include?(:last_name) 810 811 assert_nothing_raised do 812 ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/interleaved/pass_3") 813 end 814 end 815 782 816 def test_migrator_verbosity 783 817 ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) … … 818 852 819 853 ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") 820 assert_equal(2, ActiveRecord::Migrator.current_version) 854 assert_equal(2, ActiveRecord::Migrator.current_version) 821 855 822 856 ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") 823 assert_equal(1, ActiveRecord::Migrator.current_version) 857 assert_equal(1, ActiveRecord::Migrator.current_version) 824 858 825 859 ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") 826 assert_equal(0, ActiveRecord::Migrator.current_version) 860 assert_equal(0, ActiveRecord::Migrator.current_version) 827 861 828 862 ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") 829 assert_equal(0, ActiveRecord::Migrator.current_version) 863 assert_equal(0, ActiveRecord::Migrator.current_version) 830 864 end 831 865 … … 840 874 end 841 875 842 def test_schema_ info_table_name876 def test_schema_migrations_table_name 843 877 ActiveRecord::Base.table_name_prefix = "prefix_" 844 878 ActiveRecord::Base.table_name_suffix = "_suffix" 845 879 Reminder.reset_table_name 846 assert_equal "prefix_schema_ info_suffix", ActiveRecord::Migrator.schema_info_table_name880 assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name 847 881 ActiveRecord::Base.table_name_prefix = "" 848 882 ActiveRecord::Base.table_name_suffix = "" 849 883 Reminder.reset_table_name 850 assert_equal "schema_ info", ActiveRecord::Migrator.schema_info_table_name884 assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name 851 885 ensure 852 886 ActiveRecord::Base.table_name_prefix = "" trunk/activerecord/test/cases/schema_dumper_test.rb
r8969 r9244 17 17 assert_match %r{create_table "accounts"}, output 18 18 assert_match %r{create_table "authors"}, output 19 assert_no_match %r{create_table "schema_ info"}, output19 assert_no_match %r{create_table "schema_migrations"}, output 20 20 end 21 21 … … 82 82 assert_no_match %r{create_table "accounts"}, output 83 83 assert_match %r{create_table "authors"}, output 84 assert_no_match %r{create_table "schema_ info"}, output84 assert_no_match %r{create_table "schema_migrations"}, output 85 85 end 86 86 … … 93 93 assert_no_match %r{create_table "accounts"}, output 94 94 assert_match %r{create_table "authors"}, output 95 assert_no_match %r{create_table "schema_ info"}, output95 assert_no_match %r{create_table "schema_migrations"}, output 96 96 end 97 97 trunk/activerecord/test/schema/sybase.drop.sql
r8659 r9244 32 32 DROP TABLE mixed_case_monkeys 33 33 DROP TABLE minimalistics 34 DROP TABLE schema_ info34 DROP TABLE schema_migrations 35 35 go