activerecord-sqlserver-adapter 3.2.18 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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