activerecord-jdbcsqlserver-adapter 50.1.0 → 51.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.travis.yml +4 -5
  4. data/BACKERS.md +32 -0
  5. data/CHANGELOG.md +21 -87
  6. data/README.md +2 -3
  7. data/VERSION +1 -1
  8. data/activerecord-jdbcsqlserver-adapter.gemspec +3 -2
  9. data/appveyor.yml +1 -1
  10. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +3 -1
  11. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +3 -1
  12. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +55 -0
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +4 -2
  14. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +5 -3
  15. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +41 -18
  16. data/lib/active_record/connection_adapters/sqlserver/jdbc_overrides.rb +2 -12
  17. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -0
  18. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +21 -0
  19. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +72 -52
  20. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +15 -7
  21. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +0 -4
  22. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +5 -0
  23. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +3 -6
  24. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +27 -10
  25. data/lib/active_record/tasks/sqlserver_database_tasks.rb +2 -2
  26. data/lib/activerecord-jdbcsqlserver-adapter.rb +1 -1
  27. data/lib/arel/visitors/sqlserver.rb +16 -3
  28. data/test/bin/setup.sh +19 -0
  29. data/test/cases/adapter_test_sqlserver.rb +17 -20
  30. data/test/cases/coerced_tests.rb +117 -11
  31. data/test/cases/column_test_sqlserver.rb +1 -1
  32. data/test/cases/helper_sqlserver.rb +6 -1
  33. data/test/cases/pessimistic_locking_test_sqlserver.rb +28 -11
  34. data/test/cases/schema_dumper_test_sqlserver.rb +10 -10
  35. data/test/cases/specific_schema_test_sqlserver.rb +0 -6
  36. data/test/cases/trigger_test_sqlserver.rb +31 -0
  37. data/test/config.yml +2 -2
  38. data/test/models/sqlserver/trigger.rb +7 -0
  39. data/test/models/sqlserver/trigger_history.rb +3 -0
  40. data/test/schema/sqlserver_specific_schema.rb +39 -5
  41. data/test/support/sql_counter_sqlserver.rb +3 -2
  42. metadata +23 -16
  43. data/RAILS5-TODO.md +0 -5
  44. data/lib/jdbc_mssql_driver_loader.rb +0 -22
  45. data/test/models/sqlserver/dot_table_name.rb +0 -3
@@ -1,3 +1,5 @@
1
+ require 'pry'
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
@@ -90,18 +92,6 @@ module ActiveRecord
90
92
  @connection.configure_connection
91
93
  end
92
94
 
93
- # @Overwrite
94
- # Had some special logic and skipped using gem's internal query methods
95
- def select_rows(sql, name = nil, binds = [])
96
-
97
- # In some cases the limit is converted to a `TOP(1)` but the bind parameter is still in the array
98
- if !binds.empty? && sql.include?('TOP(1)')
99
- binds = binds.delete_if {|b| b.name == 'LIMIT' }
100
- end
101
-
102
- exec_query(sql, name, binds).rows
103
- end
104
-
105
95
  # Have to reset this because the default arjdbc functionality is to return false unless a level is passed in
106
96
  def supports_transaction_isolation?
107
97
  true
@@ -16,6 +16,20 @@ module ActiveRecord
16
16
  end
17
17
  end
18
18
 
19
+ def add_column_options!(sql, options)
20
+ sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
21
+ if options[:null] == false
22
+ sql << " NOT NULL"
23
+ end
24
+ if options[:is_identity] == true
25
+ sql << " IDENTITY(1,1)"
26
+ end
27
+ if options[:primary_key] == true
28
+ sql << " PRIMARY KEY"
29
+ end
30
+ sql
31
+ end
32
+
19
33
  def action_sql(action, dependency)
20
34
  case dependency
21
35
  when :restrict
@@ -28,6 +42,14 @@ module ActiveRecord
28
42
  end
29
43
  end
30
44
 
45
+ def options_include_default?(options)
46
+ super || options_primary_key_with_nil_default?(options)
47
+ end
48
+
49
+ def options_primary_key_with_nil_default?(options)
50
+ options[:primary_key] && options.include?(:default) && options[:default].nil?
51
+ end
52
+
31
53
  end
32
54
  end
33
55
  end
@@ -3,13 +3,34 @@ module ActiveRecord
3
3
  module SQLServer
4
4
  module SchemaDumper
5
5
 
6
+ SQLSEVER_NO_LIMIT_TYPES = [
7
+ 'text',
8
+ 'ntext',
9
+ 'varchar(max)',
10
+ 'nvarchar(max)',
11
+ 'varbinary(max)'
12
+ ].freeze
13
+
6
14
  private
7
15
 
16
+ def explicit_primary_key_default?(column)
17
+ column.is_primary? && !column.is_identity?
18
+ end
19
+
20
+ def schema_limit(column)
21
+ return if SQLSEVER_NO_LIMIT_TYPES.include?(column.sql_type)
22
+ super
23
+ end
24
+
8
25
  def schema_collation(column)
9
26
  return unless column.collation
10
27
  column.collation if column.collation != collation
11
28
  end
12
29
 
30
+ def default_primary_key?(column)
31
+ super && column.is_primary? && column.is_identity?
32
+ end
33
+
13
34
  end
14
35
  end
15
36
  end
@@ -7,35 +7,6 @@ module ActiveRecord
7
7
  @native_database_types ||= initialize_native_database_types.freeze
8
8
  end
9
9
 
10
- def tables(name = nil)
11
- ActiveSupport::Deprecation.warn 'Passing arguments to #tables is deprecated without replacement.' if name
12
- tables_sql('BASE TABLE')
13
- end
14
-
15
- def table_exists?(table_name)
16
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
17
- #table_exists? currently checks both tables and views.
18
- This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
19
- Use #data_source_exists? instead.
20
- MSG
21
- data_source_exists?(table_name)
22
- end
23
-
24
- def data_source_exists?(table_name)
25
- return false if table_name.blank?
26
- unquoted_table_name = SQLServer::Utils.extract_identifiers(table_name).object
27
- super(unquoted_table_name)
28
- end
29
-
30
- def views
31
- tables_sql('VIEW')
32
- end
33
-
34
- def view_exists?(table_name)
35
- identifier = SQLServer::Utils.extract_identifiers(table_name)
36
- super(identifier.object)
37
- end
38
-
39
10
  def create_table(table_name, comment: nil, **options)
40
11
  res = super
41
12
  clear_cache!
@@ -43,7 +14,18 @@ module ActiveRecord
43
14
  end
44
15
 
45
16
  def drop_table(table_name, options = {})
46
- if options[:if_exists] && @version_year != 2016
17
+ # Mimic CASCADE option as best we can.
18
+ if options[:force] == :cascade
19
+ execute_procedure(:sp_fkeys, pktable_name: table_name).each do |fkdata|
20
+ fktable = fkdata['FKTABLE_NAME']
21
+ fkcolmn = fkdata['FKCOLUMN_NAME']
22
+ pktable = fkdata['PKTABLE_NAME']
23
+ pkcolmn = fkdata['PKCOLUMN_NAME']
24
+ remove_foreign_key fktable, name: fkdata['FK_NAME']
25
+ do_execute "DELETE FROM #{quote_table_name(fktable)} WHERE #{quote_column_name(fkcolmn)} IN ( SELECT #{quote_column_name(pkcolmn)} FROM #{quote_table_name(pktable)} )"
26
+ end
27
+ end
28
+ if options[:if_exists] && @version_year < 2016
47
29
  execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}"
48
30
  else
49
31
  super
@@ -103,10 +85,36 @@ module ActiveRecord
103
85
  end
104
86
 
105
87
  def primary_keys(table_name)
106
- primaries = schema_cache.columns(table_name).select(&:is_primary?).map(&:name)
88
+ primaries = primary_keys_select(table_name)
107
89
  primaries.present? ? primaries : identity_columns(table_name).map(&:name)
108
90
  end
109
91
 
92
+ def primary_keys_select(table_name)
93
+ identifier = database_prefix_identifier(table_name)
94
+ database = identifier.fully_qualified_database_quoted
95
+ sql = %{
96
+ SELECT KCU.COLUMN_NAME AS [name]
97
+ FROM #{database}.INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
98
+ LEFT OUTER JOIN #{database}.INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
99
+ ON KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
100
+ AND KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
101
+ AND KCU.CONSTRAINT_CATALOG = TC.CONSTRAINT_CATALOG
102
+ AND KCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
103
+ AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
104
+ WHERE KCU.TABLE_NAME = #{prepared_statements ? COLUMN_DEFINITION_BIND_STRING_0 : quote(identifier.object)}
105
+ AND KCU.TABLE_SCHEMA = #{identifier.schema.blank? ? 'schema_name()' : (prepared_statements ? COLUMN_DEFINITION_BIND_STRING_1 : quote(identifier.schema))}
106
+ AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
107
+ ORDER BY KCU.ORDINAL_POSITION ASC
108
+ }.gsub(/[[:space:]]/, ' ')
109
+ binds = []
110
+ if prepared_statements
111
+ nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
112
+ binds << Relation::QueryAttribute.new('TABLE_NAME', identifier.object, nv128)
113
+ binds << Relation::QueryAttribute.new('TABLE_SCHEMA', identifier.schema, nv128) unless identifier.schema.blank?
114
+ end
115
+ sp_executesql(sql, 'SCHEMA', binds).map { |r| r['name'] }
116
+ end
117
+
110
118
  def rename_table(table_name, new_name)
111
119
  do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
112
120
  rename_table_indexes(table_name, new_name)
@@ -130,9 +138,11 @@ module ActiveRecord
130
138
  remove_indexes(table_name, column_name)
131
139
  end
132
140
  sql_commands << "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(options[:default], column_object)} WHERE #{quote_column_name(column_name)} IS NULL" if !options[:null].nil? && options[:null] == false && !options[:default].nil?
133
- 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])}"
134
- sql_commands[-1] << ' NOT NULL' if !options[:null].nil? && options[:null] == false
135
- if options_include_default?(options)
141
+ sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, limit: options[:limit], precision: options[:precision], scale: options[:scale])}"
142
+ sql_commands.last << ' NOT NULL' if !options[:null].nil? && options[:null] == false
143
+ if options.key?(:default) && default_constraint_name(table_name, column_name).present?
144
+ change_column_default(table_name, column_name, options[:default])
145
+ elsif options_include_default?(options)
136
146
  sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote_default_expression(options[:default], column_object)} FOR #{quote_column_name(column_name)}"
137
147
  end
138
148
  # Add any removed indexes back
@@ -140,6 +150,7 @@ module ActiveRecord
140
150
  sql_commands << "CREATE INDEX #{quote_table_name(index.name)} ON #{quote_table_name(table_name)} (#{index.columns.map { |c| quote_column_name(c) }.join(', ')})"
141
151
  end
142
152
  sql_commands.each { |c| do_execute(c) }
153
+ clear_cache!
143
154
  end
144
155
 
145
156
  def change_column_default(table_name, column_name, default_or_changes)
@@ -194,7 +205,7 @@ module ActiveRecord
194
205
  end
195
206
  end
196
207
 
197
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
208
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, **)
198
209
  type_limitable = %w(string integer float char nchar varchar nvarchar).include?(type.to_s)
199
210
  limit = nil unless type_limitable
200
211
  case type.to_s
@@ -240,19 +251,40 @@ module ActiveRecord
240
251
  if !allow_null.nil? && allow_null == false && !default.nil?
241
252
  do_execute("UPDATE #{table_id} SET #{column_id}=#{quote(default)} WHERE #{column_id} IS NULL")
242
253
  end
243
- sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
254
+ sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column.type, limit: column.limit, precision: column.precision, scale: column.scale}"
244
255
  sql << ' NOT NULL' if !allow_null.nil? && allow_null == false
245
256
  do_execute sql
246
257
  end
247
258
 
259
+ private
248
260
 
249
- protected
261
+ def data_source_sql(name = nil, type: nil)
262
+ scope = quoted_scope name, type: type
263
+ table_name = lowercase_schema_reflection_sql 'TABLE_NAME'
264
+ sql = "SELECT #{table_name}"
265
+ sql << ' FROM INFORMATION_SCHEMA.TABLES'
266
+ sql << ' WHERE TABLE_CATALOG = DB_NAME()'
267
+ sql << " AND TABLE_SCHEMA = #{quote(scope[:schema])}"
268
+ sql << " AND TABLE_NAME = #{quote(scope[:name])}" if scope[:name]
269
+ sql << " AND TABLE_TYPE = #{quote(scope[:type])}" if scope[:type]
270
+ sql << " ORDER BY #{table_name}"
271
+ sql
272
+ end
273
+
274
+ def quoted_scope(name = nil, type: nil)
275
+ identifier = SQLServer::Utils.extract_identifiers(name)
276
+ {}.tap do |scope|
277
+ scope[:schema] = identifier.schema || 'dbo'
278
+ scope[:name] = identifier.object if identifier.object
279
+ scope[:type] = type if type
280
+ end
281
+ end
250
282
 
251
283
  # === SQLServer Specific ======================================== #
252
284
 
253
285
  def initialize_native_database_types
254
286
  {
255
- primary_key: 'int NOT NULL IDENTITY(1,1) PRIMARY KEY',
287
+ primary_key: 'bigint NOT NULL IDENTITY(1,1) PRIMARY KEY',
256
288
  primary_key_nonclustered: 'int NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED',
257
289
  integer: { name: 'int', limit: 4 },
258
290
  bigint: { name: 'bigint' },
@@ -286,21 +318,11 @@ module ActiveRecord
286
318
  }
287
319
  end
288
320
 
289
- def tables_sql(type)
290
- tn = lowercase_schema_reflection_sql 'TABLE_NAME'
291
- sql = "SELECT #{tn} FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = '#{type}' ORDER BY TABLE_NAME"
292
- select_values sql, 'SCHEMA'
293
- end
294
-
295
321
  COLUMN_DEFINITION_BIND_STRING_0 = defined?(JRUBY_VERSION) ? '?' : '@0'
296
322
  COLUMN_DEFINITION_BIND_STRING_1 = defined?(JRUBY_VERSION) ? '?' : '@1'
297
323
 
298
324
  def column_definitions(table_name)
299
- identifier = if database_prefix_remote_server?
300
- SQLServer::Utils.extract_identifiers("#{database_prefix}#{table_name}")
301
- else
302
- SQLServer::Utils.extract_identifiers(table_name)
303
- end
325
+ identifier = database_prefix_identifier(table_name)
304
326
  database = identifier.fully_qualified_database_quoted
305
327
  view_exists = view_exists?(table_name)
306
328
  view_tblnm = view_table_name(table_name) if view_exists
@@ -482,7 +504,7 @@ module ActiveRecord
482
504
  @view_information ||= {}
483
505
  @view_information[table_name] ||= begin
484
506
  identifier = SQLServer::Utils.extract_identifiers(table_name)
485
- view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{identifier.object}'", 'SCHEMA'
507
+ view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = #{quote(identifier.object)}", 'SCHEMA'
486
508
  if view_info
487
509
  view_info = view_info.with_indifferent_access
488
510
  if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
@@ -505,8 +527,6 @@ module ActiveRecord
505
527
  match_data ? match_data[1] : column_name
506
528
  end
507
529
 
508
- private
509
-
510
530
  def create_table_definition(*args)
511
531
  SQLServer::TableDefinition.new(*args)
512
532
  end
@@ -5,10 +5,13 @@ module ActiveRecord
5
5
  module ColumnMethods
6
6
 
7
7
  def primary_key(name, type = :primary_key, **options)
8
- return super unless type == :uuid
9
- options[:default] = options.fetch(:default, 'NEWID()')
10
- options[:primary_key] = true
11
- column name, type, options
8
+ if [:integer, :bigint].include?(type)
9
+ options[:is_identity] = true unless options.key?(:default)
10
+ elsif type == :uuid
11
+ options[:default] = options.fetch(:default, 'NEWID()')
12
+ options[:primary_key] = true
13
+ end
14
+ super
12
15
  end
13
16
 
14
17
  def primary_key_nonclustered(*args, **options)
@@ -98,9 +101,14 @@ module ActiveRecord
98
101
  class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
99
102
  include ColumnMethods
100
103
 
101
- def new_column_definition(name, type, options)
102
- type = :datetime2 if type == :datetime && options[:precision]
103
- super name, type, options
104
+ def new_column_definition(name, type, **options)
105
+ case type
106
+ when :datetime
107
+ type = :datetime2 if options[:precision]
108
+ when :primary_key
109
+ options[:is_identity] = true
110
+ end
111
+ super
104
112
  end
105
113
  end
106
114
 
@@ -4,10 +4,6 @@ module ActiveRecord
4
4
  module Type
5
5
  class BigInteger < Integer
6
6
 
7
- def type
8
- :bigint
9
- end
10
-
11
7
  def sqlserver_type
12
8
  'bigint'.freeze
13
9
  end
@@ -23,6 +23,11 @@ module ActiveRecord
23
23
  @value.inspect
24
24
  end
25
25
 
26
+ def eql?(other)
27
+ self.class == other.class && self.value == other.value
28
+ end
29
+ alias :== :eql?
30
+
26
31
  end
27
32
  end
28
33
  end
@@ -51,7 +51,9 @@ module ActiveRecord
51
51
  private
52
52
 
53
53
  def fast_string_to_time(string)
54
- fast_string_to_time_zone.strptime(string, fast_string_to_time_format).time
54
+ time = ActiveSupport::TimeZone['UTC'].strptime(string, fast_string_to_time_format)
55
+ new_time(time.year, time.month, time.day, time.hour,
56
+ time.min, time.sec, Rational(time.nsec, 1_000))
55
57
  rescue ArgumentError
56
58
  super
57
59
  end
@@ -59,11 +61,6 @@ module ActiveRecord
59
61
  def fast_string_to_time_format
60
62
  "#{::Time::DATE_FORMATS[:_sqlserver_datetime]}.%N".freeze
61
63
  end
62
-
63
- def fast_string_to_time_zone
64
- ::Time.zone || ActiveSupport::TimeZone['UTC']
65
- end
66
-
67
64
  end
68
65
  end
69
66
  end
@@ -3,6 +3,7 @@ require 'active_record'
3
3
  require 'arel_sqlserver'
4
4
  require 'active_record/connection_adapters/abstract_adapter'
5
5
  require 'active_record/connection_adapters/sqlserver/core_ext/active_record'
6
+ require 'active_record/connection_adapters/sqlserver/core_ext/calculations'
6
7
  require 'active_record/connection_adapters/sqlserver/core_ext/explain' unless defined? JRUBY_VERSION
7
8
  require 'active_record/connection_adapters/sqlserver/core_ext/explain_subscriber'
8
9
  require 'active_record/connection_adapters/sqlserver/core_ext/attribute_methods'
@@ -56,11 +57,13 @@ module ActiveRecord
56
57
 
57
58
  cattr_accessor :cs_equality_operator, instance_accessor: false
58
59
  cattr_accessor :use_output_inserted, instance_accessor: false
60
+ cattr_accessor :exclude_output_inserted_table_names, instance_accessor: false
59
61
  cattr_accessor :showplan_option, instance_accessor: false
60
62
  cattr_accessor :lowercase_schema_reflection
61
63
 
62
64
  self.cs_equality_operator = 'COLLATE Latin1_General_CS_AS_WS'
63
65
  self.use_output_inserted = true
66
+ self.exclude_output_inserted_table_names = Concurrent::Map.new { false }
64
67
 
65
68
  def initialize(connection, logger = nil, config = {})
66
69
  super(connection, logger, config)
@@ -85,14 +88,6 @@ module ActiveRecord
85
88
  SQLServer::SchemaCreation.new self
86
89
  end
87
90
 
88
- def supports_migrations?
89
- true
90
- end
91
-
92
- def supports_primary_key?
93
- true
94
- end
95
-
96
91
  def supports_ddl_transactions?
97
92
  true
98
93
  end
@@ -159,10 +154,10 @@ module ActiveRecord
159
154
 
160
155
  def disable_referential_integrity
161
156
  tables = tables_with_referential_integrity
162
- tables.each { |t| do_execute "ALTER TABLE #{t} NOCHECK CONSTRAINT ALL" }
157
+ tables.each { |t| do_execute "ALTER TABLE #{quote_table_name(t)} NOCHECK CONSTRAINT ALL" }
163
158
  yield
164
159
  ensure
165
- tables.each { |t| do_execute "ALTER TABLE #{t} CHECK CONSTRAINT ALL" }
160
+ tables.each { |t| do_execute "ALTER TABLE #{quote_table_name(t)} CHECK CONSTRAINT ALL" }
166
161
  end
167
162
 
168
163
  # === Abstract Adapter (Connection Management) ================== #
@@ -242,6 +237,14 @@ module ActiveRecord
242
237
  @connection_options[:database_prefix]
243
238
  end
244
239
 
240
+ def database_prefix_identifier(name)
241
+ if database_prefix_remote_server?
242
+ SQLServer::Utils.extract_identifiers("#{database_prefix}#{name}")
243
+ else
244
+ SQLServer::Utils.extract_identifiers(name)
245
+ end
246
+ end
247
+
245
248
  def version
246
249
  self.class::VERSION
247
250
  end
@@ -339,6 +342,20 @@ module ActiveRecord
339
342
  NoDatabaseError.new(message)
340
343
  when /data would be truncated/
341
344
  ValueTooLong.new(message)
345
+ when /Column '(.*)' is not the same data type as referencing column '(.*)' in foreign key/
346
+ pk_id, fk_id = SQLServer::Utils.extract_identifiers($1), SQLServer::Utils.extract_identifiers($2)
347
+ MismatchedForeignKey.new(
348
+ self,
349
+ message: message,
350
+ table: fk_id.schema,
351
+ foreign_key: fk_id.object,
352
+ target_table: pk_id.schema,
353
+ primary_key: pk_id.object
354
+ )
355
+ when /Cannot insert the value NULL into column.*does not allow nulls/
356
+ NotNullViolation.new(message)
357
+ when /Arithmetic overflow error/
358
+ RangeError.new(message)
342
359
  else
343
360
  super
344
361
  end