activerecord-sqlserver-adapter 6.0.0.rc1 → 6.1.0.0.rc1

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/.github/workflows/ci.yml +26 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +29 -0
  5. data/CHANGELOG.md +18 -23
  6. data/Gemfile +11 -5
  7. data/Guardfile +9 -8
  8. data/README.md +32 -3
  9. data/RUNNING_UNIT_TESTS.md +1 -1
  10. data/Rakefile +12 -16
  11. data/VERSION +1 -1
  12. data/activerecord-sqlserver-adapter.gemspec +4 -4
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +0 -4
  14. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +1 -4
  15. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +3 -13
  16. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +8 -5
  17. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +2 -3
  18. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +2 -3
  19. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
  20. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +58 -43
  21. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +7 -12
  22. data/lib/active_record/connection_adapters/sqlserver/errors.rb +0 -3
  23. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +15 -15
  24. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -3
  25. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +16 -10
  26. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +131 -105
  27. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +6 -8
  28. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +2 -2
  29. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +1 -1
  30. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +25 -7
  31. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +1 -5
  32. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +6 -10
  33. data/lib/active_record/connection_adapters/sqlserver/type.rb +36 -35
  34. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +0 -2
  35. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +0 -2
  36. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +0 -2
  37. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +2 -2
  38. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +0 -2
  39. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +2 -3
  40. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +2 -3
  41. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +0 -2
  42. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +0 -2
  43. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +0 -2
  44. data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
  45. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +0 -2
  46. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +0 -2
  47. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +0 -1
  48. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +0 -2
  49. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +0 -2
  50. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +0 -2
  51. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +0 -2
  52. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +0 -2
  53. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +0 -2
  54. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +0 -2
  55. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +2 -3
  56. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +6 -9
  57. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +0 -2
  58. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +0 -2
  59. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +1 -3
  60. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +0 -2
  61. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +0 -2
  62. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +0 -2
  63. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +0 -2
  64. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +1 -2
  65. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +1 -3
  66. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +0 -2
  67. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +1 -3
  68. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +0 -2
  69. data/lib/active_record/connection_adapters/sqlserver/utils.rb +8 -11
  70. data/lib/active_record/connection_adapters/sqlserver/version.rb +0 -2
  71. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +170 -136
  72. data/lib/active_record/connection_adapters/sqlserver_column.rb +16 -1
  73. data/lib/active_record/sqlserver_base.rb +9 -15
  74. data/lib/active_record/tasks/sqlserver_database_tasks.rb +36 -39
  75. data/lib/activerecord-sqlserver-adapter.rb +1 -1
  76. data/lib/arel/visitors/sqlserver.rb +126 -50
  77. data/lib/arel_sqlserver.rb +2 -2
  78. data/test/cases/adapter_test_sqlserver.rb +203 -190
  79. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  80. data/test/cases/change_column_null_test_sqlserver.rb +12 -12
  81. data/test/cases/coerced_tests.rb +656 -318
  82. data/test/cases/column_test_sqlserver.rb +285 -284
  83. data/test/cases/connection_test_sqlserver.rb +15 -20
  84. data/test/cases/disconnected_test_sqlserver.rb +39 -0
  85. data/test/cases/execute_procedure_test_sqlserver.rb +26 -19
  86. data/test/cases/fetch_test_sqlserver.rb +14 -22
  87. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +12 -18
  88. data/test/cases/helper_sqlserver.rb +13 -15
  89. data/test/cases/in_clause_test_sqlserver.rb +36 -9
  90. data/test/cases/index_test_sqlserver.rb +13 -15
  91. data/test/cases/json_test_sqlserver.rb +23 -25
  92. data/test/cases/lateral_test_sqlserver.rb +35 -0
  93. data/test/cases/migration_test_sqlserver.rb +71 -26
  94. data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
  95. data/test/cases/order_test_sqlserver.rb +57 -53
  96. data/test/cases/pessimistic_locking_test_sqlserver.rb +25 -33
  97. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  98. data/test/cases/rake_test_sqlserver.rb +33 -46
  99. data/test/cases/schema_dumper_test_sqlserver.rb +121 -108
  100. data/test/cases/schema_test_sqlserver.rb +18 -26
  101. data/test/cases/scratchpad_test_sqlserver.rb +2 -4
  102. data/test/cases/showplan_test_sqlserver.rb +24 -33
  103. data/test/cases/specific_schema_test_sqlserver.rb +66 -65
  104. data/test/cases/transaction_test_sqlserver.rb +16 -19
  105. data/test/cases/trigger_test_sqlserver.rb +12 -12
  106. data/test/cases/utils_test_sqlserver.rb +68 -70
  107. data/test/cases/uuid_test_sqlserver.rb +11 -13
  108. data/test/debug.rb +6 -6
  109. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  110. data/test/migrations/create_clients_and_change_column_null.rb +1 -1
  111. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +2 -4
  112. data/test/models/sqlserver/booking.rb +1 -1
  113. data/test/models/sqlserver/customers_view.rb +1 -1
  114. data/test/models/sqlserver/dollar_table_name.rb +1 -1
  115. data/test/models/sqlserver/edge_schema.rb +1 -3
  116. data/test/models/sqlserver/fk_has_fk.rb +1 -1
  117. data/test/models/sqlserver/fk_has_pk.rb +1 -1
  118. data/test/models/sqlserver/natural_pk_data.rb +2 -2
  119. data/test/models/sqlserver/natural_pk_int_data.rb +1 -1
  120. data/test/models/sqlserver/no_pk_data.rb +1 -1
  121. data/test/models/sqlserver/object_default.rb +1 -1
  122. data/test/models/sqlserver/quoted_table.rb +2 -2
  123. data/test/models/sqlserver/quoted_view_1.rb +1 -1
  124. data/test/models/sqlserver/quoted_view_2.rb +1 -1
  125. data/test/models/sqlserver/sst_memory.rb +1 -1
  126. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  127. data/test/models/sqlserver/string_default.rb +1 -1
  128. data/test/models/sqlserver/string_defaults_big_view.rb +1 -1
  129. data/test/models/sqlserver/string_defaults_view.rb +1 -1
  130. data/test/models/sqlserver/tinyint_pk.rb +1 -1
  131. data/test/models/sqlserver/trigger.rb +2 -2
  132. data/test/models/sqlserver/trigger_history.rb +1 -1
  133. data/test/models/sqlserver/upper.rb +1 -1
  134. data/test/models/sqlserver/uppered.rb +1 -1
  135. data/test/models/sqlserver/uuid.rb +1 -1
  136. data/test/schema/sqlserver_specific_schema.rb +36 -21
  137. data/test/support/coerceable_test_sqlserver.rb +1 -4
  138. data/test/support/connection_reflection.rb +1 -2
  139. data/test/support/core_ext/query_cache.rb +1 -1
  140. data/test/support/load_schema_sqlserver.rb +3 -5
  141. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  142. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
  143. data/test/support/minitest_sqlserver.rb +1 -1
  144. data/test/support/paths_sqlserver.rb +9 -11
  145. data/test/support/rake_helpers.rb +12 -10
  146. data/test/support/sql_counter_sqlserver.rb +14 -16
  147. data/test/support/test_in_memory_oltp.rb +7 -7
  148. metadata +31 -11
  149. data/.travis.yml +0 -23
@@ -5,8 +5,7 @@ module ActiveRecord
5
5
  module SQLServer
6
6
  module CoreExt
7
7
  module Explain
8
-
9
- SQLSERVER_STATEMENT_PREFIX = 'EXEC sp_executesql '
8
+ SQLSERVER_STATEMENT_PREFIX = "EXEC sp_executesql "
10
9
  SQLSERVER_STATEMENT_REGEXP = /N'(.+)', N'(.+)', (.+)/
11
10
 
12
11
  def exec_explain(queries)
@@ -22,19 +21,23 @@ module ActiveRecord
22
21
  # which uses sp_executesql to just the first argument, then unquote it. Likewise our
23
22
  # `sp_executesql` method should substitude the @n args with the quoted values.
24
23
  def unprepare_sqlserver_statement(sql, binds)
25
- return sql unless sql.starts_with?(SQLSERVER_STATEMENT_PREFIX)
24
+ return sql unless sql.start_with?(SQLSERVER_STATEMENT_PREFIX)
26
25
 
27
26
  executesql = sql.from(SQLSERVER_STATEMENT_PREFIX.length)
28
27
  executesql = executesql.match(SQLSERVER_STATEMENT_REGEXP).to_a[1]
29
28
 
30
29
  binds.each_with_index do |bind, index|
31
- value = connection.quote(bind)
30
+
31
+ value = if bind.is_a?(::ActiveModel::Attribute) then
32
+ connection.quote(bind.value_for_database)
33
+ else
34
+ connection.quote(bind)
35
+ end
32
36
  executesql = executesql.sub("@#{index}", value)
33
37
  end
34
38
 
35
39
  executesql
36
40
  end
37
-
38
41
  end
39
42
  end
40
43
  end
@@ -1,14 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_record/relation'
4
- require 'active_record/version'
3
+ require "active_record/relation"
4
+ require "active_record/version"
5
5
 
6
6
  module ActiveRecord
7
7
  module ConnectionAdapters
8
8
  module SQLServer
9
9
  module CoreExt
10
10
  module FinderMethods
11
-
12
11
  private
13
12
 
14
13
  # Same as original except we order by values in distinct select if present.
@@ -1,14 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_record/relation'
4
- require 'active_record/version'
3
+ require "active_record/relation"
4
+ require "active_record/version"
5
5
 
6
6
  module ActiveRecord
7
7
  module ConnectionAdapters
8
8
  module SQLServer
9
9
  module CoreExt
10
10
  module QueryMethods
11
-
12
11
  private
13
12
 
14
13
  # Copy of original from Rails master.
@@ -37,10 +37,6 @@ module ActiveRecord
37
37
  end
38
38
  deprecate :columns_per_multicolumn_index
39
39
 
40
- def in_clause_length
41
- 10_000
42
- end
43
-
44
40
  def sql_query_length
45
41
  65_536 * 4_096
46
42
  end
@@ -4,7 +4,7 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLServer
6
6
  module DatabaseStatements
7
- READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :dbcc, :explain, :save, :select, :set, :rollback) # :nodoc:
7
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :dbcc, :explain, :save, :select, :set, :rollback, :waitfor) # :nodoc:
8
8
  private_constant :READ_QUERY
9
9
 
10
10
  def write_query?(sql) # :nodoc:
@@ -17,6 +17,7 @@ module ActiveRecord
17
17
  end
18
18
 
19
19
  materialize_transactions
20
+ mark_transaction_written_if_write(sql)
20
21
 
21
22
  if id_insert_table_name = query_requires_identity_insert?(sql)
22
23
  with_identity_insert_enabled(id_insert_table_name) { do_execute(sql, name) }
@@ -25,12 +26,13 @@ module ActiveRecord
25
26
  end
26
27
  end
27
28
 
28
- def exec_query(sql, name = 'SQL', binds = [], prepare: false)
29
+ def exec_query(sql, name = "SQL", binds = [], prepare: false)
29
30
  if preventing_writes? && write_query?(sql)
30
31
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
31
32
  end
32
33
 
33
34
  materialize_transactions
35
+ mark_transaction_written_if_write(sql)
34
36
 
35
37
  sp_executesql(sql, name, binds, prepare: prepare)
36
38
  end
@@ -44,17 +46,17 @@ module ActiveRecord
44
46
  end
45
47
 
46
48
  def exec_delete(sql, name, binds)
47
- sql = sql.dup << '; SELECT @@ROWCOUNT AS AffectedRows'
49
+ sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
48
50
  super(sql, name, binds).rows.first.first
49
51
  end
50
52
 
51
53
  def exec_update(sql, name, binds)
52
- sql = sql.dup << '; SELECT @@ROWCOUNT AS AffectedRows'
54
+ sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
53
55
  super(sql, name, binds).rows.first.first
54
56
  end
55
57
 
56
58
  def begin_db_transaction
57
- do_execute 'BEGIN TRANSACTION'
59
+ do_execute "BEGIN TRANSACTION", "TRANSACTION"
58
60
  end
59
61
 
60
62
  def transaction_isolation_levels
@@ -67,25 +69,25 @@ module ActiveRecord
67
69
  end
68
70
 
69
71
  def set_transaction_isolation_level(isolation_level)
70
- do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}"
72
+ do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}", "TRANSACTION"
71
73
  end
72
74
 
73
75
  def commit_db_transaction
74
- do_execute 'COMMIT TRANSACTION'
76
+ do_execute "COMMIT TRANSACTION", "TRANSACTION"
75
77
  end
76
78
 
77
79
  def exec_rollback_db_transaction
78
- do_execute 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION'
80
+ do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION", "TRANSACTION"
79
81
  end
80
82
 
81
83
  include Savepoints
82
84
 
83
85
  def create_savepoint(name = current_savepoint_name)
84
- do_execute "SAVE TRANSACTION #{name}"
86
+ do_execute "SAVE TRANSACTION #{name}", "TRANSACTION"
85
87
  end
86
88
 
87
89
  def exec_rollback_to_savepoint(name = current_savepoint_name)
88
- do_execute "ROLLBACK TRANSACTION #{name}"
90
+ do_execute "ROLLBACK TRANSACTION #{name}", "TRANSACTION"
89
91
  end
90
92
 
91
93
  def release_savepoint(name = current_savepoint_name)
@@ -143,7 +145,7 @@ module ActiveRecord
143
145
  sql = +"INSERT #{insert.into}"
144
146
 
145
147
  if returning = insert.send(:insert_all).returning
146
- sql << " OUTPUT " << returning.map {|column| "INSERTED.#{quote_column_name(column)}" }.join(", ")
148
+ sql << " OUTPUT " << returning.map { |column| "INSERTED.#{quote_column_name(column)}" }.join(", ")
147
149
  end
148
150
 
149
151
  sql << " #{insert.values_list}"
@@ -159,13 +161,13 @@ module ActiveRecord
159
161
  variables.first.map { |k, v| "@#{k} = #{quote(v)}" }
160
162
  else
161
163
  variables.map { |v| quote(v) }
162
- end.join(', ')
164
+ end.join(", ")
163
165
  sql = "EXEC #{proc_name} #{vars}".strip
164
- name = 'Execute Procedure'
166
+ name = "Execute Procedure"
165
167
  log(sql, name) do
166
168
  case @connection_options[:mode]
167
169
  when :dblib
168
- result = @connection.execute(sql)
170
+ result = ensure_established_connection! { dblib_execute(sql) }
169
171
  options = { as: :hash, cache_rows: true, timezone: ActiveRecord::Base.default_timezone || :utc }
170
172
  result.each(options) do |row|
171
173
  r = row.with_indifferent_access
@@ -186,20 +188,22 @@ module ActiveRecord
186
188
 
187
189
  def use_database(database = nil)
188
190
  return if sqlserver_azure?
191
+
189
192
  name = SQLServer::Utils.extract_identifiers(database || @connection_options[:database]).quoted
190
193
  do_execute "USE #{name}" unless name.blank?
191
194
  end
192
195
 
193
196
  def user_options
194
197
  return {} if sqlserver_azure?
195
- rows = select_rows('DBCC USEROPTIONS WITH NO_INFOMSGS', 'SCHEMA')
198
+
199
+ rows = select_rows("DBCC USEROPTIONS WITH NO_INFOMSGS", "SCHEMA")
196
200
  rows = rows.first if rows.size == 2 && rows.last.empty?
197
201
  rows.reduce(HashWithIndifferentAccess.new) do |values, row|
198
202
  if row.instance_of? Hash
199
- set_option = row.values[0].gsub(/\s+/, '_')
203
+ set_option = row.values[0].gsub(/\s+/, "_")
200
204
  user_value = row.values[1]
201
- elsif row.instance_of? Array
202
- set_option = row[0].gsub(/\s+/, '_')
205
+ elsif row.instance_of? Array
206
+ set_option = row[0].gsub(/\s+/, "_")
203
207
  user_value = row[1]
204
208
  end
205
209
  values[set_option] = user_value
@@ -209,9 +213,9 @@ module ActiveRecord
209
213
 
210
214
  def user_options_dateformat
211
215
  if sqlserver_azure?
212
- select_value 'SELECT [dateformat] FROM [sys].[syslanguages] WHERE [langid] = @@LANGID', 'SCHEMA'
216
+ select_value "SELECT [dateformat] FROM [sys].[syslanguages] WHERE [langid] = @@LANGID", "SCHEMA"
213
217
  else
214
- user_options['dateformat']
218
+ user_options["dateformat"]
215
219
  end
216
220
  end
217
221
 
@@ -226,29 +230,28 @@ module ActiveRecord
226
230
  WHEN 5 THEN 'SNAPSHOT' END AS [isolation_level]
227
231
  FROM [sys].[dm_exec_sessions]
228
232
  WHERE [session_id] = @@SPID).squish
229
- select_value sql, 'SCHEMA'
233
+ select_value sql, "SCHEMA"
230
234
  else
231
- user_options['isolation_level']
235
+ user_options["isolation_level"]
232
236
  end
233
237
  end
234
238
 
235
239
  def user_options_language
236
240
  if sqlserver_azure?
237
- select_value 'SELECT @@LANGUAGE AS [language]', 'SCHEMA'
241
+ select_value "SELECT @@LANGUAGE AS [language]", "SCHEMA"
238
242
  else
239
- user_options['language']
243
+ user_options["language"]
240
244
  end
241
245
  end
242
246
 
243
247
  def newid_function
244
- select_value 'SELECT NEWID()'
248
+ select_value "SELECT NEWID()"
245
249
  end
246
250
 
247
251
  def newsequentialid_function
248
- select_value 'SELECT NEWSEQUENTIALID()'
252
+ select_value "SELECT NEWSEQUENTIALID()"
249
253
  end
250
254
 
251
-
252
255
  protected
253
256
 
254
257
  def sql_for_insert(sql, pk, binds)
@@ -263,7 +266,7 @@ module ActiveRecord
263
266
  exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql)
264
267
 
265
268
  if exclude_output_inserted
266
- id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? 'bigint' : exclude_output_inserted
269
+ id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? "bigint" : exclude_output_inserted
267
270
  <<~SQL.squish
268
271
  DECLARE @ssaIdInsertTable table (#{quoted_pk} #{id_sql_type});
269
272
  #{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk} INTO @ssaIdInsertTable"}
@@ -288,8 +291,9 @@ module ActiveRecord
288
291
 
289
292
  # === SQLServer Specific (Executing) ============================ #
290
293
 
291
- def do_execute(sql, name = 'SQL')
294
+ def do_execute(sql, name = "SQL")
292
295
  materialize_transactions
296
+ mark_transaction_written_if_write(sql)
293
297
 
294
298
  log(sql, name) { raw_connection_do(sql) }
295
299
  end
@@ -316,11 +320,12 @@ module ActiveRecord
316
320
 
317
321
  def sp_executesql_sql_type(attr)
318
322
  return attr.type.sqlserver_type if attr.type.respond_to?(:sqlserver_type)
323
+
319
324
  case value = attr.value_for_database
320
325
  when Numeric
321
- value > 2_147_483_647 ? 'bigint'.freeze : 'int'.freeze
326
+ value > 2_147_483_647 ? "bigint".freeze : "int".freeze
322
327
  else
323
- 'nvarchar(max)'.freeze
328
+ "nvarchar(max)".freeze
324
329
  end
325
330
  end
326
331
 
@@ -335,14 +340,14 @@ module ActiveRecord
335
340
  end
336
341
 
337
342
  def sp_executesql_sql(sql, types, params, name)
338
- if name == 'EXPLAIN'
343
+ if name == "EXPLAIN"
339
344
  params.each.with_index do |param, index|
340
345
  substitute_at_finder = /(@#{index})(?=(?:[^']|'[^']*')*$)/ # Finds unquoted @n values.
341
346
  sql = sql.sub substitute_at_finder, param.to_s
342
347
  end
343
348
  else
344
- types = quote(types.join(', '))
345
- params = params.map.with_index{ |p, i| "@#{i} = #{p}" }.join(', ') # Only p is needed, but with @i helps explain regexp.
349
+ types = quote(types.join(", "))
350
+ params = params.map.with_index { |p, i| "@#{i} = #{p}" }.join(", ") # Only p is needed, but with @i helps explain regexp.
346
351
  sql = "EXEC sp_executesql #{quote(sql)}"
347
352
  sql += ", #{types}, #{params}" unless params.empty?
348
353
  end
@@ -352,13 +357,7 @@ module ActiveRecord
352
357
  def raw_connection_do(sql)
353
358
  case @connection_options[:mode]
354
359
  when :dblib
355
- result = @connection.execute(sql)
356
-
357
- # TinyTDS returns false instead of raising an exception if connection fails.
358
- # Getting around this by raising an exception ourselves while this PR
359
- # https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
360
- raise TinyTds::Error, 'failed to execute statement' if result.is_a?(FalseClass)
361
-
360
+ result = ensure_established_connection! { dblib_execute(sql) }
362
361
  result.do
363
362
  end
364
363
  ensure
@@ -377,8 +376,10 @@ module ActiveRecord
377
376
 
378
377
  def exclude_output_inserted_table_name?(table_name, sql)
379
378
  return false unless exclude_output_inserted_table_names?
379
+
380
380
  table_name ||= get_table_name(sql)
381
381
  return false unless table_name
382
+
382
383
  self.class.exclude_output_inserted_table_names[table_name]
383
384
  end
384
385
 
@@ -407,7 +408,7 @@ module ActiveRecord
407
408
 
408
409
  # === SQLServer Specific (Selecting) ============================ #
409
410
 
410
- def raw_select(sql, name = 'SQL', binds = [], options = {})
411
+ def raw_select(sql, name = "SQL", binds = [], options = {})
411
412
  log(sql, name, binds) { _raw_select(sql, options) }
412
413
  end
413
414
 
@@ -421,7 +422,7 @@ module ActiveRecord
421
422
  def raw_connection_run(sql)
422
423
  case @connection_options[:mode]
423
424
  when :dblib
424
- @connection.execute(sql)
425
+ ensure_established_connection! { dblib_execute(sql) }
425
426
  end
426
427
  end
427
428
 
@@ -456,6 +457,20 @@ module ActiveRecord
456
457
  handle
457
458
  end
458
459
 
460
+ def dblib_execute(sql)
461
+ @connection.execute(sql).tap do |result|
462
+ # TinyTDS returns false instead of raising an exception if connection fails.
463
+ # Getting around this by raising an exception ourselves while this PR
464
+ # https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
465
+ raise TinyTds::Error, "failed to execute statement" if result.is_a?(FalseClass)
466
+ end
467
+ end
468
+
469
+ def ensure_established_connection!
470
+ raise TinyTds::Error, 'SQL Server client is not connected' unless @connection
471
+
472
+ yield
473
+ end
459
474
  end
460
475
  end
461
476
  end
@@ -4,7 +4,6 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLServer
6
6
  module DatabaseTasks
7
-
8
7
  def create_database(database, options = {})
9
8
  name = SQLServer::Utils.extract_identifiers(database)
10
9
  db_options = create_database_options(options)
@@ -19,7 +18,7 @@ module ActiveRecord
19
18
  end
20
19
 
21
20
  def current_database
22
- select_value 'SELECT DB_NAME()'
21
+ select_value "SELECT DB_NAME()"
23
22
  end
24
23
 
25
24
  def charset
@@ -32,20 +31,20 @@ module ActiveRecord
32
31
 
33
32
  private
34
33
 
35
- def create_database_options(options={})
34
+ def create_database_options(options = {})
36
35
  keys = [:collate]
37
36
  copts = @connection_options
38
37
  options = {
39
38
  collate: copts[:collation]
40
39
  }.merge(options.symbolize_keys).select { |_, v|
41
40
  v.present?
42
- }.slice(*keys).map { |k,v|
41
+ }.slice(*keys).map { |k, v|
43
42
  "#{k.to_s.upcase} #{v}"
44
- }.join(' ')
43
+ }.join(" ")
45
44
  options
46
45
  end
47
46
 
48
- def create_database_edition_options(options={})
47
+ def create_database_edition_options(options = {})
49
48
  keys = [:maxsize, :edition, :service_objective]
50
49
  copts = @connection_options
51
50
  edition_options = {
@@ -54,17 +53,13 @@ module ActiveRecord
54
53
  service_objective: copts[:azure_service_objective]
55
54
  }.merge(options.symbolize_keys).select { |_, v|
56
55
  v.present?
57
- }.slice(*keys).map { |k,v|
56
+ }.slice(*keys).map { |k, v|
58
57
  "#{k.to_s.upcase} = #{v}"
59
- }.join(', ')
58
+ }.join(", ")
60
59
  edition_options = "( #{edition_options} )" if edition_options.present?
61
60
  edition_options
62
61
  end
63
-
64
62
  end
65
63
  end
66
64
  end
67
65
  end
68
-
69
-
70
-
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
-
5
4
  class DeadlockVictim < WrappedDatabaseException
6
5
  end
7
-
8
-
9
6
  end