activerecord-sqlserver-adapter 5.2.1 → 6.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
4
6
  module Type
5
7
  class Varbinary < Binary
6
8
 
7
- def initialize(*args)
9
+ def initialize(**args)
8
10
  super
9
11
  @limit = 8000 if @limit.to_i == 0
10
12
  end
@@ -14,8 +16,9 @@ module ActiveRecord
14
16
  end
15
17
 
16
18
  def sqlserver_type
17
- 'varbinary'.tap do |type|
18
- type << "(#{limit})" if limit
19
+ 'varbinary'.yield_self do |type|
20
+ type += "(#{limit})" if limit
21
+ type
19
22
  end
20
23
  end
21
24
 
@@ -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 Type
5
7
  class VarbinaryMax < Varbinary
6
8
 
7
- def initialize(*args)
9
+ def initialize(**args)
8
10
  super
9
11
  @limit = 2_147_483_647
10
12
  end
@@ -14,7 +16,7 @@ module ActiveRecord
14
16
  end
15
17
 
16
18
  def sqlserver_type
17
- 'varbinary(max)'.freeze
19
+ "varbinary(max)"
18
20
  end
19
21
 
20
22
  end
@@ -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 Type
5
7
  class Varchar < Char
6
8
 
7
- def initialize(*args)
9
+ def initialize(**args)
8
10
  super
9
11
  @limit = 8000 if @limit.to_i == 0
10
12
  end
@@ -14,8 +16,9 @@ module ActiveRecord
14
16
  end
15
17
 
16
18
  def sqlserver_type
17
- 'varchar'.tap do |type|
18
- type << "(#{limit})" if limit
19
+ 'varchar'.yield_self do |type|
20
+ type += "(#{limit})" if limit
21
+ type
19
22
  end
20
23
  end
21
24
 
@@ -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 Type
5
7
  class VarcharMax < Varchar
6
8
 
7
- def initialize(*args)
9
+ def initialize(**args)
8
10
  super
9
11
  @limit = 2_147_483_647
10
12
  end
@@ -14,7 +16,7 @@ module ActiveRecord
14
16
  end
15
17
 
16
18
  def sqlserver_type
17
- 'varchar(max)'.freeze
19
+ "varchar(max)"
18
20
  end
19
21
 
20
22
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'strscan'
2
4
 
3
5
  module ActiveRecord
@@ -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 'base64'
2
4
  require 'active_record'
3
5
  require 'arel_sqlserver'
@@ -9,6 +11,7 @@ require 'active_record/connection_adapters/sqlserver/core_ext/explain_subscriber
9
11
  require 'active_record/connection_adapters/sqlserver/core_ext/attribute_methods'
10
12
  require 'active_record/connection_adapters/sqlserver/core_ext/finder_methods'
11
13
  require 'active_record/connection_adapters/sqlserver/core_ext/query_methods'
14
+ require 'active_record/connection_adapters/sqlserver/core_ext/preloader'
12
15
  require 'active_record/connection_adapters/sqlserver/version'
13
16
  require 'active_record/connection_adapters/sqlserver/type'
14
17
  require 'active_record/connection_adapters/sqlserver/database_limits'
@@ -80,6 +83,12 @@ module ActiveRecord
80
83
  SQLServer::SchemaCreation.new self
81
84
  end
82
85
 
86
+ def self.database_exists?(config)
87
+ !!ActiveRecord::Base.sqlserver_connection(config)
88
+ rescue ActiveRecord::NoDatabaseError
89
+ false
90
+ end
91
+
83
92
  def supports_ddl_transactions?
84
93
  true
85
94
  end
@@ -144,10 +153,30 @@ module ActiveRecord
144
153
  true
145
154
  end
146
155
 
156
+ def supports_lazy_transactions?
157
+ true
158
+ end
159
+
147
160
  def supports_in_memory_oltp?
148
161
  @version_year >= 2014
149
162
  end
150
163
 
164
+ def supports_insert_returning?
165
+ true
166
+ end
167
+
168
+ def supports_insert_on_duplicate_skip?
169
+ false
170
+ end
171
+
172
+ def supports_insert_on_duplicate_update?
173
+ false
174
+ end
175
+
176
+ def supports_insert_conflict_target?
177
+ false
178
+ end
179
+
151
180
  def disable_referential_integrity
152
181
  tables = tables_with_referential_integrity
153
182
  tables.each { |t| do_execute "ALTER TABLE #{quote_table_name(t)} NOCHECK CONSTRAINT ALL" }
@@ -196,7 +225,7 @@ module ActiveRecord
196
225
  # === Abstract Adapter (Misc Support) =========================== #
197
226
 
198
227
  def tables_with_referential_integrity
199
- schemas_and_tables = select_rows <<-SQL.strip_heredoc
228
+ schemas_and_tables = select_rows <<~SQL.squish
200
229
  SELECT DISTINCT s.name, o.name
201
230
  FROM sys.foreign_keys i
202
231
  INNER JOIN sys.objects o ON i.parent_object_id = o.OBJECT_ID
@@ -256,6 +285,9 @@ module ActiveRecord
256
285
  result
257
286
  end
258
287
 
288
+ def get_database_version # :nodoc:
289
+ version_year
290
+ end
259
291
 
260
292
  protected
261
293
 
@@ -325,18 +357,20 @@ module ActiveRecord
325
357
  m.register_type 'timestamp', SQLServer::Type::Timestamp.new
326
358
  end
327
359
 
328
- def translate_exception(e, message)
360
+ def translate_exception(e, message:, sql:, binds:)
329
361
  case message
330
362
  when /(cannot insert duplicate key .* with unique index) | (violation of unique key constraint)/i
331
- RecordNotUnique.new(message)
332
- when /conflicted with the foreign key constraint/i
333
- InvalidForeignKey.new(message)
363
+ RecordNotUnique.new(message, sql: sql, binds: binds)
364
+ when /(conflicted with the foreign key constraint) | (The DELETE statement conflicted with the REFERENCE constraint)/i
365
+ InvalidForeignKey.new(message, sql: sql, binds: binds)
334
366
  when /has been chosen as the deadlock victim/i
335
- DeadlockVictim.new(message)
367
+ DeadlockVictim.new(message, sql: sql, binds: binds)
336
368
  when /database .* does not exist/i
337
- NoDatabaseError.new(message)
369
+ NoDatabaseError.new(message, sql: sql, binds: binds)
338
370
  when /data would be truncated/
339
- ValueTooLong.new(message)
371
+ ValueTooLong.new(message, sql: sql, binds: binds)
372
+ when /connection timed out/
373
+ StatementTimeout.new(message, sql: sql, binds: binds)
340
374
  when /Column '(.*)' is not the same data type as referencing column '(.*)' in foreign key/
341
375
  pk_id, fk_id = SQLServer::Utils.extract_identifiers($1), SQLServer::Utils.extract_identifiers($2)
342
376
  MismatchedForeignKey.new(
@@ -348,9 +382,9 @@ module ActiveRecord
348
382
  primary_key: pk_id.object
349
383
  )
350
384
  when /Cannot insert the value NULL into column.*does not allow nulls/
351
- NotNullViolation.new(message)
385
+ NotNullViolation.new(message, sql: sql, binds: binds)
352
386
  when /Arithmetic overflow error/
353
- RangeError.new(message)
387
+ RangeError.new(message, sql: sql, binds: binds)
354
388
  else
355
389
  super
356
390
  end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  class SQLServerColumn < Column
4
6
 
5
- def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment = nil, sqlserver_options = {})
6
- @sqlserver_options = sqlserver_options || {}
7
- super(name, default, sql_type_metadata, null, table_name, default_function, collation, comment: comment)
7
+ def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **sqlserver_options)
8
+ @sqlserver_options = sqlserver_options
9
+ super
8
10
  end
9
11
 
10
12
  def is_identity?
@@ -15,6 +17,10 @@ module ActiveRecord
15
17
  @sqlserver_options[:is_primary]
16
18
  end
17
19
 
20
+ def table_name
21
+ @sqlserver_options[:table_name]
22
+ end
23
+
18
24
  def is_utf8?
19
25
  sql_type =~ /nvarchar|ntext|nchar/i
20
26
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionHandling
3
5
  def sqlserver_connection(config) #:nodoc:
@@ -11,6 +13,12 @@ module ActiveRecord
11
13
  raise ArgumentError, "Unknown connection mode in #{config.inspect}."
12
14
  end
13
15
  ConnectionAdapters::SQLServerAdapter.new(nil, nil, config.merge(mode: mode))
16
+ rescue TinyTds::Error => e
17
+ if e.message.match(/database .* does not exist/i)
18
+ raise ActiveRecord::NoDatabaseError
19
+ else
20
+ raise
21
+ end
14
22
  end
15
23
  end
16
24
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record/tasks/database_tasks'
2
4
  require 'shellwords'
3
5
  require 'ipaddr'
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record/connection_adapters/sqlserver_adapter'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Arel
2
4
  module Visitors
3
5
  class SQLServer < Arel::Visitors::ToSql
@@ -22,6 +24,12 @@ module Arel
22
24
  collector << " #{ActiveRecord::ConnectionAdapters::SQLServerAdapter.cs_equality_operator} "
23
25
  end
24
26
 
27
+ def visit_Arel_Nodes_Concat(o, collector)
28
+ visit o.left, collector
29
+ collector << " + "
30
+ visit o.right, collector
31
+ end
32
+
25
33
  def visit_Arel_Nodes_UpdateStatement(o, a)
26
34
  if o.orders.any? && o.limit.nil?
27
35
  o.limit = Nodes::Limit.new(9_223_372_036_854_775_807)
@@ -31,7 +39,7 @@ module Arel
31
39
 
32
40
  def visit_Arel_Nodes_Lock o, collector
33
41
  o.expr = Arel.sql('WITH(UPDLOCK)') if o.expr.to_s =~ /FOR UPDATE/
34
- collector << SPACE
42
+ collector << " "
35
43
  visit o.expr, collector
36
44
  end
37
45
 
@@ -52,12 +60,17 @@ module Arel
52
60
  end
53
61
  end
54
62
 
63
+ def visit_Arel_Nodes_Grouping(o, collector)
64
+ remove_invalid_ordering_from_select_statement(o.expr)
65
+ super
66
+ end
67
+
55
68
  def visit_Arel_Nodes_SelectStatement o, collector
56
69
  @select_statement = o
57
70
  distinct_One_As_One_Is_So_Not_Fetch o
58
71
  if o.with
59
72
  collector = visit o.with, collector
60
- collector << SPACE
73
+ collector << " "
61
74
  end
62
75
  collector = o.cores.inject(collector) { |c,x|
63
76
  visit_Arel_Nodes_SelectCore(x, c)
@@ -95,7 +108,7 @@ module Arel
95
108
  collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector
96
109
  end
97
110
  if o.right.any?
98
- collector << SPACE if o.left
111
+ collector << " " if o.left
99
112
  collector = inject_join o.right, collector, ' '
100
113
  end
101
114
  collector
@@ -106,7 +119,7 @@ module Arel
106
119
  collector = visit o.left, collector
107
120
  collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
108
121
  if o.right
109
- collector << SPACE
122
+ collector << " "
110
123
  visit(o.right, collector)
111
124
  else
112
125
  collector
@@ -117,16 +130,26 @@ module Arel
117
130
  collector << "LEFT OUTER JOIN "
118
131
  collector = visit o.left, collector
119
132
  collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
120
- collector << SPACE
133
+ collector << " "
121
134
  visit o.right, collector
122
135
  end
123
136
 
137
+ def collect_in_clause(left, right, collector)
138
+ if Array === right
139
+ right.each { |node| remove_invalid_ordering_from_select_statement(node) }
140
+ else
141
+ remove_invalid_ordering_from_select_statement(right)
142
+ end
143
+
144
+ super
145
+ end
146
+
124
147
  # SQLServer ToSql/Visitor (Additions)
125
148
 
126
149
  def visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, options = {}
127
150
  if select_statement_lock?
128
151
  collector = visit @select_statement.lock, collector
129
- collector << SPACE if options[:space]
152
+ collector << " " if options[:space]
130
153
  end
131
154
  collector
132
155
  end
@@ -134,12 +157,11 @@ module Arel
134
157
  def visit_Orders_And_Let_Fetch_Happen o, collector
135
158
  make_Fetch_Possible_And_Deterministic o
136
159
  unless o.orders.empty?
137
- collector << SPACE
138
- collector << ORDER_BY
160
+ collector << " ORDER BY "
139
161
  len = o.orders.length - 1
140
162
  o.orders.each_with_index { |x, i|
141
163
  collector = visit(x, collector)
142
- collector << COMMA unless len == i
164
+ collector << ", " unless len == i
143
165
  }
144
166
  end
145
167
  collector
@@ -196,7 +218,7 @@ module Arel
196
218
  elsif Arel::Nodes::SqlLiteral === core.from
197
219
  Arel::Table.new(core.from)
198
220
  elsif Arel::Nodes::JoinSource === core.source
199
- Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left
221
+ Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left.left
200
222
  end
201
223
  end
202
224
 
@@ -213,6 +235,14 @@ module Arel
213
235
  ).quoted
214
236
  end
215
237
 
238
+ # Need to remove ordering from subqueries unless TOP/OFFSET also used. Otherwise, SQLServer
239
+ # returns error "The ORDER BY clause is invalid in views, inline functions, derived tables,
240
+ # subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified."
241
+ def remove_invalid_ordering_from_select_statement(node)
242
+ return unless Arel::Nodes::SelectStatement === node
243
+
244
+ node.orders = [] unless node.offset || node.limit
245
+ end
216
246
  end
217
247
  end
218
248
  end
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'arel'
2
4
  require 'arel/visitors/sqlserver'
@@ -5,15 +5,15 @@ Write-Output "Setting up..."
5
5
 
6
6
  Write-Output "Setting variables..."
7
7
  $serverName = $env:COMPUTERNAME
8
- $instances = @('SQL2012SP1', 'SQL2014')
8
+ $instanceNames = @('SQL2014')
9
9
  $smo = 'Microsoft.SqlServer.Management.Smo.'
10
10
  $wmi = new-object ($smo + 'Wmi.ManagedComputer')
11
11
 
12
12
  Write-Output "Configure Instances..."
13
- foreach ($instance in $instances) {
14
- Write-Output "Instance $instance ..."
13
+ foreach ($instanceName in $instanceNames) {
14
+ Write-Output "Instance $instanceName ..."
15
15
  Write-Output "Enable TCP/IP and port 1433..."
16
- $uri = "ManagedComputer[@Name='$serverName']/ServerInstance[@Name='$instance']/ServerProtocol[@Name='Tcp']"
16
+ $uri = "ManagedComputer[@Name='$serverName']/ServerInstance[@Name='$instanceName']/ServerProtocol[@Name='Tcp']"
17
17
  $tcp = $wmi.GetSmoObject($uri)
18
18
  $tcp.IsEnabled = $true
19
19
  foreach ($ipAddress in $Tcp.IPAddresses) {
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cases/helper_sqlserver'
2
4
  require 'models/topic'
3
5
  require 'models/task'
@@ -13,7 +15,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
13
15
  let(:basic_update_sql) { "UPDATE [customers] SET [address_street] = NULL WHERE [id] = 2" }
14
16
  let(:basic_select_sql) { "SELECT * FROM [customers] WHERE ([customers].[id] = 1)" }
15
17
 
16
- it 'has basic and non-senstive information in the adpaters inspect method' do
18
+ it 'has basic and non-sensitive information in the adapters inspect method' do
17
19
  string = connection.inspect
18
20
  _(string).must_match %r{ActiveRecord::ConnectionAdapters::SQLServerAdapter}
19
21
  _(string).must_match %r{version\: \d.\d}
@@ -65,6 +67,25 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
65
67
  assert_equal 'customers', connection.send(:get_table_name, basic_select_sql)
66
68
  end
67
69
 
70
+ it 'test bad connection' do
71
+ assert_raise ActiveRecord::NoDatabaseError do
72
+ config = ActiveRecord::Base.configurations['arunit'].merge(database: 'inexistent_activerecord_unittest')
73
+ ActiveRecord::Base.sqlserver_connection config
74
+ end
75
+ end
76
+
77
+ it 'test database exists returns false if database does not exist' do
78
+ config = ActiveRecord::Base.configurations['arunit'].merge(database: 'inexistent_activerecord_unittest')
79
+ assert_not ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(config),
80
+ 'expected database to not exist'
81
+ end
82
+
83
+ it 'test database exists returns true when the database exists' do
84
+ config = ActiveRecord::Base.configurations['arunit']
85
+ assert ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(config),
86
+ "expected database #{config[:database]} to exist"
87
+ end
88
+
68
89
  describe 'with different language' do
69
90
 
70
91
  before do
@@ -429,5 +450,48 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
429
450
  end
430
451
  end
431
452
 
453
+ describe 'block writes to a database' do
454
+ def setup
455
+ @conn = ActiveRecord::Base.connection
456
+ @connection_handler = ActiveRecord::Base.connection_handler
457
+ end
458
+
459
+ def test_errors_when_an_insert_query_is_called_while_preventing_writes
460
+ assert_raises(ActiveRecord::ReadOnlyError) do
461
+ @connection_handler.while_preventing_writes do
462
+ @conn.insert("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
463
+ end
464
+ end
465
+ end
466
+
467
+ def test_errors_when_an_update_query_is_called_while_preventing_writes
468
+ @conn.insert("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
469
+
470
+ assert_raises(ActiveRecord::ReadOnlyError) do
471
+ @connection_handler.while_preventing_writes do
472
+ @conn.update("UPDATE [subscribers] SET [subscribers].[name] = 'Aidan' WHERE [subscribers].[nick] = 'aido'")
473
+ end
474
+ end
475
+ end
476
+
477
+ def test_errors_when_a_delete_query_is_called_while_preventing_writes
478
+ @conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
479
+
480
+ assert_raises(ActiveRecord::ReadOnlyError) do
481
+ @connection_handler.while_preventing_writes do
482
+ @conn.execute("DELETE FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
483
+ end
484
+ end
485
+ end
486
+
487
+ def test_doesnt_error_when_a_select_query_is_called_while_preventing_writes
488
+ @conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
489
+
490
+ @connection_handler.while_preventing_writes do
491
+ assert_equal 1, @conn.execute("SELECT * FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
492
+ end
493
+ end
494
+ end
495
+
432
496
  end
433
497