activerecord-sqlserver-adapter 7.2.7 → 8.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.
@@ -14,22 +14,24 @@ module ActiveRecord
14
14
  res
15
15
  end
16
16
 
17
- def drop_table(table_name, **options)
18
- # Mimic CASCADE option as best we can.
19
- if options[:force] == :cascade
20
- execute_procedure(:sp_fkeys, pktable_name: table_name).each do |fkdata|
21
- fktable = fkdata["FKTABLE_NAME"]
22
- fkcolmn = fkdata["FKCOLUMN_NAME"]
23
- pktable = fkdata["PKTABLE_NAME"]
24
- pkcolmn = fkdata["PKCOLUMN_NAME"]
25
- remove_foreign_key fktable, name: fkdata["FK_NAME"]
26
- execute "DELETE FROM #{quote_table_name(fktable)} WHERE #{quote_column_name(fkcolmn)} IN ( SELECT #{quote_column_name(pkcolmn)} FROM #{quote_table_name(pktable)} )"
17
+ def drop_table(*table_names, **options)
18
+ table_names.each do |table_name|
19
+ # Mimic CASCADE option as best we can.
20
+ if options[:force] == :cascade
21
+ execute_procedure(:sp_fkeys, pktable_name: table_name).each do |fkdata|
22
+ fktable = fkdata["FKTABLE_NAME"]
23
+ fkcolmn = fkdata["FKCOLUMN_NAME"]
24
+ pktable = fkdata["PKTABLE_NAME"]
25
+ pkcolmn = fkdata["PKCOLUMN_NAME"]
26
+ remove_foreign_key fktable, name: fkdata["FK_NAME"]
27
+ execute "DELETE FROM #{quote_table_name(fktable)} WHERE #{quote_column_name(fkcolmn)} IN ( SELECT #{quote_column_name(pkcolmn)} FROM #{quote_table_name(pktable)} )"
28
+ end
29
+ end
30
+ if options[:if_exists] && version_year < 2016
31
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}", "SCHEMA"
32
+ else
33
+ super
27
34
  end
28
- end
29
- if options[:if_exists] && version_year < 2016
30
- execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}", "SCHEMA"
31
- else
32
- super
33
35
  end
34
36
  end
35
37
 
@@ -37,18 +39,16 @@ module ActiveRecord
37
39
  data = select("EXEC sp_helpindex #{quote(table_name)}", "SCHEMA") rescue []
38
40
 
39
41
  data.reduce([]) do |indexes, index|
40
- index = index.with_indifferent_access
41
-
42
- if index[:index_description].match?(/primary key/)
42
+ if index['index_description'].match?(/primary key/)
43
43
  indexes
44
44
  else
45
- name = index[:index_name]
46
- unique = index[:index_description].match?(/unique/)
45
+ name = index['index_name']
46
+ unique = index['index_description'].match?(/unique/)
47
47
  where = select_value("SELECT [filter_definition] FROM sys.indexes WHERE name = #{quote(name)}", "SCHEMA")
48
48
  orders = {}
49
49
  columns = []
50
50
 
51
- index[:index_keys].split(",").each do |column|
51
+ index['index_keys'].split(",").each do |column|
52
52
  column.strip!
53
53
 
54
54
  if column.end_with?("(-)")
@@ -347,16 +347,12 @@ module ActiveRecord
347
347
 
348
348
  def columns_for_distinct(columns, orders)
349
349
  order_columns = orders.reject(&:blank?).map { |s|
350
- s = visitor.compile(s) unless s.is_a?(String)
350
+ s = s.to_sql unless s.is_a?(String)
351
351
  s.gsub(/\s+(?:ASC|DESC)\b/i, "")
352
352
  .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
353
- }
354
- .reject(&:blank?)
355
- .reject { |s| columns.include?(s) }
353
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
356
354
 
357
- order_columns_aliased = order_columns.map.with_index { |column, i| "#{column} AS alias_#{i}" }
358
-
359
- (order_columns_aliased << super).join(", ")
355
+ (order_columns << super).join(", ")
360
356
  end
361
357
 
362
358
  def update_table_definition(table_name, base)
@@ -484,16 +480,15 @@ module ActiveRecord
484
480
  end
485
481
 
486
482
  def column_definitions(table_name)
487
- identifier = database_prefix_identifier(table_name)
488
- database = identifier.fully_qualified_database_quoted
489
- view_exists = view_exists?(table_name)
490
- view_tblnm = view_table_name(table_name) if view_exists
483
+ identifier = database_prefix_identifier(table_name)
484
+ database = identifier.fully_qualified_database_quoted
485
+ view_exists = view_exists?(table_name)
491
486
 
492
487
  if view_exists
493
488
  sql = <<~SQL
494
489
  SELECT LOWER(c.COLUMN_NAME) AS [name], c.COLUMN_DEFAULT AS [default]
495
490
  FROM #{database}.INFORMATION_SCHEMA.COLUMNS c
496
- WHERE c.TABLE_NAME = #{quote(view_tblnm)}
491
+ WHERE c.TABLE_NAME = #{quote(view_table_name(table_name))}
497
492
  SQL
498
493
  results = internal_exec_query(sql, "SCHEMA")
499
494
  default_functions = results.each.with_object({}) { |row, out| out[row["name"]] = row["default"] }.compact
@@ -502,88 +497,103 @@ module ActiveRecord
502
497
  sql = column_definitions_sql(database, identifier)
503
498
 
504
499
  binds = []
505
- nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
500
+ nv128 = SQLServer::Type::UnicodeVarchar.new(limit: 128)
506
501
  binds << Relation::QueryAttribute.new("TABLE_NAME", identifier.object, nv128)
507
502
  binds << Relation::QueryAttribute.new("TABLE_SCHEMA", identifier.schema, nv128) unless identifier.schema.blank?
503
+
508
504
  results = internal_exec_query(sql, "SCHEMA", binds)
505
+ raise ActiveRecord::StatementInvalid, "Table '#{table_name}' doesn't exist" if results.empty?
509
506
 
510
507
  columns = results.map do |ci|
511
- ci = ci.symbolize_keys
512
- ci[:_type] = ci[:type]
513
- ci[:table_name] = view_tblnm || table_name
514
- ci[:type] = case ci[:type]
515
- when /^bit|image|text|ntext|datetime$/
516
- ci[:type]
517
- when /^datetime2|datetimeoffset$/i
518
- "#{ci[:type]}(#{ci[:datetime_precision]})"
519
- when /^time$/i
520
- "#{ci[:type]}(#{ci[:datetime_precision]})"
521
- when /^numeric|decimal$/i
522
- "#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
523
- when /^float|real$/i
524
- "#{ci[:type]}"
525
- when /^char|nchar|varchar|nvarchar|binary|varbinary|bigint|int|smallint$/
526
- ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
527
- else
528
- ci[:type]
529
- end
530
- ci[:default_value],
531
- ci[:default_function] = begin
532
- default = ci[:default_value]
533
- if default.nil? && view_exists
534
- view_column = views_real_column_name(table_name, ci[:name]).downcase
535
- default = default_functions[view_column] if view_column.present?
536
- end
537
- case default
538
- when nil
539
- [nil, nil]
540
- when /\A\((\w+\(\))\)\Z/
541
- default_function = Regexp.last_match[1]
542
- [nil, default_function]
543
- when /\A\(N'(.*)'\)\Z/m
544
- string_literal = SQLServer::Utils.unquote_string(Regexp.last_match[1])
545
- [string_literal, nil]
546
- when /CREATE DEFAULT/mi
547
- [nil, nil]
548
- else
549
- type = case ci[:type]
550
- when /smallint|int|bigint/ then ci[:_type]
551
- else ci[:type]
552
- end
553
- value = default.match(/\A\((.*)\)\Z/m)[1]
554
- value = select_value("SELECT CAST(#{value} AS #{type}) AS value", "SCHEMA")
555
- [value, nil]
556
- end
508
+ col = {
509
+ name: ci["name"],
510
+ numeric_scale: ci["numeric_scale"],
511
+ numeric_precision: ci["numeric_precision"],
512
+ datetime_precision: ci["datetime_precision"],
513
+ collation: ci["collation"],
514
+ ordinal_position: ci["ordinal_position"],
515
+ length: ci["length"]
516
+ }
517
+
518
+ col[:table_name] = view_exists ? view_table_name(table_name) : table_name
519
+ col[:type] = column_type(ci: ci)
520
+ col[:default_value], col[:default_function] = default_value_and_function(default: ci['default_value'],
521
+ name: ci['name'],
522
+ type: col[:type],
523
+ original_type: ci['type'],
524
+ view_exists: view_exists,
525
+ table_name: table_name,
526
+ default_functions: default_functions)
527
+
528
+ col[:null] = ci['is_nullable'].to_i == 1
529
+ col[:is_primary] = ci['is_primary'].to_i == 1
530
+
531
+ if [true, false].include?(ci['is_identity'])
532
+ col[:is_identity] = ci['is_identity']
533
+ else
534
+ col[:is_identity] = ci['is_identity'].to_i == 1
557
535
  end
558
- ci[:null] = ci[:is_nullable].to_i == 1
559
- ci.delete(:is_nullable)
560
- ci[:is_primary] = ci[:is_primary].to_i == 1
561
- ci[:is_identity] = ci[:is_identity].to_i == 1 unless [TrueClass, FalseClass].include?(ci[:is_identity].class)
562
- ci
563
- end
564
536
 
565
- # Since Rails 7, it's expected that all adapter raise error when table doesn't exists.
566
- # I'm not aware of the possibility of tables without columns on SQL Server (postgres have those).
567
- # Raise error if the method return an empty array
568
- columns.tap do |result|
569
- raise ActiveRecord::StatementInvalid, "Table '#{table_name}' doesn't exist" if result.empty?
537
+ col
570
538
  end
539
+
540
+ columns
571
541
  end
572
542
 
573
- def column_definitions_sql(database, identifier)
574
- database = "TEMPDB" if identifier.temporary_table?
575
- schema_name = "schema_name()"
543
+ def default_value_and_function(default:, name:, type:, original_type:, view_exists:, table_name:, default_functions:)
544
+ if default.nil? && view_exists
545
+ view_column = views_real_column_name(table_name, name).downcase
546
+ default = default_functions[view_column] if view_column.present?
547
+ end
548
+
549
+ case default
550
+ when nil
551
+ [nil, nil]
552
+ when /\A\((\w+\(\))\)\Z/
553
+ default_function = Regexp.last_match[1]
554
+ [nil, default_function]
555
+ when /\A\(N'(.*)'\)\Z/m
556
+ string_literal = SQLServer::Utils.unquote_string(Regexp.last_match[1])
557
+ [string_literal, nil]
558
+ when /CREATE DEFAULT/mi
559
+ [nil, nil]
560
+ else
561
+ type = case type
562
+ when /smallint|int|bigint/ then original_type
563
+ else type
564
+ end
565
+ value = default.match(/\A\((.*)\)\Z/m)[1]
566
+ value = select_value("SELECT CAST(#{value} AS #{type}) AS value", "SCHEMA")
567
+ [value, nil]
568
+ end
569
+ end
576
570
 
577
- if prepared_statements
578
- object_name = "@0"
579
- schema_name = "@1" if identifier.schema.present?
571
+ def column_type(ci:)
572
+ case ci['type']
573
+ when /^bit|image|text|ntext|datetime$/
574
+ ci['type']
575
+ when /^datetime2|datetimeoffset$/i
576
+ "#{ci['type']}(#{ci['datetime_precision']})"
577
+ when /^time$/i
578
+ "#{ci['type']}(#{ci['datetime_precision']})"
579
+ when /^numeric|decimal$/i
580
+ "#{ci['type']}(#{ci['numeric_precision']},#{ci['numeric_scale']})"
581
+ when /^float|real$/i
582
+ "#{ci['type']}"
583
+ when /^char|nchar|varchar|nvarchar|binary|varbinary|bigint|int|smallint$/
584
+ ci['length'].to_i == -1 ? "#{ci['type']}(max)" : "#{ci['type']}(#{ci['length']})"
580
585
  else
581
- object_name = quote(identifier.object)
582
- schema_name = quote(identifier.schema) if identifier.schema.present?
586
+ ci['type']
583
587
  end
588
+ end
584
589
 
585
- object_id_arg = identifier.schema.present? ? "CONCAT('.',#{schema_name},'.',#{object_name})" : "CONCAT('..',#{object_name})"
586
- object_id_arg = "CONCAT('#{database}',#{object_id_arg})"
590
+ def column_definitions_sql(database, identifier)
591
+ object_name = prepared_statements ? "@0" : quote(identifier.object)
592
+ schema_name = if identifier.schema.blank?
593
+ "schema_name()"
594
+ else
595
+ prepared_statements ? "@1" : quote(identifier.schema)
596
+ end
587
597
 
588
598
  %{
589
599
  SELECT
@@ -638,7 +648,7 @@ module ActiveRecord
638
648
  AND k.unique_index_id = ic.index_id
639
649
  AND c.column_id = ic.column_id
640
650
  WHERE
641
- o.Object_ID = Object_ID(#{object_id_arg})
651
+ o.name = #{object_name}
642
652
  AND s.name = #{schema_name}
643
653
  ORDER BY
644
654
  c.column_id
@@ -660,7 +670,7 @@ module ActiveRecord
660
670
  end
661
671
 
662
672
  def remove_default_constraint(table_name, column_name)
663
- # If there are foreign keys in this table, we could still get back a 2D array, so flatten just in case.
673
+ # If their are foreign keys in this table, we could still get back a 2D array, so flatten just in case.
664
674
  execute_procedure(:sp_helpconstraint, table_name, "nomsg").flatten.select do |row|
665
675
  row["constraint_type"] == "DEFAULT on column #{column_name}"
666
676
  end.each do |row|
@@ -697,7 +707,7 @@ module ActiveRecord
697
707
  elsif s.match?(/^\s*UPDATE\s+.*/i)
698
708
  s.match(/UPDATE\s+([^\(\s]+)\s*/i)[1]
699
709
  else
700
- s.match(/FROM[\s|\(]+((\[[^\(\]]+\])|[^\(\s]+)\s*/i)[1]
710
+ s.match(/FROM\s+((\[[^\(\]]+\])|[^\(\s]+)\s*/i)[1]
701
711
  end.strip
702
712
  end
703
713
 
@@ -713,25 +723,26 @@ module ActiveRecord
713
723
 
714
724
  def view_table_name(table_name)
715
725
  view_info = view_information(table_name)
716
- view_info ? get_table_name(view_info["VIEW_DEFINITION"]) : table_name
726
+ view_info.present? ? get_table_name(view_info["VIEW_DEFINITION"]) : table_name
717
727
  end
718
728
 
719
729
  def view_information(table_name)
720
730
  @view_information ||= {}
731
+
721
732
  @view_information[table_name] ||= begin
722
733
  identifier = SQLServer::Utils.extract_identifiers(table_name)
723
734
  information_query_table = identifier.database.present? ? "[#{identifier.database}].[INFORMATION_SCHEMA].[VIEWS]" : "[INFORMATION_SCHEMA].[VIEWS]"
724
- view_info = select_one "SELECT * FROM #{information_query_table} WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", "SCHEMA"
725
-
726
- if view_info
727
- view_info = view_info.with_indifferent_access
728
- if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
729
- view_info[:VIEW_DEFINITION] = begin
730
- select_values("EXEC sp_helptext #{identifier.object_quoted}", "SCHEMA").join
731
- rescue
732
- warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
733
- nil
734
- end
735
+
736
+ view_info = select_one("SELECT * FROM #{information_query_table} WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", "SCHEMA").to_h
737
+
738
+ if view_info.present?
739
+ if view_info['VIEW_DEFINITION'].blank? || view_info['VIEW_DEFINITION'].length == 4000
740
+ view_info['VIEW_DEFINITION'] = begin
741
+ select_values("EXEC sp_helptext #{identifier.object_quoted}", "SCHEMA").join
742
+ rescue
743
+ warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
744
+ nil
745
+ end
735
746
  end
736
747
  end
737
748
 
@@ -740,8 +751,8 @@ module ActiveRecord
740
751
  end
741
752
 
742
753
  def views_real_column_name(table_name, column_name)
743
- view_definition = view_information(table_name)[:VIEW_DEFINITION]
744
- return column_name unless view_definition
754
+ view_definition = view_information(table_name)['VIEW_DEFINITION']
755
+ return column_name if view_definition.blank?
745
756
 
746
757
  # Remove "CREATE VIEW ... AS SELECT ..." and then match the column name.
747
758
  match_data = view_definition.sub(/CREATE\s+VIEW.*AS\s+SELECT\s/, '').match(/([\w-]*)\s+AS\s+#{column_name}\W/im)
@@ -16,6 +16,7 @@ module ActiveRecord
16
16
  sql = to_sql(arel)
17
17
  result = with_showplan_on { internal_exec_query(sql, "EXPLAIN", binds) }
18
18
  printer = showplan_printer.new(result)
19
+
19
20
  printer.pp
20
21
  end
21
22
 
@@ -36,9 +36,10 @@ module ActiveRecord
36
36
 
37
37
  def cast_value(value)
38
38
  value = super
39
- return if value.blank?
40
39
 
41
- value = value.change year: 2000, month: 01, day: 01
40
+ return value unless value.is_a?(::Time)
41
+
42
+ value = value.change(year: 2000, month: 01, day: 01)
42
43
  apply_seconds_precision(value)
43
44
  end
44
45
 
@@ -81,10 +81,6 @@ module ActiveRecord
81
81
  parts.hash
82
82
  end
83
83
 
84
- def temporary_table?
85
- object.start_with?("#")
86
- end
87
-
88
84
  protected
89
85
 
90
86
  def parse_raw_name
@@ -4,9 +4,7 @@ require "tiny_tds"
4
4
  require "base64"
5
5
  require "active_record"
6
6
  require "arel_sqlserver"
7
- require "active_record/connection_adapters/abstract_adapter"
8
7
  require "active_record/connection_adapters/sqlserver/core_ext/active_record"
9
- require "active_record/connection_adapters/sqlserver/core_ext/calculations"
10
8
  require "active_record/connection_adapters/sqlserver/core_ext/explain"
11
9
  require "active_record/connection_adapters/sqlserver/core_ext/explain_subscriber"
12
10
  require "active_record/connection_adapters/sqlserver/core_ext/attribute_methods"
@@ -431,36 +429,28 @@ module ActiveRecord
431
429
  TYPE_MAP
432
430
  end
433
431
 
434
- def translate_exception(e, message:, sql:, binds:)
432
+ def translate_exception(exception, message:, sql:, binds:)
435
433
  case message
436
434
  when /(SQL Server client is not connected)|(failed to execute statement)/i
437
- ConnectionNotEstablished.new(message)
435
+ ConnectionNotEstablished.new(message, connection_pool: @pool)
438
436
  when /(cannot insert duplicate key .* with unique index) | (violation of (unique|primary) key constraint)/i
439
- RecordNotUnique.new(message, sql: sql, binds: binds)
437
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
440
438
  when /(conflicted with the foreign key constraint) | (The DELETE statement conflicted with the REFERENCE constraint)/i
441
- InvalidForeignKey.new(message, sql: sql, binds: binds)
439
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
442
440
  when /has been chosen as the deadlock victim/i
443
- DeadlockVictim.new(message, sql: sql, binds: binds)
441
+ DeadlockVictim.new(message, sql: sql, binds: binds, connection_pool: @pool)
444
442
  when /database .* does not exist/i
445
- NoDatabaseError.new(message)
443
+ NoDatabaseError.new(message, connection_pool: @pool)
446
444
  when /data would be truncated/
447
- ValueTooLong.new(message, sql: sql, binds: binds)
445
+ ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
448
446
  when /connection timed out/
449
- StatementTimeout.new(message, sql: sql, binds: binds)
447
+ StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
450
448
  when /Column '(.*)' is not the same data type as referencing column '(.*)' in foreign key/
451
- pk_id, fk_id = SQLServer::Utils.extract_identifiers($1), SQLServer::Utils.extract_identifiers($2)
452
- MismatchedForeignKey.new(
453
- self,
454
- message: message,
455
- table: fk_id.schema,
456
- foreign_key: fk_id.object,
457
- target_table: pk_id.schema,
458
- primary_key: pk_id.object
459
- )
449
+ MismatchedForeignKey.new(message: message, connection_pool: @pool)
460
450
  when /Cannot insert the value NULL into column.*does not allow nulls/
461
- NotNullViolation.new(message, sql: sql, binds: binds)
451
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
462
452
  when /Arithmetic overflow error/
463
- RangeError.new(message, sql: sql, binds: binds)
453
+ RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
464
454
  else
465
455
  super
466
456
  end
@@ -7,17 +7,10 @@ require "models/post"
7
7
  require "models/subscriber"
8
8
  require "models/minimalistic"
9
9
  require "models/college"
10
- require "models/dog"
11
- require "models/other_dog"
12
10
 
13
11
  class AdapterTestSQLServer < ActiveRecord::TestCase
14
12
  fixtures :tasks
15
13
 
16
- let(:arunit_connection) { Topic.lease_connection }
17
- let(:arunit2_connection) { College.lease_connection }
18
- let(:arunit_database) { arunit_connection.pool.db_config.database }
19
- let(:arunit2_database) { arunit2_connection.pool.db_config.database }
20
-
21
14
  let(:basic_insert_sql) { "INSERT INTO [funny_jokes] ([name]) VALUES('Knock knock')" }
22
15
  let(:basic_update_sql) { "UPDATE [customers] SET [address_street] = NULL WHERE [id] = 2" }
23
16
  let(:basic_select_sql) { "SELECT * FROM [customers] WHERE ([customers].[id] = 1)" }
@@ -57,7 +50,8 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
57
50
  assert Topic.table_exists?, "Topics table name of 'dbo.topics' should return true for exists."
58
51
 
59
52
  # Test when database and owner included in table name.
60
- Topic.table_name = "#{arunit_database}.dbo.topics"
53
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
54
+ Topic.table_name = "#{db_config.database}.dbo.topics"
61
55
  assert Topic.table_exists?, "Topics table name of '[DATABASE].dbo.topics' should return true for exists."
62
56
  ensure
63
57
  Topic.table_name = "topics"
@@ -65,6 +59,12 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
65
59
  end
66
60
 
67
61
  it "test table existence across database schemas" do
62
+ arunit_connection = Topic.lease_connection
63
+ arunit2_connection = College.lease_connection
64
+
65
+ arunit_database = arunit_connection.pool.db_config.database
66
+ arunit2_database = arunit2_connection.pool.db_config.database
67
+
68
68
  # Assert that connections use different default databases schemas.
69
69
  assert_not_equal arunit_database, arunit2_database
70
70
 
@@ -200,9 +200,6 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
200
200
  @identity_insert_sql_non_dbo_sp = "EXEC sp_executesql N'INSERT INTO [test].[aliens] ([id],[name]) VALUES (@0, @1)', N'@0 int, @1 nvarchar(255)', @0 = 420, @1 = N'Mork'"
201
201
  @identity_insert_sql_non_dbo_unquoted_sp = "EXEC sp_executesql N'INSERT INTO test.aliens (id, name) VALUES (@0, @1)', N'@0 int, @1 nvarchar(255)', @0 = 420, @1 = N'Mork'"
202
202
  @identity_insert_sql_non_dbo_unordered_sp = "EXEC sp_executesql N'INSERT INTO [test].[aliens] ([name],[id]) VALUES (@0, @1)', N'@0 nvarchar(255), @1 int', @0 = N'Mork', @1 = 420"
203
-
204
- @non_identity_insert_sql_cross_database = "INSERT INTO #{arunit2_database}.dbo.dogs SELECT * FROM #{arunit_database}.dbo.dogs"
205
- @identity_insert_sql_cross_database = "INSERT INTO #{arunit2_database}.dbo.dogs(id) SELECT id FROM #{arunit_database}.dbo.dogs"
206
203
  end
207
204
 
208
205
  it "return quoted table_name to #query_requires_identity_insert? when INSERT sql contains id column" do
@@ -219,32 +216,20 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
219
216
  assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_sp)
220
217
  assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unquoted_sp)
221
218
  assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unordered_sp)
222
-
223
- assert_equal "[#{arunit2_database}].[dbo].[dogs]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_cross_database)
224
219
  end
225
220
 
226
221
  it "return false to #query_requires_identity_insert? for normal SQL" do
227
- [basic_insert_sql, basic_update_sql, basic_select_sql, @non_identity_insert_sql_cross_database].each do |sql|
222
+ [basic_insert_sql, basic_update_sql, basic_select_sql].each do |sql|
228
223
  assert !connection.send(:query_requires_identity_insert?, sql), "SQL was #{sql}"
229
224
  end
230
225
  end
231
226
 
232
- it "find identity column" do
227
+ it "find identity column using #identity_columns" do
233
228
  task_id_column = Task.columns_hash["id"]
234
229
  assert_equal task_id_column.name, connection.send(:identity_columns, Task.table_name).first.name
235
230
  assert_equal task_id_column.sql_type, connection.send(:identity_columns, Task.table_name).first.sql_type
236
231
  end
237
232
 
238
- it "find identity column cross database" do
239
- id_column = Dog.columns_hash["id"]
240
- assert_equal id_column.name, arunit2_connection.send(:identity_columns, Dog.table_name).first.name
241
- assert_equal id_column.sql_type, arunit2_connection.send(:identity_columns, Dog.table_name).first.sql_type
242
-
243
- id_column = OtherDog.columns_hash["id"]
244
- assert_equal id_column.name, arunit_connection.send(:identity_columns, OtherDog.table_name).first.name
245
- assert_equal id_column.sql_type, arunit_connection.send(:identity_columns, OtherDog.table_name).first.sql_type
246
- end
247
-
248
233
  it "return an empty array when calling #identity_columns for a table_name with no identity" do
249
234
  _(connection.send(:identity_columns, Subscriber.table_name)).must_equal []
250
235
  end
@@ -479,13 +464,13 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
479
464
  assert SSTestStringDefaultsView.lease_connection.data_source_exists?(SSTestStringDefaultsView.table_name)
480
465
  end
481
466
 
482
- # That have more than 4000 chars for their defintion
467
+ # That have more than 4000 chars for their definition
483
468
 
484
- it "cope with null returned for the defintion" do
469
+ it "cope with null returned for the definition" do
485
470
  assert_nothing_raised() { SSTestStringDefaultsBigView.columns }
486
471
  end
487
472
 
488
- it "using alternate view defintion still be able to find real default" do
473
+ it "using alternate view definition still be able to find real default" do
489
474
  assert_equal "null", SSTestStringDefaultsBigView.new.pretend_null,
490
475
  SSTestStringDefaultsBigView.columns_hash["pretend_null"].inspect
491
476
  end
@@ -557,7 +542,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
557
542
  @conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
558
543
 
559
544
  ActiveRecord::Base.while_preventing_writes do
560
- assert_equal 1, @conn.execute("SELECT * FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
545
+ assert_equal 1, @conn.execute("SELECT * FROM [subscribers] WHERE [subscribers].[nick] = 'aido'").count
561
546
  end
562
547
  end
563
548
  end
@@ -596,6 +581,28 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
596
581
  end
597
582
  end
598
583
 
584
+ describe "mismatched foreign keys error" do
585
+ def setup
586
+ @conn = ActiveRecord::Base.lease_connection
587
+ end
588
+
589
+ it 'raises an error when the foreign key is mismatched' do
590
+ error = assert_raises(ActiveRecord::MismatchedForeignKey) do
591
+ @conn.add_reference :engines, :old_car
592
+ @conn.add_foreign_key :engines, :old_cars
593
+ end
594
+
595
+ assert_match(
596
+ %r/Column 'old_cars\.id' is not the same data type as referencing column 'engines\.old_car_id' in foreign key '.*'/,
597
+ error.message
598
+ )
599
+ assert_not_nil error.cause
600
+ assert_equal @conn.pool, error.connection_pool
601
+ ensure
602
+ @conn.execute("ALTER TABLE engines DROP COLUMN old_car_id") rescue nil
603
+ end
604
+ end
605
+
599
606
  describe "placeholder conditions" do
600
607
  it 'using time placeholder' do
601
608
  assert_equal Task.where("starting < ?", Time.now).count, 1
@@ -609,13 +616,4 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
609
616
  assert_equal Task.where("starting < ?", DateTime.current).count, 1
610
617
  end
611
618
  end
612
-
613
- describe "distinct select query" do
614
- it "generated SQL does not contain unnecessary alias projection" do
615
- sqls = capture_sql do
616
- Post.includes(:comments).joins(:comments).first
617
- end
618
- assert_no_match(/AS alias_0/, sqls.first)
619
- end
620
- end
621
619
  end