activerecord-sqlserver-adapter 5.2.1 → 6.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 (149) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/issue_template.md +23 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +29 -0
  6. data/.travis.yml +6 -8
  7. data/CHANGELOG.md +38 -24
  8. data/{Dockerfile → Dockerfile.ci} +1 -1
  9. data/Gemfile +48 -41
  10. data/Guardfile +9 -8
  11. data/README.md +9 -30
  12. data/RUNNING_UNIT_TESTS.md +3 -0
  13. data/Rakefile +14 -16
  14. data/VERSION +1 -1
  15. data/activerecord-sqlserver-adapter.gemspec +25 -14
  16. data/appveyor.yml +24 -17
  17. data/docker-compose.ci.yml +7 -5
  18. data/guides/RELEASING.md +11 -0
  19. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -4
  20. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +3 -4
  21. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -4
  22. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +3 -3
  23. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
  24. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +8 -7
  25. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +36 -0
  26. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +6 -4
  27. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -0
  28. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +88 -44
  29. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +9 -12
  30. data/lib/active_record/connection_adapters/sqlserver/errors.rb +2 -3
  31. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +46 -8
  32. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +16 -5
  33. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -7
  34. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +190 -164
  35. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +4 -2
  36. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +3 -1
  37. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +8 -8
  38. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +2 -2
  39. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +43 -44
  40. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +7 -9
  41. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +3 -3
  42. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -4
  43. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -3
  44. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +7 -4
  45. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +2 -2
  46. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +4 -3
  47. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +8 -8
  48. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -2
  49. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -2
  50. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -4
  51. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -3
  52. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -3
  53. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -1
  54. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -4
  55. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -3
  56. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -3
  57. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -4
  58. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -3
  59. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -2
  60. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -3
  61. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +6 -6
  62. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +8 -9
  63. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -3
  64. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -3
  65. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -4
  66. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -2
  67. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -3
  68. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -5
  69. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -4
  70. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +4 -3
  71. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -5
  72. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -4
  73. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -5
  74. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -4
  75. data/lib/active_record/connection_adapters/sqlserver/type.rb +37 -35
  76. data/lib/active_record/connection_adapters/sqlserver/utils.rb +10 -11
  77. data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
  78. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +128 -92
  79. data/lib/active_record/connection_adapters/sqlserver_column.rb +9 -5
  80. data/lib/active_record/sqlserver_base.rb +9 -1
  81. data/lib/active_record/tasks/sqlserver_database_tasks.rb +28 -32
  82. data/lib/activerecord-sqlserver-adapter.rb +3 -1
  83. data/lib/arel/visitors/sqlserver.rb +58 -24
  84. data/lib/arel_sqlserver.rb +4 -2
  85. data/test/appveyor/dbsetup.ps1 +4 -4
  86. data/test/cases/adapter_test_sqlserver.rb +214 -171
  87. data/test/cases/change_column_null_test_sqlserver.rb +14 -12
  88. data/test/cases/coerced_tests.rb +631 -356
  89. data/test/cases/column_test_sqlserver.rb +283 -284
  90. data/test/cases/connection_test_sqlserver.rb +17 -20
  91. data/test/cases/execute_procedure_test_sqlserver.rb +20 -20
  92. data/test/cases/fetch_test_sqlserver.rb +16 -22
  93. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
  94. data/test/cases/helper_sqlserver.rb +15 -15
  95. data/test/cases/in_clause_test_sqlserver.rb +36 -0
  96. data/test/cases/index_test_sqlserver.rb +15 -15
  97. data/test/cases/json_test_sqlserver.rb +25 -25
  98. data/test/cases/migration_test_sqlserver.rb +25 -29
  99. data/test/cases/order_test_sqlserver.rb +53 -54
  100. data/test/cases/pessimistic_locking_test_sqlserver.rb +27 -33
  101. data/test/cases/rake_test_sqlserver.rb +33 -45
  102. data/test/cases/schema_dumper_test_sqlserver.rb +107 -109
  103. data/test/cases/schema_test_sqlserver.rb +20 -26
  104. data/test/cases/scratchpad_test_sqlserver.rb +4 -4
  105. data/test/cases/showplan_test_sqlserver.rb +28 -35
  106. data/test/cases/specific_schema_test_sqlserver.rb +68 -65
  107. data/test/cases/transaction_test_sqlserver.rb +18 -20
  108. data/test/cases/trigger_test_sqlserver.rb +14 -13
  109. data/test/cases/utils_test_sqlserver.rb +70 -70
  110. data/test/cases/uuid_test_sqlserver.rb +13 -14
  111. data/test/debug.rb +8 -6
  112. data/test/migrations/create_clients_and_change_column_null.rb +3 -1
  113. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +4 -4
  114. data/test/models/sqlserver/booking.rb +3 -1
  115. data/test/models/sqlserver/customers_view.rb +3 -1
  116. data/test/models/sqlserver/datatype.rb +2 -0
  117. data/test/models/sqlserver/datatype_migration.rb +2 -0
  118. data/test/models/sqlserver/dollar_table_name.rb +3 -1
  119. data/test/models/sqlserver/edge_schema.rb +3 -3
  120. data/test/models/sqlserver/fk_has_fk.rb +3 -1
  121. data/test/models/sqlserver/fk_has_pk.rb +3 -1
  122. data/test/models/sqlserver/natural_pk_data.rb +4 -2
  123. data/test/models/sqlserver/natural_pk_int_data.rb +3 -1
  124. data/test/models/sqlserver/no_pk_data.rb +3 -1
  125. data/test/models/sqlserver/object_default.rb +3 -1
  126. data/test/models/sqlserver/quoted_table.rb +4 -2
  127. data/test/models/sqlserver/quoted_view_1.rb +3 -1
  128. data/test/models/sqlserver/quoted_view_2.rb +3 -1
  129. data/test/models/sqlserver/sst_memory.rb +3 -1
  130. data/test/models/sqlserver/string_default.rb +3 -1
  131. data/test/models/sqlserver/string_defaults_big_view.rb +3 -1
  132. data/test/models/sqlserver/string_defaults_view.rb +3 -1
  133. data/test/models/sqlserver/tinyint_pk.rb +3 -1
  134. data/test/models/sqlserver/trigger.rb +4 -2
  135. data/test/models/sqlserver/trigger_history.rb +3 -1
  136. data/test/models/sqlserver/upper.rb +3 -1
  137. data/test/models/sqlserver/uppered.rb +3 -1
  138. data/test/models/sqlserver/uuid.rb +3 -1
  139. data/test/schema/sqlserver_specific_schema.rb +22 -22
  140. data/test/support/coerceable_test_sqlserver.rb +15 -9
  141. data/test/support/connection_reflection.rb +3 -2
  142. data/test/support/core_ext/query_cache.rb +4 -1
  143. data/test/support/load_schema_sqlserver.rb +5 -5
  144. data/test/support/minitest_sqlserver.rb +3 -1
  145. data/test/support/paths_sqlserver.rb +11 -11
  146. data/test/support/rake_helpers.rb +13 -10
  147. data/test/support/sql_counter_sqlserver.rb +3 -4
  148. data/test/support/test_in_memory_oltp.rb +9 -7
  149. metadata +17 -7
@@ -1,13 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
4
6
  module CoreExt
5
7
  module ActiveRecord
6
-
7
8
  extend ActiveSupport::Concern
8
9
 
9
10
  module ClassMethods
10
-
11
11
  def execute_procedure(proc_name, *variables)
12
12
  if connection.respond_to?(:execute_procedure)
13
13
  connection.execute_procedure(proc_name, *variables)
@@ -15,9 +15,7 @@ module ActiveRecord
15
15
  []
16
16
  end
17
17
  end
18
-
19
18
  end
20
-
21
19
  end
22
20
  end
23
21
  end
@@ -1,12 +1,12 @@
1
- require 'active_record/attribute_methods'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/attribute_methods"
2
4
 
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  module SQLServer
6
8
  module CoreExt
7
9
  module AttributeMethods
8
-
9
-
10
10
  private
11
11
 
12
12
  def attributes_for_update(attribute_names)
@@ -15,7 +15,6 @@ module ActiveRecord
15
15
  column && column.respond_to?(:is_identity?) && column.is_identity?
16
16
  end
17
17
  end
18
-
19
18
  end
20
19
  end
21
20
  end
@@ -1,12 +1,13 @@
1
- require 'active_record/relation'
2
- require 'active_record/version'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/relation"
4
+ require "active_record/version"
3
5
 
4
6
  module ActiveRecord
5
7
  module ConnectionAdapters
6
8
  module SQLServer
7
9
  module CoreExt
8
10
  module Calculations
9
-
10
11
  # Same as original except we don't perform PostgreSQL hack that removes ordering.
11
12
  def calculate(operation, column_name)
12
13
  if has_include?(column_name)
@@ -15,7 +16,7 @@ module ActiveRecord
15
16
  if operation.to_s.downcase == "count"
16
17
  unless distinct_value || distinct_select?(column_name || select_for_count)
17
18
  relation.distinct!
18
- relation.select_values = [ klass.primary_key || table[Arel.star] ]
19
+ relation.select_values = [klass.primary_key || table[Arel.star]]
19
20
  end
20
21
  end
21
22
 
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
4
6
  module CoreExt
5
7
  module Explain
6
-
7
- SQLSERVER_STATEMENT_PREFIX = 'EXEC sp_executesql '.freeze
8
+ SQLSERVER_STATEMENT_PREFIX = "EXEC sp_executesql "
8
9
  SQLSERVER_STATEMENT_REGEXP = /N'(.+)', N'(.+)', (.+)/
9
10
 
10
11
  def exec_explain(queries)
@@ -32,7 +33,6 @@ module ActiveRecord
32
33
 
33
34
  executesql
34
35
  end
35
-
36
36
  end
37
37
  end
38
38
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ActiveSupport.on_load(:active_record) do
2
4
  silence_warnings do
3
5
  # Already defined in Rails
@@ -1,23 +1,24 @@
1
- require 'active_record/relation'
2
- require 'active_record/version'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/relation"
4
+ require "active_record/version"
3
5
 
4
6
  module ActiveRecord
5
7
  module ConnectionAdapters
6
8
  module SQLServer
7
9
  module CoreExt
8
10
  module FinderMethods
9
-
10
11
  private
11
12
 
12
13
  # Same as original except we order by values in distinct select if present.
13
14
  def construct_relation_for_exists(conditions)
14
- if distinct_value && offset_value
15
- relation = limit!(1)
15
+ conditions = sanitize_forbidden_attributes(conditions)
16
16
 
17
+ if distinct_value && offset_value
17
18
  if select_values.present?
18
- relation = relation.order(*select_values)
19
+ relation = order(*select_values).limit!(1)
19
20
  else
20
- relation = relation.except(:order)
21
+ relation = except(:order).limit!(1)
21
22
  end
22
23
  else
23
24
  relation = except(:select, :distinct, :order)._select!(::ActiveRecord::FinderMethods::ONE_AS_ONE).limit!(1)
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/associations/preloader"
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ module SQLServer
8
+ module CoreExt
9
+ module Preloader
10
+ private
11
+
12
+ def records_for(ids)
13
+ ids.each_slice(in_clause_length).flat_map do |slice|
14
+ scope.where(association_key_name => slice).load do |record|
15
+ # Processing only the first owner
16
+ # because the record is modified but not an owner
17
+ owner = owners_by_key[convert_key(record[association_key_name])].first
18
+ association = owner.association(reflection.name)
19
+ association.set_inverse_instance(record)
20
+ end.records
21
+ end
22
+ end
23
+
24
+ def in_clause_length
25
+ 10_000
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ ActiveSupport.on_load(:active_record) do
34
+ mod = ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::Preloader
35
+ ActiveRecord::Associations::Preloader::Association.prepend(mod)
36
+ end
@@ -1,15 +1,17 @@
1
- require 'active_record/relation'
2
- require 'active_record/version'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/relation"
4
+ require "active_record/version"
3
5
 
4
6
  module ActiveRecord
5
7
  module ConnectionAdapters
6
8
  module SQLServer
7
9
  module CoreExt
8
10
  module QueryMethods
9
-
10
11
  private
11
12
 
12
- # Copy of original from Rails master. This patch can be removed when adapter supports Rails 6.
13
+ # Copy of original from Rails master.
14
+ # This patch can be removed when adapter supports Rails version greater than 6.0.2.2
13
15
  def table_name_matches?(from)
14
16
  table_name = Regexp.escape(table.name)
15
17
  quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
@@ -9,10 +11,12 @@ module ActiveRecord
9
11
  def column_name_length
10
12
  128
11
13
  end
14
+ deprecate :column_name_length
12
15
 
13
16
  def table_name_length
14
17
  128
15
18
  end
19
+ deprecate :table_name_length
16
20
 
17
21
  def index_name_length
18
22
  128
@@ -21,14 +25,17 @@ module ActiveRecord
21
25
  def columns_per_table
22
26
  1024
23
27
  end
28
+ deprecate :columns_per_table
24
29
 
25
30
  def indexes_per_table
26
31
  999
27
32
  end
33
+ deprecate :indexes_per_table
28
34
 
29
35
  def columns_per_multicolumn_index
30
36
  16
31
37
  end
38
+ deprecate :columns_per_multicolumn_index
32
39
 
33
40
  def in_clause_length
34
41
  10_000
@@ -37,10 +44,12 @@ module ActiveRecord
37
44
  def sql_query_length
38
45
  65_536 * 4_096
39
46
  end
47
+ deprecate :sql_query_length
40
48
 
41
49
  def joins_per_query
42
50
  256
43
51
  end
52
+ deprecate :joins_per_query
44
53
 
45
54
  private
46
55
 
@@ -1,9 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
4
6
  module DatabaseStatements
7
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :dbcc, :explain, :save, :select, :set, :rollback) # :nodoc:
8
+ private_constant :READ_QUERY
9
+
10
+ def write_query?(sql) # :nodoc:
11
+ !READ_QUERY.match?(sql)
12
+ end
5
13
 
6
14
  def execute(sql, name = nil)
15
+ if preventing_writes? && write_query?(sql)
16
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
17
+ end
18
+
19
+ materialize_transactions
20
+
7
21
  if id_insert_table_name = query_requires_identity_insert?(sql)
8
22
  with_identity_insert_enabled(id_insert_table_name) { do_execute(sql, name) }
9
23
  else
@@ -11,7 +25,13 @@ module ActiveRecord
11
25
  end
12
26
  end
13
27
 
14
- def exec_query(sql, name = 'SQL', binds = [], prepare: false)
28
+ def exec_query(sql, name = "SQL", binds = [], prepare: false)
29
+ if preventing_writes? && write_query?(sql)
30
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
31
+ end
32
+
33
+ materialize_transactions
34
+
15
35
  sp_executesql(sql, name, binds, prepare: prepare)
16
36
  end
17
37
 
@@ -24,17 +44,17 @@ module ActiveRecord
24
44
  end
25
45
 
26
46
  def exec_delete(sql, name, binds)
27
- sql = sql.dup << '; SELECT @@ROWCOUNT AS AffectedRows'
47
+ sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
28
48
  super(sql, name, binds).rows.first.first
29
49
  end
30
50
 
31
51
  def exec_update(sql, name, binds)
32
- sql = sql.dup << '; SELECT @@ROWCOUNT AS AffectedRows'
52
+ sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
33
53
  super(sql, name, binds).rows.first.first
34
54
  end
35
55
 
36
56
  def begin_db_transaction
37
- do_execute 'BEGIN TRANSACTION'
57
+ do_execute "BEGIN TRANSACTION"
38
58
  end
39
59
 
40
60
  def transaction_isolation_levels
@@ -51,11 +71,11 @@ module ActiveRecord
51
71
  end
52
72
 
53
73
  def commit_db_transaction
54
- do_execute 'COMMIT TRANSACTION'
74
+ do_execute "COMMIT TRANSACTION"
55
75
  end
56
76
 
57
77
  def exec_rollback_db_transaction
58
- do_execute 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION'
78
+ do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
59
79
  end
60
80
 
61
81
  include Savepoints
@@ -71,9 +91,11 @@ module ActiveRecord
71
91
  def release_savepoint(name = current_savepoint_name)
72
92
  end
73
93
 
74
- def case_sensitive_comparison(table, attribute, column, value)
94
+ def case_sensitive_comparison(attribute, value)
95
+ column = column_for_attribute(attribute)
96
+
75
97
  if column.collation && !column.case_sensitive?
76
- table[attribute].eq(Arel::Nodes::Bin.new(value))
98
+ attribute.eq(Arel::Nodes::Bin.new(value))
77
99
  else
78
100
  super
79
101
  end
@@ -89,12 +111,12 @@ module ActiveRecord
89
111
  end
90
112
  end
91
113
 
92
- table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name table}".dup }
93
- total_sql = Array.wrap(combine_multi_statements(table_deletes + fixture_inserts))
114
+ table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name table}" }
115
+ total_sqls = Array.wrap(table_deletes + fixture_inserts)
94
116
 
95
117
  disable_referential_integrity do
96
118
  transaction(requires_new: true) do
97
- total_sql.each do |sql|
119
+ total_sqls.each do |sql|
98
120
  execute sql, "Fixtures Load"
99
121
  yield if block_given?
100
122
  end
@@ -107,11 +129,6 @@ module ActiveRecord
107
129
  end
108
130
  private :can_perform_case_insensitive_comparison_for?
109
131
 
110
- def combine_multi_statements(total_sql)
111
- total_sql
112
- end
113
- private :combine_multi_statements
114
-
115
132
  def default_insert_value(column)
116
133
  if column.is_identity?
117
134
  table_name = quote(quote_table_name(column.table_name))
@@ -122,16 +139,29 @@ module ActiveRecord
122
139
  end
123
140
  private :default_insert_value
124
141
 
142
+ def build_insert_sql(insert) # :nodoc:
143
+ sql = +"INSERT #{insert.into}"
144
+
145
+ if returning = insert.send(:insert_all).returning
146
+ sql << " OUTPUT " << returning.map { |column| "INSERTED.#{quote_column_name(column)}" }.join(", ")
147
+ end
148
+
149
+ sql << " #{insert.values_list}"
150
+ sql
151
+ end
152
+
125
153
  # === SQLServer Specific ======================================== #
126
154
 
127
155
  def execute_procedure(proc_name, *variables)
156
+ materialize_transactions
157
+
128
158
  vars = if variables.any? && variables.first.is_a?(Hash)
129
159
  variables.first.map { |k, v| "@#{k} = #{quote(v)}" }
130
160
  else
131
161
  variables.map { |v| quote(v) }
132
- end.join(', ')
162
+ end.join(", ")
133
163
  sql = "EXEC #{proc_name} #{vars}".strip
134
- name = 'Execute Procedure'
164
+ name = "Execute Procedure"
135
165
  log(sql, name) do
136
166
  case @connection_options[:mode]
137
167
  when :dblib
@@ -156,20 +186,22 @@ module ActiveRecord
156
186
 
157
187
  def use_database(database = nil)
158
188
  return if sqlserver_azure?
189
+
159
190
  name = SQLServer::Utils.extract_identifiers(database || @connection_options[:database]).quoted
160
191
  do_execute "USE #{name}" unless name.blank?
161
192
  end
162
193
 
163
194
  def user_options
164
195
  return {} if sqlserver_azure?
165
- rows = select_rows('DBCC USEROPTIONS WITH NO_INFOMSGS', 'SCHEMA')
196
+
197
+ rows = select_rows("DBCC USEROPTIONS WITH NO_INFOMSGS", "SCHEMA")
166
198
  rows = rows.first if rows.size == 2 && rows.last.empty?
167
199
  rows.reduce(HashWithIndifferentAccess.new) do |values, row|
168
200
  if row.instance_of? Hash
169
- set_option = row.values[0].gsub(/\s+/, '_')
201
+ set_option = row.values[0].gsub(/\s+/, "_")
170
202
  user_value = row.values[1]
171
- elsif row.instance_of? Array
172
- set_option = row[0].gsub(/\s+/, '_')
203
+ elsif row.instance_of? Array
204
+ set_option = row[0].gsub(/\s+/, "_")
173
205
  user_value = row[1]
174
206
  end
175
207
  values[set_option] = user_value
@@ -179,9 +211,9 @@ module ActiveRecord
179
211
 
180
212
  def user_options_dateformat
181
213
  if sqlserver_azure?
182
- select_value 'SELECT [dateformat] FROM [sys].[syslanguages] WHERE [langid] = @@LANGID', 'SCHEMA'
214
+ select_value "SELECT [dateformat] FROM [sys].[syslanguages] WHERE [langid] = @@LANGID", "SCHEMA"
183
215
  else
184
- user_options['dateformat']
216
+ user_options["dateformat"]
185
217
  end
186
218
  end
187
219
 
@@ -196,43 +228,44 @@ module ActiveRecord
196
228
  WHEN 5 THEN 'SNAPSHOT' END AS [isolation_level]
197
229
  FROM [sys].[dm_exec_sessions]
198
230
  WHERE [session_id] = @@SPID).squish
199
- select_value sql, 'SCHEMA'
231
+ select_value sql, "SCHEMA"
200
232
  else
201
- user_options['isolation_level']
233
+ user_options["isolation_level"]
202
234
  end
203
235
  end
204
236
 
205
237
  def user_options_language
206
238
  if sqlserver_azure?
207
- select_value 'SELECT @@LANGUAGE AS [language]', 'SCHEMA'
239
+ select_value "SELECT @@LANGUAGE AS [language]", "SCHEMA"
208
240
  else
209
- user_options['language']
241
+ user_options["language"]
210
242
  end
211
243
  end
212
244
 
213
245
  def newid_function
214
- select_value 'SELECT NEWID()'
246
+ select_value "SELECT NEWID()"
215
247
  end
216
248
 
217
249
  def newsequentialid_function
218
- select_value 'SELECT NEWSEQUENTIALID()'
250
+ select_value "SELECT NEWSEQUENTIALID()"
219
251
  end
220
252
 
221
-
222
253
  protected
223
254
 
224
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
255
+ def sql_for_insert(sql, pk, binds)
225
256
  if pk.nil?
226
257
  table_name = query_requires_identity_insert?(sql)
227
258
  pk = primary_key(table_name)
228
259
  end
260
+
229
261
  sql = if pk && use_output_inserted? && !database_prefix_remote_server?
230
262
  quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
231
263
  table_name ||= get_table_name(sql)
232
264
  exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql)
265
+
233
266
  if exclude_output_inserted
234
- id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? 'bigint' : exclude_output_inserted
235
- <<-SQL.strip_heredoc
267
+ id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? "bigint" : exclude_output_inserted
268
+ <<~SQL.squish
236
269
  DECLARE @ssaIdInsertTable table (#{quoted_pk} #{id_sql_type});
237
270
  #{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk} INTO @ssaIdInsertTable"}
238
271
  SELECT CAST(#{quoted_pk} AS #{id_sql_type}) FROM @ssaIdInsertTable
@@ -256,7 +289,9 @@ module ActiveRecord
256
289
 
257
290
  # === SQLServer Specific (Executing) ============================ #
258
291
 
259
- def do_execute(sql, name = 'SQL')
292
+ def do_execute(sql, name = "SQL")
293
+ materialize_transactions
294
+
260
295
  log(sql, name) { raw_connection_do(sql) }
261
296
  end
262
297
 
@@ -282,11 +317,12 @@ module ActiveRecord
282
317
 
283
318
  def sp_executesql_sql_type(attr)
284
319
  return attr.type.sqlserver_type if attr.type.respond_to?(:sqlserver_type)
320
+
285
321
  case value = attr.value_for_database
286
322
  when Numeric
287
- value > 2_147_483_647 ? 'bigint'.freeze : 'int'.freeze
323
+ value > 2_147_483_647 ? "bigint".freeze : "int".freeze
288
324
  else
289
- 'nvarchar(max)'.freeze
325
+ "nvarchar(max)".freeze
290
326
  end
291
327
  end
292
328
 
@@ -301,16 +337,16 @@ module ActiveRecord
301
337
  end
302
338
 
303
339
  def sp_executesql_sql(sql, types, params, name)
304
- if name == 'EXPLAIN'
340
+ if name == "EXPLAIN"
305
341
  params.each.with_index do |param, index|
306
342
  substitute_at_finder = /(@#{index})(?=(?:[^']|'[^']*')*$)/ # Finds unquoted @n values.
307
343
  sql = sql.sub substitute_at_finder, param.to_s
308
344
  end
309
345
  else
310
- types = quote(types.join(', '))
311
- params = params.map.with_index{ |p, i| "@#{i} = #{p}" }.join(', ') # Only p is needed, but with @i helps explain regexp.
346
+ types = quote(types.join(", "))
347
+ params = params.map.with_index { |p, i| "@#{i} = #{p}" }.join(", ") # Only p is needed, but with @i helps explain regexp.
312
348
  sql = "EXEC sp_executesql #{quote(sql)}"
313
- sql << ", #{types}, #{params}" unless params.empty?
349
+ sql += ", #{types}, #{params}" unless params.empty?
314
350
  end
315
351
  sql
316
352
  end
@@ -318,7 +354,14 @@ module ActiveRecord
318
354
  def raw_connection_do(sql)
319
355
  case @connection_options[:mode]
320
356
  when :dblib
321
- @connection.execute(sql).do
357
+ result = @connection.execute(sql)
358
+
359
+ # TinyTDS returns false instead of raising an exception if connection fails.
360
+ # Getting around this by raising an exception ourselves while this PR
361
+ # https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
362
+ raise TinyTds::Error, "failed to execute statement" if result.is_a?(FalseClass)
363
+
364
+ result.do
322
365
  end
323
366
  ensure
324
367
  @update_sql = false
@@ -336,8 +379,10 @@ module ActiveRecord
336
379
 
337
380
  def exclude_output_inserted_table_name?(table_name, sql)
338
381
  return false unless exclude_output_inserted_table_names?
382
+
339
383
  table_name ||= get_table_name(sql)
340
384
  return false unless table_name
385
+
341
386
  self.class.exclude_output_inserted_table_names[table_name]
342
387
  end
343
388
 
@@ -366,7 +411,7 @@ module ActiveRecord
366
411
 
367
412
  # === SQLServer Specific (Selecting) ============================ #
368
413
 
369
- def raw_select(sql, name = 'SQL', binds = [], options = {})
414
+ def raw_select(sql, name = "SQL", binds = [], options = {})
370
415
  log(sql, name, binds) { _raw_select(sql, options) }
371
416
  end
372
417
 
@@ -414,7 +459,6 @@ module ActiveRecord
414
459
  end
415
460
  handle
416
461
  end
417
-
418
462
  end
419
463
  end
420
464
  end
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
4
6
  module DatabaseTasks
5
-
6
7
  def create_database(database, options = {})
7
8
  name = SQLServer::Utils.extract_identifiers(database)
8
9
  db_options = create_database_options(options)
@@ -17,7 +18,7 @@ module ActiveRecord
17
18
  end
18
19
 
19
20
  def current_database
20
- select_value 'SELECT DB_NAME()'
21
+ select_value "SELECT DB_NAME()"
21
22
  end
22
23
 
23
24
  def charset
@@ -30,20 +31,20 @@ module ActiveRecord
30
31
 
31
32
  private
32
33
 
33
- def create_database_options(options={})
34
+ def create_database_options(options = {})
34
35
  keys = [:collate]
35
36
  copts = @connection_options
36
37
  options = {
37
38
  collate: copts[:collation]
38
39
  }.merge(options.symbolize_keys).select { |_, v|
39
40
  v.present?
40
- }.slice(*keys).map { |k,v|
41
+ }.slice(*keys).map { |k, v|
41
42
  "#{k.to_s.upcase} #{v}"
42
- }.join(' ')
43
+ }.join(" ")
43
44
  options
44
45
  end
45
46
 
46
- def create_database_edition_options(options={})
47
+ def create_database_edition_options(options = {})
47
48
  keys = [:maxsize, :edition, :service_objective]
48
49
  copts = @connection_options
49
50
  edition_options = {
@@ -52,17 +53,13 @@ module ActiveRecord
52
53
  service_objective: copts[:azure_service_objective]
53
54
  }.merge(options.symbolize_keys).select { |_, v|
54
55
  v.present?
55
- }.slice(*keys).map { |k,v|
56
+ }.slice(*keys).map { |k, v|
56
57
  "#{k.to_s.upcase} = #{v}"
57
- }.join(', ')
58
+ }.join(", ")
58
59
  edition_options = "( #{edition_options} )" if edition_options.present?
59
60
  edition_options
60
61
  end
61
-
62
62
  end
63
63
  end
64
64
  end
65
65
  end
66
-
67
-
68
-
@@ -1,7 +1,6 @@
1
- module ActiveRecord
1
+ # frozen_string_literal: true
2
2
 
3
+ module ActiveRecord
3
4
  class DeadlockVictim < WrappedDatabaseException
4
5
  end
5
-
6
-
7
6
  end