activerecord-sqlserver-adapter 6.1.2.1 → 7.2.4
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.
- checksums.yaml +4 -4
- data/.devcontainer/Dockerfile +30 -0
- data/.devcontainer/boot.sh +22 -0
- data/.devcontainer/devcontainer.json +38 -0
- data/.devcontainer/docker-compose.yml +42 -0
- data/.github/workflows/ci.yml +7 -4
- data/.gitignore +3 -1
- data/CHANGELOG.md +19 -42
- data/Dockerfile.ci +3 -3
- data/Gemfile +6 -1
- data/MIT-LICENSE +1 -1
- data/README.md +113 -27
- data/RUNNING_UNIT_TESTS.md +27 -14
- data/Rakefile +2 -6
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +3 -3
- data/appveyor.yml +4 -6
- data/docker-compose.ci.yml +2 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/abstract_adapter.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +6 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -23
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +10 -7
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +12 -2
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +24 -16
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -31
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +143 -155
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +5 -5
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +57 -56
- data/lib/active_record/connection_adapters/sqlserver/savepoints.rb +26 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +14 -12
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +11 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +213 -57
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +13 -2
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +4 -6
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +19 -1
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +21 -10
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +187 -187
- data/lib/active_record/connection_adapters/sqlserver_column.rb +1 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +42 -33
- data/lib/arel/visitors/sqlserver.rb +77 -34
- data/test/cases/active_schema_test_sqlserver.rb +127 -0
- data/test/cases/adapter_test_sqlserver.rb +114 -26
- data/test/cases/coerced_tests.rb +1121 -340
- data/test/cases/column_test_sqlserver.rb +67 -64
- data/test/cases/connection_test_sqlserver.rb +3 -6
- data/test/cases/dbconsole.rb +19 -0
- data/test/cases/disconnected_test_sqlserver.rb +8 -5
- data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
- data/test/cases/enum_test_sqlserver.rb +49 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +9 -5
- data/test/cases/fetch_test_sqlserver.rb +19 -0
- data/test/cases/helper_sqlserver.rb +11 -5
- data/test/cases/index_test_sqlserver.rb +8 -6
- data/test/cases/json_test_sqlserver.rb +1 -1
- data/test/cases/lateral_test_sqlserver.rb +2 -2
- data/test/cases/migration_test_sqlserver.rb +19 -1
- data/test/cases/optimizer_hints_test_sqlserver.rb +21 -12
- data/test/cases/pessimistic_locking_test_sqlserver.rb +8 -7
- data/test/cases/primary_keys_test_sqlserver.rb +2 -2
- data/test/cases/rake_test_sqlserver.rb +10 -5
- data/test/cases/schema_dumper_test_sqlserver.rb +155 -109
- data/test/cases/schema_test_sqlserver.rb +64 -1
- data/test/cases/showplan_test_sqlserver.rb +7 -7
- data/test/cases/specific_schema_test_sqlserver.rb +17 -13
- data/test/cases/transaction_test_sqlserver.rb +13 -8
- data/test/cases/trigger_test_sqlserver.rb +20 -0
- data/test/cases/utils_test_sqlserver.rb +2 -2
- data/test/cases/uuid_test_sqlserver.rb +8 -0
- data/test/cases/view_test_sqlserver.rb +58 -0
- data/test/config.yml +1 -2
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +1 -1
- data/test/models/sqlserver/alien.rb +5 -0
- data/test/models/sqlserver/table_with_spaces.rb +5 -0
- data/test/models/sqlserver/trigger.rb +8 -0
- data/test/schema/sqlserver_specific_schema.rb +54 -6
- data/test/support/coerceable_test_sqlserver.rb +4 -4
- data/test/support/connection_reflection.rb +3 -9
- data/test/support/core_ext/query_cache.rb +7 -1
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
- data/test/support/query_assertions.rb +49 -0
- data/test/support/rake_helpers.rb +3 -1
- data/test/support/table_definition_sqlserver.rb +24 -0
- data/test/support/test_in_memory_oltp.rb +2 -2
- metadata +41 -17
- data/lib/active_record/sqlserver_base.rb +0 -18
- data/test/cases/scratchpad_test_sqlserver.rb +0 -8
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
- data/test/support/sql_counter_sqlserver.rb +0 -29
@@ -18,9 +18,6 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
18
18
|
it "has basic and non-sensitive information in the adapters inspect method" do
|
19
19
|
string = connection.inspect
|
20
20
|
_(string).must_match %r{ActiveRecord::ConnectionAdapters::SQLServerAdapter}
|
21
|
-
_(string).must_match %r{version\: \d.\d}
|
22
|
-
_(string).must_match %r{mode: dblib}
|
23
|
-
_(string).must_match %r{azure: (true|false)}
|
24
21
|
_(string).wont_match %r{host}
|
25
22
|
_(string).wont_match %r{password}
|
26
23
|
_(string).wont_match %r{username}
|
@@ -32,7 +29,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
32
29
|
end
|
33
30
|
|
34
31
|
it "raises invalid statement error for bad SQL" do
|
35
|
-
assert_raise(ActiveRecord::StatementInvalid) { Topic.
|
32
|
+
assert_raise(ActiveRecord::StatementInvalid) { Topic.lease_connection.update("UPDATE XXX") }
|
36
33
|
end
|
37
34
|
|
38
35
|
it "is has our adapter_name" do
|
@@ -62,8 +59,8 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
62
59
|
end
|
63
60
|
|
64
61
|
it "test table existence across database schemas" do
|
65
|
-
arunit_connection = Topic.
|
66
|
-
arunit2_connection = College.
|
62
|
+
arunit_connection = Topic.lease_connection
|
63
|
+
arunit2_connection = College.lease_connection
|
67
64
|
|
68
65
|
arunit_database = arunit_connection.pool.db_config.database
|
69
66
|
arunit2_database = arunit2_connection.pool.db_config.database
|
@@ -102,16 +99,17 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
102
99
|
it "test bad connection" do
|
103
100
|
assert_raise ActiveRecord::NoDatabaseError do
|
104
101
|
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
105
|
-
configuration = db_config.configuration_hash.merge(database: "
|
106
|
-
ActiveRecord::
|
102
|
+
configuration = db_config.configuration_hash.merge(database: "nonexistent_activerecord_unittest")
|
103
|
+
connection = ActiveRecord::ConnectionAdapters::SQLServerAdapter.new(configuration)
|
104
|
+
connection.exec_query("SELECT 1")
|
107
105
|
end
|
108
106
|
end
|
109
107
|
|
110
108
|
it "test database exists returns false if database does not exist" do
|
111
109
|
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
112
|
-
configuration = db_config.configuration_hash.merge(database: "
|
110
|
+
configuration = db_config.configuration_hash.merge(database: "nonexistent_activerecord_unittest")
|
113
111
|
assert_not ActiveRecord::ConnectionAdapters::SQLServerAdapter.database_exists?(configuration),
|
114
|
-
"expected database to not exist"
|
112
|
+
"expected database #{configuration[:database]} to not exist"
|
115
113
|
end
|
116
114
|
|
117
115
|
it "test database exists returns true when the database exists" do
|
@@ -193,17 +191,31 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
193
191
|
@identity_insert_sql_unquoted = "INSERT INTO funny_jokes (id, name) VALUES(420, 'Knock knock')"
|
194
192
|
@identity_insert_sql_unordered = "INSERT INTO [funny_jokes] ([name],[id]) VALUES('Knock knock',420)"
|
195
193
|
@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'"
|
196
|
-
@identity_insert_sql_unquoted_sp = "EXEC sp_executesql N'INSERT INTO
|
194
|
+
@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'"
|
197
195
|
@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"
|
196
|
+
|
197
|
+
@identity_insert_sql_non_dbo = "INSERT INTO [test].[aliens] ([id],[name]) VALUES(420,'Mork')"
|
198
|
+
@identity_insert_sql_non_dbo_unquoted = "INSERT INTO test.aliens ([id],[name]) VALUES(420,'Mork')"
|
199
|
+
@identity_insert_sql_non_dbo_unordered = "INSERT INTO [test].[aliens] ([name],[id]) VALUES('Mork',420)"
|
200
|
+
@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'"
|
201
|
+
@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'"
|
202
|
+
@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"
|
198
203
|
end
|
199
204
|
|
200
205
|
it "return quoted table_name to #query_requires_identity_insert? when INSERT sql contains id column" do
|
201
|
-
assert_equal "funny_jokes",
|
202
|
-
assert_equal "funny_jokes",
|
203
|
-
assert_equal "funny_jokes",
|
204
|
-
assert_equal "funny_jokes",
|
205
|
-
assert_equal "funny_jokes",
|
206
|
-
assert_equal "funny_jokes",
|
206
|
+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql)
|
207
|
+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unquoted)
|
208
|
+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unordered)
|
209
|
+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_sp)
|
210
|
+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unquoted_sp)
|
211
|
+
assert_equal "[funny_jokes]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_unordered_sp)
|
212
|
+
|
213
|
+
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo)
|
214
|
+
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unquoted)
|
215
|
+
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unordered)
|
216
|
+
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_sp)
|
217
|
+
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unquoted_sp)
|
218
|
+
assert_equal "[test].[aliens]", connection.send(:query_requires_identity_insert?, @identity_insert_sql_non_dbo_unordered_sp)
|
207
219
|
end
|
208
220
|
|
209
221
|
it "return false to #query_requires_identity_insert? for normal SQL" do
|
@@ -270,29 +282,31 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
270
282
|
end
|
271
283
|
|
272
284
|
it "NOT ALLOW by default the deletion of a referenced parent" do
|
273
|
-
SSTestHasPk.
|
285
|
+
SSTestHasPk.lease_connection.disable_referential_integrity {}
|
274
286
|
assert_raise(ActiveRecord::StatementInvalid) { @parent.destroy }
|
275
287
|
end
|
276
288
|
|
277
289
|
it "ALLOW deletion of referenced parent using #disable_referential_integrity block" do
|
278
|
-
SSTestHasPk.
|
290
|
+
assert_difference("SSTestHasPk.count", -1) do
|
291
|
+
SSTestHasPk.lease_connection.disable_referential_integrity { @parent.destroy }
|
292
|
+
end
|
279
293
|
end
|
280
294
|
|
281
295
|
it "again NOT ALLOW deletion of referenced parent after #disable_referential_integrity block" do
|
282
296
|
assert_raise(ActiveRecord::StatementInvalid) do
|
283
|
-
SSTestHasPk.
|
297
|
+
SSTestHasPk.lease_connection.disable_referential_integrity {}
|
284
298
|
@parent.destroy
|
285
299
|
end
|
286
300
|
end
|
287
301
|
|
288
302
|
it "not disable referential integrity for the same table twice" do
|
289
|
-
tables = SSTestHasPk.
|
303
|
+
tables = SSTestHasPk.lease_connection.tables_with_referential_integrity
|
290
304
|
assert_equal tables.size, tables.uniq.size
|
291
305
|
end
|
292
306
|
end
|
293
307
|
|
294
308
|
describe "database statements" do
|
295
|
-
it "run the database consistency checker
|
309
|
+
it "run the database consistency checker 'user_options' command" do
|
296
310
|
skip "on azure" if connection_sqlserver_azure?
|
297
311
|
keys = [:textsize, :language, :isolation_level, :dateformat]
|
298
312
|
user_options = connection.user_options
|
@@ -331,7 +345,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
331
345
|
assert_equal "tinyint", connection.type_to_sql(:integer, limit: 1)
|
332
346
|
end
|
333
347
|
|
334
|
-
it "create bigints when limit is
|
348
|
+
it "create bigints when limit is greater than 4" do
|
335
349
|
assert_equal "bigint", connection.type_to_sql(:integer, limit: 5)
|
336
350
|
assert_equal "bigint", connection.type_to_sql(:integer, limit: 6)
|
337
351
|
assert_equal "bigint", connection.type_to_sql(:integer, limit: 7)
|
@@ -373,11 +387,28 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
373
387
|
assert_match(/CREATE VIEW sst_customers_view/, view_info["VIEW_DEFINITION"])
|
374
388
|
end
|
375
389
|
|
390
|
+
it "allows connection#view_information to work with qualified object names" do
|
391
|
+
view_info = connection.send(:view_information, "[activerecord_unittest].[dbo].[sst_customers_view]")
|
392
|
+
assert_equal("sst_customers_view", view_info["TABLE_NAME"])
|
393
|
+
assert_match(/CREATE VIEW sst_customers_view/, view_info["VIEW_DEFINITION"])
|
394
|
+
end
|
395
|
+
|
396
|
+
it "allows connection#view_information to work across databases when using qualified object names" do
|
397
|
+
# College is defined in activerecord_unittest2 database.
|
398
|
+
view_info = College.lease_connection.send(:view_information, "[activerecord_unittest].[dbo].[sst_customers_view]")
|
399
|
+
assert_equal("sst_customers_view", view_info["TABLE_NAME"])
|
400
|
+
assert_match(/CREATE VIEW sst_customers_view/, view_info["VIEW_DEFINITION"])
|
401
|
+
end
|
402
|
+
|
376
403
|
it "allow the connection#view_table_name method to return true table_name for the view" do
|
377
404
|
assert_equal "customers", connection.send(:view_table_name, "sst_customers_view")
|
378
405
|
assert_equal "topics", connection.send(:view_table_name, "topics"), "No view here, the same table name should come back."
|
379
406
|
end
|
380
407
|
|
408
|
+
it "allow the connection#view_table_name method to return true table_name for the view for other connections" do
|
409
|
+
assert_equal "customers", College.lease_connection.send(:view_table_name, "[activerecord_unittest].[dbo].[sst_customers_view]")
|
410
|
+
assert_equal "topics", College.lease_connection.send(:view_table_name, "topics"), "No view here, the same table name should come back."
|
411
|
+
end
|
381
412
|
# With same column names
|
382
413
|
|
383
414
|
it "have matching column objects" do
|
@@ -402,7 +433,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
402
433
|
end
|
403
434
|
|
404
435
|
it "respond true to data_source_exists?" do
|
405
|
-
assert SSTestCustomersView.
|
436
|
+
assert SSTestCustomersView.lease_connection.data_source_exists?(SSTestCustomersView.table_name)
|
406
437
|
end
|
407
438
|
|
408
439
|
# With aliased column names
|
@@ -430,7 +461,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
430
461
|
end
|
431
462
|
|
432
463
|
it "respond true to data_source_exists?" do
|
433
|
-
assert SSTestStringDefaultsView.
|
464
|
+
assert SSTestStringDefaultsView.lease_connection.data_source_exists?(SSTestStringDefaultsView.table_name)
|
434
465
|
end
|
435
466
|
|
436
467
|
# That have more than 4000 chars for their defintion
|
@@ -476,7 +507,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
476
507
|
|
477
508
|
describe "block writes to a database" do
|
478
509
|
def setup
|
479
|
-
@conn = ActiveRecord::Base.
|
510
|
+
@conn = ActiveRecord::Base.lease_connection
|
480
511
|
end
|
481
512
|
|
482
513
|
def test_errors_when_an_insert_query_is_called_while_preventing_writes
|
@@ -515,4 +546,61 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
515
546
|
end
|
516
547
|
end
|
517
548
|
end
|
549
|
+
|
550
|
+
describe 'table is in non-dbo schema' do
|
551
|
+
it "records can be created successfully" do
|
552
|
+
assert_difference("Alien.count", 1) do
|
553
|
+
Alien.create!(name: 'Trisolarans')
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
it 'records can be inserted using SQL' do
|
558
|
+
assert_difference("Alien.count", 2) do
|
559
|
+
Alien.lease_connection.exec_insert("insert into [test].[aliens] (id, name) VALUES(1, 'Trisolarans'), (2, 'Xenomorph')")
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
describe 'table names contains spaces' do
|
565
|
+
it 'records can be created successfully' do
|
566
|
+
assert_difference("TableWithSpaces.count", 1) do
|
567
|
+
TableWithSpaces.create!(name: 'Bob')
|
568
|
+
end
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
describe "exec_insert" do
|
573
|
+
it 'values clause should be case-insensitive' do
|
574
|
+
assert_difference("Post.count", 4) do
|
575
|
+
first_insert = connection.exec_insert("INSERT INTO [posts] ([id],[title],[body]) VALUES(100, 'Title', 'Body'), (102, 'Title', 'Body')")
|
576
|
+
second_insert = connection.exec_insert("INSERT INTO [posts] ([id],[title],[body]) values(113, 'Body', 'Body'), (114, 'Body', 'Body')")
|
577
|
+
|
578
|
+
assert_equal first_insert.rows.map(&:first), [100, 102]
|
579
|
+
assert_equal second_insert.rows.map(&:first), [113, 114]
|
580
|
+
end
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
describe "placeholder conditions" do
|
585
|
+
it 'using time placeholder' do
|
586
|
+
assert_equal Task.where("starting < ?", Time.now).count, 1
|
587
|
+
end
|
588
|
+
|
589
|
+
it 'using date placeholder' do
|
590
|
+
assert_equal Task.where("starting < ?", Date.today).count, 1
|
591
|
+
end
|
592
|
+
|
593
|
+
it 'using date-time placeholder' do
|
594
|
+
assert_equal Task.where("starting < ?", DateTime.current).count, 1
|
595
|
+
end
|
596
|
+
end
|
597
|
+
|
598
|
+
describe "distinct select query" do
|
599
|
+
it "generated SQL does not contain unnecessary alias projection" do
|
600
|
+
sqls = capture_sql do
|
601
|
+
Post.includes(:comments).joins(:comments).first
|
602
|
+
end
|
603
|
+
assert_no_match(/AS alias_0/, sqls.first)
|
604
|
+
end
|
605
|
+
end
|
518
606
|
end
|