activerecord-sqlserver-adapter 3.2.18 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +4 -28
  3. data/VERSION +1 -1
  4. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -7
  5. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +6 -9
  6. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +3 -25
  7. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +4 -14
  8. data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +1 -3
  9. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +2 -4
  10. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +74 -80
  11. data/lib/active_record/connection_adapters/sqlserver/errors.rb +10 -14
  12. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +24 -15
  13. data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +24 -19
  14. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +28 -0
  15. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +118 -77
  16. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +10 -13
  17. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +8 -11
  18. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +2 -5
  19. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +23 -0
  20. data/lib/active_record/connection_adapters/sqlserver/utils.rb +4 -10
  21. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +121 -247
  22. data/lib/active_record/connection_adapters/sqlserver_column.rb +116 -0
  23. data/lib/active_record/sqlserver_base.rb +28 -0
  24. data/lib/active_record/sqlserver_test_case.rb +17 -0
  25. data/lib/arel/arel_sqlserver.rb +5 -0
  26. data/lib/arel/nodes_sqlserver.rb +14 -0
  27. data/lib/arel/select_manager_sqlserver.rb +62 -0
  28. data/lib/arel/visitors/sqlserver.rb +251 -188
  29. metadata +32 -10
  30. data/lib/active_record/connection_adapters/sqlserver/core_ext/database_statements.rb +0 -97
@@ -1,35 +1,31 @@
1
1
  module ActiveRecord
2
-
3
2
  class LostConnection < WrappedDatabaseException
4
3
  end
5
-
4
+
6
5
  class DeadlockVictim < WrappedDatabaseException
7
6
  end
8
-
7
+
9
8
  module ConnectionAdapters
10
9
  module Sqlserver
11
10
  module Errors
12
-
13
11
  LOST_CONNECTION_EXCEPTIONS = {
14
- :dblib => ['TinyTds::Error'],
15
- :odbc => ['ODBC::Error']
12
+ dblib: ['TinyTds::Error'],
13
+ odbc: ['ODBC::Error']
16
14
  }.freeze
17
-
15
+
18
16
  LOST_CONNECTION_MESSAGES = {
19
- :dblib => [/closed connection/, /dead or not enabled/, /server failed/i],
20
- :odbc => [/link failure/, /server failed/, /connection was already closed/, /invalid handle/i]
17
+ dblib: [/closed connection/, /dead or not enabled/, /server failed/i],
18
+ odbc: [/link failure/, /server failed/, /connection was already closed/, /invalid handle/i]
21
19
  }.freeze
22
-
23
-
20
+
24
21
  def lost_connection_exceptions
25
22
  exceptions = LOST_CONNECTION_EXCEPTIONS[@connection_options[:mode]]
26
- @lost_connection_exceptions ||= exceptions ? exceptions.map{ |e| e.constantize rescue nil }.compact : []
23
+ @lost_connection_exceptions ||= exceptions ? exceptions.map { |e| e.constantize rescue nil }.compact : []
27
24
  end
28
-
25
+
29
26
  def lost_connection_messages
30
27
  LOST_CONNECTION_MESSAGES[@connection_options[:mode]]
31
28
  end
32
-
33
29
  end
34
30
  end
35
31
  end
@@ -2,10 +2,9 @@ module ActiveRecord
2
2
  module ConnectionAdapters
3
3
  module Sqlserver
4
4
  module Quoting
5
-
6
5
  QUOTED_TRUE, QUOTED_FALSE = '1', '0'
7
6
  QUOTED_STRING_PREFIX = 'N'
8
-
7
+
9
8
  def quote(value, column = nil)
10
9
  case value
11
10
  when String, ActiveSupport::Multibyte::Chars
@@ -13,6 +12,8 @@ module ActiveRecord
13
12
  value.to_i.to_s
14
13
  elsif column && column.type == :binary
15
14
  column.class.string_to_binary(value)
15
+ elsif column && [:uuid, :uniqueidentifier].include?(column.type)
16
+ "'#{quote_string(value)}'"
16
17
  elsif value.is_utf8? || (column && column.type == :string)
17
18
  "#{quoted_string_prefix}'#{quote_string(value)}'"
18
19
  else
@@ -32,11 +33,11 @@ module ActiveRecord
32
33
  super
33
34
  end
34
35
  end
35
-
36
+
36
37
  def quoted_string_prefix
37
38
  QUOTED_STRING_PREFIX
38
39
  end
39
-
40
+
40
41
  def quote_string(string)
41
42
  string.to_s.gsub(/\'/, "''")
42
43
  end
@@ -48,7 +49,20 @@ module ActiveRecord
48
49
  def quote_table_name(name)
49
50
  quote_column_name(name)
50
51
  end
51
-
52
+
53
+ def quote_database_name(name)
54
+ schema_cache.quote_name(name, false)
55
+ end
56
+
57
+ # Does not quote function default values for UUID columns
58
+ def quote_default_value(value, column)
59
+ if column.type == :uuid && value =~ /\(\)/
60
+ value
61
+ else
62
+ quote(value)
63
+ end
64
+ end
65
+
52
66
  def substitute_at(column, index)
53
67
  if column.respond_to?(:sql_type) && column.sql_type == 'timestamp'
54
68
  nil
@@ -69,19 +83,15 @@ module ActiveRecord
69
83
  if value.acts_like?(:time)
70
84
  time_zone_qualified_value = quoted_value_acts_like_time_filter(value)
71
85
  if value.is_a?(Date)
72
- time_zone_qualified_value.to_time.xmlschema.to(18)
86
+ time_zone_qualified_value.iso8601(3).to(18)
73
87
  else
74
- # CHANGED [Ruby 1.8] Not needed when 1.8 is dropped.
75
- if value.is_a?(ActiveSupport::TimeWithZone) && RUBY_VERSION < '1.9'
76
- time_zone_qualified_value = time_zone_qualified_value.to_time
77
- end
78
88
  time_zone_qualified_value.iso8601(3).to(22)
79
89
  end
80
90
  else
81
91
  quoted_date(value)
82
92
  end
83
93
  end
84
-
94
+
85
95
  def quoted_full_iso8601(value)
86
96
  if value.acts_like?(:time)
87
97
  value.is_a?(Date) ? quoted_value_acts_like_time_filter(value).to_time.xmlschema.to(18) : quoted_value_acts_like_time_filter(value).iso8601(7).to(22)
@@ -92,21 +102,20 @@ module ActiveRecord
92
102
 
93
103
  def quoted_date(value)
94
104
  if value.acts_like?(:time) && value.respond_to?(:usec)
95
- "#{super}.#{sprintf("%03d",value.usec/1000)}"
105
+ "#{super}.#{sprintf('%03d', value.usec / 1000)}"
96
106
  elsif value.acts_like?(:date)
97
107
  value.to_s(:_sqlserver_dateformat)
98
108
  else
99
109
  super
100
110
  end
101
111
  end
102
-
112
+
103
113
  protected
104
-
114
+
105
115
  def quoted_value_acts_like_time_filter(value)
106
116
  zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
107
117
  value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
108
118
  end
109
-
110
119
  end
111
120
  end
112
121
  end
@@ -2,9 +2,8 @@ module ActiveRecord
2
2
  module ConnectionAdapters
3
3
  module Sqlserver
4
4
  class SchemaCache < ActiveRecord::ConnectionAdapters::SchemaCache
5
-
6
5
  attr_reader :view_information
7
-
6
+
8
7
  def initialize(conn)
9
8
  super
10
9
  @table_names = nil
@@ -12,16 +11,16 @@ module ActiveRecord
12
11
  @view_information = {}
13
12
  @quoted_names = {}
14
13
  end
15
-
14
+
16
15
  # Superclass Overrides
17
-
16
+
18
17
  def table_exists?(table_name)
19
18
  return false if table_name.blank?
20
19
  key = table_name_key(table_name)
21
20
  return @tables[key] if @tables.key? key
22
21
  @tables[key] = connection.table_exists?(table_name)
23
22
  end
24
-
23
+
25
24
  def clear!
26
25
  super
27
26
  @table_names = nil
@@ -29,7 +28,7 @@ module ActiveRecord
29
28
  @view_information.clear
30
29
  @quoted_names.clear
31
30
  end
32
-
31
+
33
32
  def clear_table_cache!(table_name)
34
33
  key = table_name_key(table_name)
35
34
  super(key)
@@ -45,41 +44,47 @@ module ActiveRecord
45
44
  end
46
45
  @view_information.delete key
47
46
  end
48
-
47
+
49
48
  # SQL Server Specific
50
-
49
+
51
50
  def table_names
52
51
  @table_names ||= connection.tables
53
52
  end
54
-
53
+
55
54
  def view_names
56
55
  @view_names ||= connection.views
57
56
  end
58
-
57
+
59
58
  def view_exists?(table_name)
60
59
  table_exists?(table_name)
61
60
  end
62
-
61
+
63
62
  def view_information(table_name)
64
63
  key = table_name_key(table_name)
65
64
  return @view_information[key] if @view_information.key? key
66
65
  @view_information[key] = connection.send(:view_information, table_name)
67
66
  end
68
-
69
- def quote_name(name)
67
+
68
+ def quote_name(name, split_on_dots = true)
70
69
  return @quoted_names[name] if @quoted_names.key? name
71
- @quoted_names[name] = name.to_s.split('.').map{ |n| n =~ /^\[.*\]$/ ? n : "[#{n.to_s.gsub(']', ']]')}]" }.join('.')
70
+
71
+ @quoted_names[name] = if split_on_dots
72
+ name.to_s.split('.').map { |n| quote_name_part(n) }.join('.')
73
+ else
74
+ quote_name_part(name.to_s)
75
+ end
72
76
  end
73
-
74
-
77
+
75
78
  private
76
-
79
+
80
+ def quote_name_part(part)
81
+ part =~ /^\[.*\]$/ ? part : "[#{part.to_s.gsub(']', ']]')}]"
82
+ end
83
+
77
84
  def table_name_key(table_name)
78
85
  Utils.unqualify_table_name(table_name)
79
86
  end
80
-
81
87
  end
82
88
  end
83
89
  end
84
90
  end
85
-
@@ -0,0 +1,28 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ class SchemaCreation < AbstractAdapter::SchemaCreation
5
+ private
6
+
7
+ def visit_ColumnDefinition(o)
8
+ sql = super
9
+ if o.primary_key? && o.type == :uuid
10
+ sql << ' PRIMARY KEY '
11
+ add_column_options!(sql, column_options(o))
12
+ end
13
+ sql
14
+ end
15
+
16
+ def add_column_options!(sql, options)
17
+ column = options.fetch(:column) { return super }
18
+ if [:uniqueidentifier, :uuid].include?(column.type) && options[:default] =~ /\(\)/
19
+ sql << " DEFAULT #{options.delete(:default)}"
20
+ super
21
+ else
22
+ super
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -2,7 +2,6 @@ module ActiveRecord
2
2
  module ConnectionAdapters
3
3
  module Sqlserver
4
4
  module SchemaStatements
5
-
6
5
  def native_database_types
7
6
  @native_database_types ||= initialize_native_database_types.freeze
8
7
  end
@@ -18,8 +17,8 @@ module ActiveRecord
18
17
  end
19
18
 
20
19
  def indexes(table_name, name = nil)
21
- data = select("EXEC sp_helpindex #{quote(table_name)}",name) rescue []
22
- data.inject([]) do |indexes,index|
20
+ data = select("EXEC sp_helpindex #{quote(table_name)}", name) rescue []
21
+ data.reduce([]) do |indexes, index|
23
22
  index = index.with_indifferent_access
24
23
  if index[:index_description] =~ /primary key/
25
24
  indexes
@@ -36,62 +35,77 @@ module ActiveRecord
36
35
  end
37
36
  end
38
37
 
39
- def columns(table_name, name = nil)
38
+ def columns(table_name, _name = nil)
40
39
  return [] if table_name.blank?
41
- column_definitions(table_name).collect do |ci|
42
- sqlserver_options = ci.except(:name,:default_value,:type,:null).merge(:database_year=>database_year)
40
+ column_definitions(table_name).map do |ci|
41
+ sqlserver_options = ci.except(:name, :default_value, :type, :null).merge(database_year: database_year)
43
42
  SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
44
43
  end
45
44
  end
46
45
 
46
+ # like postgres, sqlserver requires the ORDER BY columns in the select list for distinct queries, and
47
+ # requires that the ORDER BY include the distinct column. Unfortunately, sqlserver does not support
48
+ # DISTINCT ON () like Posgres, or FIRST_VALUE() like Oracle (at least before SQL Server 2012). Because
49
+ # of these facts, we don't actually add any extra columns for distinct, but instead have to create
50
+ # a subquery with ROW_NUMBER() and DENSE_RANK() in our monkey-patches to Arel.
51
+ def columns_for_distinct(columns, _orders) #:nodoc:
52
+ columns
53
+ end
54
+
47
55
  def rename_table(table_name, new_name)
48
56
  do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
57
+ rename_table_indexes(table_name, new_name)
49
58
  end
50
59
 
51
- def remove_column(table_name, *column_names)
52
- raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
53
- ActiveSupport::Deprecation.warn 'Passing array to remove_columns is deprecated, please use multiple arguments, like: `remove_columns(:posts, :foo, :bar)`', caller if column_names.flatten!
54
- column_names.flatten.each do |column_name|
55
- remove_check_constraints(table_name, column_name)
56
- remove_default_constraint(table_name, column_name)
57
- remove_indexes(table_name, column_name)
58
- do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
59
- end
60
+ def remove_column(table_name, column_name, _type = nil)
61
+ raise ArgumentError.new('You must specify at least one column name. Example: remove_column(:people, :first_name)') if column_name.is_a? Array
62
+ remove_check_constraints(table_name, column_name)
63
+ remove_default_constraint(table_name, column_name)
64
+ remove_indexes(table_name, column_name)
65
+ do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
60
66
  end
61
67
 
62
68
  def change_column(table_name, column_name, type, options = {})
63
69
  sql_commands = []
64
70
  indexes = []
65
- column_object = schema_cache.columns[table_name].detect { |c| c.name.to_s == column_name.to_s }
71
+ column_object = schema_cache.columns(table_name).find { |c| c.name.to_s == column_name.to_s }
66
72
 
67
73
  if options_include_default?(options) || (column_object && column_object.type != type.to_sym)
68
- remove_default_constraint(table_name,column_name)
69
- indexes = indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }
74
+ remove_default_constraint(table_name, column_name)
75
+ indexes = indexes(table_name).select { |index| index.columns.include?(column_name.to_s) }
70
76
  remove_indexes(table_name, column_name)
71
77
  end
72
- sql_commands << "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(options[:default])} WHERE #{quote_column_name(column_name)} IS NULL" if !options[:null].nil? && options[:null] == false && !options[:default].nil?
78
+ sql_commands << "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(options[:default], column_object)} WHERE #{quote_column_name(column_name)} IS NULL" if !options[:null].nil? && options[:null] == false && !options[:default].nil?
73
79
  sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
74
- sql_commands[-1] << " NOT NULL" if !options[:null].nil? && options[:null] == false
80
+ sql_commands[-1] << ' NOT NULL' if !options[:null].nil? && options[:null] == false
75
81
  if options_include_default?(options)
76
- sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name,column_name)} DEFAULT #{quote(options[:default])} FOR #{quote_column_name(column_name)}"
82
+ sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote_default_value(options[:default], column_object)} FOR #{quote_column_name(column_name)}"
77
83
  end
78
84
 
79
- #Add any removed indexes back
85
+ # Add any removed indexes back
80
86
  indexes.each do |index|
81
- sql_commands << "CREATE INDEX #{quote_table_name(index.name)} ON #{quote_table_name(table_name)} (#{index.columns.collect {|c|quote_column_name(c)}.join(', ')})"
87
+ sql_commands << "CREATE INDEX #{quote_table_name(index.name)} ON #{quote_table_name(table_name)} (#{index.columns.map { |c| quote_column_name(c) }.join(', ')})"
82
88
  end
83
89
  sql_commands.each { |c| do_execute(c) }
84
90
  end
85
91
 
86
92
  def change_column_default(table_name, column_name, default)
87
93
  remove_default_constraint(table_name, column_name)
88
- do_execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote(default)} FOR #{quote_column_name(column_name)}"
94
+ column_object = schema_cache.columns(table_name).find { |c| c.name.to_s == column_name.to_s }
95
+ do_execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote_default_value(default, column_object)} FOR #{quote_column_name(column_name)}"
96
+ schema_cache.clear_table_cache!(table_name)
89
97
  end
90
98
 
91
99
  def rename_column(table_name, column_name, new_column_name)
92
100
  schema_cache.clear_table_cache!(table_name)
93
101
  detect_column_for! table_name, column_name
94
102
  do_execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
103
+ rename_column_indexes(table_name, column_name, new_column_name)
104
+ schema_cache.clear_table_cache!(table_name)
105
+ end
106
+
107
+ def rename_index(table_name, old_name, new_name)
108
+ execute "EXEC sp_rename N'#{table_name}.#{old_name}', N'#{new_name}', N'INDEX'"
95
109
  end
96
110
 
97
111
  def remove_index!(table_name, index_name)
@@ -99,15 +113,15 @@ module ActiveRecord
99
113
  end
100
114
 
101
115
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
102
- type_limitable = ['string','integer','float','char','nchar','varchar','nvarchar'].include?(type.to_s)
116
+ type_limitable = %w(string integer float char nchar varchar nvarchar).include?(type.to_s)
103
117
  limit = nil unless type_limitable
104
118
  case type.to_s
105
119
  when 'integer'
106
120
  case limit
107
- when 1..2 then 'smallint'
108
- when 3..4, nil then 'integer'
109
- when 5..8 then 'bigint'
110
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
121
+ when 1..2 then 'smallint'
122
+ when 3..4, nil then 'integer'
123
+ when 5..8 then 'bigint'
124
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
111
125
  end
112
126
  else
113
127
  super
@@ -120,7 +134,7 @@ module ActiveRecord
120
134
  do_execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
121
135
  end
122
136
  sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
123
- sql << " NOT NULL" if !allow_null.nil? && allow_null == false
137
+ sql << ' NOT NULL' if !allow_null.nil? && allow_null == false
124
138
  do_execute sql
125
139
  end
126
140
 
@@ -130,33 +144,33 @@ module ActiveRecord
130
144
  tables('VIEW')
131
145
  end
132
146
 
133
-
134
147
  protected
135
148
 
136
149
  # === SQLServer Specific ======================================== #
137
150
 
138
151
  def initialize_native_database_types
139
152
  {
140
- :primary_key => "int NOT NULL IDENTITY(1,1) PRIMARY KEY",
141
- :string => { :name => native_string_database_type, :limit => 255 },
142
- :text => { :name => native_text_database_type },
143
- :integer => { :name => "int", :limit => 4 },
144
- :float => { :name => "float", :limit => 8 },
145
- :decimal => { :name => "decimal" },
146
- :datetime => { :name => "datetime" },
147
- :timestamp => { :name => "datetime" },
148
- :time => { :name => native_time_database_type },
149
- :date => { :name => native_date_database_type },
150
- :binary => { :name => native_binary_database_type },
151
- :boolean => { :name => "bit"},
153
+ primary_key: 'int NOT NULL IDENTITY(1,1) PRIMARY KEY',
154
+ string: { name: native_string_database_type, limit: 255 },
155
+ text: { name: native_text_database_type },
156
+ integer: { name: 'int', limit: 4 },
157
+ float: { name: 'float', limit: 8 },
158
+ decimal: { name: 'decimal' },
159
+ datetime: { name: 'datetime' },
160
+ timestamp: { name: 'datetime' },
161
+ time: { name: native_time_database_type },
162
+ date: { name: native_date_database_type },
163
+ binary: { name: native_binary_database_type },
164
+ boolean: { name: 'bit' },
165
+ uuid: { name: 'uniqueidentifier' },
152
166
  # These are custom types that may move somewhere else for good schema_dumper.rb hacking to output them.
153
- :char => { :name => 'char' },
154
- :varchar_max => { :name => 'varchar(max)' },
155
- :nchar => { :name => "nchar" },
156
- :nvarchar => { :name => "nvarchar", :limit => 255 },
157
- :nvarchar_max => { :name => "nvarchar(max)" },
158
- :ntext => { :name => "ntext" },
159
- :ss_timestamp => { :name => 'timestamp' }
167
+ char: { name: 'char' },
168
+ varchar_max: { name: 'varchar(max)' },
169
+ nchar: { name: 'nchar' },
170
+ nvarchar: { name: 'nvarchar', limit: 255 },
171
+ nvarchar_max: { name: 'nvarchar(max)' },
172
+ ntext: { name: 'ntext' },
173
+ ss_timestamp: { name: 'timestamp' }
160
174
  }
161
175
  end
162
176
 
@@ -208,29 +222,29 @@ module ActiveRecord
208
222
  ON o.object_id = c.object_id
209
223
  AND c.name = columns.COLUMN_NAME
210
224
  WHERE columns.TABLE_NAME = @0
211
- AND columns.TABLE_SCHEMA = #{table_schema.blank? ? "schema_name()" : "@1"}
225
+ AND columns.TABLE_SCHEMA = #{table_schema.blank? ? 'schema_name()' : '@1'}
212
226
  ORDER BY columns.ordinal_position
213
- }.gsub(/[ \t\r\n]+/,' ')
227
+ }.gsub(/[ \t\r\n]+/, ' ')
214
228
  binds = [['table_name', table_name]]
215
- binds << ['table_schema',table_schema] unless table_schema.blank?
229
+ binds << ['table_schema', table_schema] unless table_schema.blank?
216
230
  results = do_exec_query(sql, 'SCHEMA', binds)
217
- results.collect do |ci|
231
+ results.map do |ci|
218
232
  ci = ci.symbolize_keys
219
233
  ci[:type] = case ci[:type]
220
- when /^bit|image|text|ntext|datetime$/
221
- ci[:type]
222
- when /^numeric|decimal$/i
223
- "#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
224
- when /^float|real$/i
225
- "#{ci[:type]}(#{ci[:numeric_precision]})"
226
- when /^char|nchar|varchar|nvarchar|varbinary|bigint|int|smallint$/
227
- ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
228
- else
229
- ci[:type]
230
- end
234
+ when /^bit|image|text|ntext|datetime$/
235
+ ci[:type]
236
+ when /^numeric|decimal$/i
237
+ "#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
238
+ when /^float|real$/i
239
+ "#{ci[:type]}(#{ci[:numeric_precision]})"
240
+ when /^char|nchar|varchar|nvarchar|varbinary|bigint|int|smallint$/
241
+ ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
242
+ else
243
+ ci[:type]
244
+ end
231
245
  if ci[:default_value].nil? && schema_cache.view_names.include?(table_name)
232
246
  real_table_name = table_name_or_views_table_name(table_name)
233
- real_column_name = views_real_column_name(table_name,ci[:name])
247
+ real_column_name = views_real_column_name(table_name, ci[:name])
234
248
  col_default_sql = "SELECT c.COLUMN_DEFAULT FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS c WHERE c.TABLE_NAME = '#{real_table_name}' AND c.COLUMN_NAME = '#{real_column_name}'"
235
249
  ci[:default_value] = select_value col_default_sql, 'SCHEMA'
236
250
  end
@@ -238,13 +252,14 @@ module ActiveRecord
238
252
  when nil, '(null)', '(NULL)'
239
253
  nil
240
254
  when /\A\((\w+\(\))\)\Z/
241
- ci[:default_function] = $1
255
+ ci[:default_function] = Regexp.last_match[1]
242
256
  nil
243
257
  else
244
258
  match_data = ci[:default_value].match(/\A\(+N?'?(.*?)'?\)+\Z/m)
245
259
  match_data ? match_data[1] : nil
246
260
  end
247
- ci[:null] = ci[:is_nullable].to_i == 1 ; ci.delete(:is_nullable)
261
+ ci[:null] = ci[:is_nullable].to_i == 1
262
+ ci.delete(:is_nullable)
248
263
  ci[:is_primary] = ci[:is_primary].to_i == 1
249
264
  ci[:is_identity] = ci[:is_identity].to_i == 1 unless [TrueClass, FalseClass].include?(ci[:is_identity].class)
250
265
  ci
@@ -268,18 +283,18 @@ module ActiveRecord
268
283
  end
269
284
 
270
285
  def remove_indexes(table_name, column_name)
271
- indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }.each do |index|
272
- remove_index(table_name, {:name => index.name})
286
+ indexes(table_name).select { |index| index.columns.include?(column_name.to_s) }.each do |index|
287
+ remove_index(table_name, name: index.name)
273
288
  end
274
289
  end
275
290
 
276
291
  # === SQLServer Specific (Misc Helpers) ========================= #
277
292
 
278
293
  def get_table_name(sql)
279
- if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)\s+INTO\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
280
- $2 || $3
294
+ if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
295
+ Regexp.last_match[3] || Regexp.last_match[4]
281
296
  elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
282
- $1
297
+ Regexp.last_match[1]
283
298
  else
284
299
  nil
285
300
  end
@@ -290,7 +305,7 @@ module ActiveRecord
290
305
  end
291
306
 
292
307
  def detect_column_for!(table_name, column_name)
293
- unless column = schema_cache.columns[table_name].detect { |c| c.name == column_name.to_s }
308
+ unless column = schema_cache.columns(table_name).find { |c| c.name == column_name.to_s }
294
309
  raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
295
310
  end
296
311
  column
@@ -329,7 +344,7 @@ module ActiveRecord
329
344
  schema_cache.view_names.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
330
345
  end
331
346
 
332
- def views_real_column_name(table_name,column_name)
347
+ def views_real_column_name(table_name, column_name)
333
348
  view_definition = schema_cache.view_information(table_name)[:VIEW_DEFINITION]
334
349
  match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
335
350
  match_data ? match_data[1] : column_name
@@ -351,6 +366,27 @@ module ActiveRecord
351
366
  !(sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil?
352
367
  end
353
368
 
369
+ def strip_ident_from_update(sql)
370
+ # We can't update Identiy columns in sqlserver. So, strip out the id from the update.
371
+ # There has to be a better way to handle this, but this'll do for now.
372
+ table_name = get_table_name(sql)
373
+ id_column = identity_column(table_name)
374
+
375
+ if id_column
376
+ regex_col_name = Regexp.quote(quote_column_name(id_column.name))
377
+ if sql =~ /, #{regex_col_name} = @?[0-9]*/
378
+ sql = sql.gsub(/, #{regex_col_name} = @?[0-9]*/, '')
379
+ elsif sql =~ /\s#{regex_col_name} = @?[0-9]*,/
380
+ sql = sql.gsub(/\s#{regex_col_name} = @?[0-9]*,/, '')
381
+ end
382
+ end
383
+ sql
384
+ end
385
+
386
+ def update_sql?(sql)
387
+ !(sql =~ /^\s*(UPDATE|EXEC sp_executesql N'UPDATE)/i).nil?
388
+ end
389
+
354
390
  def with_identity_insert_enabled(table_name)
355
391
  table_name = quote_table_name(table_name_or_views_table_name(table_name))
356
392
  set_identity_insert(table_name, true)
@@ -362,14 +398,19 @@ module ActiveRecord
362
398
  def set_identity_insert(table_name, enable = true)
363
399
  sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
364
400
  do_execute sql, 'SCHEMA'
365
- rescue Exception => e
401
+ rescue Exception
366
402
  raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
367
403
  end
368
404
 
369
405
  def identity_column(table_name)
370
- schema_cache.columns[table_name].detect(&:is_identity?)
406
+ schema_cache.columns(table_name).find(&:is_identity?)
371
407
  end
372
408
 
409
+ private
410
+
411
+ def create_table_definition(name, temporary, options)
412
+ TableDefinition.new native_database_types, name, temporary, options
413
+ end
373
414
  end
374
415
  end
375
416
  end