activerecord-sqlserver-adapter 5.2.0 → 6.0.1

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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/issue_template.md +23 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +29 -0
  6. data/.travis.yml +6 -8
  7. data/CHANGELOG.md +46 -11
  8. data/{Dockerfile → Dockerfile.ci} +2 -2
  9. data/Gemfile +48 -41
  10. data/Guardfile +9 -8
  11. data/README.md +9 -37
  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 +22 -2
  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 +44 -0
  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 +28 -0
  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 +10 -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 +197 -165
  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 +37 -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/float.rb +3 -3
  53. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -3
  54. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -1
  55. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -4
  56. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -3
  57. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -3
  58. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -4
  59. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -3
  60. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -2
  61. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -3
  62. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +6 -6
  63. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +8 -9
  64. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -3
  65. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -3
  66. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -4
  67. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -2
  68. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -3
  69. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -5
  70. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -4
  71. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +4 -3
  72. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -5
  73. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -4
  74. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -5
  75. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -4
  76. data/lib/active_record/connection_adapters/sqlserver/utils.rb +10 -11
  77. data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
  78. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +132 -92
  79. data/lib/active_record/connection_adapters/sqlserver_column.rb +9 -5
  80. data/lib/active_record/sqlserver_base.rb +9 -1
  81. data/lib/active_record/tasks/sqlserver_database_tasks.rb +28 -32
  82. data/lib/activerecord-sqlserver-adapter.rb +3 -1
  83. data/lib/arel/visitors/sqlserver.rb +58 -24
  84. data/lib/arel_sqlserver.rb +4 -2
  85. data/test/appveyor/dbsetup.ps1 +4 -4
  86. data/test/cases/adapter_test_sqlserver.rb +223 -180
  87. data/test/cases/change_column_null_test_sqlserver.rb +17 -15
  88. data/test/cases/coerced_tests.rb +654 -360
  89. data/test/cases/column_test_sqlserver.rb +635 -604
  90. data/test/cases/connection_test_sqlserver.rb +18 -21
  91. data/test/cases/execute_procedure_test_sqlserver.rb +20 -20
  92. data/test/cases/fetch_test_sqlserver.rb +17 -23
  93. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
  94. data/test/cases/helper_sqlserver.rb +20 -15
  95. data/test/cases/in_clause_test_sqlserver.rb +36 -0
  96. data/test/cases/index_test_sqlserver.rb +15 -15
  97. data/test/cases/json_test_sqlserver.rb +25 -25
  98. data/test/cases/migration_test_sqlserver.rb +30 -26
  99. data/test/cases/order_test_sqlserver.rb +53 -54
  100. data/test/cases/pessimistic_locking_test_sqlserver.rb +31 -37
  101. data/test/cases/rake_test_sqlserver.rb +44 -56
  102. data/test/cases/schema_dumper_test_sqlserver.rb +117 -112
  103. data/test/cases/schema_test_sqlserver.rb +20 -26
  104. data/test/cases/scratchpad_test_sqlserver.rb +4 -4
  105. data/test/cases/showplan_test_sqlserver.rb +32 -39
  106. data/test/cases/specific_schema_test_sqlserver.rb +75 -72
  107. data/test/cases/transaction_test_sqlserver.rb +27 -29
  108. data/test/cases/trigger_test_sqlserver.rb +18 -17
  109. data/test/cases/utils_test_sqlserver.rb +78 -78
  110. data/test/cases/uuid_test_sqlserver.rb +19 -20
  111. data/test/debug.rb +8 -6
  112. data/test/migrations/create_clients_and_change_column_null.rb +3 -1
  113. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +4 -4
  114. data/test/models/sqlserver/booking.rb +3 -1
  115. data/test/models/sqlserver/customers_view.rb +3 -1
  116. data/test/models/sqlserver/datatype.rb +2 -0
  117. data/test/models/sqlserver/datatype_migration.rb +2 -0
  118. data/test/models/sqlserver/dollar_table_name.rb +3 -1
  119. data/test/models/sqlserver/edge_schema.rb +3 -3
  120. data/test/models/sqlserver/fk_has_fk.rb +3 -1
  121. data/test/models/sqlserver/fk_has_pk.rb +3 -1
  122. data/test/models/sqlserver/natural_pk_data.rb +4 -2
  123. data/test/models/sqlserver/natural_pk_int_data.rb +3 -1
  124. data/test/models/sqlserver/no_pk_data.rb +3 -1
  125. data/test/models/sqlserver/object_default.rb +3 -1
  126. data/test/models/sqlserver/quoted_table.rb +4 -2
  127. data/test/models/sqlserver/quoted_view_1.rb +3 -1
  128. data/test/models/sqlserver/quoted_view_2.rb +3 -1
  129. data/test/models/sqlserver/sst_memory.rb +3 -1
  130. data/test/models/sqlserver/string_default.rb +3 -1
  131. data/test/models/sqlserver/string_defaults_big_view.rb +3 -1
  132. data/test/models/sqlserver/string_defaults_view.rb +3 -1
  133. data/test/models/sqlserver/tinyint_pk.rb +3 -1
  134. data/test/models/sqlserver/trigger.rb +4 -2
  135. data/test/models/sqlserver/trigger_history.rb +3 -1
  136. data/test/models/sqlserver/upper.rb +3 -1
  137. data/test/models/sqlserver/uppered.rb +3 -1
  138. data/test/models/sqlserver/uuid.rb +3 -1
  139. data/test/schema/datatypes/2012.sql +1 -0
  140. data/test/schema/sqlserver_specific_schema.rb +31 -21
  141. data/test/support/coerceable_test_sqlserver.rb +15 -9
  142. data/test/support/connection_reflection.rb +3 -2
  143. data/test/support/core_ext/query_cache.rb +4 -1
  144. data/test/support/load_schema_sqlserver.rb +5 -5
  145. data/test/support/minitest_sqlserver.rb +3 -1
  146. data/test/support/paths_sqlserver.rb +11 -11
  147. data/test/support/rake_helpers.rb +13 -10
  148. data/test/support/sql_counter_sqlserver.rb +3 -4
  149. data/test/support/test_in_memory_oltp.rb +9 -7
  150. metadata +23 -13
  151. data/BACKERS.md +0 -32
  152. data/circle.yml +0 -38
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  class SQLServerColumn < Column
4
-
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)
6
+ def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **sqlserver_options)
7
+ @sqlserver_options = sqlserver_options
8
+ super
8
9
  end
9
10
 
10
11
  def is_identity?
@@ -15,6 +16,10 @@ module ActiveRecord
15
16
  @sqlserver_options[:is_primary]
16
17
  end
17
18
 
19
+ def table_name
20
+ @sqlserver_options[:table_name]
21
+ end
22
+
18
23
  def is_utf8?
19
24
  sql_type =~ /nvarchar|ntext|nchar/i
20
25
  end
@@ -22,7 +27,6 @@ module ActiveRecord
22
27
  def case_sensitive?
23
28
  collation && collation.match(/_CS/)
24
29
  end
25
-
26
30
  end
27
31
  end
28
32
  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:
@@ -6,11 +8,17 @@ module ActiveRecord
6
8
  mode = config[:mode].to_s.downcase.underscore.to_sym
7
9
  case mode
8
10
  when :dblib
9
- require 'tiny_tds'
11
+ require "tiny_tds"
10
12
  else
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,14 +1,14 @@
1
- require 'active_record/tasks/database_tasks'
2
- require 'shellwords'
3
- require 'ipaddr'
4
- require 'socket'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/tasks/database_tasks"
4
+ require "shellwords"
5
+ require "ipaddr"
6
+ require "socket"
5
7
 
6
8
  module ActiveRecord
7
9
  module Tasks
8
-
9
10
  class SQLServerDatabaseTasks
10
-
11
- DEFAULT_COLLATION = 'SQL_Latin1_General_CP1_CI_AS'
11
+ DEFAULT_COLLATION = "SQL_Latin1_General_CP1_CI_AS"
12
12
 
13
13
  delegate :connection, :establish_connection, :clear_active_connections!,
14
14
  to: ActiveRecord::Base
@@ -19,10 +19,10 @@ module ActiveRecord
19
19
 
20
20
  def create(master_established = false)
21
21
  establish_master_connection unless master_established
22
- connection.create_database configuration['database'], configuration.merge('collation' => default_collation)
22
+ connection.create_database configuration["database"], configuration.merge("collation" => default_collation)
23
23
  establish_connection configuration
24
- rescue ActiveRecord::StatementInvalid => error
25
- if /database .* already exists/i === error.message
24
+ rescue ActiveRecord::StatementInvalid => e
25
+ if /database .* already exists/i === e.message
26
26
  raise DatabaseAlreadyExists
27
27
  else
28
28
  raise
@@ -31,7 +31,7 @@ module ActiveRecord
31
31
 
32
32
  def drop
33
33
  establish_master_connection
34
- connection.drop_database configuration['database']
34
+ connection.drop_database configuration["database"]
35
35
  end
36
36
 
37
37
  def charset
@@ -50,7 +50,7 @@ module ActiveRecord
50
50
 
51
51
  def structure_dump(filename, extra_flags)
52
52
  server_arg = "-S #{Shellwords.escape(configuration['host'])}"
53
- server_arg += ":#{Shellwords.escape(configuration['port'])}" if configuration['port']
53
+ server_arg += ":#{Shellwords.escape(configuration['port'])}" if configuration["port"]
54
54
  command = [
55
55
  "defncopy-ttds",
56
56
  server_arg,
@@ -63,13 +63,14 @@ module ActiveRecord
63
63
  command.concat(table_args)
64
64
  view_args = connection.views.map { |v| Shellwords.escape(v) }
65
65
  command.concat(view_args)
66
- raise 'Error dumping database' unless Kernel.system(command.join(' '))
66
+ raise "Error dumping database" unless Kernel.system(command.join(" "))
67
+
67
68
  dump = File.read(filename)
68
- dump.gsub!(/^USE .*$\nGO\n/, '') # Strip db USE statements
69
- dump.gsub!(/^GO\n/, '') # Strip db GO statements
70
- dump.gsub!(/nvarchar\(8000\)/, 'nvarchar(4000)') # Fix nvarchar(8000) column defs
71
- dump.gsub!(/nvarchar\(-1\)/, 'nvarchar(max)') # Fix nvarchar(-1) column defs
72
- dump.gsub!(/text\(\d+\)/, 'text') # Fix text(16) column defs
69
+ dump.gsub!(/^USE .*$\nGO\n/, "") # Strip db USE statements
70
+ dump.gsub!(/^GO\n/, "") # Strip db GO statements
71
+ dump.gsub!(/nvarchar\(8000\)/, "nvarchar(4000)") # Fix nvarchar(8000) column defs
72
+ dump.gsub!(/nvarchar\(-1\)/, "nvarchar(max)") # Fix nvarchar(-1) column defs
73
+ dump.gsub!(/text\(\d+\)/, "text") # Fix text(16) column defs
73
74
  File.open(filename, "w") { |file| file.puts dump }
74
75
  end
75
76
 
@@ -77,7 +78,6 @@ module ActiveRecord
77
78
  connection.execute File.read(filename)
78
79
  end
79
80
 
80
-
81
81
  private
82
82
 
83
83
  def configuration
@@ -85,25 +85,22 @@ module ActiveRecord
85
85
  end
86
86
 
87
87
  def default_collation
88
- configuration['collation'] || DEFAULT_COLLATION
88
+ configuration["collation"] || DEFAULT_COLLATION
89
89
  end
90
90
 
91
91
  def establish_master_connection
92
- establish_connection configuration.merge('database' => 'master')
92
+ establish_connection configuration.merge("database" => "master")
93
93
  end
94
-
95
94
  end
96
95
 
97
96
  module DatabaseTasksSQLServer
98
-
99
97
  extend ActiveSupport::Concern
100
98
 
101
99
  module ClassMethods
102
-
103
100
  LOCAL_IPADDR = [
104
- IPAddr.new('192.168.0.0/16'),
105
- IPAddr.new('10.0.0.0/8'),
106
- IPAddr.new('172.16.0.0/12')
101
+ IPAddr.new("192.168.0.0/16"),
102
+ IPAddr.new("10.0.0.0/8"),
103
+ IPAddr.new("172.16.0.0/12")
107
104
  ]
108
105
 
109
106
  private
@@ -113,21 +110,20 @@ module ActiveRecord
113
110
  end
114
111
 
115
112
  def configuration_host_ip(configuration)
116
- return nil unless configuration['host']
117
- Socket::getaddrinfo(configuration['host'], 'echo', Socket::AF_INET)[0][3]
113
+ return nil unless configuration["host"]
114
+
115
+ Socket::getaddrinfo(configuration["host"], "echo", Socket::AF_INET)[0][3]
118
116
  end
119
117
 
120
118
  def local_ipaddr?(host_ip)
121
119
  return false unless host_ip
120
+
122
121
  LOCAL_IPADDR.any? { |ip| ip.include?(host_ip) }
123
122
  end
124
-
125
123
  end
126
-
127
124
  end
128
125
 
129
126
  DatabaseTasks.register_task %r{sqlserver}, SQLServerDatabaseTasks
130
127
  DatabaseTasks.send :include, DatabaseTasksSQLServer
131
-
132
128
  end
133
129
  end
@@ -1 +1,3 @@
1
- require 'active_record/connection_adapters/sqlserver_adapter'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/connection_adapters/sqlserver_adapter"
@@ -1,20 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Arel
2
4
  module Visitors
3
5
  class SQLServer < Arel::Visitors::ToSql
4
-
5
6
  OFFSET = " OFFSET "
6
7
  ROWS = " ROWS"
7
8
  FETCH = " FETCH NEXT "
8
9
  FETCH0 = " FETCH FIRST (SELECT 0) "
9
10
  ROWS_ONLY = " ROWS ONLY"
10
11
 
11
-
12
12
  private
13
13
 
14
14
  # SQLServer ToSql/Visitor (Overides)
15
15
 
16
16
  def visit_Arel_Nodes_BindParam o, collector
17
- collector.add_bind(o.value) { |i| "@#{i-1}" }
17
+ collector.add_bind(o.value) { |i| "@#{i - 1}" }
18
18
  end
19
19
 
20
20
  def visit_Arel_Nodes_Bin o, collector
@@ -22,6 +22,12 @@ module Arel
22
22
  collector << " #{ActiveRecord::ConnectionAdapters::SQLServerAdapter.cs_equality_operator} "
23
23
  end
24
24
 
25
+ def visit_Arel_Nodes_Concat(o, collector)
26
+ visit o.left, collector
27
+ collector << " + "
28
+ visit o.right, collector
29
+ end
30
+
25
31
  def visit_Arel_Nodes_UpdateStatement(o, a)
26
32
  if o.orders.any? && o.limit.nil?
27
33
  o.limit = Nodes::Limit.new(9_223_372_036_854_775_807)
@@ -30,8 +36,8 @@ module Arel
30
36
  end
31
37
 
32
38
  def visit_Arel_Nodes_Lock o, collector
33
- o.expr = Arel.sql('WITH(UPDLOCK)') if o.expr.to_s =~ /FOR UPDATE/
34
- collector << SPACE
39
+ o.expr = Arel.sql("WITH(UPDLOCK)") if o.expr.to_s =~ /FOR UPDATE/
40
+ collector << " "
35
41
  visit o.expr, collector
36
42
  end
37
43
 
@@ -52,14 +58,19 @@ module Arel
52
58
  end
53
59
  end
54
60
 
61
+ def visit_Arel_Nodes_Grouping(o, collector)
62
+ remove_invalid_ordering_from_select_statement(o.expr)
63
+ super
64
+ end
65
+
55
66
  def visit_Arel_Nodes_SelectStatement o, collector
56
67
  @select_statement = o
57
68
  distinct_One_As_One_Is_So_Not_Fetch o
58
69
  if o.with
59
70
  collector = visit o.with, collector
60
- collector << SPACE
71
+ collector << " "
61
72
  end
62
- collector = o.cores.inject(collector) { |c,x|
73
+ collector = o.cores.inject(collector) { |c, x|
63
74
  visit_Arel_Nodes_SelectCore(x, c)
64
75
  }
65
76
  collector = visit_Orders_And_Let_Fetch_Happen o, collector
@@ -73,15 +84,17 @@ module Arel
73
84
  # Apparently, o.engine.connection can actually be a different adapter
74
85
  # than sqlserver. Can be removed if fixed in ActiveRecord. See:
75
86
  # github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/450
76
- table_name = begin
77
- if o.class.engine.connection.respond_to?(:sqlserver?) && o.class.engine.connection.database_prefix_remote_server?
78
- remote_server_table_name(o)
79
- else
87
+ table_name =
88
+ begin
89
+ if o.class.engine.connection.respond_to?(:sqlserver?) && o.class.engine.connection.database_prefix_remote_server?
90
+ remote_server_table_name(o)
91
+ else
92
+ quote_table_name(o.name)
93
+ end
94
+ rescue Exception
80
95
  quote_table_name(o.name)
81
96
  end
82
- rescue Exception => e
83
- quote_table_name(o.name)
84
- end
97
+
85
98
  if o.table_alias
86
99
  collector << "#{table_name} #{quote_table_name o.table_alias}"
87
100
  else
@@ -95,8 +108,8 @@ 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
99
- collector = inject_join o.right, collector, ' '
111
+ collector << " " if o.left
112
+ collector = inject_join o.right, collector, " "
100
113
  end
101
114
  collector
102
115
  end
@@ -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
@@ -156,6 +178,7 @@ module Arel
156
178
 
157
179
  def node_value(node)
158
180
  return nil unless node
181
+
159
182
  case node.expr
160
183
  when NilClass then nil
161
184
  when Numeric then node.expr
@@ -169,9 +192,11 @@ module Arel
169
192
 
170
193
  def make_Fetch_Possible_And_Deterministic o
171
194
  return if o.limit.nil? && o.offset.nil?
195
+
172
196
  t = table_From_Statement o
173
197
  pk = primary_Key_From_Table t
174
198
  return unless pk
199
+
175
200
  if o.orders.empty?
176
201
  # Prefer deterministic vs a simple `(SELECT NULL)` expr.
177
202
  o.orders = [pk.asc]
@@ -196,14 +221,15 @@ module Arel
196
221
  elsif Arel::Nodes::SqlLiteral === core.from
197
222
  Arel::Table.new(core.from)
198
223
  elsif Arel::Nodes::JoinSource === core.source
199
- Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left
224
+ Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left.left
200
225
  end
201
226
  end
202
227
 
203
228
  def primary_Key_From_Table t
204
229
  return unless t
230
+
205
231
  column_name = @connection.schema_cache.primary_keys(t.name) ||
206
- @connection.schema_cache.columns_hash(t.name).first.try(:second).try(:name)
232
+ @connection.schema_cache.columns_hash(t.name).first.try(:second).try(:name)
207
233
  column_name ? t[column_name] : nil
208
234
  end
209
235
 
@@ -213,6 +239,14 @@ module Arel
213
239
  ).quoted
214
240
  end
215
241
 
242
+ # Need to remove ordering from subqueries unless TOP/OFFSET also used. Otherwise, SQLServer
243
+ # returns error "The ORDER BY clause is invalid in views, inline functions, derived tables,
244
+ # subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified."
245
+ def remove_invalid_ordering_from_select_statement(node)
246
+ return unless Arel::Nodes::SelectStatement === node
247
+
248
+ node.orders = [] unless node.offset || node.limit
249
+ end
216
250
  end
217
251
  end
218
252
  end
@@ -1,2 +1,4 @@
1
- require 'arel'
2
- require 'arel/visitors/sqlserver'
1
+ # frozen_string_literal: true
2
+
3
+ require "arel"
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,72 +1,91 @@
1
- require 'cases/helper_sqlserver'
2
- require 'models/topic'
3
- require 'models/task'
4
- require 'models/post'
5
- require 'models/subscriber'
6
- require 'models/minimalistic'
1
+ # frozen_string_literal: true
7
2
 
8
- class AdapterTestSQLServer < ActiveRecord::TestCase
3
+ require "cases/helper_sqlserver"
4
+ require "models/topic"
5
+ require "models/task"
6
+ require "models/post"
7
+ require "models/subscriber"
8
+ require "models/minimalistic"
9
9
 
10
+ class AdapterTestSQLServer < ActiveRecord::TestCase
10
11
  fixtures :tasks
11
12
 
12
13
  let(:basic_insert_sql) { "INSERT INTO [funny_jokes] ([name]) VALUES('Knock knock')" }
13
14
  let(:basic_update_sql) { "UPDATE [customers] SET [address_street] = NULL WHERE [id] = 2" }
14
15
  let(:basic_select_sql) { "SELECT * FROM [customers] WHERE ([customers].[id] = 1)" }
15
16
 
16
- it 'has basic and non-senstive information in the adpaters inspect method' do
17
+ it "has basic and non-sensitive information in the adapters inspect method" do
17
18
  string = connection.inspect
18
- string.must_match %r{ActiveRecord::ConnectionAdapters::SQLServerAdapter}
19
- string.must_match %r{version\: \d.\d}
20
- string.must_match %r{mode: dblib}
21
- string.must_match %r{azure: (true|false)}
22
- string.wont_match %r{host}
23
- string.wont_match %r{password}
24
- string.wont_match %r{username}
25
- string.wont_match %r{port}
19
+ _(string).must_match %r{ActiveRecord::ConnectionAdapters::SQLServerAdapter}
20
+ _(string).must_match %r{version\: \d.\d}
21
+ _(string).must_match %r{mode: dblib}
22
+ _(string).must_match %r{azure: (true|false)}
23
+ _(string).wont_match %r{host}
24
+ _(string).wont_match %r{password}
25
+ _(string).wont_match %r{username}
26
+ _(string).wont_match %r{port}
26
27
  end
27
28
 
28
- it 'has a 128 max #table_alias_length' do
29
+ it "has a 128 max #table_alias_length" do
29
30
  assert connection.table_alias_length <= 128
30
31
  end
31
32
 
32
- it 'raises invalid statement error for bad SQL' do
33
+ it "raises invalid statement error for bad SQL" do
33
34
  assert_raise(ActiveRecord::StatementInvalid) { Topic.connection.update("UPDATE XXX") }
34
35
  end
35
36
 
36
- it 'is has our adapter_name' do
37
- assert_equal 'SQLServer', connection.adapter_name
37
+ it "is has our adapter_name" do
38
+ assert_equal "SQLServer", connection.adapter_name
38
39
  end
39
40
 
40
- it 'support DDL in transactions' do
41
+ it "support DDL in transactions" do
41
42
  assert connection.supports_ddl_transactions?
42
43
  end
43
44
 
44
- it 'allow owner table name prefixs like dbo to still allow table exists to return true' do
45
+ it "allow owner table name prefixs like dbo to still allow table exists to return true" do
45
46
  begin
46
- assert_equal 'topics', Topic.table_name
47
+ assert_equal "topics", Topic.table_name
47
48
  assert Topic.table_exists?
48
- Topic.table_name = 'dbo.topics'
49
- assert Topic.table_exists?, 'Tasks table name of dbo.topics should return true for exists.'
49
+ Topic.table_name = "dbo.topics"
50
+ assert Topic.table_exists?, "Tasks table name of dbo.topics should return true for exists."
50
51
  ensure
51
- Topic.table_name = 'topics'
52
+ Topic.table_name = "topics"
52
53
  end
53
54
  end
54
55
 
55
- it 'return true to insert sql query for inserts only' do
56
- assert connection.send(:insert_sql?,'INSERT...')
56
+ it "return true to insert sql query for inserts only" do
57
+ assert connection.send(:insert_sql?, "INSERT...")
57
58
  assert connection.send(:insert_sql?, "EXEC sp_executesql N'INSERT INTO [fk_test_has_fks] ([fk_id]) VALUES (@0); SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident', N'@0 int', @0 = 0")
58
- assert !connection.send(:insert_sql?,'UPDATE...')
59
- assert !connection.send(:insert_sql?,'SELECT...')
59
+ assert !connection.send(:insert_sql?, "UPDATE...")
60
+ assert !connection.send(:insert_sql?, "SELECT...")
61
+ end
62
+
63
+ it "return unquoted table name object from basic INSERT UPDATE and SELECT statements" do
64
+ assert_equal "funny_jokes", connection.send(:get_table_name, basic_insert_sql)
65
+ assert_equal "customers", connection.send(:get_table_name, basic_update_sql)
66
+ assert_equal "customers", connection.send(:get_table_name, basic_select_sql)
60
67
  end
61
68
 
62
- it 'return unquoted table name object from basic INSERT UPDATE and SELECT statements' do
63
- assert_equal 'funny_jokes', connection.send(:get_table_name, basic_insert_sql)
64
- assert_equal 'customers', connection.send(:get_table_name, basic_update_sql)
65
- assert_equal 'customers', connection.send(:get_table_name, basic_select_sql)
69
+ it "test bad connection" do
70
+ assert_raise ActiveRecord::NoDatabaseError do
71
+ config = ActiveRecord::Base.configurations["arunit"].merge(database: "inexistent_activerecord_unittest")
72
+ ActiveRecord::Base.sqlserver_connection config
73
+ end
66
74
  end
67
75
 
68
- describe 'with different language' do
76
+ it "test database exists returns false if database does not exist" do
77
+ config = ActiveRecord::Base.configurations["arunit"].merge(database: "inexistent_activerecord_unittest")
78
+ assert_not ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(config),
79
+ "expected database to not exist"
80
+ end
69
81
 
82
+ it "test database exists returns true when the database exists" do
83
+ config = ActiveRecord::Base.configurations["arunit"]
84
+ assert ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(config),
85
+ "expected database #{config[:database]} to exist"
86
+ end
87
+
88
+ describe "with different language" do
70
89
  before do
71
90
  @default_language = connection.user_options_language
72
91
  end
@@ -76,18 +95,18 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
76
95
  connection.send :initialize_dateformatter
77
96
  end
78
97
 
79
- it 'memos users dateformat' do
98
+ it "memos users dateformat" do
80
99
  connection.execute("SET LANGUAGE us_english") rescue nil
81
100
  dateformat = connection.instance_variable_get(:@database_dateformat)
82
- assert_equal 'mdy', dateformat
101
+ assert_equal "mdy", dateformat
83
102
  end
84
103
 
85
- it 'has a dateformatter' do
104
+ it "has a dateformatter" do
86
105
  assert Date::DATE_FORMATS[:_sqlserver_dateformat]
87
106
  assert Time::DATE_FORMATS[:_sqlserver_dateformat]
88
107
  end
89
108
 
90
- it 'does a datetime insertion when language is german' do
109
+ it "does a datetime insertion when language is german" do
91
110
  connection.execute("SET LANGUAGE deutsch")
92
111
  connection.send :initialize_dateformatter
93
112
  assert_nothing_raised do
@@ -96,40 +115,36 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
96
115
  Task.create! starting: starting, ending: ending
97
116
  end
98
117
  end
99
-
100
118
  end
101
119
 
102
- describe 'testing #lowercase_schema_reflection' do
103
-
120
+ describe "testing #lowercase_schema_reflection" do
104
121
  before do
105
122
  SSTestUpper.delete_all
106
- SSTestUpper.create COLUMN1: 'Got a minute?', COLUMN2: 419
107
- SSTestUpper.create COLUMN1: 'Favorite number?', COLUMN2: 69
123
+ SSTestUpper.create COLUMN1: "Got a minute?", COLUMN2: 419
124
+ SSTestUpper.create COLUMN1: "Favorite number?", COLUMN2: 69
108
125
  end
109
126
 
110
127
  after do
111
128
  connection.lowercase_schema_reflection = false
112
129
  end
113
130
 
114
- it 'not lowercase schema reflection by default' do
115
- assert SSTestUpper.columns_hash['COLUMN1']
116
- assert_equal 'Got a minute?', SSTestUpper.first.COLUMN1
117
- assert_equal 'Favorite number?', SSTestUpper.last.COLUMN1
118
- assert SSTestUpper.columns_hash['COLUMN2']
131
+ it "not lowercase schema reflection by default" do
132
+ assert SSTestUpper.columns_hash["COLUMN1"]
133
+ assert_equal "Got a minute?", SSTestUpper.first.COLUMN1
134
+ assert_equal "Favorite number?", SSTestUpper.last.COLUMN1
135
+ assert SSTestUpper.columns_hash["COLUMN2"]
119
136
  end
120
137
 
121
- it 'lowercase schema reflection when set' do
138
+ it "lowercase schema reflection when set" do
122
139
  connection.lowercase_schema_reflection = true
123
- assert SSTestUppered.columns_hash['column1']
124
- assert_equal 'Got a minute?', SSTestUppered.first.column1
125
- assert_equal 'Favorite number?', SSTestUppered.last.column1
126
- assert SSTestUppered.columns_hash['column2']
140
+ assert SSTestUppered.columns_hash["column1"]
141
+ assert_equal "Got a minute?", SSTestUppered.first.column1
142
+ assert_equal "Favorite number?", SSTestUppered.last.column1
143
+ assert SSTestUppered.columns_hash["column2"]
127
144
  end
128
-
129
145
  end
130
146
 
131
- describe 'identity inserts' do
132
-
147
+ describe "identity inserts" do
133
148
  before do
134
149
  @identity_insert_sql = "INSERT INTO [funny_jokes] ([id],[name]) VALUES(420,'Knock knock')"
135
150
  @identity_insert_sql_unquoted = "INSERT INTO funny_jokes (id, name) VALUES(420, 'Knock knock')"
@@ -139,62 +154,60 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
139
154
  @identity_insert_sql_unordered_sp = "EXEC sp_executesql N'INSERT INTO [funny_jokes] ([name],[id]) VALUES (@0, @1)', N'@0 nvarchar(255), @1 int', @0 = N'Knock knock', @1 = 420"
140
155
  end
141
156
 
142
- it 'return quoted table_name to #query_requires_identity_insert? when INSERT sql contains id column' do
143
- assert_equal 'funny_jokes', connection.send(:query_requires_identity_insert?,@identity_insert_sql)
144
- assert_equal 'funny_jokes', connection.send(:query_requires_identity_insert?,@identity_insert_sql_unquoted)
145
- assert_equal 'funny_jokes', connection.send(:query_requires_identity_insert?,@identity_insert_sql_unordered)
146
- assert_equal 'funny_jokes', connection.send(:query_requires_identity_insert?,@identity_insert_sql_sp)
147
- assert_equal 'funny_jokes', connection.send(:query_requires_identity_insert?,@identity_insert_sql_unquoted_sp)
148
- assert_equal 'funny_jokes', connection.send(:query_requires_identity_insert?,@identity_insert_sql_unordered_sp)
157
+ it "return quoted table_name to #query_requires_identity_insert? when INSERT sql contains id column" do
158
+ assert_equal "funny_jokes", connection.send(:query_requires_identity_insert?, @identity_insert_sql)
159
+ assert_equal "funny_jokes", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unquoted)
160
+ assert_equal "funny_jokes", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unordered)
161
+ assert_equal "funny_jokes", connection.send(:query_requires_identity_insert?, @identity_insert_sql_sp)
162
+ assert_equal "funny_jokes", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unquoted_sp)
163
+ assert_equal "funny_jokes", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unordered_sp)
149
164
  end
150
165
 
151
- it 'return false to #query_requires_identity_insert? for normal SQL' do
166
+ it "return false to #query_requires_identity_insert? for normal SQL" do
152
167
  [basic_insert_sql, basic_update_sql, basic_select_sql].each do |sql|
153
- assert !connection.send(:query_requires_identity_insert?,sql), "SQL was #{sql}"
168
+ assert !connection.send(:query_requires_identity_insert?, sql), "SQL was #{sql}"
154
169
  end
155
170
  end
156
171
 
157
- it 'find identity column using #identity_columns' do
158
- task_id_column = Task.columns_hash['id']
172
+ it "find identity column using #identity_columns" do
173
+ task_id_column = Task.columns_hash["id"]
159
174
  assert_equal task_id_column.name, connection.send(:identity_columns, Task.table_name).first.name
160
175
  assert_equal task_id_column.sql_type, connection.send(:identity_columns, Task.table_name).first.sql_type
161
176
  end
162
177
 
163
- it 'return an empty array when calling #identity_columns for a table_name with no identity' do
164
- connection.send(:identity_columns, Subscriber.table_name).must_equal []
178
+ it "return an empty array when calling #identity_columns for a table_name with no identity" do
179
+ _(connection.send(:identity_columns, Subscriber.table_name)).must_equal []
165
180
  end
166
-
167
181
  end
168
182
 
169
- describe 'quoting' do
170
-
171
- it 'return 1 for #quoted_true' do
172
- assert_equal '1', connection.quoted_true
183
+ describe "quoting" do
184
+ it "return 1 for #quoted_true" do
185
+ assert_equal "1", connection.quoted_true
173
186
  end
174
187
 
175
- it 'return 0 for #quoted_false' do
176
- assert_equal '0', connection.quoted_false
188
+ it "return 0 for #quoted_false" do
189
+ assert_equal "0", connection.quoted_false
177
190
  end
178
191
 
179
- it 'not escape backslash characters like abstract adapter' do
192
+ it "not escape backslash characters like abstract adapter" do
180
193
  string_with_backslashs = "\\n"
181
194
  assert_equal string_with_backslashs, connection.quote_string(string_with_backslashs)
182
195
  end
183
196
 
184
- it 'quote column names with brackets' do
185
- assert_equal '[foo]', connection.quote_column_name(:foo)
186
- assert_equal '[foo]', connection.quote_column_name('foo')
187
- assert_equal '[foo].[bar]', connection.quote_column_name('foo.bar')
197
+ it "quote column names with brackets" do
198
+ assert_equal "[foo]", connection.quote_column_name(:foo)
199
+ assert_equal "[foo]", connection.quote_column_name("foo")
200
+ assert_equal "[foo].[bar]", connection.quote_column_name("foo.bar")
188
201
  end
189
202
 
190
- it 'not quote already quoted column names with brackets' do
191
- assert_equal '[foo]', connection.quote_column_name('[foo]')
192
- assert_equal '[foo].[bar]', connection.quote_column_name('[foo].[bar]')
203
+ it "not quote already quoted column names with brackets" do
204
+ assert_equal "[foo]", connection.quote_column_name("[foo]")
205
+ assert_equal "[foo].[bar]", connection.quote_column_name("[foo].[bar]")
193
206
  end
194
207
 
195
- it 'quote table names like columns' do
196
- assert_equal '[foo].[bar]', connection.quote_column_name('foo.bar')
197
- assert_equal '[foo].[bar].[baz]', connection.quote_column_name('foo.bar.baz')
208
+ it "quote table names like columns" do
209
+ assert_equal "[foo].[bar]", connection.quote_column_name("foo.bar")
210
+ assert_equal "[foo].[bar].[baz]", connection.quote_column_name("foo.bar.baz")
198
211
  end
199
212
 
200
213
  it "surround string with national prefix" do
@@ -204,44 +217,40 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
204
217
  it "escape all single quotes by repeating them" do
205
218
  assert_equal "N'''quotation''s'''", connection.quote("'quotation's'")
206
219
  end
207
-
208
220
  end
209
221
 
210
- describe 'disabling referential integrity' do
211
-
222
+ describe "disabling referential integrity" do
212
223
  before do
213
224
  connection.disable_referential_integrity { SSTestHasPk.delete_all; SSTestHasFk.delete_all }
214
225
  @parent = SSTestHasPk.create!
215
226
  @member = SSTestHasFk.create!(fk_id: @parent.id)
216
227
  end
217
228
 
218
- it 'NOT ALLOW by default the deletion of a referenced parent' do
219
- SSTestHasPk.connection.disable_referential_integrity { }
229
+ it "NOT ALLOW by default the deletion of a referenced parent" do
230
+ SSTestHasPk.connection.disable_referential_integrity {}
220
231
  assert_raise(ActiveRecord::StatementInvalid) { @parent.destroy }
221
232
  end
222
233
 
223
- it 'ALLOW deletion of referenced parent using #disable_referential_integrity block' do
234
+ it "ALLOW deletion of referenced parent using #disable_referential_integrity block" do
224
235
  SSTestHasPk.connection.disable_referential_integrity { @parent.destroy }
225
236
  end
226
237
 
227
- it 'again NOT ALLOW deletion of referenced parent after #disable_referential_integrity block' do
238
+ it "again NOT ALLOW deletion of referenced parent after #disable_referential_integrity block" do
228
239
  assert_raise(ActiveRecord::StatementInvalid) do
229
- SSTestHasPk.connection.disable_referential_integrity { }
240
+ SSTestHasPk.connection.disable_referential_integrity {}
230
241
  @parent.destroy
231
242
  end
232
243
  end
233
244
 
234
- it 'not disable referential integrity for the same table twice' do
245
+ it "not disable referential integrity for the same table twice" do
235
246
  tables = SSTestHasPk.connection.tables_with_referential_integrity
236
247
  assert_equal tables.size, tables.uniq.size
237
248
  end
238
-
239
249
  end
240
250
 
241
- describe 'database statements' do
242
-
251
+ describe "database statements" do
243
252
  it "run the database consistency checker useroptions command" do
244
- skip 'on azure' if connection_sqlserver_azure?
253
+ skip "on azure" if connection_sqlserver_azure?
245
254
  keys = [:textsize, :language, :isolation_level, :dateformat]
246
255
  user_options = connection.user_options
247
256
  keys.each do |key|
@@ -251,183 +260,217 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
251
260
  end
252
261
 
253
262
  it "return a underscored key hash with indifferent access of the results" do
254
- skip 'on azure' if connection_sqlserver_azure?
263
+ skip "on azure" if connection_sqlserver_azure?
255
264
  user_options = connection.user_options
256
- assert_equal 'read committed', user_options['isolation_level']
257
- assert_equal 'read committed', user_options[:isolation_level]
265
+ assert_equal "read committed", user_options["isolation_level"]
266
+ assert_equal "read committed", user_options[:isolation_level]
258
267
  end
259
-
260
268
  end
261
269
 
262
- describe 'schema statements' do
263
-
264
- it 'create integers when no limit supplied' do
265
- assert_equal 'integer', connection.type_to_sql(:integer)
270
+ describe "schema statements" do
271
+ it "create integers when no limit supplied" do
272
+ assert_equal "integer", connection.type_to_sql(:integer)
266
273
  end
267
274
 
268
- it 'create integers when limit is 4' do
269
- assert_equal 'integer', connection.type_to_sql(:integer, limit: 4)
275
+ it "create integers when limit is 4" do
276
+ assert_equal "integer", connection.type_to_sql(:integer, limit: 4)
270
277
  end
271
278
 
272
- it 'create integers when limit is 3' do
273
- assert_equal 'integer', connection.type_to_sql(:integer, limit: 3)
279
+ it "create integers when limit is 3" do
280
+ assert_equal "integer", connection.type_to_sql(:integer, limit: 3)
274
281
  end
275
282
 
276
- it 'create smallints when limit is 2' do
277
- assert_equal 'smallint', connection.type_to_sql(:integer, limit: 2)
283
+ it "create smallints when limit is 2" do
284
+ assert_equal "smallint", connection.type_to_sql(:integer, limit: 2)
278
285
  end
279
286
 
280
- it 'create tinyints when limit is 1' do
281
- assert_equal 'tinyint', connection.type_to_sql(:integer, limit: 1)
287
+ it "create tinyints when limit is 1" do
288
+ assert_equal "tinyint", connection.type_to_sql(:integer, limit: 1)
282
289
  end
283
290
 
284
- it 'create bigints when limit is greateer than 4' do
285
- assert_equal 'bigint', connection.type_to_sql(:integer, limit: 5)
286
- assert_equal 'bigint', connection.type_to_sql(:integer, limit: 6)
287
- assert_equal 'bigint', connection.type_to_sql(:integer, limit: 7)
288
- assert_equal 'bigint', connection.type_to_sql(:integer, limit: 8)
291
+ it "create bigints when limit is greateer than 4" do
292
+ assert_equal "bigint", connection.type_to_sql(:integer, limit: 5)
293
+ assert_equal "bigint", connection.type_to_sql(:integer, limit: 6)
294
+ assert_equal "bigint", connection.type_to_sql(:integer, limit: 7)
295
+ assert_equal "bigint", connection.type_to_sql(:integer, limit: 8)
289
296
  end
290
297
 
291
- it 'create floats when no limit supplied' do
292
- assert_equal 'float', connection.type_to_sql(:float)
298
+ it "create floats when no limit supplied" do
299
+ assert_equal "float", connection.type_to_sql(:float)
293
300
  end
294
-
295
301
  end
296
302
 
297
- describe 'views' do
298
-
303
+ describe "views" do
299
304
  # Using connection.views
300
305
 
301
- it 'return an array' do
306
+ it "return an array" do
302
307
  assert_instance_of Array, connection.views
303
308
  end
304
309
 
305
- it 'find SSTestCustomersView table name' do
306
- connection.views.must_include 'sst_customers_view'
310
+ it "find SSTestCustomersView table name" do
311
+ _(connection.views).must_include "sst_customers_view"
307
312
  end
308
313
 
309
- it 'work with dynamic finders' do
310
- name = 'MetaSkills'
314
+ it "work with dynamic finders" do
315
+ name = "MetaSkills"
311
316
  customer = SSTestCustomersView.create! name: name
312
317
  assert_equal customer, SSTestCustomersView.find_by_name(name)
313
318
  end
314
319
 
315
- it 'not contain system views' do
316
- systables = ['sysconstraints','syssegments']
320
+ it "not contain system views" do
321
+ systables = ["sysconstraints", "syssegments"]
317
322
  systables.each do |systable|
318
323
  assert !connection.views.include?(systable), "This systable #{systable} should not be in the views array."
319
324
  end
320
325
  end
321
326
 
322
- it 'allow the connection#view_information method to return meta data on the view' do
323
- view_info = connection.send(:view_information,'sst_customers_view')
324
- assert_equal('sst_customers_view', view_info['TABLE_NAME'])
325
- assert_match(/CREATE VIEW sst_customers_view/, view_info['VIEW_DEFINITION'])
327
+ it "allow the connection#view_information method to return meta data on the view" do
328
+ view_info = connection.send(:view_information, "sst_customers_view")
329
+ assert_equal("sst_customers_view", view_info["TABLE_NAME"])
330
+ assert_match(/CREATE VIEW sst_customers_view/, view_info["VIEW_DEFINITION"])
326
331
  end
327
332
 
328
- it 'allow the connection#view_table_name method to return true table_name for the view' do
329
- assert_equal 'customers', connection.send(:view_table_name,'sst_customers_view')
330
- assert_equal 'topics', connection.send(:view_table_name,'topics'), 'No view here, the same table name should come back.'
333
+ it "allow the connection#view_table_name method to return true table_name for the view" do
334
+ assert_equal "customers", connection.send(:view_table_name, "sst_customers_view")
335
+ assert_equal "topics", connection.send(:view_table_name, "topics"), "No view here, the same table name should come back."
331
336
  end
332
337
 
333
338
  # With same column names
334
339
 
335
- it 'have matching column objects' do
336
- columns = ['id','name','balance']
340
+ it "have matching column objects" do
341
+ columns = ["id", "name", "balance"]
337
342
  assert !SSTestCustomersView.columns.blank?
338
343
  assert_equal columns.size, SSTestCustomersView.columns.size
339
344
  columns.each do |colname|
340
345
  assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
341
- SSTestCustomersView.columns_hash[colname],
342
- "Column name #{colname.inspect} was not found in these columns #{SSTestCustomersView.columns.map(&:name).inspect}"
346
+ SSTestCustomersView.columns_hash[colname],
347
+ "Column name #{colname.inspect} was not found in these columns #{SSTestCustomersView.columns.map(&:name).inspect}"
343
348
  end
344
349
  end
345
350
 
346
- it 'find identity column' do
347
- SSTestCustomersView.primary_key.must_equal 'id'
348
- connection.primary_key(SSTestCustomersView.table_name).must_equal 'id'
349
- SSTestCustomersView.columns_hash['id'].must_be :is_identity?
351
+ it "find identity column" do
352
+ _(SSTestCustomersView.primary_key).must_equal "id"
353
+ _(connection.primary_key(SSTestCustomersView.table_name)).must_equal "id"
354
+ _(SSTestCustomersView.columns_hash["id"]).must_be :is_identity?
350
355
  end
351
356
 
352
- it 'find default values' do
357
+ it "find default values" do
353
358
  assert_equal 0, SSTestCustomersView.new.balance
354
359
  end
355
360
 
356
- it 'respond true to data_source_exists?' do
361
+ it "respond true to data_source_exists?" do
357
362
  assert SSTestCustomersView.connection.data_source_exists?(SSTestCustomersView.table_name)
358
363
  end
359
364
 
360
365
  # With aliased column names
361
366
 
362
- it 'have matching column objects' do
363
- columns = ['id','pretend_null']
367
+ it "have matching column objects" do
368
+ columns = ["id", "pretend_null"]
364
369
  assert !SSTestStringDefaultsView.columns.blank?
365
370
  assert_equal columns.size, SSTestStringDefaultsView.columns.size
366
371
  columns.each do |colname|
367
372
  assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
368
- SSTestStringDefaultsView.columns_hash[colname],
369
- "Column name #{colname.inspect} was not found in these columns #{SSTestStringDefaultsView.columns.map(&:name).inspect}"
373
+ SSTestStringDefaultsView.columns_hash[colname],
374
+ "Column name #{colname.inspect} was not found in these columns #{SSTestStringDefaultsView.columns.map(&:name).inspect}"
370
375
  end
371
376
  end
372
377
 
373
- it 'find identity column' do
374
- SSTestStringDefaultsView.primary_key.must_equal 'id'
375
- connection.primary_key(SSTestStringDefaultsView.table_name).must_equal 'id'
376
- SSTestStringDefaultsView.columns_hash['id'].must_be :is_identity?
378
+ it "find identity column" do
379
+ _(SSTestStringDefaultsView.primary_key).must_equal "id"
380
+ _(connection.primary_key(SSTestStringDefaultsView.table_name)).must_equal "id"
381
+ _(SSTestStringDefaultsView.columns_hash["id"]).must_be :is_identity?
377
382
  end
378
383
 
379
- it 'find default values' do
380
- assert_equal 'null', SSTestStringDefaultsView.new.pretend_null,
381
- SSTestStringDefaultsView.columns_hash['pretend_null'].inspect
384
+ it "find default values" do
385
+ assert_equal "null", SSTestStringDefaultsView.new.pretend_null,
386
+ SSTestStringDefaultsView.columns_hash["pretend_null"].inspect
382
387
  end
383
388
 
384
- it 'respond true to data_source_exists?' do
389
+ it "respond true to data_source_exists?" do
385
390
  assert SSTestStringDefaultsView.connection.data_source_exists?(SSTestStringDefaultsView.table_name)
386
391
  end
387
392
 
388
393
  # That have more than 4000 chars for their defintion
389
394
 
390
- it 'cope with null returned for the defintion' do
395
+ it "cope with null returned for the defintion" do
391
396
  assert_nothing_raised() { SSTestStringDefaultsBigView.columns }
392
397
  end
393
398
 
394
- it 'using alternate view defintion still be able to find real default' do
395
- assert_equal 'null', SSTestStringDefaultsBigView.new.pretend_null,
396
- SSTestStringDefaultsBigView.columns_hash['pretend_null'].inspect
399
+ it "using alternate view defintion still be able to find real default" do
400
+ assert_equal "null", SSTestStringDefaultsBigView.new.pretend_null,
401
+ SSTestStringDefaultsBigView.columns_hash["pretend_null"].inspect
397
402
  end
398
-
399
403
  end
400
404
 
401
- describe 'database_prefix_remote_server?' do
402
-
405
+ describe "database_prefix_remote_server?" do
403
406
  after do
404
407
  connection_options.delete(:database_prefix)
405
408
  end
406
409
 
407
- it 'returns false if database_prefix is not configured' do
410
+ it "returns false if database_prefix is not configured" do
408
411
  assert_equal false, connection.database_prefix_remote_server?
409
412
  end
410
413
 
411
- it 'returns true if database_prefix has been set' do
414
+ it "returns true if database_prefix has been set" do
412
415
  connection_options[:database_prefix] = "server.database.schema."
413
416
  assert_equal true, connection.database_prefix_remote_server?
414
417
  end
415
418
 
416
- it 'returns false if database_prefix has been set incorrectly' do
419
+ it "returns false if database_prefix has been set incorrectly" do
417
420
  connection_options[:database_prefix] = "server.database.schema"
418
421
  assert_equal false, connection.database_prefix_remote_server?
419
422
  end
420
-
421
423
  end
422
424
 
423
- it 'in_memory_oltp' do
424
- if ENV['IN_MEMORY_OLTP'] && connection.supports_in_memory_oltp?
425
- SSTMemory.primary_key.must_equal 'id'
426
- SSTMemory.columns_hash['id'].must_be :is_identity?
425
+ it "in_memory_oltp" do
426
+ if ENV["IN_MEMORY_OLTP"] && connection.supports_in_memory_oltp?
427
+ _(SSTMemory.primary_key).must_equal "id"
428
+ _(SSTMemory.columns_hash["id"]).must_be :is_identity?
427
429
  else
428
- skip 'supports_in_memory_oltp? => false'
430
+ skip "supports_in_memory_oltp? => false"
429
431
  end
430
432
  end
431
433
 
432
- end
434
+ describe "block writes to a database" do
435
+ def setup
436
+ @conn = ActiveRecord::Base.connection
437
+ @connection_handler = ActiveRecord::Base.connection_handler
438
+ end
433
439
 
440
+ def test_errors_when_an_insert_query_is_called_while_preventing_writes
441
+ assert_raises(ActiveRecord::ReadOnlyError) do
442
+ @connection_handler.while_preventing_writes do
443
+ @conn.insert("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
444
+ end
445
+ end
446
+ end
447
+
448
+ def test_errors_when_an_update_query_is_called_while_preventing_writes
449
+ @conn.insert("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
450
+
451
+ assert_raises(ActiveRecord::ReadOnlyError) do
452
+ @connection_handler.while_preventing_writes do
453
+ @conn.update("UPDATE [subscribers] SET [subscribers].[name] = 'Aidan' WHERE [subscribers].[nick] = 'aido'")
454
+ end
455
+ end
456
+ end
457
+
458
+ def test_errors_when_a_delete_query_is_called_while_preventing_writes
459
+ @conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
460
+
461
+ assert_raises(ActiveRecord::ReadOnlyError) do
462
+ @connection_handler.while_preventing_writes do
463
+ @conn.execute("DELETE FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
464
+ end
465
+ end
466
+ end
467
+
468
+ def test_doesnt_error_when_a_select_query_is_called_while_preventing_writes
469
+ @conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
470
+
471
+ @connection_handler.while_preventing_writes do
472
+ assert_equal 1, @conn.execute("SELECT * FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
473
+ end
474
+ end
475
+ end
476
+ end