activerecord-sqlserver-adapter 5.2.1 → 6.0.2

Sign up to get free protection for your applications and to get access to all the features.
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