activerecord-sqlserver-adapter 5.2.1 → 6.0.2

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 (153) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/issue_template.md +23 -0
  4. data/.github/workflows/ci.yml +26 -0
  5. data/.gitignore +1 -0
  6. data/.rubocop.yml +29 -0
  7. data/CHANGELOG.md +58 -20
  8. data/{Dockerfile → Dockerfile.ci} +1 -1
  9. data/Gemfile +48 -41
  10. data/Guardfile +9 -8
  11. data/README.md +28 -31
  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 +210 -163
  35. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +8 -8
  36. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +4 -2
  37. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +3 -1
  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.rb +38 -35
  42. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +3 -3
  43. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -4
  44. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -3
  45. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +7 -4
  46. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +2 -2
  47. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +4 -3
  48. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +8 -8
  49. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -2
  50. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -2
  51. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -4
  52. data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
  53. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -3
  54. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -3
  55. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -1
  56. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -4
  57. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -3
  58. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -3
  59. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -4
  60. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -3
  61. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -2
  62. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -3
  63. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +6 -6
  64. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +8 -9
  65. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -3
  66. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -3
  67. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -4
  68. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -2
  69. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -3
  70. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -5
  71. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -4
  72. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +4 -3
  73. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -5
  74. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -4
  75. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -5
  76. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -4
  77. data/lib/active_record/connection_adapters/sqlserver/utils.rb +10 -11
  78. data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
  79. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +145 -94
  80. data/lib/active_record/connection_adapters/sqlserver_column.rb +9 -5
  81. data/lib/active_record/sqlserver_base.rb +9 -1
  82. data/lib/active_record/tasks/sqlserver_database_tasks.rb +28 -32
  83. data/lib/activerecord-sqlserver-adapter.rb +3 -1
  84. data/lib/arel/visitors/sqlserver.rb +108 -34
  85. data/lib/arel_sqlserver.rb +4 -2
  86. data/test/appveyor/dbsetup.ps1 +4 -4
  87. data/test/cases/adapter_test_sqlserver.rb +246 -171
  88. data/test/cases/change_column_null_test_sqlserver.rb +14 -12
  89. data/test/cases/coerced_tests.rb +722 -381
  90. data/test/cases/column_test_sqlserver.rb +287 -285
  91. data/test/cases/connection_test_sqlserver.rb +17 -20
  92. data/test/cases/execute_procedure_test_sqlserver.rb +20 -20
  93. data/test/cases/fetch_test_sqlserver.rb +16 -22
  94. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
  95. data/test/cases/helper_sqlserver.rb +15 -15
  96. data/test/cases/in_clause_test_sqlserver.rb +36 -0
  97. data/test/cases/index_test_sqlserver.rb +15 -15
  98. data/test/cases/json_test_sqlserver.rb +25 -25
  99. data/test/cases/lateral_test_sqlserver.rb +35 -0
  100. data/test/cases/migration_test_sqlserver.rb +67 -27
  101. data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
  102. data/test/cases/order_test_sqlserver.rb +53 -54
  103. data/test/cases/pessimistic_locking_test_sqlserver.rb +27 -33
  104. data/test/cases/rake_test_sqlserver.rb +33 -45
  105. data/test/cases/schema_dumper_test_sqlserver.rb +115 -109
  106. data/test/cases/schema_test_sqlserver.rb +20 -26
  107. data/test/cases/scratchpad_test_sqlserver.rb +4 -4
  108. data/test/cases/showplan_test_sqlserver.rb +28 -35
  109. data/test/cases/specific_schema_test_sqlserver.rb +68 -65
  110. data/test/cases/transaction_test_sqlserver.rb +18 -20
  111. data/test/cases/trigger_test_sqlserver.rb +14 -13
  112. data/test/cases/utils_test_sqlserver.rb +70 -70
  113. data/test/cases/uuid_test_sqlserver.rb +13 -14
  114. data/test/debug.rb +8 -6
  115. data/test/migrations/create_clients_and_change_column_null.rb +3 -1
  116. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +4 -4
  117. data/test/models/sqlserver/booking.rb +3 -1
  118. data/test/models/sqlserver/customers_view.rb +3 -1
  119. data/test/models/sqlserver/datatype.rb +2 -0
  120. data/test/models/sqlserver/datatype_migration.rb +2 -0
  121. data/test/models/sqlserver/dollar_table_name.rb +3 -1
  122. data/test/models/sqlserver/edge_schema.rb +3 -3
  123. data/test/models/sqlserver/fk_has_fk.rb +3 -1
  124. data/test/models/sqlserver/fk_has_pk.rb +3 -1
  125. data/test/models/sqlserver/natural_pk_data.rb +4 -2
  126. data/test/models/sqlserver/natural_pk_int_data.rb +3 -1
  127. data/test/models/sqlserver/no_pk_data.rb +3 -1
  128. data/test/models/sqlserver/object_default.rb +3 -1
  129. data/test/models/sqlserver/quoted_table.rb +4 -2
  130. data/test/models/sqlserver/quoted_view_1.rb +3 -1
  131. data/test/models/sqlserver/quoted_view_2.rb +3 -1
  132. data/test/models/sqlserver/sst_memory.rb +3 -1
  133. data/test/models/sqlserver/string_default.rb +3 -1
  134. data/test/models/sqlserver/string_defaults_big_view.rb +3 -1
  135. data/test/models/sqlserver/string_defaults_view.rb +3 -1
  136. data/test/models/sqlserver/tinyint_pk.rb +3 -1
  137. data/test/models/sqlserver/trigger.rb +4 -2
  138. data/test/models/sqlserver/trigger_history.rb +3 -1
  139. data/test/models/sqlserver/upper.rb +3 -1
  140. data/test/models/sqlserver/uppered.rb +3 -1
  141. data/test/models/sqlserver/uuid.rb +3 -1
  142. data/test/schema/sqlserver_specific_schema.rb +31 -21
  143. data/test/support/coerceable_test_sqlserver.rb +15 -9
  144. data/test/support/connection_reflection.rb +3 -2
  145. data/test/support/core_ext/query_cache.rb +4 -1
  146. data/test/support/load_schema_sqlserver.rb +5 -5
  147. data/test/support/minitest_sqlserver.rb +3 -1
  148. data/test/support/paths_sqlserver.rb +11 -11
  149. data/test/support/rake_helpers.rb +13 -10
  150. data/test/support/sql_counter_sqlserver.rb +3 -4
  151. data/test/support/test_in_memory_oltp.rb +9 -7
  152. metadata +27 -12
  153. data/.travis.yml +0 -25
@@ -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, :waitfor) # :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
@@ -1,11 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
4
6
  module Quoting
5
-
6
- QUOTED_TRUE = '1'.freeze
7
- QUOTED_FALSE = '0'.freeze
8
- QUOTED_STRING_PREFIX = 'N'.freeze
7
+ QUOTED_TRUE = "1".freeze
8
+ QUOTED_FALSE = "0".freeze
9
+ QUOTED_STRING_PREFIX = "N".freeze
9
10
 
10
11
  def fetch_type_metadata(sql_type, sqlserver_options = {})
11
12
  cast_type = lookup_cast_type(sql_type)
@@ -61,13 +62,51 @@ module ActiveRecord
61
62
  end
62
63
 
63
64
  def quoted_date(value)
64
- if value.acts_like?(:date)
65
- Type::Date.new.serialize(value)
66
- else value.acts_like?(:time)
65
+ if value.acts_like?(:time)
67
66
  Type::DateTime.new.serialize(value)
67
+ elsif value.acts_like?(:date)
68
+ Type::Date.new.serialize(value)
69
+ else
70
+ value
68
71
  end
69
72
  end
70
73
 
74
+ def column_name_matcher
75
+ COLUMN_NAME
76
+ end
77
+
78
+ def column_name_with_order_matcher
79
+ COLUMN_NAME_WITH_ORDER
80
+ end
81
+
82
+ COLUMN_NAME = /
83
+ \A
84
+ (
85
+ (?:
86
+ # [database_name].[database_owner].[table_name].[column_name] | function(one or no argument)
87
+ ((?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
88
+ )
89
+ (?:\s+AS\s+(?:\w+|\[\w+\]))?
90
+ )
91
+ (?:\s*,\s*\g<1>)*
92
+ \z
93
+ /ix
94
+
95
+ COLUMN_NAME_WITH_ORDER = /
96
+ \A
97
+ (
98
+ (?:
99
+ # [database_name].[database_owner].[table_name].[column_name] | function(one or no argument)
100
+ ((?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
101
+ )
102
+ (?:\s+ASC|\s+DESC)?
103
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
104
+ )
105
+ (?:\s*,\s*\g<1>)*
106
+ \z
107
+ /ix
108
+
109
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
71
110
 
72
111
  private
73
112
 
@@ -92,7 +131,6 @@ module ActiveRecord
92
131
  super
93
132
  end
94
133
  end
95
-
96
134
  end
97
135
  end
98
136
  end