aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb')
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb217
1 files changed, 123 insertions, 94 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb
index 04a6443159..341a388121 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb
@@ -13,12 +13,14 @@ require 'bigdecimal/util'
#
# Modifications (ODBC): Mark Imbriaco <mark.imbriaco@pobox.com>
# Date: 6/26/2005
-#
-# Current maintainer: Ryan Tomayko <rtomayko@gmail.com>
-#
+
# Modifications (Migrations): Tom Ward <tom@popdog.net>
# Date: 27/10/2005
#
+# Modifications (Numerous fixes as maintainer): Ryan Tomayko <rtomayko@gmail.com>
+# Date: Up to July 2006
+
+# Current maintainer: Tom Ward <tom@popdog.net>
module ActiveRecord
class Base
@@ -81,7 +83,7 @@ module ActiveRecord
else super
end
end
-
+
def cast_to_time(value)
return value if value.is_a?(Time)
time_array = ParseDate.parsedate(value)
@@ -89,6 +91,8 @@ module ActiveRecord
end
def cast_to_datetime(value)
+ return value.to_time if value.is_a?(DBI::Timestamp)
+
if value.is_a?(Time)
if value.year != 0 and value.month != 0 and value.day != 0
return value
@@ -96,9 +100,24 @@ module ActiveRecord
return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
end
end
+
+ if value.is_a?(DateTime)
+ return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
+ end
+
return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil
value
end
+
+ # TODO: Find less hack way to convert DateTime objects into Times
+
+ def self.string_to_time(value)
+ if value.is_a?(DateTime)
+ return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
+ else
+ super
+ end
+ end
# These methods will only allow the adapter to insert binary data with a length of 7K or less
# because of a SQL Server statement length policy.
@@ -184,8 +203,8 @@ module ActiveRecord
:timestamp => { :name => "datetime" },
:time => { :name => "datetime" },
:date => { :name => "datetime" },
- :binary => { :name => "image" },
- :boolean => { :name => "bit" }
+ :binary => { :name => "image"},
+ :boolean => { :name => "bit"}
}
end
@@ -197,6 +216,18 @@ module ActiveRecord
true
end
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
+ return super unless type.to_s == 'integer'
+
+ if limit.nil? || limit == 4
+ 'integer'
+ elsif limit < 4
+ 'smallint'
+ else
+ 'bigint'
+ end
+ end
+
# CONNECTION MANAGEMENT ====================================#
# Returns true if the connection is active.
@@ -236,16 +267,20 @@ module ActiveRecord
return [] if table_name.blank?
table_name = table_name.to_s if table_name.is_a?(Symbol)
table_name = table_name.split('.')[-1] unless table_name.nil?
- sql = "SELECT COLUMN_NAME as ColName,
- COLUMN_DEFAULT as DefaultValue,
- DATA_TYPE as ColType,
- IS_NULLABLE As IsNullable,
- COL_LENGTH('#{table_name}', COLUMN_NAME) as Length,
- COLUMNPROPERTY(OBJECT_ID('#{table_name}'), COLUMN_NAME, 'IsIdentity') as IsIdentity,
- NUMERIC_PRECISION as [Precision],
- NUMERIC_SCALE as Scale
- FROM INFORMATION_SCHEMA.COLUMNS
- WHERE TABLE_NAME = '#{table_name}'"
+ sql = %Q{
+ SELECT
+ cols.COLUMN_NAME as ColName,
+ cols.COLUMN_DEFAULT as DefaultValue,
+ cols.NUMERIC_SCALE as numeric_scale,
+ cols.NUMERIC_PRECISION as numeric_precision,
+ cols.DATA_TYPE as ColType,
+ cols.IS_NULLABLE As IsNullable,
+ COL_LENGTH(cols.TABLE_NAME, cols.COLUMN_NAME) as Length,
+ COLUMNPROPERTY(OBJECT_ID(cols.TABLE_NAME), cols.COLUMN_NAME, 'IsIdentity') as IsIdentity,
+ cols.NUMERIC_SCALE as Scale
+ FROM INFORMATION_SCHEMA.COLUMNS cols
+ WHERE cols.TABLE_NAME = '#{table_name}'
+ }
# Comment out if you want to have the Columns select statment logged.
# Personally, I think it adds unnecessary bloat to the log.
# If you do comment it out, make sure to un-comment the "result" line that follows
@@ -255,7 +290,7 @@ module ActiveRecord
result.each do |field|
default = field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null/ ? nil : field[:DefaultValue]
if field[:ColType] =~ /numeric|decimal/i
- type = "#{field[:ColType]}(#{field[:Precision]},#{field[:Scale]})"
+ type = "#{field[:ColType]}(#{field[:numeric_precision]},#{field[:numeric_scale]})"
else
type = "#{field[:ColType]}(#{field[:Length]})"
end
@@ -267,62 +302,36 @@ module ActiveRecord
end
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
- begin
- table_name = get_table_name(sql)
- col = get_identity_column(table_name)
- ii_enabled = false
-
- if col != nil
- if query_contains_identity_column(sql, col)
- begin
- execute enable_identity_insert(table_name, true)
- ii_enabled = true
- rescue Exception => e
- raise ActiveRecordError, "IDENTITY_INSERT could not be turned ON"
- end
- end
- end
- log(sql, name) do
- @connection.execute(sql).finish
- id_value || select_one("SELECT @@IDENTITY AS Ident")["Ident"]
- end
- ensure
- if ii_enabled
- begin
- execute enable_identity_insert(table_name, false)
- rescue Exception => e
- raise ActiveRecordError, "IDENTITY_INSERT could not be turned OFF"
- end
- end
- end
+ execute(sql, name)
+ id_value || select_one("SELECT @@IDENTITY AS Ident")["Ident"]
end
+ def update(sql, name = nil)
+ execute(sql, name) do |handle|
+ handle.rows
+ end || select_one("SELECT @@ROWCOUNT AS AffectedRows")["AffectedRows"]
+ end
+
+ alias_method :delete, :update
+
def execute(sql, name = nil)
- if sql =~ /^\s*INSERT/i
- insert(sql, name)
- elsif sql =~ /^\s*UPDATE|^\s*DELETE/i
+ if sql =~ /^\s*INSERT/i && (table_name = query_requires_identity_insert?(sql))
log(sql, name) do
- ret = @connection.execute(sql).finish
- retVal = select_one("SELECT @@ROWCOUNT AS AffectedRows")["AffectedRows"]
+ with_identity_insert_enabled(table_name) do
+ @connection.execute(sql) do |handle|
+ yield(handle) if block_given?
+ end
+ end
end
else
log(sql, name) do
- if block_given?
- @connection.execute(sql) do |sth|
- yield(sth)
- end
- else
- @connection.execute(sql).finish
+ @connection.execute(sql) do |handle|
+ yield(handle) if block_given?
end
end
end
end
-
- def update(sql, name = nil)
- execute(sql, name)
- end
- alias_method :delete, :update
-
+
def begin_db_transaction
@connection["AutoCommit"] = false
rescue Exception => e
@@ -356,14 +365,6 @@ module ActiveRecord
string.gsub(/\'/, "''")
end
- def quoted_true
- "1"
- end
-
- def quoted_false
- "0"
- end
-
def quote_column_name(name)
"[#{name}]"
end
@@ -420,7 +421,7 @@ module ActiveRecord
end
def tables(name = nil)
- execute("SELECT table_name from information_schema.tables WHERE table_type = 'BASE TABLE'", name) do |sth|
+ execute("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'", name) do |sth|
sth.inject([]) do |tables, field|
table_name = field[0]
tables << table_name unless table_name == 'dtproperties'
@@ -450,19 +451,25 @@ module ActiveRecord
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
-
+ # Adds a new column to the named table.
+ # See TableDefinition#column for details of the options you can use.
+ def add_column(table_name, column_name, type, options = {})
+ add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
+ add_column_options!(add_column_sql, options)
+ # TODO: Add support to mimic date columns, using constraints to mark them as such in the database
+ # add_column_sql << " CONSTRAINT ck__#{table_name}__#{column_name}__date_only CHECK ( CONVERT(CHAR(12), #{quote_column_name(column_name)}, 14)='00:00:00:000' )" if type == :date
+ execute(add_column_sql)
+ 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], options[:precision], options[:scale])}"]
- if options[:default]
+ unless options[:default].nil?
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}"
+ sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(options[:default])} FOR #{column_name}"
end
sql_commands.each {|c|
execute(c)
@@ -470,22 +477,32 @@ module ActiveRecord
end
def remove_column(table_name, column_name)
+ remove_check_constraints(table_name, column_name)
remove_default_constraint(table_name, column_name)
- execute "ALTER TABLE #{table_name} DROP COLUMN #{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|
+ constraints = 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"
+
+ constraints.each do |constraint|
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
- }
+ end
+ end
+
+ def remove_check_constraints(table_name, column_name)
+ # TODO remove all constraints in single method
+ constraints = select "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{table_name}' and COLUMN_NAME = '#{column_name}'"
+ constraints.each do |constraint|
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["CONSTRAINT_NAME"]}"
+ end
end
def remove_index(table_name, options = {})
execute "DROP INDEX #{table_name}.#{quote_column_name(index_name(table_name, options))}"
end
- private
+ private
def select(sql, name = nil)
rows = []
repair_special_columns(sql)
@@ -494,7 +511,10 @@ module ActiveRecord
record = {}
row.column_names.each do |col|
record[col] = row[col]
- record[col] = record[col].to_time if record[col].is_a? DBI::Timestamp
+ if record[col].is_a? DBI::Timestamp
+ ts = record[col]
+ record[col] = DateTime.new(ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.sec)
+ end
end
rows << record
end
@@ -502,10 +522,21 @@ module ActiveRecord
rows
end
- def enable_identity_insert(table_name, enable = true)
- if has_identity_column(table_name)
- "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
- end
+ # Turns IDENTITY_INSERT ON for table during execution of the block
+ # N.B. This sets the state of IDENTITY_INSERT to OFF after the
+ # block has been executed without regard to its previous state
+
+ def with_identity_insert_enabled(table_name, &block)
+ set_identity_insert(table_name, true)
+ yield
+ ensure
+ set_identity_insert(table_name, false)
+ end
+
+ def set_identity_insert(table_name, enable = true)
+ execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
+ rescue Exception => e
+ raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
end
def get_table_name(sql)
@@ -518,11 +549,7 @@ module ActiveRecord
end
end
- def has_identity_column(table_name)
- !get_identity_column(table_name).nil?
- end
-
- def get_identity_column(table_name)
+ def identity_column(table_name)
@table_columns = {} unless @table_columns
@table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
@table_columns[table_name].each do |col|
@@ -532,8 +559,10 @@ module ActiveRecord
return nil
end
- def query_contains_identity_column(sql, col)
- sql =~ /\[#{col}\]/
+ def query_requires_identity_insert?(sql)
+ table_name = get_table_name(sql)
+ id_column = identity_column(table_name)
+ sql =~ /\[#{id_column}\]/ ? table_name : nil
end
def change_order_direction(order)