activerecord-sqlserver-adapter-odbc-extended 8.0.10

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 (102) hide show
  1. checksums.yaml +7 -0
  2. data/.github/issue_template.md +22 -0
  3. data/.github/workflows/ci.yml +32 -0
  4. data/.gitignore +9 -0
  5. data/.rubocop.yml +69 -0
  6. data/CHANGELOG.md +5 -0
  7. data/CODE_OF_CONDUCT.md +132 -0
  8. data/Dockerfile.ci +14 -0
  9. data/Gemfile +26 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +104 -0
  12. data/RUNNING_UNIT_TESTS.md +38 -0
  13. data/Rakefile +45 -0
  14. data/VERSION +1 -0
  15. data/activerecord-sqlserver-adapter-odbc-extended.gemspec +34 -0
  16. data/compose.ci.yaml +15 -0
  17. data/lib/active_record/connection_adapters/extended_sqlserver_adapter.rb +204 -0
  18. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +41 -0
  19. data/lib/active_record/connection_adapters/sqlserver/odbc_database_statements.rb +234 -0
  20. data/lib/active_record/connection_adapters/sqlserver/type/binary_ext.rb +25 -0
  21. data/lib/activerecord-sqlserver-adapter-odbc-extended.rb +12 -0
  22. data/test/appveyor/dbsetup.ps1 +27 -0
  23. data/test/appveyor/dbsetup.sql +11 -0
  24. data/test/cases/active_schema_test_sqlserver.rb +127 -0
  25. data/test/cases/adapter_test_sqlserver.rb +648 -0
  26. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  27. data/test/cases/change_column_null_test_sqlserver.rb +44 -0
  28. data/test/cases/coerced_tests.rb +2796 -0
  29. data/test/cases/column_test_sqlserver.rb +848 -0
  30. data/test/cases/connection_test_sqlserver.rb +138 -0
  31. data/test/cases/dbconsole.rb +19 -0
  32. data/test/cases/disconnected_test_sqlserver.rb +42 -0
  33. data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
  34. data/test/cases/enum_test_sqlserver.rb +49 -0
  35. data/test/cases/execute_procedure_test_sqlserver.rb +57 -0
  36. data/test/cases/fetch_test_sqlserver.rb +88 -0
  37. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +72 -0
  38. data/test/cases/helper_sqlserver.rb +61 -0
  39. data/test/cases/migration_test_sqlserver.rb +144 -0
  40. data/test/cases/order_test_sqlserver.rb +153 -0
  41. data/test/cases/pessimistic_locking_test_sqlserver.rb +102 -0
  42. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  43. data/test/cases/rake_test_sqlserver.rb +198 -0
  44. data/test/cases/schema_dumper_test_sqlserver.rb +296 -0
  45. data/test/cases/schema_test_sqlserver.rb +111 -0
  46. data/test/cases/trigger_test_sqlserver.rb +51 -0
  47. data/test/cases/utils_test_sqlserver.rb +129 -0
  48. data/test/cases/uuid_test_sqlserver.rb +54 -0
  49. data/test/cases/view_test_sqlserver.rb +58 -0
  50. data/test/config.yml +38 -0
  51. data/test/debug.rb +16 -0
  52. data/test/fixtures/1px.gif +0 -0
  53. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  54. data/test/migrations/create_clients_and_change_column_null.rb +25 -0
  55. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
  56. data/test/models/sqlserver/alien.rb +5 -0
  57. data/test/models/sqlserver/booking.rb +5 -0
  58. data/test/models/sqlserver/composite_pk.rb +9 -0
  59. data/test/models/sqlserver/customers_view.rb +5 -0
  60. data/test/models/sqlserver/datatype.rb +5 -0
  61. data/test/models/sqlserver/datatype_migration.rb +10 -0
  62. data/test/models/sqlserver/dollar_table_name.rb +5 -0
  63. data/test/models/sqlserver/edge_schema.rb +13 -0
  64. data/test/models/sqlserver/fk_has_fk.rb +5 -0
  65. data/test/models/sqlserver/fk_has_pk.rb +5 -0
  66. data/test/models/sqlserver/natural_pk_data.rb +6 -0
  67. data/test/models/sqlserver/natural_pk_int_data.rb +5 -0
  68. data/test/models/sqlserver/no_pk_data.rb +5 -0
  69. data/test/models/sqlserver/object_default.rb +5 -0
  70. data/test/models/sqlserver/quoted_table.rb +9 -0
  71. data/test/models/sqlserver/quoted_view_1.rb +5 -0
  72. data/test/models/sqlserver/quoted_view_2.rb +5 -0
  73. data/test/models/sqlserver/sst_memory.rb +5 -0
  74. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  75. data/test/models/sqlserver/string_default.rb +5 -0
  76. data/test/models/sqlserver/string_defaults_big_view.rb +5 -0
  77. data/test/models/sqlserver/string_defaults_view.rb +5 -0
  78. data/test/models/sqlserver/table_with_spaces.rb +5 -0
  79. data/test/models/sqlserver/tinyint_pk.rb +5 -0
  80. data/test/models/sqlserver/trigger.rb +17 -0
  81. data/test/models/sqlserver/trigger_history.rb +5 -0
  82. data/test/models/sqlserver/upper.rb +5 -0
  83. data/test/models/sqlserver/uppered.rb +5 -0
  84. data/test/models/sqlserver/uuid.rb +5 -0
  85. data/test/schema/datatypes/2012.sql +56 -0
  86. data/test/schema/enable-in-memory-oltp.sql +81 -0
  87. data/test/schema/sqlserver_specific_schema.rb +363 -0
  88. data/test/support/coerceable_test_sqlserver.rb +55 -0
  89. data/test/support/connection_reflection.rb +32 -0
  90. data/test/support/core_ext/query_cache.rb +38 -0
  91. data/test/support/load_schema_sqlserver.rb +29 -0
  92. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
  93. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
  94. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
  95. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
  96. data/test/support/minitest_sqlserver.rb +3 -0
  97. data/test/support/paths_sqlserver.rb +50 -0
  98. data/test/support/query_assertions.rb +49 -0
  99. data/test/support/rake_helpers.rb +46 -0
  100. data/test/support/table_definition_sqlserver.rb +24 -0
  101. data/test/support/test_in_memory_oltp.rb +17 -0
  102. metadata +240 -0
@@ -0,0 +1,648 @@
1
+ # frozen_string_literal: true
2
+
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
+ require "models/college"
10
+ require "models/discount"
11
+
12
+ class AdapterTestSQLServer < ActiveRecord::TestCase
13
+ fixtures :tasks
14
+
15
+ let(:basic_insert_sql) { "INSERT INTO [funny_jokes] ([name]) VALUES('Knock knock')" }
16
+ let(:basic_update_sql) { "UPDATE [customers] SET [address_street] = NULL WHERE [id] = 2" }
17
+ let(:basic_select_sql) { "SELECT * FROM [customers] WHERE ([customers].[id] = 1)" }
18
+
19
+ it "has basic and non-sensitive information in the adapters inspect method" do
20
+ string = connection.inspect
21
+ _(string).must_match %r{ActiveRecord::ConnectionAdapters::SQLServerAdapter}
22
+ _(string).must_match %r{mode: (dblib|odbc)}
23
+ _(string).wont_match %r{host}
24
+ _(string).wont_match %r{password}
25
+ _(string).wont_match %r{username}
26
+ _(string).wont_match %r{port}
27
+ end
28
+
29
+ it "has a 128 max #table_alias_length" do
30
+ assert connection.table_alias_length <= 128
31
+ end
32
+
33
+ it "raises invalid statement error for bad SQL" do
34
+ assert_raise(ActiveRecord::StatementInvalid) { Topic.lease_connection.update("UPDATE XXX") }
35
+ end
36
+
37
+ it "is has our adapter_name" do
38
+ assert_equal "SQLServer", connection.adapter_name
39
+ end
40
+
41
+ it "support DDL in transactions" do
42
+ assert connection.supports_ddl_transactions?
43
+ end
44
+
45
+ it "table exists works if table name prefixed by schema and owner" do
46
+ begin
47
+ assert_equal "topics", Topic.table_name
48
+ assert Topic.table_exists?
49
+
50
+ # Test when owner included in table name.
51
+ Topic.table_name = "dbo.topics"
52
+ assert Topic.table_exists?, "Topics table name of 'dbo.topics' should return true for exists."
53
+
54
+ # Test when database and owner included in table name.
55
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
56
+ Topic.table_name = "#{db_config.database}.dbo.topics"
57
+ assert Topic.table_exists?, "Topics table name of '[DATABASE].dbo.topics' should return true for exists."
58
+ ensure
59
+ Topic.table_name = "topics"
60
+ end
61
+ end
62
+
63
+ it "test table existence across database schemas" do
64
+ arunit_connection = Topic.lease_connection
65
+ arunit2_connection = College.lease_connection
66
+
67
+ arunit_database = arunit_connection.pool.db_config.database
68
+ arunit2_database = arunit2_connection.pool.db_config.database
69
+
70
+ # Assert that connections use different default databases schemas.
71
+ assert_not_equal arunit_database, arunit2_database
72
+
73
+ # Assert that the Topics table exists when using the Topics connection.
74
+ assert arunit_connection.table_exists?('topics'), 'Topics table exists using table name'
75
+ assert arunit_connection.table_exists?('dbo.topics'), 'Topics table exists using owner and table name'
76
+ assert arunit_connection.table_exists?("#{arunit_database}.dbo.topics"), 'Topics table exists using database, owner and table name'
77
+
78
+ # Assert that the Colleges table exists when using the Colleges connection.
79
+ assert arunit2_connection.table_exists?('colleges'), 'College table exists using table name'
80
+ assert arunit2_connection.table_exists?('dbo.colleges'), 'College table exists using owner and table name'
81
+ assert arunit2_connection.table_exists?("#{arunit2_database}.dbo.colleges"), 'College table exists using database, owner and table name'
82
+
83
+ # Assert that the tables exist when using each others connection.
84
+ assert arunit_connection.table_exists?("#{arunit2_database}.dbo.colleges"), 'Colleges table exists using Topics connection'
85
+ assert arunit2_connection.table_exists?("#{arunit_database}.dbo.topics"), 'Topics table exists using Colleges connection'
86
+ end
87
+
88
+ it "return true to insert sql query for inserts only" do
89
+ assert connection.send(:insert_sql?, "INSERT...")
90
+ 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")
91
+ assert !connection.send(:insert_sql?, "UPDATE...")
92
+ assert !connection.send(:insert_sql?, "SELECT...")
93
+ end
94
+
95
+ it "return unquoted table name object from basic INSERT UPDATE and SELECT statements" do
96
+ assert_equal "funny_jokes", connection.send(:get_table_name, basic_insert_sql)
97
+ assert_equal "customers", connection.send(:get_table_name, basic_update_sql)
98
+ assert_equal "customers", connection.send(:get_table_name, basic_select_sql)
99
+ end
100
+
101
+ it "test bad connection" do
102
+ assert_raise ActiveRecord::NoDatabaseError do
103
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
104
+ configuration = db_config.configuration_hash.merge(database: "nonexistent_activerecord_unittest")
105
+ connection = ActiveRecord::ConnectionAdapters::SQLServerAdapter.new(configuration)
106
+ connection.exec_query("SELECT 1")
107
+ end
108
+ end
109
+
110
+ it "test database exists returns false if database does not exist" do
111
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
112
+ configuration = db_config.configuration_hash.merge(database: "nonexistent_activerecord_unittest")
113
+ assert_not ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(configuration),
114
+ "expected database #{configuration[:database]} to not exist"
115
+ end
116
+
117
+ it "test database exists returns true when the database exists" do
118
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
119
+ assert ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(db_config.configuration_hash),
120
+ "expected database #{db_config.database} to exist"
121
+ end
122
+
123
+ it "test primary key violation" do
124
+ Post.create!(id: 0, title: 'Setup', body: 'Create post with primary key of zero')
125
+
126
+ assert_raise ActiveRecord::RecordNotUnique do
127
+ Post.create!(id: 0, title: 'Test', body: 'Try to create another post with primary key of zero')
128
+ end
129
+ end
130
+
131
+ describe "with different language" do
132
+ before do
133
+ @default_language = connection.user_options_language
134
+ end
135
+
136
+ after do
137
+ connection.execute("SET LANGUAGE #{@default_language}") rescue nil
138
+ connection.send :initialize_dateformatter
139
+ end
140
+
141
+ it "memos users dateformat" do
142
+ connection.execute("SET LANGUAGE us_english") rescue nil
143
+ dateformat = connection.instance_variable_get(:@database_dateformat)
144
+ assert_equal "mdy", dateformat
145
+ end
146
+
147
+ it "has a dateformatter" do
148
+ assert Date::DATE_FORMATS[:_sqlserver_dateformat]
149
+ assert Time::DATE_FORMATS[:_sqlserver_dateformat]
150
+ end
151
+
152
+ it "does a datetime insertion when language is german" do
153
+ connection.execute("SET LANGUAGE deutsch")
154
+ connection.send :initialize_dateformatter
155
+ assert_nothing_raised do
156
+ starting = Time.utc(2000, 1, 31, 5, 42, 0)
157
+ ending = Time.new(2006, 12, 31)
158
+ Task.create! starting: starting, ending: ending
159
+ end
160
+ end
161
+ end
162
+
163
+ describe "testing #lowercase_schema_reflection" do
164
+ before do
165
+ SSTestUpper.delete_all
166
+ SSTestUpper.create COLUMN1: "Got a minute?", COLUMN2: 419
167
+ SSTestUpper.create COLUMN1: "Favorite number?", COLUMN2: 69
168
+ end
169
+
170
+ after do
171
+ connection.lowercase_schema_reflection = false
172
+ end
173
+
174
+ it "not lowercase schema reflection by default" do
175
+ assert SSTestUpper.columns_hash["COLUMN1"]
176
+ assert_equal "Got a minute?", SSTestUpper.first.COLUMN1
177
+ assert_equal "Favorite number?", SSTestUpper.last.COLUMN1
178
+ assert SSTestUpper.columns_hash["COLUMN2"]
179
+ end
180
+
181
+ it "lowercase schema reflection when set" do
182
+ connection.lowercase_schema_reflection = true
183
+ assert SSTestUppered.columns_hash["column1"]
184
+ assert_equal "Got a minute?", SSTestUppered.first.column1
185
+ assert_equal "Favorite number?", SSTestUppered.last.column1
186
+ assert SSTestUppered.columns_hash["column2"]
187
+ end
188
+
189
+ it "destroys model with no associations" do
190
+ connection.lowercase_schema_reflection = true
191
+
192
+ assert_nothing_raised do
193
+ discount = Discount.create!
194
+ discount.destroy!
195
+ end
196
+ end
197
+
198
+ it "destroys model with association" do
199
+ connection.lowercase_schema_reflection = true
200
+
201
+ assert_nothing_raised do
202
+ post = Post.create!(title: 'Setup', body: 'Record to be deleted')
203
+ post.destroy!
204
+ end
205
+ end
206
+ end
207
+
208
+ describe "identity inserts" do
209
+ before do
210
+ @identity_insert_sql = "INSERT INTO [funny_jokes] ([id],[name]) VALUES(420,'Knock knock')"
211
+ @identity_insert_sql_unquoted = "INSERT INTO funny_jokes (id, name) VALUES(420, 'Knock knock')"
212
+ @identity_insert_sql_unordered = "INSERT INTO [funny_jokes] ([name],[id]) VALUES('Knock knock',420)"
213
+ @identity_insert_sql_sp = "EXEC sp_executesql N'INSERT INTO [funny_jokes] ([id],[name]) VALUES (@0, @1)', N'@0 int, @1 nvarchar(255)', @0 = 420, @1 = N'Knock knock'"
214
+ @identity_insert_sql_unquoted_sp = "EXEC sp_executesql N'INSERT INTO funny_jokes (id, name) VALUES (@0, @1)', N'@0 int, @1 nvarchar(255)', @0 = 420, @1 = N'Knock knock'"
215
+ @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"
216
+
217
+ @identity_insert_sql_non_dbo = "INSERT INTO [test].[aliens] ([id],[name]) VALUES(420,'Mork')"
218
+ @identity_insert_sql_non_dbo_unquoted = "INSERT INTO test.aliens ([id],[name]) VALUES(420,'Mork')"
219
+ @identity_insert_sql_non_dbo_unordered = "INSERT INTO [test].[aliens] ([name],[id]) VALUES('Mork',420)"
220
+ @identity_insert_sql_non_dbo_sp = "EXEC sp_executesql N'INSERT INTO [test].[aliens] ([id],[name]) VALUES (@0, @1)', N'@0 int, @1 nvarchar(255)', @0 = 420, @1 = N'Mork'"
221
+ @identity_insert_sql_non_dbo_unquoted_sp = "EXEC sp_executesql N'INSERT INTO test.aliens (id, name) VALUES (@0, @1)', N'@0 int, @1 nvarchar(255)', @0 = 420, @1 = N'Mork'"
222
+ @identity_insert_sql_non_dbo_unordered_sp = "EXEC sp_executesql N'INSERT INTO [test].[aliens] ([name],[id]) VALUES (@0, @1)', N'@0 nvarchar(255), @1 int', @0 = N'Mork', @1 = 420"
223
+ end
224
+
225
+ it "return quoted table_name to #query_requires_identity_insert? when INSERT sql contains id column" do
226
+ assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql)
227
+ assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unquoted)
228
+ assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unordered)
229
+ assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_sp)
230
+ assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unquoted_sp)
231
+ assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unordered_sp)
232
+
233
+ assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo)
234
+ assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unquoted)
235
+ assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unordered)
236
+ assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_sp)
237
+ assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unquoted_sp)
238
+ assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unordered_sp)
239
+ end
240
+
241
+ it "return false to #query_requires_identity_insert? for normal SQL" do
242
+ [basic_insert_sql, basic_update_sql, basic_select_sql].each do |sql|
243
+ assert !connection.send(:query_requires_identity_insert?, sql), "SQL was #{sql}"
244
+ end
245
+ end
246
+
247
+ it "find identity column using #identity_columns" do
248
+ task_id_column = Task.columns_hash["id"]
249
+ assert_equal task_id_column.name, connection.send(:identity_columns, Task.table_name).first.name
250
+ assert_equal task_id_column.sql_type, connection.send(:identity_columns, Task.table_name).first.sql_type
251
+ end
252
+
253
+ it "return an empty array when calling #identity_columns for a table_name with no identity" do
254
+ _(connection.send(:identity_columns, Subscriber.table_name)).must_equal []
255
+ end
256
+ end
257
+
258
+ describe "quoting" do
259
+ it "return 1 for #quoted_true" do
260
+ assert_equal "1", connection.quoted_true
261
+ end
262
+
263
+ it "return 0 for #quoted_false" do
264
+ assert_equal "0", connection.quoted_false
265
+ end
266
+
267
+ it "not escape backslash characters like abstract adapter" do
268
+ string_with_backslashs = "\\n"
269
+ assert_equal string_with_backslashs, connection.quote_string(string_with_backslashs)
270
+ end
271
+
272
+ it "quote column names with brackets" do
273
+ assert_equal "[foo]", connection.quote_column_name(:foo)
274
+ assert_equal "[foo]", connection.quote_column_name("foo")
275
+ assert_equal "[foo].[bar]", connection.quote_column_name("foo.bar")
276
+ end
277
+
278
+ it "not quote already quoted column names with brackets" do
279
+ assert_equal "[foo]", connection.quote_column_name("[foo]")
280
+ assert_equal "[foo].[bar]", connection.quote_column_name("[foo].[bar]")
281
+ end
282
+
283
+ it "quote table names like columns" do
284
+ assert_equal "[foo].[bar]", connection.quote_column_name("foo.bar")
285
+ assert_equal "[foo].[bar].[baz]", connection.quote_column_name("foo.bar.baz")
286
+ end
287
+
288
+ it "surround string with national prefix" do
289
+ assert_equal "N'foo'", connection.quote("foo")
290
+ end
291
+
292
+ it "escape all single quotes by repeating them" do
293
+ assert_equal "N'''quotation''s'''", connection.quote("'quotation's'")
294
+ end
295
+ end
296
+
297
+ describe "disabling referential integrity" do
298
+ before do
299
+ connection.disable_referential_integrity { SSTestHasPk.delete_all; SSTestHasFk.delete_all }
300
+ @parent = SSTestHasPk.create!
301
+ @member = SSTestHasFk.create!(fk_id: @parent.id)
302
+ end
303
+
304
+ it "NOT ALLOW by default the deletion of a referenced parent" do
305
+ SSTestHasPk.lease_connection.disable_referential_integrity {}
306
+ assert_raise(ActiveRecord::StatementInvalid) { @parent.destroy }
307
+ end
308
+
309
+ it "ALLOW deletion of referenced parent using #disable_referential_integrity block" do
310
+ assert_difference("SSTestHasPk.count", -1) do
311
+ SSTestHasPk.lease_connection.disable_referential_integrity { @parent.destroy }
312
+ end
313
+ end
314
+
315
+ it "again NOT ALLOW deletion of referenced parent after #disable_referential_integrity block" do
316
+ assert_raise(ActiveRecord::StatementInvalid) do
317
+ SSTestHasPk.lease_connection.disable_referential_integrity {}
318
+ @parent.destroy
319
+ end
320
+ end
321
+
322
+ it "not disable referential integrity for the same table twice" do
323
+ tables = SSTestHasPk.lease_connection.tables_with_referential_integrity
324
+ assert_equal tables.size, tables.uniq.size
325
+ end
326
+ end
327
+
328
+ describe "database statements" do
329
+ it "run the database consistency checker 'user_options' command" do
330
+ skip "on azure" if connection_sqlserver_azure?
331
+ keys = [:textsize, :language, :isolation_level, :dateformat]
332
+ user_options = connection.user_options
333
+ keys.each do |key|
334
+ msg = "Expected key:#{key} in user_options:#{user_options.inspect}"
335
+ assert user_options.key?(key), msg
336
+ end
337
+ end
338
+
339
+ it "return a underscored key hash with indifferent access of the results" do
340
+ skip "on azure" if connection_sqlserver_azure?
341
+ user_options = connection.user_options
342
+ assert_equal "read committed", user_options["isolation_level"]
343
+ assert_equal "read committed", user_options[:isolation_level]
344
+ end
345
+ end
346
+
347
+ describe "schema statements" do
348
+ it "create integers when no limit supplied" do
349
+ assert_equal "integer", connection.type_to_sql(:integer)
350
+ end
351
+
352
+ it "create integers when limit is 4" do
353
+ assert_equal "integer", connection.type_to_sql(:integer, limit: 4)
354
+ end
355
+
356
+ it "create integers when limit is 3" do
357
+ assert_equal "integer", connection.type_to_sql(:integer, limit: 3)
358
+ end
359
+
360
+ it "create smallints when limit is 2" do
361
+ assert_equal "smallint", connection.type_to_sql(:integer, limit: 2)
362
+ end
363
+
364
+ it "create tinyints when limit is 1" do
365
+ assert_equal "tinyint", connection.type_to_sql(:integer, limit: 1)
366
+ end
367
+
368
+ it "create bigints when limit is greater than 4" do
369
+ assert_equal "bigint", connection.type_to_sql(:integer, limit: 5)
370
+ assert_equal "bigint", connection.type_to_sql(:integer, limit: 6)
371
+ assert_equal "bigint", connection.type_to_sql(:integer, limit: 7)
372
+ assert_equal "bigint", connection.type_to_sql(:integer, limit: 8)
373
+ end
374
+
375
+ it "create floats when no limit supplied" do
376
+ assert_equal "float", connection.type_to_sql(:float)
377
+ end
378
+ end
379
+
380
+ describe "views" do
381
+ # Using connection.views
382
+
383
+ it "return an array" do
384
+ assert_instance_of Array, connection.views
385
+ end
386
+
387
+ it "find SSTestCustomersView table name" do
388
+ _(connection.views).must_include "sst_customers_view"
389
+ end
390
+
391
+ it "work with dynamic finders" do
392
+ name = "MetaSkills"
393
+ customer = SSTestCustomersView.create! name: name
394
+ assert_equal customer, SSTestCustomersView.find_by_name(name)
395
+ end
396
+
397
+ it "not contain system views" do
398
+ systables = ["sysconstraints", "syssegments"]
399
+ systables.each do |systable|
400
+ assert !connection.views.include?(systable), "This systable #{systable} should not be in the views array."
401
+ end
402
+ end
403
+
404
+ it "allow the connection#view_information method to return meta data on the view" do
405
+ view_info = connection.send(:view_information, "sst_customers_view")
406
+ assert_equal("sst_customers_view", view_info["TABLE_NAME"])
407
+ assert_match(/CREATE VIEW sst_customers_view/, view_info["VIEW_DEFINITION"])
408
+ end
409
+
410
+ it "allows connection#view_information to work with qualified object names" do
411
+ view_info = connection.send(:view_information, "[activerecord_unittest].[dbo].[sst_customers_view]")
412
+ assert_equal("sst_customers_view", view_info["TABLE_NAME"])
413
+ assert_match(/CREATE VIEW sst_customers_view/, view_info["VIEW_DEFINITION"])
414
+ end
415
+
416
+ it "allows connection#view_information to work across databases when using qualified object names" do
417
+ # College is defined in activerecord_unittest2 database.
418
+ view_info = College.lease_connection.send(:view_information, "[activerecord_unittest].[dbo].[sst_customers_view]")
419
+ assert_equal("sst_customers_view", view_info["TABLE_NAME"])
420
+ assert_match(/CREATE VIEW sst_customers_view/, view_info["VIEW_DEFINITION"])
421
+ end
422
+
423
+ it "allow the connection#view_table_name method to return true table_name for the view" do
424
+ assert_equal "customers", connection.send(:view_table_name, "sst_customers_view")
425
+ assert_equal "topics", connection.send(:view_table_name, "topics"), "No view here, the same table name should come back."
426
+ end
427
+
428
+ it "allow the connection#view_table_name method to return true table_name for the view for other connections" do
429
+ assert_equal "customers", College.lease_connection.send(:view_table_name, "[activerecord_unittest].[dbo].[sst_customers_view]")
430
+ assert_equal "topics", College.lease_connection.send(:view_table_name, "topics"), "No view here, the same table name should come back."
431
+ end
432
+ # With same column names
433
+
434
+ it "have matching column objects" do
435
+ columns = ["id", "name", "balance"]
436
+ assert !SSTestCustomersView.columns.blank?
437
+ assert_equal columns.size, SSTestCustomersView.columns.size
438
+ columns.each do |colname|
439
+ assert_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Column,
440
+ SSTestCustomersView.columns_hash[colname],
441
+ "Column name #{colname.inspect} was not found in these columns #{SSTestCustomersView.columns.map(&:name).inspect}"
442
+ end
443
+ end
444
+
445
+ it "find identity column" do
446
+ _(SSTestCustomersView.primary_key).must_equal "id"
447
+ _(connection.primary_key(SSTestCustomersView.table_name)).must_equal "id"
448
+ _(SSTestCustomersView.columns_hash["id"]).must_be :is_identity?
449
+ end
450
+
451
+ it "find default values" do
452
+ assert_equal 0, SSTestCustomersView.new.balance
453
+ end
454
+
455
+ it "respond true to data_source_exists?" do
456
+ assert SSTestCustomersView.lease_connection.data_source_exists?(SSTestCustomersView.table_name)
457
+ end
458
+
459
+ # With aliased column names
460
+
461
+ it "have matching column objects" do
462
+ columns = ["id", "pretend_null"]
463
+ assert !SSTestStringDefaultsView.columns.blank?
464
+ assert_equal columns.size, SSTestStringDefaultsView.columns.size
465
+ columns.each do |colname|
466
+ assert_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Column,
467
+ SSTestStringDefaultsView.columns_hash[colname],
468
+ "Column name #{colname.inspect} was not found in these columns #{SSTestStringDefaultsView.columns.map(&:name).inspect}"
469
+ end
470
+ end
471
+
472
+ it "find identity column" do
473
+ _(SSTestStringDefaultsView.primary_key).must_equal "id"
474
+ _(connection.primary_key(SSTestStringDefaultsView.table_name)).must_equal "id"
475
+ _(SSTestStringDefaultsView.columns_hash["id"]).must_be :is_identity?
476
+ end
477
+
478
+ it "find default values" do
479
+ assert_equal "null", SSTestStringDefaultsView.new.pretend_null,
480
+ SSTestStringDefaultsView.columns_hash["pretend_null"].inspect
481
+ end
482
+
483
+ it "respond true to data_source_exists?" do
484
+ assert SSTestStringDefaultsView.lease_connection.data_source_exists?(SSTestStringDefaultsView.table_name)
485
+ end
486
+
487
+ # That have more than 4000 chars for their definition
488
+
489
+ it "cope with null returned for the definition" do
490
+ assert_nothing_raised() { SSTestStringDefaultsBigView.columns }
491
+ end
492
+
493
+ it "using alternate view definition still be able to find real default" do
494
+ assert_equal "null", SSTestStringDefaultsBigView.new.pretend_null,
495
+ SSTestStringDefaultsBigView.columns_hash["pretend_null"].inspect
496
+ end
497
+ end
498
+
499
+ describe "database_prefix_remote_server?" do
500
+ after do
501
+ connection_options.delete(:database_prefix)
502
+ end
503
+
504
+ it "returns false if database_prefix is not configured" do
505
+ assert_equal false, connection.database_prefix_remote_server?
506
+ end
507
+
508
+ it "returns true if database_prefix has been set" do
509
+ connection_options[:database_prefix] = "server.database.schema."
510
+ assert_equal true, connection.database_prefix_remote_server?
511
+ end
512
+
513
+ it "returns false if database_prefix has been set incorrectly" do
514
+ connection_options[:database_prefix] = "server.database.schema"
515
+ assert_equal false, connection.database_prefix_remote_server?
516
+ end
517
+ end
518
+
519
+ it "in_memory_oltp" do
520
+ if ENV["IN_MEMORY_OLTP"] && connection.supports_in_memory_oltp?
521
+ _(SSTMemory.primary_key).must_equal "id"
522
+ _(SSTMemory.columns_hash["id"]).must_be :is_identity?
523
+ else
524
+ skip "supports_in_memory_oltp? => false"
525
+ end
526
+ end
527
+
528
+ describe "block writes to a database" do
529
+ def setup
530
+ @conn = ActiveRecord::Base.lease_connection
531
+ end
532
+
533
+ def test_errors_when_an_insert_query_is_called_while_preventing_writes
534
+ assert_raises(ActiveRecord::ReadOnlyError) do
535
+ ActiveRecord::Base.while_preventing_writes do
536
+ @conn.insert("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
537
+ end
538
+ end
539
+ end
540
+
541
+ def test_errors_when_an_update_query_is_called_while_preventing_writes
542
+ @conn.insert("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
543
+
544
+ assert_raises(ActiveRecord::ReadOnlyError) do
545
+ ActiveRecord::Base.while_preventing_writes do
546
+ @conn.update("UPDATE [subscribers] SET [subscribers].[name] = 'Aidan' WHERE [subscribers].[nick] = 'aido'")
547
+ end
548
+ end
549
+ end
550
+
551
+ def test_errors_when_a_delete_query_is_called_while_preventing_writes
552
+ @conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
553
+
554
+ assert_raises(ActiveRecord::ReadOnlyError) do
555
+ ActiveRecord::Base.while_preventing_writes do
556
+ @conn.execute("DELETE FROM [subscribers] WHERE [subscribers].[nick] = 'aido'")
557
+ end
558
+ end
559
+ end
560
+
561
+ def test_doesnt_error_when_a_select_query_is_called_while_preventing_writes
562
+ @conn.execute("INSERT INTO [subscribers] ([nick]) VALUES ('aido')")
563
+
564
+ ActiveRecord::Base.while_preventing_writes do
565
+ assert_equal 1, @conn.execute("SELECT * FROM [subscribers] WHERE [subscribers].[nick] = 'aido'").count
566
+ end
567
+ end
568
+ end
569
+
570
+ describe 'table is in non-dbo schema' do
571
+ it "records can be created successfully" do
572
+ assert_difference("Alien.count", 1) do
573
+ Alien.create!(name: 'Trisolarans')
574
+ end
575
+ end
576
+
577
+ it 'records can be inserted using SQL' do
578
+ assert_difference("Alien.count", 2) do
579
+ Alien.lease_connection.exec_insert("insert into [test].[aliens] (id, name) VALUES(1, 'Trisolarans'), (2, 'Xenomorph')")
580
+ end
581
+ end
582
+ end
583
+
584
+ describe 'table names contains spaces' do
585
+ it 'records can be created successfully' do
586
+ assert_difference("TableWithSpaces.count", 1) do
587
+ TableWithSpaces.create!(name: 'Bob')
588
+ end
589
+ end
590
+ end
591
+
592
+ describe "exec_insert" do
593
+ it 'values clause should be case-insensitive' do
594
+ assert_difference("Post.count", 4) do
595
+ first_insert = connection.exec_insert("INSERT INTO [posts] ([id],[title],[body]) VALUES(100, 'Title', 'Body'), (102, 'Title', 'Body')")
596
+ second_insert = connection.exec_insert("INSERT INTO [posts] ([id],[title],[body]) values(113, 'Body', 'Body'), (114, 'Body', 'Body')")
597
+
598
+ assert_equal first_insert.rows.map(&:first), [100, 102]
599
+ assert_equal second_insert.rows.map(&:first), [113, 114]
600
+ end
601
+ end
602
+ end
603
+
604
+ describe "mismatched foreign keys error" do
605
+ def setup
606
+ @conn = ActiveRecord::Base.lease_connection
607
+ end
608
+
609
+ it 'raises an error when the foreign key is mismatched' do
610
+ error = assert_raises(ActiveRecord::MismatchedForeignKey) do
611
+ @conn.add_reference :engines, :old_car
612
+ @conn.add_foreign_key :engines, :old_cars
613
+ end
614
+
615
+ assert_match(
616
+ %r/Column 'old_cars\.id' is not the same data type as referencing column 'engines\.old_car_id' in foreign key '.*'/,
617
+ error.message
618
+ )
619
+ assert_not_nil error.cause
620
+ assert_equal @conn.pool, error.connection_pool
621
+ ensure
622
+ @conn.execute("ALTER TABLE engines DROP COLUMN old_car_id") rescue nil
623
+ end
624
+ end
625
+
626
+ describe "placeholder conditions" do
627
+ it 'using time placeholder' do
628
+ assert_equal Task.where("starting < ?", Time.now).count, 1
629
+ end
630
+
631
+ it 'using date placeholder' do
632
+ assert_equal Task.where("starting < ?", Date.today).count, 1
633
+ end
634
+
635
+ it 'using date-time placeholder' do
636
+ assert_equal Task.where("starting < ?", DateTime.current).count, 1
637
+ end
638
+ end
639
+
640
+ describe "distinct select query" do
641
+ it "generated SQL does not contain unnecessary alias projection" do
642
+ sqls = capture_sql do
643
+ Post.includes(:comments).joins(:comments).first
644
+ end
645
+ assert_no_match(/AS alias_0/, sqls.first)
646
+ end
647
+ end
648
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+ require "migrations/create_clients_and_change_column_collation"
5
+
6
+ class ChangeColumnCollationTestSqlServer < ActiveRecord::TestCase
7
+ before do
8
+ @old_verbose = ActiveRecord::Migration.verbose
9
+ ActiveRecord::Migration.verbose = false
10
+ CreateClientsAndChangeColumnCollation.new.up
11
+ end
12
+
13
+ after do
14
+ CreateClientsAndChangeColumnCollation.new.down
15
+ ActiveRecord::Migration.verbose = @old_verbose
16
+ end
17
+
18
+ def find_column(table, name)
19
+ table.find { |column| column.name == name }
20
+ end
21
+
22
+ let(:clients_table) { connection.columns("clients") }
23
+ let(:name_column) { find_column(clients_table, "name") }
24
+ let(:code_column) { find_column(clients_table, "code") }
25
+
26
+ it "change column collation to other than default" do
27
+ _(name_column.collation).must_equal "SQL_Latin1_General_CP1_CS_AS"
28
+ end
29
+
30
+ it "change column collation to default" do
31
+ _(code_column.collation).must_equal "SQL_Latin1_General_CP1_CI_AS"
32
+ end
33
+ end