activerecord-sqlserver-adapter 5.2.1 → 6.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 (146) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/issue_template.md +23 -0
  4. data/.travis.yml +6 -8
  5. data/CHANGELOG.md +22 -32
  6. data/{Dockerfile → Dockerfile.ci} +1 -1
  7. data/Gemfile +42 -41
  8. data/README.md +9 -30
  9. data/RUNNING_UNIT_TESTS.md +3 -0
  10. data/Rakefile +2 -0
  11. data/VERSION +1 -1
  12. data/activerecord-sqlserver-adapter.gemspec +25 -14
  13. data/appveyor.yml +24 -17
  14. data/docker-compose.ci.yml +7 -5
  15. data/guides/RELEASING.md +11 -0
  16. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -0
  17. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +2 -0
  18. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +2 -0
  19. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +3 -1
  20. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
  21. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +6 -4
  22. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +36 -0
  23. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +4 -1
  24. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -0
  25. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +55 -14
  26. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +2 -0
  27. data/lib/active_record/connection_adapters/sqlserver/errors.rb +2 -0
  28. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +38 -0
  29. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +16 -3
  30. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +2 -0
  31. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +93 -70
  32. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +2 -0
  33. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +2 -0
  34. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +2 -0
  35. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +2 -0
  36. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +42 -40
  37. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +3 -1
  38. data/lib/active_record/connection_adapters/sqlserver/type.rb +2 -0
  39. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +3 -1
  40. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -2
  41. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -1
  42. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +5 -2
  43. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +2 -0
  44. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +3 -1
  45. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +7 -6
  46. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -0
  47. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -0
  48. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -2
  49. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -1
  50. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -1
  51. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -0
  52. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -2
  53. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -1
  54. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -1
  55. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -2
  56. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -1
  57. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -0
  58. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -1
  59. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +5 -4
  60. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +2 -0
  61. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -1
  62. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -1
  63. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -2
  64. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -0
  65. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -1
  66. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -3
  67. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -2
  68. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +3 -1
  69. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -3
  70. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -2
  71. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -3
  72. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -2
  73. data/lib/active_record/connection_adapters/sqlserver/utils.rb +2 -0
  74. data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -0
  75. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +44 -10
  76. data/lib/active_record/connection_adapters/sqlserver_column.rb +9 -3
  77. data/lib/active_record/sqlserver_base.rb +8 -0
  78. data/lib/active_record/tasks/sqlserver_database_tasks.rb +2 -0
  79. data/lib/activerecord-sqlserver-adapter.rb +2 -0
  80. data/lib/arel/visitors/sqlserver.rb +40 -10
  81. data/lib/arel_sqlserver.rb +2 -0
  82. data/test/appveyor/dbsetup.ps1 +4 -4
  83. data/test/cases/adapter_test_sqlserver.rb +65 -1
  84. data/test/cases/change_column_null_test_sqlserver.rb +2 -0
  85. data/test/cases/coerced_tests.rb +644 -187
  86. data/test/cases/column_test_sqlserver.rb +2 -1
  87. data/test/cases/connection_test_sqlserver.rb +2 -0
  88. data/test/cases/execute_procedure_test_sqlserver.rb +2 -0
  89. data/test/cases/fetch_test_sqlserver.rb +2 -0
  90. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +4 -2
  91. data/test/cases/helper_sqlserver.rb +2 -0
  92. data/test/cases/in_clause_test_sqlserver.rb +36 -0
  93. data/test/cases/index_test_sqlserver.rb +2 -0
  94. data/test/cases/json_test_sqlserver.rb +2 -0
  95. data/test/cases/migration_test_sqlserver.rb +4 -2
  96. data/test/cases/order_test_sqlserver.rb +2 -0
  97. data/test/cases/pessimistic_locking_test_sqlserver.rb +2 -0
  98. data/test/cases/rake_test_sqlserver.rb +2 -0
  99. data/test/cases/schema_dumper_test_sqlserver.rb +3 -1
  100. data/test/cases/schema_test_sqlserver.rb +2 -0
  101. data/test/cases/scratchpad_test_sqlserver.rb +2 -0
  102. data/test/cases/showplan_test_sqlserver.rb +4 -2
  103. data/test/cases/specific_schema_test_sqlserver.rb +2 -0
  104. data/test/cases/transaction_test_sqlserver.rb +2 -1
  105. data/test/cases/trigger_test_sqlserver.rb +2 -1
  106. data/test/cases/utils_test_sqlserver.rb +2 -0
  107. data/test/cases/uuid_test_sqlserver.rb +2 -1
  108. data/test/debug.rb +2 -0
  109. data/test/migrations/create_clients_and_change_column_null.rb +2 -0
  110. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +2 -0
  111. data/test/models/sqlserver/booking.rb +2 -0
  112. data/test/models/sqlserver/customers_view.rb +2 -0
  113. data/test/models/sqlserver/datatype.rb +2 -0
  114. data/test/models/sqlserver/datatype_migration.rb +2 -0
  115. data/test/models/sqlserver/dollar_table_name.rb +2 -0
  116. data/test/models/sqlserver/edge_schema.rb +2 -0
  117. data/test/models/sqlserver/fk_has_fk.rb +2 -0
  118. data/test/models/sqlserver/fk_has_pk.rb +2 -0
  119. data/test/models/sqlserver/natural_pk_data.rb +2 -0
  120. data/test/models/sqlserver/natural_pk_int_data.rb +2 -0
  121. data/test/models/sqlserver/no_pk_data.rb +2 -0
  122. data/test/models/sqlserver/object_default.rb +2 -0
  123. data/test/models/sqlserver/quoted_table.rb +2 -0
  124. data/test/models/sqlserver/quoted_view_1.rb +2 -0
  125. data/test/models/sqlserver/quoted_view_2.rb +2 -0
  126. data/test/models/sqlserver/sst_memory.rb +2 -0
  127. data/test/models/sqlserver/string_default.rb +2 -0
  128. data/test/models/sqlserver/string_defaults_big_view.rb +2 -0
  129. data/test/models/sqlserver/string_defaults_view.rb +2 -0
  130. data/test/models/sqlserver/tinyint_pk.rb +2 -0
  131. data/test/models/sqlserver/trigger.rb +2 -0
  132. data/test/models/sqlserver/trigger_history.rb +2 -0
  133. data/test/models/sqlserver/upper.rb +2 -0
  134. data/test/models/sqlserver/uppered.rb +2 -0
  135. data/test/models/sqlserver/uuid.rb +2 -0
  136. data/test/schema/sqlserver_specific_schema.rb +2 -0
  137. data/test/support/coerceable_test_sqlserver.rb +14 -5
  138. data/test/support/connection_reflection.rb +2 -0
  139. data/test/support/core_ext/query_cache.rb +3 -0
  140. data/test/support/load_schema_sqlserver.rb +2 -0
  141. data/test/support/minitest_sqlserver.rb +2 -0
  142. data/test/support/paths_sqlserver.rb +2 -0
  143. data/test/support/rake_helpers.rb +1 -0
  144. data/test/support/sql_counter_sqlserver.rb +3 -0
  145. data/test/support/test_in_memory_oltp.rb +2 -0
  146. metadata +18 -9
@@ -1,11 +1,13 @@
1
1
  version: "2.2"
2
2
  services:
3
- database:
3
+ sqlserver:
4
4
  image: metaskills/mssql-server-linux-rails
5
5
  ci:
6
6
  environment:
7
- - ACTIVERECORD_UNITTEST_HOST=database
8
- build: .
9
- command: wait-for database:1433 -- bundle exec rake test
7
+ - ACTIVERECORD_UNITTEST_HOST=sqlserver
8
+ build:
9
+ context: .
10
+ dockerfile: Dockerfile.ci
11
+ command: wait-for sqlserver:1433 -- bundle exec rake test
10
12
  depends_on:
11
- - "database"
13
+ - "sqlserver"
@@ -0,0 +1,11 @@
1
+ # Releasing
2
+
3
+ ## Building locally
4
+
5
+ If you want to build the gem to test it locally run `bundle exec rake build`.
6
+
7
+ This command will build the gem in `pkg/activerecord-sqlserver-adapter-A.B.C.gem`, where `A.B.C` is the version in `VERSION` file.
8
+
9
+ ## Releasing to RubyGems
10
+
11
+ Run `bundle exec rake release` to build the gem locally and push the `gem` file to RubyGems.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record/attribute_methods'
2
4
 
3
5
  module ActiveRecord
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record/relation'
2
4
  require 'active_record/version'
3
5
 
@@ -1,10 +1,12 @@
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
8
 
7
- SQLSERVER_STATEMENT_PREFIX = 'EXEC sp_executesql '.freeze
9
+ SQLSERVER_STATEMENT_PREFIX = 'EXEC sp_executesql '
8
10
  SQLSERVER_STATEMENT_REGEXP = /N'(.+)', N'(.+)', (.+)/
9
11
 
10
12
  def exec_explain(queries)
@@ -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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record/relation'
2
4
  require 'active_record/version'
3
5
 
@@ -11,13 +13,13 @@ module ActiveRecord
11
13
 
12
14
  # Same as original except we order by values in distinct select if present.
13
15
  def construct_relation_for_exists(conditions)
14
- if distinct_value && offset_value
15
- relation = limit!(1)
16
+ conditions = sanitize_forbidden_attributes(conditions)
16
17
 
18
+ if distinct_value && offset_value
17
19
  if select_values.present?
18
- relation = relation.order(*select_values)
20
+ relation = order(*select_values).limit!(1)
19
21
  else
20
- relation = relation.except(:order)
22
+ relation = except(:order).limit!(1)
21
23
  end
22
24
  else
23
25
  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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record/relation'
2
4
  require 'active_record/version'
3
5
 
@@ -9,7 +11,8 @@ module ActiveRecord
9
11
 
10
12
  private
11
13
 
12
- # Copy of original from Rails master. This patch can be removed when adapter supports Rails 6.
14
+ # Copy of original from Rails master.
15
+ # This patch can be removed when adapter supports Rails version greater than 6.0.2.2
13
16
  def table_name_matches?(from)
14
17
  table_name = Regexp.escape(table.name)
15
18
  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
@@ -12,6 +26,12 @@ module ActiveRecord
12
26
  end
13
27
 
14
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
 
@@ -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,9 +139,22 @@ 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
@@ -221,18 +251,20 @@ module ActiveRecord
221
251
 
222
252
  protected
223
253
 
224
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
254
+ def sql_for_insert(sql, pk, binds)
225
255
  if pk.nil?
226
256
  table_name = query_requires_identity_insert?(sql)
227
257
  pk = primary_key(table_name)
228
258
  end
259
+
229
260
  sql = if pk && use_output_inserted? && !database_prefix_remote_server?
230
261
  quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
231
262
  table_name ||= get_table_name(sql)
232
263
  exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql)
264
+
233
265
  if exclude_output_inserted
234
266
  id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? 'bigint' : exclude_output_inserted
235
- <<-SQL.strip_heredoc
267
+ <<~SQL.squish
236
268
  DECLARE @ssaIdInsertTable table (#{quoted_pk} #{id_sql_type});
237
269
  #{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk} INTO @ssaIdInsertTable"}
238
270
  SELECT CAST(#{quoted_pk} AS #{id_sql_type}) FROM @ssaIdInsertTable
@@ -257,6 +289,8 @@ module ActiveRecord
257
289
  # === SQLServer Specific (Executing) ============================ #
258
290
 
259
291
  def do_execute(sql, name = 'SQL')
292
+ materialize_transactions
293
+
260
294
  log(sql, name) { raw_connection_do(sql) }
261
295
  end
262
296
 
@@ -310,7 +344,7 @@ module ActiveRecord
310
344
  types = quote(types.join(', '))
311
345
  params = params.map.with_index{ |p, i| "@#{i} = #{p}" }.join(', ') # Only p is needed, but with @i helps explain regexp.
312
346
  sql = "EXEC sp_executesql #{quote(sql)}"
313
- sql << ", #{types}, #{params}" unless params.empty?
347
+ sql += ", #{types}, #{params}" unless params.empty?
314
348
  end
315
349
  sql
316
350
  end
@@ -318,7 +352,14 @@ module ActiveRecord
318
352
  def raw_connection_do(sql)
319
353
  case @connection_options[:mode]
320
354
  when :dblib
321
- @connection.execute(sql).do
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
+
362
+ result.do
322
363
  end
323
364
  ensure
324
365
  @update_sql = false
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
 
3
5
  class DeadlockVictim < WrappedDatabaseException
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
@@ -68,6 +70,42 @@ module ActiveRecord
68
70
  end
69
71
  end
70
72
 
73
+ def column_name_matcher
74
+ COLUMN_NAME
75
+ end
76
+
77
+ def column_name_with_order_matcher
78
+ COLUMN_NAME_WITH_ORDER
79
+ end
80
+
81
+ COLUMN_NAME = /
82
+ \A
83
+ (
84
+ (?:
85
+ # [table_name].[column_name] | function(one or no argument)
86
+ ((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
87
+ )
88
+ (?:\s+AS\s+(?:\w+|\[\w+\]))?
89
+ )
90
+ (?:\s*,\s*\g<1>)*
91
+ \z
92
+ /ix
93
+
94
+ COLUMN_NAME_WITH_ORDER = /
95
+ \A
96
+ (
97
+ (?:
98
+ # [table_name].[column_name] | function(one or no argument)
99
+ ((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
100
+ )
101
+ (?:\s+ASC|\s+DESC)?
102
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
103
+ )
104
+ (?:\s*,\s*\g<1>)*
105
+ \z
106
+ /ix
107
+
108
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
71
109
 
72
110
  private
73
111
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
@@ -6,15 +8,26 @@ module ActiveRecord
6
8
  private
7
9
 
8
10
  def visit_TableDefinition(o)
11
+ if_not_exists = o.if_not_exists
12
+
9
13
  if o.as
10
14
  table_name = quote_table_name(o.temporary ? "##{o.name}" : o.name)
11
15
  query = o.as.respond_to?(:to_sql) ? o.as.to_sql : o.as
12
16
  projections, source = query.match(%r{SELECT\s+(.*)?\s+FROM\s+(.*)?}).captures
13
- select_into = "SELECT #{projections} INTO #{table_name} FROM #{source}"
17
+ sql = "SELECT #{projections} INTO #{table_name} FROM #{source}"
14
18
  else
15
19
  o.instance_variable_set :@as, nil
16
- super
20
+ o.instance_variable_set :@if_not_exists, false
21
+ sql = super
17
22
  end
23
+
24
+ if if_not_exists
25
+ o.instance_variable_set :@if_not_exists, true
26
+ table_name = o.temporary ? "##{o.name}" : o.name
27
+ sql = "IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='#{table_name}' and xtype='U') #{sql}"
28
+ end
29
+
30
+ sql
18
31
  end
19
32
 
20
33
  def add_column_options!(sql, options)
@@ -34,7 +47,7 @@ module ActiveRecord
34
47
  def action_sql(action, dependency)
35
48
  case dependency
36
49
  when :restrict
37
- raise ArgumentError, <<-MSG.strip_heredoc
50
+ raise ArgumentError, <<~MSG.squish
38
51
  '#{dependency}' is not supported for :on_update or :on_delete.
39
52
  Supported values are: :nullify, :cascade
40
53
  MSG