Permalink
Browse files

Added migration support to SQL Server adapter (please someone do the …

…same for Oracle and DB2) (closes #2625) [Tom Ward]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2778 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
1 parent cb0e27c commit 816f37a2adde4449a17712cb0891a15f2c2ea7c0 @dhh dhh committed Oct 28, 2005
View
@@ -1,5 +1,7 @@
*SVN*
+* Added migration support to SQL Server adapter (please someone do the same for Oracle and DB2) #2625 [Tom Ward]
+
* Use AR::Base.silence rather than AR::Base.logger.silence in fixtures to preserve Log4r compatibility. #2618 [dansketcher@gmail.com]
* Constraints are cloned so they can't be inadvertently modified while they're
@@ -707,6 +707,22 @@ def class_name(table_name = table_name) # :nodoc:
class_name
end
+ # Indicates whether the table associated with this class exists
+ def table_exists?
+ if connection.respond_to?(:tables)
+ connection.tables.include? table_name
+ else
+ # if the connection adapter hasn't implemented tables, there are two crude tests that can be
+ # used - see if getting column info raises an error, or if the number of columns returned is zero
+ begin
+ reset_column_information
+ columns.size > 0
+ rescue ActiveRecord::StatementInvalid
+ false
+ end
+ end
+ end
+
# Returns an array of column objects for the table associated with this class.
def columns
unless @columns
@@ -184,23 +184,25 @@ def add_index(table_name, column_name, options = {})
# remove_index :accounts, :column => :branch_id
# Remove the index named by_branch_party in the accounts table.
# remove_index :accounts, :name => :by_branch_party
+
def remove_index(table_name, options = {})
+ execute "DROP INDEX #{index_name(table_name, options)} ON #{table_name}"
+ end
+
+ def index_name(table_name, options) #:nodoc:
if Hash === options # legacy support
if options[:column]
- index_name = "#{table_name}_#{options[:column]}_index"
+ "#{table_name}_#{options[:column]}_index"
elsif options[:name]
- index_name = options[:name]
+ options[:name]
else
raise ArgumentError, "You must specify the index name"
end
else
- index_name = "#{table_name}_#{options}_index"
+ "#{table_name}_#{options}_index"
end
-
- execute "DROP INDEX #{index_name} ON #{table_name}"
end
-
# Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
# entire structure of the database.
def structure_dump
@@ -13,6 +13,10 @@
#
# Current maintainer: Ryan Tomayko <rtomayko@gmail.com>
#
+# Modifications (Migrations): Tom Ward <tom@popdog.net>
+# Date: 27/10/2005
+#
+
module ActiveRecord
class Base
def self.sqlserver_connection(config) #:nodoc:
@@ -73,6 +77,7 @@ def type_cast(value)
when :datetime then cast_to_datetime(value)
when :timestamp then cast_to_time(value)
when :time then cast_to_time(value)
+ when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
else value
end
end
@@ -183,6 +188,10 @@ def native_database_types
def adapter_name
'SQLServer'
end
+
+ def supports_migrations? #:nodoc:
+ true
+ end
def select_all(sql, name = nil)
select(sql, name)
@@ -332,6 +341,63 @@ def drop_database(name)
def create_database(name)
execute "CREATE DATABASE #{name}"
end
+
+ def tables(name = nil)
+ tables = []
+ execute("SELECT table_name from information_schema.tables WHERE table_type = 'BASE TABLE'", name).each {|field| tables << field[0]}
+ tables
+ end
+
+ def indexes(table_name, name = nil)
+ indexes = []
+ execute("EXEC sp_helpindex #{table_name}", name).each do |index|
+ unique = index[1] =~ /unique/
+ primary = index[1] =~ /primary key/
+ if !primary
+ indexes << IndexDefinition.new(table_name, index[0], unique, index[2].split(", "))
+ end
+ end
+ indexes
+ end
+
+ def rename_table(name, new_name)
+ execute "EXEC sp_rename '#{name}', '#{new_name}'"
+ end
+
+ def remove_column(table_name, column_name)
+ execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
+ end
+
+ def rename_column(table, column, new_column_name)
+ execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
+ end
+
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
+ sql_commands = ["ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit])}"]
+ if options[:default]
+ remove_default_constraint(table_name, column_name)
+ sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{options[:default]} FOR #{column_name}"
+ end
+ sql_commands.each {|c|
+ execute(c)
+ }
+ end
+
+ def remove_column(table_name, column_name)
+ remove_default_constraint(table_name, column_name)
+ execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
+ end
+
+ def remove_default_constraint(table_name, column_name)
+ defaults = select "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
+ defaults.each {|constraint|
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
+ }
+ end
+
+ def remove_index(table_name, options = {})
+ execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
+ end
private
def select(sql, name = nil)
@@ -16,6 +16,7 @@ class CreditCard < ActiveRecord::Base; end
class MasterCreditCard < ActiveRecord::Base; end
class Post < ActiveRecord::Base; end
class Computer < ActiveRecord::Base; end
+class NonExistentTable < ActiveRecord::Base; end
class LoosePerson < ActiveRecord::Base
attr_protected :credit_rating, :administrator
@@ -42,6 +43,11 @@ class Task < ActiveRecord::Base
class BasicsTest < Test::Unit::TestCase
fixtures :topics, :companies, :developers, :projects, :computers
+ def test_table_exists
+ assert !NonExistentTable.table_exists?
+ assert Topic.table_exists?
+ end
+
def test_set_attributes
topic = Topic.find(1)
topic.attributes = { "title" => "Budget", "author_name" => "Jason" }
@@ -4,7 +4,6 @@
require File.dirname(__FILE__) + '/fixtures/migrations/2_we_need_reminders'
if ActiveRecord::Base.connection.supports_migrations?
-
class Reminder < ActiveRecord::Base; end
class MigrationTest < Test::Unit::TestCase
@@ -19,6 +18,7 @@ def teardown
Reminder.connection.drop_table("reminders") rescue nil
Reminder.connection.drop_table("people_reminders") rescue nil
+ Reminder.connection.drop_table("prefix_reminders_suffix") rescue nil
Reminder.reset_column_information
Person.connection.remove_column("people", "last_name") rescue nil
@@ -92,11 +92,29 @@ def test_create_table_with_defaults
Person.connection.drop_table :testings rescue nil
end
- def test_add_column_not_null
+ # SQL Server will not allow you to add a NOT NULL column
+ # to a table without specifying a default value, so the
+ # following test must be skipped
+ unless current_adapter?(:SQLServerAdapter)
+ def test_add_column_not_null_without_default
+ Person.connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+ Person.connection.add_column :testings, :bar, :string, :null => false
+
+ assert_raises(ActiveRecord::StatementInvalid) do
+ Person.connection.execute "insert into testings (foo, bar) values ('hello', NULL)"
+ end
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+ end
+
+ def test_add_column_not_null_with_default
Person.connection.create_table :testings do |t|
t.column :foo, :string
end
- Person.connection.add_column :testings, :bar, :string, :null => false
+ Person.connection.add_column :testings, :bar, :string, :null => false, :default => "default"
assert_raises(ActiveRecord::StatementInvalid) do
Person.connection.execute "insert into testings (foo, bar) values ('hello', NULL)"
@@ -128,7 +146,14 @@ def test_native_types
assert_equal String, bob.bio.class
assert_equal Fixnum, bob.age.class
assert_equal Time, bob.birthday.class
- assert_equal Date, bob.favorite_day.class
+
+ if current_adapter?(:SQLServerAdapter)
+ # SQL Server doesn't differentiate between date/time
+ assert_equal Time, bob.favorite_day.class
+ else
+ assert_equal Date, bob.favorite_day.class
+ end
+
assert_equal TrueClass, bob.male?.class
end
@@ -162,11 +187,11 @@ def test_add_remove_single_field_using_symbol_arguments
def test_add_rename
Person.delete_all
-
- Person.connection.add_column "people", "girlfriend", :string
- Person.create :girlfriend => 'bobette'
-
+
begin
+ Person.connection.add_column "people", "girlfriend", :string
+ Person.create :girlfriend => 'bobette'
+
Person.connection.rename_column "people", "girlfriend", "exgirlfriend"
Person.reset_column_information
@@ -237,8 +262,8 @@ def test_change_column_with_new_default
end
def test_add_table
- assert_raises(ActiveRecord::StatementInvalid) { Reminder.column_methods_hash }
-
+ assert !Reminder.table_exists?
+
WeNeedReminders.up
assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
@@ -250,7 +275,7 @@ def test_add_table
def test_migrator
assert !Person.column_methods_hash.include?(:last_name)
- assert_raises(ActiveRecord::StatementInvalid) { Reminder.column_methods_hash }
+ assert !Reminder.table_exists?
ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/')
@@ -270,14 +295,13 @@ def test_migrator
def test_migrator_one_up
assert !Person.column_methods_hash.include?(:last_name)
- assert_raises(ActiveRecord::StatementInvalid) { Reminder.column_methods_hash }
-
+ assert !Reminder.table_exists?
+
ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1)
Person.reset_column_information
assert Person.column_methods_hash.include?(:last_name)
- assert_raises(ActiveRecord::StatementInvalid) { Reminder.column_methods_hash }
-
+ assert !Reminder.table_exists?
ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 2)
@@ -292,23 +316,23 @@ def test_migrator_one_down
Person.reset_column_information
assert Person.column_methods_hash.include?(:last_name)
- assert_raises(ActiveRecord::StatementInvalid) { Reminder.column_methods_hash }
+ assert !Reminder.table_exists?
end
def test_migrator_one_up_one_down
ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1)
ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/', 0)
assert !Person.column_methods_hash.include?(:last_name)
- assert_raises(ActiveRecord::StatementInvalid) { Reminder.column_methods_hash }
+ assert !Reminder.table_exists?
end
def test_migrator_going_down_due_to_version_target
ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1)
ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations/', 0)
assert !Person.column_methods_hash.include?(:last_name)
- assert_raises(ActiveRecord::StatementInvalid) { Reminder.column_methods_hash }
+ assert !Reminder.table_exists?
ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations/')
@@ -359,13 +383,11 @@ def test_proper_table_name
end
def test_add_drop_table_with_prefix_and_suffix
- assert_raises(ActiveRecord::StatementInvalid) { Reminder.column_methods_hash }
-
+ assert !Reminder.table_exists?
ActiveRecord::Base.table_name_prefix = 'prefix_'
ActiveRecord::Base.table_name_suffix = '_suffix'
Reminder.reset_table_name
WeNeedReminders.up
-
assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
assert_equal "hello world", Reminder.find(:first).content
@@ -375,7 +397,5 @@ def test_add_drop_table_with_prefix_and_suffix
ActiveRecord::Base.table_name_suffix = ''
Reminder.reset_table_name
end
-
- end
-
-end
+ end
+end

0 comments on commit 816f37a

Please sign in to comment.