aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
authorRick Olson <technoweenie@gmail.com>2008-04-09 16:20:15 +0000
committerRick Olson <technoweenie@gmail.com>2008-04-09 16:20:15 +0000
commit8a5a9dcbf64843f064b6e8a0b9c6eea8f0b8536e (patch)
tree27064d2d5ebccdd40bf5c4f43fcf5b2e3e7e57f3 /activerecord/lib
parent78c2d9fc223e7a9945aee65c838f7ce78e9ddb3e (diff)
downloadrails-8a5a9dcbf64843f064b6e8a0b9c6eea8f0b8536e.tar.gz
rails-8a5a9dcbf64843f064b6e8a0b9c6eea8f0b8536e.tar.bz2
rails-8a5a9dcbf64843f064b6e8a0b9c6eea8f0b8536e.zip
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
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb50
-rw-r--r--activerecord/lib/active_record/migration.rb83
-rw-r--r--activerecord/lib/active_record/schema.rb17
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb6
4 files changed, 91 insertions, 65 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 393d5c130e..c986f0c6f1 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -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 "CREATE TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version #{type_to_sql(:string)})"
- execute "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES(#{current_version})"
- rescue ActiveRecord::StatementInvalid
- # Schema has been initialized, make sure version is a string
- version_column = columns(:schema_info).detect { |c| c.name == "version" }
-
- # can't just alter the table, since SQLite can't deal
- unless version_column.type == :string
- version = ActiveRecord::Migrator.current_version
- execute "DROP TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)}"
- 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 => false) do |schema_migrations_table|
+ schema_migrations_table.column :version, :string, :null => false
end
- end
- end
+ add_index sm_table, :version, :unique => true,
+ :name => '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) > 0
- return "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES (#{current_schema})"
+ old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").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("SELECT version FROM #{sm_table}").map(&:to_i)
+ versions = Dir['db/migrate/[0-9]*_*.rb'].map do |filename|
+ filename.split('/').last.split('_').first.to_i
+ end
+
+ execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" unless migrated.include?(version.to_i)
+ (versions - migrated).select { |v| v < version.to_i }.each do |v|
+ execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
+ end
+ end
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
if native = native_database_types[type]
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 4c10cd806c..573c6a4f02 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -123,7 +123,8 @@ module ActiveRecord
#
# To run migrations against the currently configured database, use
# <tt>rake db:migrate</tt>. This will update the database by running all of the
- # pending migrations, creating the <tt>schema_info</tt> table if missing.
+ # pending migrations, creating the <tt>schema_migrations</tt> table
+ # (see "About the schema_migrations table" section below) if missing.
#
# To roll the database back to a previous migration version, use
# <tt>rake db:migrate VERSION=X</tt> where <tt>X</tt> is the version to which
@@ -216,6 +217,21 @@ module ActiveRecord
#
# The phrase "Updating salaries..." 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
+ # <tt>schema_info</tt> when using migrations. This table contained the
+ # version of the schema as of the last applied migration.
+ #
+ # Starting with Rails 2.1, the <tt>schema_info</tt> table is
+ # (automatically) replaced by the <tt>schema_migrations</tt> 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 "interleaved" migrations will be automatically applied, and
+ # when migrating down, never-applied "interleaved" migrations will be skipped.
class Migration
@@verbose = true
cattr_accessor :verbose
@@ -315,15 +331,12 @@ module ActiveRecord
class << self
def migrate(migrations_path, target_version = nil)
case
- when target_version.nil?, current_version < target_version
- up(migrations_path, target_version)
- when current_version > 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 > 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 + "schema_info" + 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("SELECT version FROM #{schema_info_table_name}").to_i
+ Base.connection.select_values(
+ "SELECT version FROM #{schema_migrations_table_name}").map(&: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("This database does not yet support migrations") 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? && !@target_version.nil? && @target_version > 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? && runnable.first == current
-
# skip the last migration if we're headed down, but not ALL the way down
runnable.pop if down? && !target.nil?
runnable.each do |migration|
Base.logger.info "Migrating to #{migration} (#{migration.version})"
- 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? && migrated.include?(migration.version.to_i)
+
+ if down? && !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 > 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("SELECT version FROM #{sm_table}").map(&: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("DELETE FROM #{sm_table} WHERE version = '#{version}'")
+ else
+ Base.connection.insert("INSERT INTO #{sm_table} (version) VALUES ('#{version}')")
end
-
- Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{version}")
end
def up?
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 9d50efb74f..d6b254fcf9 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -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 => 15) do
+ # ActiveRecord::Schema.define(:version => 20380119000001) do
# ...
# end
def self.define(info={}, &block)
instance_eval(&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 })
- "#{k} = #{v}"
- end
-
- Base.connection.update "UPDATE #{Migrator.schema_info_table_name} SET #{info.join(", ")}"
+ unless info[:version].blank?
+ initialize_schema_migrations_table
+ assume_migrated_upto_version info[:version]
end
end
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 286306874e..826662d3ee 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -30,11 +30,11 @@ module ActiveRecord
def initialize(connection)
@connection = connection
@types = @connection.native_database_types
- @info = @connection.select_one("SELECT * FROM schema_info") rescue nil
+ @version = Migrator::current_version rescue nil
end
def header(stream)
- define_params = @info ? ":version => #{@info['version']}" : ""
+ define_params = @version ? ":version => #{@version}" : ""
stream.puts <<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 ["schema_info", 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