diff options
author | David Heinemeier Hansson <david@loudthinking.com> | 2006-03-18 03:02:32 +0000 |
---|---|---|
committer | David Heinemeier Hansson <david@loudthinking.com> | 2006-03-18 03:02:32 +0000 |
commit | ea617708c2ad9a770a1f3950e7a0028e9764d989 (patch) | |
tree | d8f83feaed81362dc01d3e9563be388bf962c1c8 /activerecord | |
parent | 1340eb1a7ea19a9356cc041d084abb8d553ee529 (diff) | |
download | rails-ea617708c2ad9a770a1f3950e7a0028e9764d989.tar.gz rails-ea617708c2ad9a770a1f3950e7a0028e9764d989.tar.bz2 rails-ea617708c2ad9a770a1f3950e7a0028e9764d989.zip |
Added migrations support to the Sybase adapter (closes #4293) [John R. Sheets]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@3905 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'activerecord')
-rw-r--r-- | activerecord/CHANGELOG | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/sybase_adapter.rb | 112 | ||||
-rw-r--r-- | activerecord/lib/active_record/migration.rb | 2 | ||||
-rwxr-xr-x | activerecord/test/abstract_unit.rb | 5 | ||||
-rwxr-xr-x | activerecord/test/base_test.rb | 14 | ||||
-rw-r--r-- | activerecord/test/binary_test.rb | 2 | ||||
-rw-r--r-- | activerecord/test/fixtures/db_definitions/sybase.sql | 42 | ||||
-rw-r--r-- | activerecord/test/migration_test.rb | 15 |
8 files changed, 142 insertions, 52 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index beb8d8084b..2fba682e4c 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -177,7 +177,7 @@ * Fixed that calling HasOneProxy#build_model repeatedly would cause saving to happen #4058 [anna@wota.jp] -* Added Sybase database adapter that relies on the Sybase Open Client bindings (see http://raa.ruby-lang.org/project/sybase-ctlib) #3765 [John Sheets]. It's almost completely Active Record compliant, but has the following caveats: +* Added Sybase database adapter that relies on the Sybase Open Client bindings (see http://raa.ruby-lang.org/project/sybase-ctlib) #3765 [John Sheets]. It's almost completely Active Record compliant (including migrations), but has the following caveats: * Does not support DATE SQL column types; use DATETIME instead. * Date columns on HABTM join tables are returned as String, not Time. diff --git a/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb b/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb index d87045a7aa..67d1c368eb 100644 --- a/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb @@ -3,6 +3,9 @@ # Date: 01 Mar 2006 # # Based on code from Will Sobel (http://dev.rubyonrails.org/ticket/2030) +# +# 17 Mar 2006: Added support for migrations; fixed issues with :boolean columns. +# require 'active_record/connection_adapters/abstract_adapter' @@ -49,20 +52,34 @@ module ActiveRecord # * <tt>:password</tt> -- Defaults to empty string. # # Usage Notes: - # * Does not support DATE SQL column types; use DATETIME instead. - # * Date columns on HABTM join tables are returned as String, not Time. - # * Insertions are potentially broken for :polymorphic join tables - # * BLOB column access not yet fully supported - # + # + # * The sybase-ctlib bindings do not support the DATE SQL column type; use DATETIME instead. + # * Table and column names are limited to 30 chars in Sybase 12.5 + # * :binary columns not yet supported + # * :boolean columns use the BIT SQL type, which does not allow nulls or + # indexes. If a DEFAULT is not specified for ALTER TABLE commands, the + # column will be declared with DEFAULT 0 (false). + # + # Migrations: + # + # The Sybase adapter supports migrations, but for ALTER TABLE commands to + # work, the database must have the database option 'select into' set to + # 'true' with sp_dboption (see below). The sp_helpdb command lists the current + # options for all databases. + # + # 1> use mydb + # 2> go + # 1> master..sp_dboption mydb, "select into", true + # 2> go + # 1> checkpoint + # 2> go class SybaseAdapter < AbstractAdapter # :nodoc: class ColumnWithIdentity < Column attr_reader :identity, :primary def initialize(name, default, sql_type = nil, nullable = nil, identity = nil, primary = nil) super(name, default, sql_type, nullable) - @default = type_cast(default) - @identity = identity - @primary = primary + @default, @identity, @primary = type_cast(default), identity, primary end def simplified_type(field_type) @@ -102,7 +119,7 @@ module ActiveRecord def native_database_types { :primary_key => "numeric(9,0) IDENTITY PRIMARY KEY", - :string => { :name => "varchar", :limit => 255 }, + :string => { :name => "varchar", :limit => 255 }, :text => { :name => "text" }, :integer => { :name => "int" }, :float => { :name => "float", :limit => 8 }, @@ -111,7 +128,7 @@ module ActiveRecord :time => { :name => "time" }, :date => { :name => "datetime" }, :binary => { :name => "image"}, - :boolean => { :name => "tinyint", :limit => 1 } + :boolean => { :name => "bit" } } end @@ -129,7 +146,8 @@ module ActiveRecord def reconnect! raise "Sybase Connection Adapter does not yet support reconnect!" - #@connection.close rescue nil + # disconnect! + # connect! # Not yet implemented end # Check for a limit statement and parse out the limit and @@ -230,11 +248,6 @@ module ActiveRecord indexes end - def remove_index(table_name, options = {}) - # Override for different Sybase SQL syntax. - execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}" - end - def quoted_true "1" end @@ -253,7 +266,7 @@ module ActiveRecord else "'#{quote_string(value)}'" end - when NilClass then "NULL" + when NilClass then (column && column.type == :boolean) ? '0' : "NULL" when TrueClass then '1' when FalseClass then '0' when Float, Fixnum, Bignum then value.to_s @@ -318,7 +331,70 @@ module ActiveRecord end end + def supports_migrations? #:nodoc: + true + end + + def rename_table(name, new_name) + execute "EXEC sp_rename '#{name}', '#{new_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} MODIFY #{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_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 + + def add_column_options!(sql, options) #:nodoc: + sql << " DEFAULT #{quote(options[:default], options[:column])}" unless options[:default].nil? + + if check_null_for_column?(options[:column], sql) + sql << (options[:null] == false ? " NOT NULL" : " NULL") + end + sql + end + private + def check_null_for_column?(col, sql) + # Sybase columns are NOT NULL by default, so explicitly set NULL + # if :null option is omitted. Disallow NULLs for boolean. + type = col.nil? ? "" : col[:type] + + # Ignore :null if a primary key + return false if type =~ /PRIMARY KEY/i + + # Ignore :null if a :boolean or BIT column + if (sql =~ /\s+bit(\s+DEFAULT)?/i) || type == :boolean + # If no default clause found on a boolean column, add one. + sql << " DEFAULT 0" if $1.nil? + return false + end + true + end + # Return the last value of the identity global value. def last_insert_id @connection.sql("SELECT @@IDENTITY") @@ -364,7 +440,7 @@ module ActiveRecord log(sql, name) do if normal_select? # If limit is not explicitly set, return all results. - @logger.debug "Setting row count to (#{@limit})" if @logger + @logger.debug "Setting row count to (#{@limit || 'off'})" if @logger # Run a normal select @connection.set_rowcount(@limit || 0) diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 9b7954dcdf..47706e4a4f 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -101,7 +101,7 @@ module ActiveRecord # == Database support # # Migrations are currently supported in MySQL, PostgreSQL, SQLite, - # SQL Server, and Oracle (all supported databases except DB2). + # SQL Server, Sybase, and Oracle (all supported databases except DB2). # # == More examples # diff --git a/activerecord/test/abstract_unit.rb b/activerecord/test/abstract_unit.rb index 6c089f3696..29e4601fee 100755 --- a/activerecord/test/abstract_unit.rb +++ b/activerecord/test/abstract_unit.rb @@ -22,9 +22,10 @@ class Test::Unit::TestCase #:nodoc: def assert_date_from_db(expected, actual, message = nil) # SQL Server doesn't have a separate column type just for dates, # so the time is in the string and incorrectly formatted - - if current_adapter?(:SQLServerAdapter) || current_adapter?(:SybaseAdapter) + if current_adapter?(:SQLServerAdapter) assert_equal expected.strftime("%Y/%m/%d 00:00:00"), actual.strftime("%Y/%m/%d 00:00:00") + elsif current_adapter?(:SybaseAdapter) + assert_equal expected.to_s, actual.to_date.to_s, message else assert_equal expected.to_s, actual.to_s, message end diff --git a/activerecord/test/base_test.rb b/activerecord/test/base_test.rb index 40782729d9..58dd133aeb 100755 --- a/activerecord/test/base_test.rb +++ b/activerecord/test/base_test.rb @@ -569,7 +569,13 @@ class BasicsTest < Test::Unit::TestCase topic = Topic.find(topic.id) assert_nil topic.last_read - assert_nil topic.approved + + # Sybase adapter does not allow nulls in boolean columns + if current_adapter?(:SybaseAdapter) + assert topic.approved == false + else + assert_nil topic.approved + end end def test_equality @@ -1177,7 +1183,11 @@ class BasicsTest < Test::Unit::TestCase assert xml.include?(%(<content>Have a nice day</content>)) assert xml.include?(%(<author-email-address>david@loudthinking.com</author-email-address>)) assert xml.include?(%(<parent-id></parent-id>)) - assert xml.include?(%(<last-read type="date">2004-04-15</last-read>)) + if current_adapter?(:SybaseAdapter) + assert xml.include?(%(<last-read type="datetime">2004-04-15T00:00:00-05:00</last-read>)) + else + assert xml.include?(%(<last-read type="date">2004-04-15</last-read>)) + end # Oracle doesn't have true boolean or time-only fields unless current_adapter?(:OracleAdapter) assert xml.include?(%(<approved type="boolean">false</approved>)), "Approved should be a boolean" diff --git a/activerecord/test/binary_test.rb b/activerecord/test/binary_test.rb index e7a87a4833..38a5d50935 100644 --- a/activerecord/test/binary_test.rb +++ b/activerecord/test/binary_test.rb @@ -20,7 +20,7 @@ class BinaryTest < Test::Unit::TestCase # Without using prepared statements, it makes no sense to test # BLOB data with DB2 or Firebird, because the length of a statement # is limited to 32KB. - unless %w(SQLServer DB2 Oracle Firebird).include? ActiveRecord::Base.connection.adapter_name + unless %w(SQLServer Sybase DB2 Oracle Firebird).include? ActiveRecord::Base.connection.adapter_name def test_load_save bin = Binary.new bin.data = @data diff --git a/activerecord/test/fixtures/db_definitions/sybase.sql b/activerecord/test/fixtures/db_definitions/sybase.sql index 28164d8a14..07f670867e 100644 --- a/activerecord/test/fixtures/db_definitions/sybase.sql +++ b/activerecord/test/fixtures/db_definitions/sybase.sql @@ -29,8 +29,8 @@ CREATE TABLE topics ( bonus_time time NULL, last_read datetime NULL, content varchar(255) NULL, - approved tinyint default 1 NULL, - replies_count int default 0 NULL, + approved bit default 1, + replies_count int default 0, parent_id int NULL, type varchar(50) NULL ) @@ -46,7 +46,7 @@ CREATE TABLE developers ( CREATE TABLE projects ( id numeric(9,0) IDENTITY PRIMARY KEY, name varchar(100) NULL, - type VARCHAR(255) NULL + type varchar(255) NULL ) CREATE TABLE developers_projects ( @@ -85,18 +85,18 @@ CREATE TABLE subscribers ( CREATE TABLE booleantests ( id numeric(9,0) IDENTITY PRIMARY KEY, - value integer NULL + value int NULL ) CREATE TABLE auto_id_tests ( auto_id numeric(9,0) IDENTITY PRIMARY KEY, - value integer NULL + value int NULL ) CREATE TABLE entrants ( id numeric(9,0) IDENTITY PRIMARY KEY, - name VARCHAR(255) NOT NULL, - course_id INTEGER NOT NULL + name varchar(255) NOT NULL, + course_id int NOT NULL ) CREATE TABLE colnametests ( @@ -118,8 +118,8 @@ CREATE TABLE mixins ( CREATE TABLE people ( id numeric(9,0) IDENTITY PRIMARY KEY, - first_name VARCHAR(40) NOT NULL, - lock_version INTEGER DEFAULT 0 + first_name varchar(40) NOT NULL, + lock_version int DEFAULT 0 ) CREATE TABLE readers ( @@ -135,28 +135,28 @@ CREATE TABLE binaries ( CREATE TABLE computers ( id numeric(9,0) IDENTITY PRIMARY KEY, - developer INTEGER NOT NULL, - extendedWarranty INTEGER NOT NULL + developer int NOT NULL, + extendedWarranty int NOT NULL ) CREATE TABLE posts ( id numeric(9,0) IDENTITY PRIMARY KEY, - author_id INTEGER NULL, - title VARCHAR(255) NOT NULL, - body VARCHAR(2048) NOT NULL, - type VARCHAR(255) NOT NULL + author_id int NULL, + title varchar(255) NOT NULL, + body varchar(2048) NOT NULL, + type varchar(255) NOT NULL ) CREATE TABLE comments ( id numeric(9,0) IDENTITY PRIMARY KEY, - post_id INTEGER NOT NULL, - body VARCHAR(2048) NOT NULL, - type VARCHAR(255) NOT NULL + post_id int NOT NULL, + body varchar(2048) NOT NULL, + type varchar(255) NOT NULL ) CREATE TABLE authors ( id numeric(9,0) IDENTITY PRIMARY KEY, - name VARCHAR(255) NOT NULL + name varchar(255) NOT NULL ) CREATE TABLE tasks ( @@ -167,8 +167,8 @@ CREATE TABLE tasks ( CREATE TABLE categories ( id numeric(9,0) IDENTITY PRIMARY KEY, - name VARCHAR(255) NOT NULL, - type VARCHAR(255) NOT NULL + name varchar(255) NOT NULL, + type varchar(255) NOT NULL ) CREATE TABLE categories_posts ( diff --git a/activerecord/test/migration_test.rb b/activerecord/test/migration_test.rb index f4d6a2b7ef..55eed450c7 100644 --- a/activerecord/test/migration_test.rb +++ b/activerecord/test/migration_test.rb @@ -54,8 +54,11 @@ if ActiveRecord::Base.connection.supports_migrations? assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) } assert_nothing_raised { Person.connection.remove_index("people", "last_name") } - assert_nothing_raised { Person.connection.add_index("people", %w(last_name first_name administrator), :name => "named_admin") } - assert_nothing_raised { Person.connection.remove_index("people", :name => "named_admin") } + # Sybase adapter does not support indexes on :boolean columns + unless current_adapter?(:SybaseAdapter) + assert_nothing_raised { Person.connection.add_index("people", %w(last_name first_name administrator), :name => "named_admin") } + assert_nothing_raised { Person.connection.remove_index("people", :name => "named_admin") } + end end def test_create_table_adds_id @@ -110,10 +113,10 @@ if ActiveRecord::Base.connection.supports_migrations? Person.connection.drop_table :testings rescue nil end - # SQL Server will not allow you to add a NOT NULL column + # SQL Server and Sybase 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) + unless current_adapter?(:SQLServerAdapter) || current_adapter?(:SybaseAdapter) def test_add_column_not_null_without_default Person.connection.create_table :testings do |t| t.column :foo, :string @@ -165,8 +168,8 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal Fixnum, bob.age.class assert_equal Time, bob.birthday.class - if current_adapter?(:SQLServerAdapter) or current_adapter?(:OracleAdapter) - # SQL Server and Oracle don't differentiate between date/time + if current_adapter?(:SQLServerAdapter) || current_adapter?(:OracleAdapter) || current_adapter?(:SybaseAdapter) + # SQL Server, Sybase, and Oracle don't differentiate between date/time assert_equal Time, bob.favorite_day.class else assert_equal Date, bob.favorite_day.class |