<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>activerecord/test/migrations/interleaved/pass_1/3_innocent_jointable.rb</filename>
    </added>
    <added>
      <filename>activerecord/test/migrations/interleaved/pass_2/1_people_have_last_names.rb</filename>
    </added>
    <added>
      <filename>activerecord/test/migrations/interleaved/pass_2/3_innocent_jointable.rb</filename>
    </added>
    <added>
      <filename>activerecord/test/migrations/interleaved/pass_3/1_people_have_last_names.rb</filename>
    </added>
    <added>
      <filename>activerecord/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb</filename>
    </added>
    <added>
      <filename>activerecord/test/migrations/interleaved/pass_3/3_innocent_jointable.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,5 +1,7 @@
 *SVN*
 
+* Add support for interleaving migrations by storing which migrations have run in the new schema_migrations table. Closes #11493 [jordi]
+
 * ActiveRecord::Base#sum defaults to 0 if no rows are returned.  Closes #11550 [kamal]
 
 * Ensure that respond_to? considers dynamic finder methods. Closes #11538. [floehopper]</diff>
      <filename>activerecord/CHANGELOG</filename>
    </modified>
    <modified>
      <diff>@@ -232,33 +232,41 @@ module ActiveRecord
 
       # Should not be called normally, but this operation is non-destructive.
       # The migrations module handles this automatically.
-      def initialize_schema_information(current_version=0)
-        begin
-          execute &quot;CREATE TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version #{type_to_sql(:string)})&quot;
-          execute &quot;INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES(#{current_version})&quot;
-        rescue ActiveRecord::StatementInvalid
-          # Schema has been initialized, make sure version is a string
-          version_column = columns(:schema_info).detect { |c| c.name == &quot;version&quot; }
-          
-          # can't just alter the table, since SQLite can't deal
-          unless version_column.type == :string
-            version = ActiveRecord::Migrator.current_version
-            execute &quot;DROP TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)}&quot;
-            initialize_schema_information(version)
+      def initialize_schema_migrations_table
+        sm_table = ActiveRecord::Migrator.schema_migrations_table_name
+
+        unless tables.detect { |t| t == sm_table }
+          create_table(sm_table, :id =&gt; false) do |schema_migrations_table|
+            schema_migrations_table.column :version, :string, :null =&gt; false
           end
-        end
-      end
+          add_index sm_table, :version, :unique =&gt; true,
+            :name =&gt; 'unique_schema_migrations'
+
+          # Backwards-compatibility: if we find schema_info, assume we've
+          # migrated up to that point:
+          si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix
+
+          if tables.detect { |t| t == si_table }
 
-      def dump_schema_information #:nodoc:
-        begin
-          if (current_schema = ActiveRecord::Migrator.current_version) &gt; 0
-            return &quot;INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES (#{current_schema})&quot; 
+            old_version = select_value(&quot;SELECT version FROM #{quote_table_name(si_table)}&quot;).to_i
+            assume_migrated_upto_version(old_version)
+            drop_table(si_table)
           end
-        rescue ActiveRecord::StatementInvalid 
-          # No Schema Info
         end
       end
 
+      def assume_migrated_upto_version(version)
+        sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
+        migrated = select_values(&quot;SELECT version FROM #{sm_table}&quot;).map(&amp;:to_i)
+        versions = Dir['db/migrate/[0-9]*_*.rb'].map do |filename|
+          filename.split('/').last.split('_').first.to_i
+        end
+
+        execute &quot;INSERT INTO #{sm_table} (version) VALUES ('#{version}')&quot; unless migrated.include?(version.to_i)
+        (versions - migrated).select { |v| v &lt; version.to_i }.each do |v|
+          execute &quot;INSERT INTO #{sm_table} (version) VALUES ('#{v}')&quot;
+        end
+      end
 
       def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
         if native = native_database_types[type]</diff>
      <filename>activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb</filename>
    </modified>
    <modified>
      <diff>@@ -123,7 +123,8 @@ module ActiveRecord
   # 
   # To run migrations against the currently configured database, use
   # &lt;tt&gt;rake db:migrate&lt;/tt&gt;. This will update the database by running all of the
-  # pending migrations, creating the &lt;tt&gt;schema_info&lt;/tt&gt; table if missing.
+  # pending migrations, creating the &lt;tt&gt;schema_migrations&lt;/tt&gt; table
+  # (see &quot;About the schema_migrations table&quot; section below) if missing.
   #
   # To roll the database back to a previous migration version, use
   # &lt;tt&gt;rake db:migrate VERSION=X&lt;/tt&gt; where &lt;tt&gt;X&lt;/tt&gt; is the version to which
@@ -216,6 +217,21 @@ module ActiveRecord
   #
   # The phrase &quot;Updating salaries...&quot; would then be printed, along with the
   # benchmark for the block when the block completes.
+  #
+  # == About the schema_migrations table
+  #
+  # Rails versions 2.0 and prior used to create a table called
+  # &lt;tt&gt;schema_info&lt;/tt&gt; when using migrations. This table contained the
+  # version of the schema as of the last applied migration.
+  #
+  # Starting with Rails 2.1, the &lt;tt&gt;schema_info&lt;/tt&gt; table is
+  # (automatically) replaced by the &lt;tt&gt;schema_migrations&lt;/tt&gt; table, which
+  # contains the version numbers of all the migrations applied.
+  #
+  # As a result, it is now possible to add migration files that are numbered
+  # lower than the current schema version: when migrating up, those
+  # never-applied &quot;interleaved&quot; migrations will be automatically applied, and
+  # when migrating down, never-applied &quot;interleaved&quot; migrations will be skipped.
   class Migration
     @@verbose = true
     cattr_accessor :verbose
@@ -315,15 +331,12 @@ module ActiveRecord
     class &lt;&lt; self
       def migrate(migrations_path, target_version = nil)
         case
-          when target_version.nil?, current_version &lt; target_version
-            up(migrations_path, target_version)
-          when current_version &gt; target_version
-            down(migrations_path, target_version)
-          when current_version == target_version
-            return # You're on the right version
+          when target_version.nil?              then up(migrations_path, target_version)
+          when current_version &gt; target_version then down(migrations_path, target_version)
+          else                                       up(migrations_path, target_version)
         end
       end
-      
+
       def rollback(migrations_path, steps=1)
         migrator = self.new(:down, migrations_path)
         start_index = migrator.migrations.index(migrator.current_migration)
@@ -346,12 +359,13 @@ module ActiveRecord
         self.new(direction, migrations_path, target_version).run
       end
 
-      def schema_info_table_name
-        Base.table_name_prefix + &quot;schema_info&quot; + Base.table_name_suffix
+      def schema_migrations_table_name
+        Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
       end
 
       def current_version
-        Base.connection.select_value(&quot;SELECT version FROM #{schema_info_table_name}&quot;).to_i
+        Base.connection.select_values(
+          &quot;SELECT version FROM #{schema_migrations_table_name}&quot;).map(&amp;:to_i).max || 0
       end
 
       def proper_table_name(name)
@@ -362,7 +376,7 @@ module ActiveRecord
 
     def initialize(direction, migrations_path, target_version = nil)
       raise StandardError.new(&quot;This database does not yet support migrations&quot;) unless Base.connection.supports_migrations?
-      Base.connection.initialize_schema_information
+      Base.connection.initialize_schema_migrations_table
       @direction, @migrations_path, @target_version = direction, migrations_path, target_version      
     end
 
@@ -383,25 +397,31 @@ module ActiveRecord
     def migrate
       current = migrations.detect { |m| m.version == current_version }
       target = migrations.detect { |m| m.version == @target_version }
-            
+
       if target.nil? &amp;&amp; !@target_version.nil? &amp;&amp; @target_version &gt; 0
         raise UnknownMigrationVersionError.new(@target_version)
       end
       
-      start = migrations.index(current) || 0
-      finish = migrations.index(target) || migrations.size - 1      
+      start = up? ? 0 : (migrations.index(current) || 0)
+      finish = migrations.index(target) || migrations.size - 1
       runnable = migrations[start..finish]
       
-      # skip the current migration if we're heading upwards
-      runnable.shift if up? &amp;&amp; runnable.first == current
-      
       # skip the last migration if we're headed down, but not ALL the way down
       runnable.pop if down? &amp;&amp; !target.nil?
       
       runnable.each do |migration|
         Base.logger.info &quot;Migrating to #{migration} (#{migration.version})&quot;
-        migration.migrate(@direction)
-        set_schema_version_after_migrating(migration)
+
+        # On our way up, we skip migrating the ones we've already migrated
+        # On our way down, we skip reverting the ones we've never migrated
+        next if up? &amp;&amp; migrated.include?(migration.version.to_i)
+
+        if down? &amp;&amp; !migrated.include?(migration.version.to_i)
+          migration.announce 'never migrated, skipping'; migration.write
+        else
+          migration.migrate(@direction)
+          record_version_state_after_migrating(migration.version)
+        end
       end
     end
 
@@ -412,7 +432,7 @@ module ActiveRecord
         migrations = files.inject([]) do |klasses, file|
           version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
           
-          raise IllegalMigrationNameError.new(f) unless version
+          raise IllegalMigrationNameError.new(file) unless version
           version = version.to_i
           
           if klasses.detect { |m| m.version == version }
@@ -433,19 +453,24 @@ module ActiveRecord
     end
 
     def pending_migrations
-      migrations.select { |m| m.version &gt; current_version }
+      already_migrated = migrated
+      migrations.reject { |m| already_migrated.include?(m.version.to_i) }
+    end
+
+    def migrated
+      sm_table = self.class.schema_migrations_table_name
+      Base.connection.select_values(&quot;SELECT version FROM #{sm_table}&quot;).map(&amp;:to_i).sort
     end
 
     private
-      def set_schema_version_after_migrating(migration)
-        version = migration.version
-            
+      def record_version_state_after_migrating(version)
+        sm_table = self.class.schema_migrations_table_name
+
         if down?
-          after = migrations[migrations.index(migration) + 1]
-          version = after ? after.version : 0
+          Base.connection.update(&quot;DELETE FROM #{sm_table} WHERE version = '#{version}'&quot;)
+        else
+          Base.connection.insert(&quot;INSERT INTO #{sm_table} (version) VALUES ('#{version}')&quot;)
         end
-            
-        Base.connection.update(&quot;UPDATE #{self.class.schema_info_table_name} SET version = #{version}&quot;)
       end
 
       def up?</diff>
      <filename>activerecord/lib/active_record/migration.rb</filename>
    </modified>
    <modified>
      <diff>@@ -34,24 +34,17 @@ module ActiveRecord
     # #add_index, etc.).
     #
     # The +info+ hash is optional, and if given is used to define metadata
-    # about the current schema (like the schema's version):
+    # about the current schema (currently, only the schema's version):
     #
-    #   ActiveRecord::Schema.define(:version =&gt; 15) do
+    #   ActiveRecord::Schema.define(:version =&gt; 20380119000001) do
     #     ...
     #   end
     def self.define(info={}, &amp;block)
       instance_eval(&amp;block)
 
-      unless info.empty?
-        initialize_schema_information
-        cols = columns('schema_info')
-
-        info = info.map do |k,v|
-          v = Base.connection.quote(v, cols.detect { |c| c.name == k.to_s })
-          &quot;#{k} = #{v}&quot;
-        end
-
-        Base.connection.update &quot;UPDATE #{Migrator.schema_info_table_name} SET #{info.join(&quot;, &quot;)}&quot;
+      unless info[:version].blank?
+        initialize_schema_migrations_table
+        assume_migrated_upto_version info[:version]
       end
     end
   end</diff>
      <filename>activerecord/lib/active_record/schema.rb</filename>
    </modified>
    <modified>
      <diff>@@ -30,11 +30,11 @@ module ActiveRecord
       def initialize(connection)
         @connection = connection
         @types = @connection.native_database_types
-        @info = @connection.select_one(&quot;SELECT * FROM schema_info&quot;) rescue nil
+        @version = Migrator::current_version rescue nil
       end
 
       def header(stream)
-        define_params = @info ? &quot;:version =&gt; #{@info['version']}&quot; : &quot;&quot;
+        define_params = @version ? &quot;:version =&gt; #{@version}&quot; : &quot;&quot;
 
         stream.puts &lt;&lt;HEADER
 # This file is auto-generated from the current state of the database. Instead of editing this file, 
@@ -59,7 +59,7 @@ HEADER
 
       def tables(stream)
         @connection.tables.sort.each do |tbl|
-          next if [&quot;schema_info&quot;, ignore_tables].flatten.any? do |ignored|
+          next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
             case ignored
             when String; tbl == ignored
             when Regexp; tbl =~ ignored</diff>
      <filename>activerecord/lib/active_record/schema_dumper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -25,8 +25,8 @@ if ActiveRecord::Base.connection.supports_migrations?
       end
 
       assert_nothing_raised { @connection.select_all &quot;SELECT * FROM fruits&quot; }
-      assert_nothing_raised { @connection.select_all &quot;SELECT * FROM schema_info&quot; }
-      assert_equal 7, @connection.select_one(&quot;SELECT version FROM schema_info&quot;)['version'].to_i
+      assert_nothing_raised { @connection.select_all &quot;SELECT * FROM schema_migrations&quot; }
+      assert_equal 7, ActiveRecord::Migrator::current_version
     end
   end
 </diff>
      <filename>activerecord/test/cases/ar_schema_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -7,6 +7,7 @@ require 'models/topic'
 require MIGRATIONS_ROOT + &quot;/valid/1_people_have_last_names&quot;
 require MIGRATIONS_ROOT + &quot;/valid/2_we_need_reminders&quot;
 require MIGRATIONS_ROOT + &quot;/decimal/1_give_me_big_numbers&quot;
+require MIGRATIONS_ROOT + &quot;/interleaved/pass_3/2_i_raise_on_down&quot;
 
 if ActiveRecord::Base.connection.supports_migrations?
   class BigNumber &lt; ActiveRecord::Base; end
@@ -34,8 +35,8 @@ if ActiveRecord::Base.connection.supports_migrations?
     end
 
     def teardown
-      ActiveRecord::Base.connection.initialize_schema_information
-      ActiveRecord::Base.connection.update &quot;UPDATE #{ActiveRecord::Migrator.schema_info_table_name} SET version = 0&quot;
+      ActiveRecord::Base.connection.initialize_schema_migrations_table
+      ActiveRecord::Base.connection.execute &quot;DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}&quot;
 
       %w(reminders people_reminders prefix_reminders_suffix).each do |table|
         Reminder.connection.drop_table(table) rescue nil
@@ -779,6 +780,39 @@ if ActiveRecord::Base.connection.supports_migrations?
       assert !Reminder.table_exists?
     end
 
+    def test_finds_migrations
+      migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + &quot;/valid&quot;).migrations
+      [['1', 'people_have_last_names'],
+       ['2', 'we_need_reminders'],
+       ['3', 'innocent_jointable']].each_with_index do |pair, i|
+        migrations[i].version == pair.first
+        migrations[1].name    == pair.last
+      end
+    end
+
+    def test_finds_pending_migrations
+      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + &quot;/interleaved/pass_2&quot;, 1)
+      migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + &quot;/interleaved/pass_2&quot;).pending_migrations
+      assert_equal 1, migrations.size
+      migrations[0].version == '3'
+      migrations[0].name    == 'innocent_jointable'
+    end
+
+    def test_migrator_interleaved_migrations
+      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + &quot;/interleaved/pass_1&quot;)
+
+      assert_nothing_raised do
+        ActiveRecord::Migrator.up(MIGRATIONS_ROOT + &quot;/interleaved/pass_2&quot;)
+      end
+
+      Person.reset_column_information
+      assert Person.column_methods_hash.include?(:last_name)
+
+      assert_nothing_raised do
+        ActiveRecord::Migrator.down(MIGRATIONS_ROOT + &quot;/interleaved/pass_3&quot;)
+      end
+    end
+
     def test_migrator_verbosity
       ActiveRecord::Migrator.up(MIGRATIONS_ROOT + &quot;/valid&quot;, 1)
       assert PeopleHaveLastNames.message_count &gt; 0
@@ -817,16 +851,16 @@ if ActiveRecord::Base.connection.supports_migrations?
       assert_equal(3, ActiveRecord::Migrator.current_version)
       
       ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + &quot;/valid&quot;)
-      assert_equal(2, ActiveRecord::Migrator.current_version)            
+      assert_equal(2, ActiveRecord::Migrator.current_version)
       
       ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + &quot;/valid&quot;)
-      assert_equal(1, ActiveRecord::Migrator.current_version)            
+      assert_equal(1, ActiveRecord::Migrator.current_version)
       
       ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + &quot;/valid&quot;)
-      assert_equal(0, ActiveRecord::Migrator.current_version)            
+      assert_equal(0, ActiveRecord::Migrator.current_version)
       
       ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + &quot;/valid&quot;)
-      assert_equal(0, ActiveRecord::Migrator.current_version)            
+      assert_equal(0, ActiveRecord::Migrator.current_version)
     end
     
     def test_migrator_run
@@ -839,15 +873,15 @@ if ActiveRecord::Base.connection.supports_migrations?
       assert_equal(0, ActiveRecord::Migrator.current_version)
     end
 
-    def test_schema_info_table_name
+    def test_schema_migrations_table_name
       ActiveRecord::Base.table_name_prefix = &quot;prefix_&quot;
       ActiveRecord::Base.table_name_suffix = &quot;_suffix&quot;
       Reminder.reset_table_name
-      assert_equal &quot;prefix_schema_info_suffix&quot;, ActiveRecord::Migrator.schema_info_table_name
+      assert_equal &quot;prefix_schema_migrations_suffix&quot;, ActiveRecord::Migrator.schema_migrations_table_name
       ActiveRecord::Base.table_name_prefix = &quot;&quot;
       ActiveRecord::Base.table_name_suffix = &quot;&quot;
       Reminder.reset_table_name
-      assert_equal &quot;schema_info&quot;, ActiveRecord::Migrator.schema_info_table_name
+      assert_equal &quot;schema_migrations&quot;, ActiveRecord::Migrator.schema_migrations_table_name
     ensure
       ActiveRecord::Base.table_name_prefix = &quot;&quot;
       ActiveRecord::Base.table_name_suffix = &quot;&quot;</diff>
      <filename>activerecord/test/cases/migration_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -16,7 +16,7 @@ if ActiveRecord::Base.connection.respond_to?(:tables)
       output = standard_dump
       assert_match %r{create_table &quot;accounts&quot;}, output
       assert_match %r{create_table &quot;authors&quot;}, output
-      assert_no_match %r{create_table &quot;schema_info&quot;}, output
+      assert_no_match %r{create_table &quot;schema_migrations&quot;}, output
     end
 
     def test_schema_dump_excludes_sqlite_sequence
@@ -81,7 +81,7 @@ if ActiveRecord::Base.connection.respond_to?(:tables)
       output = stream.string
       assert_no_match %r{create_table &quot;accounts&quot;}, output
       assert_match %r{create_table &quot;authors&quot;}, output
-      assert_no_match %r{create_table &quot;schema_info&quot;}, output
+      assert_no_match %r{create_table &quot;schema_migrations&quot;}, output
     end
 
     def test_schema_dump_with_regexp_ignored_table
@@ -92,7 +92,7 @@ if ActiveRecord::Base.connection.respond_to?(:tables)
       output = stream.string
       assert_no_match %r{create_table &quot;accounts&quot;}, output
       assert_match %r{create_table &quot;authors&quot;}, output
-      assert_no_match %r{create_table &quot;schema_info&quot;}, output
+      assert_no_match %r{create_table &quot;schema_migrations&quot;}, output
     end
 
     def test_schema_dump_illegal_ignored_table_value</diff>
      <filename>activerecord/test/cases/schema_dumper_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -31,5 +31,5 @@ DROP TABLE legacy_things
 DROP TABLE numeric_data
 DROP TABLE mixed_case_monkeys
 DROP TABLE minimalistics
-DROP TABLE schema_info
+DROP TABLE schema_migrations
 go</diff>
      <filename>activerecord/test/schema/sybase.drop.sql</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>78c2d9fc223e7a9945aee65c838f7ce78e9ddb3e</id>
    </parent>
  </parents>
  <author>
    <name>Rick Olson</name>
    <login>technoweenie</login>
    <email>technoweenie@gmail.com</email>
  </author>
  <url>http://github.com/rails/rails/commit/8a5a9dcbf64843f064b6e8a0b9c6eea8f0b8536e</url>
  <id>8a5a9dcbf64843f064b6e8a0b9c6eea8f0b8536e</id>
  <committed-date>2008-04-09T09:20:15-07:00</committed-date>
  <authored-date>2008-04-09T09:20:15-07:00</authored-date>
  <message>Add support for interleaving migrations by storing which migrations have run in the new schema_migrations table. Closes #11493 [jordi]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@9244 5ecf4fe2-1ee6-0310-87b1-e25e094e27de</message>
  <tree>27064d2d5ebccdd40bf5c4f43fcf5b2e3e7e57f3</tree>
  <committer>
    <name>Rick Olson</name>
    <login>technoweenie</login>
    <email>technoweenie@gmail.com</email>
  </committer>
</commit>
