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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +30 -0
  3. data/.devcontainer/boot.sh +22 -0
  4. data/.devcontainer/devcontainer.json +38 -0
  5. data/.devcontainer/docker-compose.yml +42 -0
  6. data/.github/workflows/ci.yml +7 -4
  7. data/.gitignore +3 -1
  8. data/CHANGELOG.md +19 -42
  9. data/Dockerfile.ci +3 -3
  10. data/Gemfile +6 -1
  11. data/MIT-LICENSE +1 -1
  12. data/README.md +113 -27
  13. data/RUNNING_UNIT_TESTS.md +27 -14
  14. data/Rakefile +2 -6
  15. data/VERSION +1 -1
  16. data/activerecord-sqlserver-adapter.gemspec +3 -3
  17. data/appveyor.yml +4 -6
  18. data/docker-compose.ci.yml +2 -1
  19. data/lib/active_record/connection_adapters/sqlserver/core_ext/abstract_adapter.rb +20 -0
  20. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +6 -4
  21. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -23
  22. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +10 -7
  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 +12 -2
  25. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +24 -16
  26. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -31
  27. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +143 -155
  28. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +5 -5
  29. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +57 -56
  30. data/lib/active_record/connection_adapters/sqlserver/savepoints.rb +26 -0
  31. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +14 -12
  32. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +11 -0
  33. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +213 -57
  34. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +3 -3
  35. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +13 -2
  36. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +4 -6
  37. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +19 -1
  38. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +1 -1
  39. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +1 -1
  40. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +1 -1
  41. data/lib/active_record/connection_adapters/sqlserver/utils.rb +21 -10
  42. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +187 -187
  43. data/lib/active_record/connection_adapters/sqlserver_column.rb +1 -0
  44. data/lib/active_record/tasks/sqlserver_database_tasks.rb +42 -33
  45. data/lib/arel/visitors/sqlserver.rb +77 -34
  46. data/test/cases/active_schema_test_sqlserver.rb +127 -0
  47. data/test/cases/adapter_test_sqlserver.rb +114 -26
  48. data/test/cases/coerced_tests.rb +1121 -340
  49. data/test/cases/column_test_sqlserver.rb +67 -64
  50. data/test/cases/connection_test_sqlserver.rb +3 -6
  51. data/test/cases/dbconsole.rb +19 -0
  52. data/test/cases/disconnected_test_sqlserver.rb +8 -5
  53. data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
  54. data/test/cases/enum_test_sqlserver.rb +49 -0
  55. data/test/cases/execute_procedure_test_sqlserver.rb +9 -5
  56. data/test/cases/fetch_test_sqlserver.rb +19 -0
  57. data/test/cases/helper_sqlserver.rb +11 -5
  58. data/test/cases/index_test_sqlserver.rb +8 -6
  59. data/test/cases/json_test_sqlserver.rb +1 -1
  60. data/test/cases/lateral_test_sqlserver.rb +2 -2
  61. data/test/cases/migration_test_sqlserver.rb +19 -1
  62. data/test/cases/optimizer_hints_test_sqlserver.rb +21 -12
  63. data/test/cases/pessimistic_locking_test_sqlserver.rb +8 -7
  64. data/test/cases/primary_keys_test_sqlserver.rb +2 -2
  65. data/test/cases/rake_test_sqlserver.rb +10 -5
  66. data/test/cases/schema_dumper_test_sqlserver.rb +155 -109
  67. data/test/cases/schema_test_sqlserver.rb +64 -1
  68. data/test/cases/showplan_test_sqlserver.rb +7 -7
  69. data/test/cases/specific_schema_test_sqlserver.rb +17 -13
  70. data/test/cases/transaction_test_sqlserver.rb +13 -8
  71. data/test/cases/trigger_test_sqlserver.rb +20 -0
  72. data/test/cases/utils_test_sqlserver.rb +2 -2
  73. data/test/cases/uuid_test_sqlserver.rb +8 -0
  74. data/test/cases/view_test_sqlserver.rb +58 -0
  75. data/test/config.yml +1 -2
  76. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +1 -1
  77. data/test/models/sqlserver/alien.rb +5 -0
  78. data/test/models/sqlserver/table_with_spaces.rb +5 -0
  79. data/test/models/sqlserver/trigger.rb +8 -0
  80. data/test/schema/sqlserver_specific_schema.rb +54 -6
  81. data/test/support/coerceable_test_sqlserver.rb +4 -4
  82. data/test/support/connection_reflection.rb +3 -9
  83. data/test/support/core_ext/query_cache.rb +7 -1
  84. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
  85. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
  86. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
  87. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
  88. data/test/support/query_assertions.rb +49 -0
  89. data/test/support/rake_helpers.rb +3 -1
  90. data/test/support/table_definition_sqlserver.rb +24 -0
  91. data/test/support/test_in_memory_oltp.rb +2 -2
  92. metadata +41 -17
  93. data/lib/active_record/sqlserver_base.rb +0 -18
  94. data/test/cases/scratchpad_test_sqlserver.rb +0 -8
  95. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  96. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
  97. data/test/support/sql_counter_sqlserver.rb +0 -29
@@ -277,8 +277,8 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
277
277
  _(col.sql_type).must_equal "date"
278
278
  _(col.type).must_equal :date
279
279
  _(col.null).must_equal true
280
- _(col.default).must_equal connection_dblib_73? ? Date.civil(0001, 1, 1) : "0001-01-01"
281
- _(obj.date).must_equal Date.civil(0001, 1, 1)
280
+ _(col.default).must_equal connection_tds_73 ? Date.civil(1, 1, 1) : "0001-01-01"
281
+ _(obj.date).must_equal Date.civil(1, 1, 1)
282
282
  _(col.default_function).must_be_nil
283
283
  type = connection.lookup_cast_type_from_column(col)
284
284
  _(type).must_be_instance_of Type::Date
@@ -287,22 +287,22 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
287
287
  _(type.scale).must_be_nil
288
288
  # Can cast strings. SQL Server format.
289
289
  obj.date = "04-01-0001"
290
- _(obj.date).must_equal Date.civil(0001, 4, 1)
290
+ _(obj.date).must_equal Date.civil(1, 4, 1)
291
291
  obj.save!
292
- _(obj.date).must_equal Date.civil(0001, 4, 1)
292
+ _(obj.date).must_equal Date.civil(1, 4, 1)
293
293
  obj.reload
294
- _(obj.date).must_equal Date.civil(0001, 4, 1)
294
+ _(obj.date).must_equal Date.civil(1, 4, 1)
295
295
  # Can cast strings. ISO format.
296
296
  obj.date = "0001-04-01"
297
- _(obj.date).must_equal Date.civil(0001, 4, 1)
297
+ _(obj.date).must_equal Date.civil(1, 4, 1)
298
298
  obj.save!
299
- _(obj.date).must_equal Date.civil(0001, 4, 1)
299
+ _(obj.date).must_equal Date.civil(1, 4, 1)
300
300
  obj.reload
301
- _(obj.date).must_equal Date.civil(0001, 4, 1)
301
+ _(obj.date).must_equal Date.civil(1, 4, 1)
302
302
  # Can filter by date range
303
303
  _(obj).must_equal obj.class.where(date: obj.date..Date::Infinity.new).first
304
304
  # Can keep and return assigned date.
305
- assert_obj_set_and_save :date, Date.civil(1972, 04, 14)
305
+ assert_obj_set_and_save :date, Date.civil(1972, 4, 14)
306
306
  # Can accept and cast time objects.
307
307
  obj.date = Time.utc(2010, 4, 14, 12, 34, 56, 3000)
308
308
  _(obj.date).must_equal Date.civil(2010, 4, 14)
@@ -315,7 +315,7 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
315
315
  _(col.sql_type).must_equal "datetime"
316
316
  _(col.type).must_equal :datetime
317
317
  _(col.null).must_equal true
318
- time = Time.utc 1753, 01, 01, 00, 00, 00, 123000
318
+ time = Time.utc 1753, 1, 1, 0, 0, 0, 123000
319
319
  _(col.default).must_equal time, "Microseconds were <#{col.default.usec}> vs <123000>"
320
320
  _(obj.datetime).must_equal time, "Microseconds were <#{obj.datetime.usec}> vs <123000>"
321
321
  _(col.default_function).must_be_nil
@@ -327,7 +327,7 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
327
327
  obj.save!
328
328
  _(obj).must_equal obj.class.where(datetime: time).first
329
329
  # Can save to proper accuracy and return again.
330
- time = Time.utc 2010, 04, 01, 12, 34, 56, 3000
330
+ time = Time.utc 2010, 4, 1, 12, 34, 56, 3000
331
331
  obj.datetime = time
332
332
  _(obj.datetime).must_equal time, "Microseconds were <#{obj.datetime.usec}> vs <3000>"
333
333
  obj.save!
@@ -338,8 +338,8 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
338
338
  # Can filter by datetime range
339
339
  _(obj).must_equal obj.class.where(datetime: time..DateTime::Infinity.new).first
340
340
  # Will cast to true DB value on attribute write, save and return again.
341
- time = Time.utc 2010, 04, 01, 12, 34, 56, 234567
342
- time2 = Time.utc 2010, 04, 01, 12, 34, 56, 233000
341
+ time = Time.utc 2010, 4, 1, 12, 34, 56, 234567
342
+ time2 = Time.utc 2010, 4, 1, 12, 34, 56, 233000
343
343
  obj.datetime = time
344
344
  _(obj.datetime).must_equal time2, "Microseconds were <#{obj.datetime.usec}> vs <233000>"
345
345
  obj.save!
@@ -357,7 +357,7 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
357
357
  end
358
358
 
359
359
  it "datetime2" do
360
- skip "datetime2 not supported in this protocol version" unless connection_dblib_73?
360
+ skip "datetime2 not supported in this protocol version" unless connection_tds_73
361
361
  col = column("datetime2_7")
362
362
  _(col.sql_type).must_equal "datetime2(7)"
363
363
  _(col.type).must_equal :datetime
@@ -422,26 +422,28 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
422
422
  end
423
423
 
424
424
  it "datetimeoffset" do
425
- skip "datetimeoffset not supported in this protocol version" unless connection_dblib_73?
425
+ skip "datetimeoffset not supported in this protocol version" unless connection_tds_73
426
426
  col = column("datetimeoffset_7")
427
427
  _(col.sql_type).must_equal "datetimeoffset(7)"
428
428
  _(col.type).must_equal :datetimeoffset
429
429
  _(col.null).must_equal true
430
- _(col.default).must_equal Time.new(1984, 01, 24, 04, 20, 00, -28800).change(nsec: 123456700), "Nanoseconds <#{col.default.nsec}> vs <123456700>"
431
- _(obj.datetimeoffset_7).must_equal Time.new(1984, 01, 24, 04, 20, 00, -28800).change(nsec: 123456700), "Nanoseconds were <#{obj.datetimeoffset_7.nsec}> vs <999999900>"
430
+ _(col.default).must_equal Time.new(1984, 1, 24, 4, 20, 0, -28800).change(nsec: 123456700), "Nanoseconds <#{col.default.nsec}> vs <123456700>"
431
+ _(obj.datetimeoffset_7).must_equal Time.new(1984, 1, 24, 4, 20, 0, -28800).change(nsec: 123456700), "Nanoseconds were <#{obj.datetimeoffset_7.nsec}> vs <999999900>"
432
432
  _(col.default_function).must_be_nil
433
433
  type = connection.lookup_cast_type_from_column(col)
434
434
  _(type).must_be_instance_of Type::DateTimeOffset
435
435
  _(type.limit).must_be_nil
436
436
  _(type.precision).must_equal 7
437
437
  _(type.scale).must_be_nil
438
- # Can save 100 nanosecond precisoins and return again.
439
- obj.datetimeoffset_7 = Time.new(2010, 04, 01, 12, 34, 56, +18000).change(nsec: 123456755)
440
- _(obj.datetimeoffset_7).must_equal Time.new(2010, 04, 01, 12, 34, 56, +18000).change(nsec: 123456800), "Nanoseconds were <#{obj.datetimeoffset_7.nsec}> vs <123456800>"
438
+
439
+ # Can save 100 nanosecond precisions and return again.
440
+ obj.datetimeoffset_7 = Time.new(2010, 4, 1, 12, 34, 56, +18000).change(nsec: 123456755)
441
+ _(obj.datetimeoffset_7).must_equal Time.new(2010, 4, 1, 12, 34, 56, +18000).change(nsec: 123456800), "Nanoseconds were <#{obj.datetimeoffset_7.nsec}> vs <123456800>"
441
442
  obj.save!
442
- _(obj.datetimeoffset_7).must_equal Time.new(2010, 04, 01, 12, 34, 56, +18000).change(nsec: 123456800), "Nanoseconds were <#{obj.datetimeoffset_7.nsec}> vs <123456800>"
443
+ _(obj.datetimeoffset_7).must_equal Time.new(2010, 4, 1, 12, 34, 56, +18000).change(nsec: 123456800), "Nanoseconds were <#{obj.datetimeoffset_7.nsec}> vs <123456800>"
443
444
  obj.reload
444
- _(obj.datetimeoffset_7).must_equal Time.new(2010, 04, 01, 12, 34, 56, +18000).change(nsec: 123456800), "Nanoseconds were <#{obj.datetimeoffset_7.nsec}> vs <123456800>"
445
+ _(obj.datetimeoffset_7).must_equal Time.new(2010, 4, 1, 12, 34, 56, +18000).change(nsec: 123456800), "Nanoseconds were <#{obj.datetimeoffset_7.nsec}> vs <123456800>"
446
+
445
447
  # Maintains the timezone
446
448
  time = ActiveSupport::TimeZone["America/Los_Angeles"].local 2010, 12, 31, 23, 59, 59, Rational(123456800, 1000)
447
449
  obj.datetimeoffset_7 = time
@@ -449,6 +451,7 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
449
451
  obj.save!
450
452
  _(obj.datetimeoffset_7).must_equal time
451
453
  _(obj.reload.datetimeoffset_7).must_equal time
454
+
452
455
  # With other precisions.
453
456
  time = ActiveSupport::TimeZone["America/Los_Angeles"].local 2010, 12, 31, 23, 59, 59, Rational(123456755, 1000)
454
457
  col = column("datetimeoffset_3")
@@ -470,8 +473,8 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
470
473
  _(col.sql_type).must_equal "smalldatetime"
471
474
  _(col.type).must_equal :smalldatetime
472
475
  _(col.null).must_equal true
473
- _(col.default).must_equal Time.utc(1901, 01, 01, 15, 45, 00, 000)
474
- _(obj.smalldatetime).must_equal Time.utc(1901, 01, 01, 15, 45, 00, 000)
476
+ _(col.default).must_equal Time.utc(1901, 1, 1, 15, 45, 0, 0)
477
+ _(obj.smalldatetime).must_equal Time.utc(1901, 1, 1, 15, 45, 0, 0)
475
478
  _(col.default_function).must_be_nil
476
479
  type = connection.lookup_cast_type_from_column(col)
477
480
  _(type).must_be_instance_of Type::SmallDateTime
@@ -479,21 +482,21 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
479
482
  _(type.precision).must_be_nil
480
483
  _(type.scale).must_be_nil
481
484
  # Will remove fractional seconds and return again.
482
- obj.smalldatetime = Time.utc(2078, 06, 05, 4, 20, 00, 3000)
483
- _(obj.smalldatetime).must_equal Time.utc(2078, 06, 05, 4, 20, 00, 0), "Microseconds were <#{obj.smalldatetime.usec}> vs <0>"
485
+ obj.smalldatetime = Time.utc(2078, 6, 5, 4, 20, 0, 3000)
486
+ _(obj.smalldatetime).must_equal Time.utc(2078, 6, 5, 4, 20, 0, 0), "Microseconds were <#{obj.smalldatetime.usec}> vs <0>"
484
487
  obj.save!
485
- _(obj.smalldatetime).must_equal Time.utc(2078, 06, 05, 4, 20, 00, 0), "Microseconds were <#{obj.reload.smalldatetime.usec}> vs <0>"
488
+ _(obj.smalldatetime).must_equal Time.utc(2078, 6, 5, 4, 20, 0, 0), "Microseconds were <#{obj.reload.smalldatetime.usec}> vs <0>"
486
489
  obj.reload
487
- _(obj.smalldatetime).must_equal Time.utc(2078, 06, 05, 4, 20, 00, 0), "Microseconds were <#{obj.reload.smalldatetime.usec}> vs <0>"
490
+ _(obj.smalldatetime).must_equal Time.utc(2078, 6, 5, 4, 20, 0, 0), "Microseconds were <#{obj.reload.smalldatetime.usec}> vs <0>"
488
491
  end
489
492
 
490
493
  it "time(7)" do
491
- skip "time() not supported in this protocol version" unless connection_dblib_73?
494
+ skip "time() not supported in this protocol version" unless connection_tds_73
492
495
  col = column("time_7")
493
496
  _(col.sql_type).must_equal "time(7)"
494
497
  _(col.type).must_equal :time
495
498
  _(col.null).must_equal true
496
- _(col.default).must_equal Time.utc(1900, 01, 01, 04, 20, 00, Rational(288321500, 1000)), "Nanoseconds were <#{col.default.nsec}> vs <288321500>"
499
+ _(col.default).must_equal Time.utc(1900, 1, 1, 4, 20, 0, Rational(288321500, 1000)), "Nanoseconds were <#{col.default.nsec}> vs <288321500>"
497
500
  _(col.default_function).must_be_nil
498
501
  type = connection.lookup_cast_type_from_column(col)
499
502
  _(type).must_be_instance_of Type::Time
@@ -501,26 +504,26 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
501
504
  _(type.precision).must_equal 7
502
505
  _(type.scale).must_be_nil
503
506
  # Time's #usec precision (low micro)
504
- obj.time_7 = Time.utc(2000, 01, 01, 15, 45, 00, 300)
505
- _(obj.time_7).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 300), "Microseconds were <#{obj.time_7.usec}> vs <0>"
506
- _(obj.time_7).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 300), "Nanoseconds were <#{obj.time_7.nsec}> vs <300>"
507
+ obj.time_7 = Time.utc(2000, 1, 1, 15, 45, 0, 300)
508
+ _(obj.time_7).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 300), "Microseconds were <#{obj.time_7.usec}> vs <0>"
509
+ _(obj.time_7).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 300), "Nanoseconds were <#{obj.time_7.nsec}> vs <300>"
507
510
  obj.save!; obj.reload
508
- _(obj.time_7).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 300), "Microseconds were <#{obj.time_7.usec}> vs <0>"
509
- _(obj.time_7).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 300), "Nanoseconds were <#{obj.time_7.nsec}> vs <300>"
511
+ _(obj.time_7).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 300), "Microseconds were <#{obj.time_7.usec}> vs <0>"
512
+ _(obj.time_7).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 300), "Nanoseconds were <#{obj.time_7.nsec}> vs <300>"
510
513
  # Time's #usec precision (high micro)
511
- obj.time_7 = Time.utc(2000, 01, 01, 15, 45, 00, 234567)
512
- _(obj.time_7).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 234567), "Microseconds were <#{obj.time_7.usec}> vs <234567>"
514
+ obj.time_7 = Time.utc(2000, 1, 1, 15, 45, 0, 234567)
515
+ _(obj.time_7).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 234567), "Microseconds were <#{obj.time_7.usec}> vs <234567>"
513
516
  obj.save!; obj.reload
514
- _(obj.time_7).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 234567), "Microseconds were <#{obj.time_7.usec}> vs <234567>"
517
+ _(obj.time_7).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 234567), "Microseconds were <#{obj.time_7.usec}> vs <234567>"
515
518
  # Time's #usec precision (high nano rounded)
516
- obj.time_7 = Time.utc(2000, 01, 01, 15, 45, 00, Rational(288321545, 1000))
517
- _(obj.time_7).must_equal Time.utc(2000, 01, 01, 15, 45, 00, Rational(288321500, 1000)), "Nanoseconds were <#{obj.time_7.nsec}> vs <288321500>"
519
+ obj.time_7 = Time.utc(2000, 1, 1, 15, 45, 0, Rational(288321545, 1000))
520
+ _(obj.time_7).must_equal Time.utc(2000, 1, 1, 15, 45, 0, Rational(288321500, 1000)), "Nanoseconds were <#{obj.time_7.nsec}> vs <288321500>"
518
521
  obj.save!; obj.reload
519
- _(obj.time_7).must_equal Time.utc(2000, 01, 01, 15, 45, 00, Rational(288321500, 1000)), "Nanoseconds were <#{obj.time_7.nsec}> vs <288321500>"
522
+ _(obj.time_7).must_equal Time.utc(2000, 1, 1, 15, 45, 0, Rational(288321500, 1000)), "Nanoseconds were <#{obj.time_7.nsec}> vs <288321500>"
520
523
  end
521
524
 
522
525
  it "time(2)" do
523
- skip "time() not supported in this protocol version" unless connection_dblib_73?
526
+ skip "time() not supported in this protocol version" unless connection_tds_73
524
527
  col = column("time_2")
525
528
  _(col.sql_type).must_equal "time(2)"
526
529
  _(col.type).must_equal :time
@@ -533,29 +536,29 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
533
536
  _(type.precision).must_equal 2
534
537
  _(type.scale).must_be_nil
535
538
  # Always uses TinyTDS/Windows 2000-01-01 convention too.
536
- obj.time_2 = Time.utc(2015, 01, 10, 15, 45, 00, 0)
537
- _(obj.time_2).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 0)
539
+ obj.time_2 = Time.utc(2015, 1, 10, 15, 45, 0, 0)
540
+ _(obj.time_2).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 0)
538
541
  obj.save!; obj.reload
539
- _(obj.time_2).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 0)
542
+ _(obj.time_2).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 0)
540
543
  # Time's #usec precision (barely in 2 precision equal to 0.03 seconds)
541
- obj.time_2 = Time.utc(2000, 01, 01, 15, 45, 00, 30000)
542
- _(obj.time_2).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 30000), "Microseconds were <#{obj.time_2.usec}> vs <30000>"
544
+ obj.time_2 = Time.utc(2000, 1, 1, 15, 45, 0, 30000)
545
+ _(obj.time_2).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 30000), "Microseconds were <#{obj.time_2.usec}> vs <30000>"
543
546
  obj.save!; obj.reload
544
- _(obj.time_2).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 30000), "Microseconds were <#{obj.time_2.usec}> vs <30000>"
547
+ _(obj.time_2).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 30000), "Microseconds were <#{obj.time_2.usec}> vs <30000>"
545
548
  # Time's #usec precision (below 2 precision)
546
- obj.time_2 = Time.utc(2000, 01, 01, 15, 45, 00, 4000)
547
- _(obj.time_2).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 0), "Microseconds were <#{obj.time_2.usec}> vs <0>"
549
+ obj.time_2 = Time.utc(2000, 1, 1, 15, 45, 0, 4000)
550
+ _(obj.time_2).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 0), "Microseconds were <#{obj.time_2.usec}> vs <0>"
548
551
  obj.save!; obj.reload
549
- _(obj.time_2).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 0), "Microseconds were <#{obj.time_2.usec}> vs <0>"
552
+ _(obj.time_2).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 0), "Microseconds were <#{obj.time_2.usec}> vs <0>"
550
553
  end
551
554
 
552
555
  it "time using default precision" do
553
- skip "time() not supported in this protocol version" unless connection_dblib_73?
556
+ skip "time() not supported in this protocol version" unless connection_tds_73
554
557
  col = column("time_default")
555
558
  _(col.sql_type).must_equal "time(7)"
556
559
  _(col.type).must_equal :time
557
560
  _(col.null).must_equal true
558
- _(col.default).must_equal Time.utc(1900, 01, 01, 15, 03, 42, Rational(62197800, 1000)), "Nanoseconds were <#{col.default.nsec}> vs <62197800>"
561
+ _(col.default).must_equal Time.utc(1900, 1, 1, 15, 3, 42, Rational(62197800, 1000)), "Nanoseconds were <#{col.default.nsec}> vs <62197800>"
559
562
  _(col.default_function).must_be_nil
560
563
  type = connection.lookup_cast_type_from_column(col)
561
564
  _(type).must_be_instance_of Type::Time
@@ -563,22 +566,22 @@ class ColumnTestSQLServer < ActiveRecord::TestCase
563
566
  _(type.precision).must_equal 7
564
567
  _(type.scale).must_be_nil
565
568
  # Time's #usec precision (low micro)
566
- obj.time_default = Time.utc(2000, 01, 01, 15, 45, 00, 300)
567
- _(obj.time_default).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 300), "Microseconds were <#{obj.time_default.usec}> vs <0>"
568
- _(obj.time_default).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 300), "Nanoseconds were <#{obj.time_default.nsec}> vs <300>"
569
+ obj.time_default = Time.utc(2000, 1, 1, 15, 45, 0, 300)
570
+ _(obj.time_default).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 300), "Microseconds were <#{obj.time_default.usec}> vs <0>"
571
+ _(obj.time_default).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 300), "Nanoseconds were <#{obj.time_default.nsec}> vs <300>"
569
572
  obj.save!; obj.reload
570
- _(obj.time_default).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 300), "Microseconds were <#{obj.time_default.usec}> vs <0>"
571
- _(obj.time_default).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 300), "Nanoseconds were <#{obj.time_default.nsec}> vs <300>"
573
+ _(obj.time_default).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 300), "Microseconds were <#{obj.time_default.usec}> vs <0>"
574
+ _(obj.time_default).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 300), "Nanoseconds were <#{obj.time_default.nsec}> vs <300>"
572
575
  # Time's #usec precision (high micro)
573
- obj.time_default = Time.utc(2000, 01, 01, 15, 45, 00, 234567)
574
- _(obj.time_default).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 234567), "Microseconds were <#{obj.time_default.usec}> vs <234567>"
576
+ obj.time_default = Time.utc(2000, 1, 1, 15, 45, 0, 234567)
577
+ _(obj.time_default).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 234567), "Microseconds were <#{obj.time_default.usec}> vs <234567>"
575
578
  obj.save!; obj.reload
576
- _(obj.time_default).must_equal Time.utc(2000, 01, 01, 15, 45, 00, 234567), "Microseconds were <#{obj.time_default.usec}> vs <234567>"
579
+ _(obj.time_default).must_equal Time.utc(2000, 1, 1, 15, 45, 0, 234567), "Microseconds were <#{obj.time_default.usec}> vs <234567>"
577
580
  # Time's #usec precision (high nano rounded)
578
- obj.time_default = Time.utc(2000, 01, 01, 15, 45, 00, Rational(288321545, 1000))
579
- _(obj.time_default).must_equal Time.utc(2000, 01, 01, 15, 45, 00, Rational(288321500, 1000)), "Nanoseconds were <#{obj.time_default.nsec}> vs <288321500>"
581
+ obj.time_default = Time.utc(2000, 1, 1, 15, 45, 0, Rational(288321545, 1000))
582
+ _(obj.time_default).must_equal Time.utc(2000, 1, 1, 15, 45, 0, Rational(288321500, 1000)), "Nanoseconds were <#{obj.time_default.nsec}> vs <288321500>"
580
583
  obj.save!; obj.reload
581
- _(obj.time_default).must_equal Time.utc(2000, 01, 01, 15, 45, 00, Rational(288321500, 1000)), "Nanoseconds were <#{obj.time_default.nsec}> vs <288321500>"
584
+ _(obj.time_default).must_equal Time.utc(2000, 1, 1, 15, 45, 0, Rational(288321500, 1000)), "Nanoseconds were <#{obj.time_default.nsec}> vs <288321500>"
582
585
  end
583
586
 
584
587
  # Character Strings
@@ -44,9 +44,9 @@ class ConnectionTestSQLServer < ActiveRecord::TestCase
44
44
  assert connection.spid.nil?
45
45
  end
46
46
 
47
- it "reset the connection" do
47
+ it "reset raw connection on disconnect!" do
48
48
  connection.disconnect!
49
- _(connection.raw_connection).must_be_nil
49
+ _(connection.instance_variable_get(:@raw_connection)).must_be_nil
50
50
  end
51
51
 
52
52
  it "be able to disconnect and reconnect at will" do
@@ -60,9 +60,6 @@ class ConnectionTestSQLServer < ActiveRecord::TestCase
60
60
  private
61
61
 
62
62
  def disconnect_raw_connection!
63
- case connection_options[:mode]
64
- when :dblib
65
- connection.raw_connection.close rescue nil
66
- end
63
+ connection.raw_connection.close rescue nil
67
64
  end
68
65
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DbConsole < ActiveRecord::TestCase
4
+ subject { ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter }
5
+
6
+ it "uses sqlplus to connect to database" do
7
+ subject.expects(:find_cmd_and_exec).with("sqlcmd", "-d", "db", "-U", "user", "-P", "secret", "-S", "tcp:localhost,1433")
8
+
9
+ config = make_db_config(adapter: "sqlserver", database: "db", username: "user", password: "secret", host: "localhost", port: 1433)
10
+
11
+ subject.dbconsole(config)
12
+ end
13
+
14
+ private
15
+
16
+ def make_db_config(config)
17
+ ActiveRecord::DatabaseConfigurations::HashConfig.new("test", "primary", config)
18
+ end
19
+ end
@@ -5,8 +5,9 @@ require "cases/helper_sqlserver"
5
5
  class TestDisconnectedAdapter < ActiveRecord::TestCase
6
6
  self.use_transactional_tests = false
7
7
 
8
+ undef_method :setup
8
9
  def setup
9
- @connection = ActiveRecord::Base.connection
10
+ @connection = ActiveRecord::Base.lease_connection
10
11
  end
11
12
 
12
13
  teardown do
@@ -15,15 +16,16 @@ class TestDisconnectedAdapter < ActiveRecord::TestCase
15
16
  ActiveRecord::Base.establish_connection(db_config)
16
17
  end
17
18
 
18
- test "can't execute procedures while disconnected" do
19
+ test "execute procedure after disconnect reconnects" do
19
20
  @connection.execute_procedure :sp_tables, "sst_datatypes"
20
21
  @connection.disconnect!
21
- assert_raises(ActiveRecord::ConnectionNotEstablished, 'SQL Server client is not connected') do
22
+
23
+ assert_nothing_raised do
22
24
  @connection.execute_procedure :sp_tables, "sst_datatypes"
23
25
  end
24
26
  end
25
27
 
26
- test "can't execute query while disconnected" do
28
+ test "execute query after disconnect reconnects" do
27
29
  sql = "SELECT count(*) from products WHERE id IN(@0, @1)"
28
30
  binds = [
29
31
  ActiveRecord::Relation::QueryAttribute.new("id", 2, ActiveRecord::Type::BigInteger.new),
@@ -32,7 +34,8 @@ class TestDisconnectedAdapter < ActiveRecord::TestCase
32
34
 
33
35
  @connection.exec_query sql, "TEST", binds
34
36
  @connection.disconnect!
35
- assert_raises(ActiveRecord::ConnectionNotEstablished, 'SQL Server client is not connected') do
37
+
38
+ assert_nothing_raised do
36
39
  @connection.exec_query sql, "TEST", binds
37
40
  end
38
41
  end
@@ -0,0 +1,18 @@
1
+ require "cases/helper_sqlserver"
2
+ require "models/citation"
3
+ require "models/book"
4
+
5
+ class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
6
+ fixtures :citations
7
+
8
+ def test_batch_preloading_too_many_ids
9
+ in_clause_length = 10_000
10
+
11
+ # We Monkey patch Preloader to work with batches of 10_000 records.
12
+ # Expect: N Books queries + Citation query
13
+ expected_query_count = (Citation.count / in_clause_length.to_f).ceil + 1
14
+ assert_queries_count(expected_query_count) do
15
+ Citation.preload(:reference_of).to_a.size
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+
5
+ class EnumTestSQLServer < ActiveRecord::TestCase
6
+
7
+ # Check that enums are supported for all string types.
8
+ # For each type we check: cast, serialize, and update by declaration.
9
+ # We create a custom class for each type to test.
10
+ %w[char_10 varchar_50 varchar_max text nchar_10 nvarchar_50 nvarchar_max ntext].each do |col_name|
11
+ describe "support #{col_name} enums" do
12
+ let(:klass) do
13
+ Class.new(ActiveRecord::Base) do
14
+ self.table_name = 'sst_datatypes'
15
+
16
+ enum col_name, { alpha: "A", beta: "B" }
17
+ end
18
+ end
19
+
20
+ it "type.cast" do
21
+ type = klass.type_for_attribute(col_name)
22
+
23
+ assert_equal "alpha", type.cast('A')
24
+ assert_equal "beta", type.cast('B')
25
+ end
26
+
27
+ it "type.serialize" do
28
+ type = klass.type_for_attribute(col_name)
29
+
30
+ assert_equal 'A', type.serialize('A')
31
+ assert_equal 'B', type.serialize('B')
32
+
33
+ assert_equal 'A', type.serialize(:alpha)
34
+ assert_equal 'B', type.serialize(:beta)
35
+ end
36
+
37
+ it "update by declaration" do
38
+ r = klass.new
39
+
40
+ r.alpha!
41
+ assert_predicate r, :alpha?
42
+
43
+ r.beta!
44
+ assert_not_predicate r, :alpha?
45
+ assert_predicate r, :beta?
46
+ end
47
+ end
48
+ end
49
+ end
@@ -42,12 +42,16 @@ class ExecuteProcedureTestSQLServer < ActiveRecord::TestCase
42
42
  assert_equal date_base.change(usec: 0), date_proc.change(usec: 0)
43
43
  end
44
44
 
45
+ def transaction_with_procedure_and_return
46
+ ActiveRecord::Base.transaction do
47
+ connection.execute_procedure("my_getutcdate")
48
+ return
49
+ end
50
+ end
51
+
45
52
  it 'test deprecation with transaction return when executing procedure' do
46
- assert_deprecated do
47
- ActiveRecord::Base.transaction do
48
- connection.execute_procedure("my_getutcdate")
49
- return
50
- end
53
+ assert_not_deprecated(ActiveRecord.deprecator) do
54
+ transaction_with_procedure_and_return
51
55
  end
52
56
  end
53
57
  end
@@ -42,6 +42,25 @@ class FetchTestSqlserver < ActiveRecord::TestCase
42
42
  end
43
43
  end
44
44
 
45
+ describe "FROM subquery" do
46
+ let(:from_sql) { "(SELECT [books].* FROM [books]) [books]" }
47
+
48
+ it "SQL generated correctly for FROM subquery if order provided" do
49
+ query = Book.from(from_sql).order(:id).limit(5)
50
+
51
+ assert_equal query.to_sql, "SELECT [books].* FROM (SELECT [books].* FROM [books]) [books] ORDER BY [books].[id] ASC OFFSET 0 ROWS FETCH NEXT 5 ROWS ONLY"
52
+ assert_equal query.to_a.count, 5
53
+ end
54
+
55
+ it "exception thrown if FROM subquery is provided without an order" do
56
+ query = Book.from(from_sql).limit(5)
57
+
58
+ assert_raise(ActiveRecord::StatementInvalid) do
59
+ query.to_sql
60
+ end
61
+ end
62
+ end
63
+
45
64
  protected
46
65
 
47
66
  def create_10_books
@@ -7,21 +7,27 @@ require "pry"
7
7
  require "support/core_ext/query_cache"
8
8
  require "support/minitest_sqlserver"
9
9
  require "support/test_in_memory_oltp"
10
+ require "support/table_definition_sqlserver"
10
11
  require "cases/helper"
11
12
  require "support/load_schema_sqlserver"
12
13
  require "support/coerceable_test_sqlserver"
13
- require "support/sql_counter_sqlserver"
14
14
  require "support/connection_reflection"
15
+ require "support/query_assertions"
15
16
  require "mocha/minitest"
16
17
 
18
+ module ActiveSupport
19
+ class TestCase < ::Minitest::Test
20
+ include ARTest::SQLServer::CoerceableTest
21
+ end
22
+ end
23
+
17
24
  module ActiveRecord
18
25
  class TestCase < ActiveSupport::TestCase
19
26
  SQLServer = ActiveRecord::ConnectionAdapters::SQLServer
20
27
 
21
- include ARTest::SQLServer::CoerceableTest,
22
- ARTest::SQLServer::ConnectionReflection,
23
- ARTest::SQLServer::SqlCounterSqlserver,
24
- ActiveSupport::Testing::Stream
28
+ include ARTest::SQLServer::ConnectionReflection,
29
+ ActiveSupport::Testing::Stream,
30
+ ARTest::SQLServer::QueryAssertions
25
31
 
26
32
  let(:logger) { ActiveRecord::Base.logger }
27
33
 
@@ -19,29 +19,31 @@ class IndexTestSQLServer < ActiveRecord::TestCase
19
19
  end
20
20
 
21
21
  it "add index with order" do
22
- assert_sql(/CREATE.*INDEX.*\(\[last_name\] DESC\)/i) do
22
+ assert_queries_match(/CREATE.*INDEX.*\(\[last_name\] DESC\)/i) do
23
23
  connection.add_index "testings", ["last_name"], order: { last_name: :desc }
24
24
  connection.remove_index "testings", ["last_name"]
25
25
  end
26
- assert_sql(/CREATE.*INDEX.*\(\[last_name\] DESC, \[first_name\]\)/i) do
26
+ assert_queries_match(/CREATE.*INDEX.*\(\[last_name\] DESC, \[first_name\]\)/i) do
27
27
  connection.add_index "testings", ["last_name", "first_name"], order: { last_name: :desc }
28
28
  connection.remove_index "testings", ["last_name", "first_name"]
29
29
  end
30
- assert_sql(/CREATE.*INDEX.*\(\[last_name\] DESC, \[first_name\] ASC\)/i) do
30
+ assert_queries_match(/CREATE.*INDEX.*\(\[last_name\] DESC, \[first_name\] ASC\)/i) do
31
31
  connection.add_index "testings", ["last_name", "first_name"], order: { last_name: :desc, first_name: :asc }
32
32
  connection.remove_index "testings", ["last_name", "first_name"]
33
33
  end
34
34
  end
35
35
 
36
36
  it "add index with where" do
37
- assert_sql(/CREATE.*INDEX.*\(\[last_name\]\) WHERE \[first_name\] = N'john doe'/i) do
37
+ assert_queries_match(/CREATE.*INDEX.*\(\[last_name\]\) WHERE \[first_name\] = N'john doe'/i) do
38
38
  connection.add_index "testings", "last_name", where: "[first_name] = N'john doe'"
39
39
  connection.remove_index "testings", "last_name"
40
40
  end
41
41
  end
42
42
 
43
43
  it "add index with expression" do
44
- connection.execute "ALTER TABLE [testings] ADD [first_name_upper] AS UPPER([first_name])"
45
- connection.add_index "testings", "first_name_upper"
44
+ assert_nothing_raised do
45
+ connection.execute "ALTER TABLE [testings] ADD [first_name_upper] AS UPPER([first_name])"
46
+ connection.add_index "testings", "first_name_upper"
47
+ end
46
48
  end
47
49
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "cases/helper_sqlserver"
4
4
 
5
- if ActiveRecord::Base.connection.supports_json?
5
+ if ActiveRecord::Base.lease_connection.supports_json?
6
6
  class JsonTestSQLServer < ActiveRecord::TestCase
7
7
  before do
8
8
  @o1 = SSTestDatatypeMigrationJson.create! json_col: { "a" => "a", "b" => "b", "c" => "c" }
@@ -16,7 +16,7 @@ class LateralTestSQLServer < ActiveRecord::TestCase
16
16
  eq = Arel::Nodes::Equality.new(one, one)
17
17
 
18
18
  sql = author.project(Arel.star).where(author[:name].matches("David")).outer_join(subselect.lateral.as("bar")).on(eq).to_sql
19
- results = ActiveRecord::Base.connection.exec_query sql
19
+ results = ActiveRecord::Base.lease_connection.exec_query sql
20
20
  assert_equal sql, "SELECT * FROM [authors] OUTER APPLY (SELECT * FROM [posts] WHERE [posts].[author_id] = [authors].[id] AND [posts].[id] = 42 ORDER BY [posts].[id] ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) AS bar WHERE [authors].[name] LIKE N'David'"
21
21
  assert_equal results.length, 1
22
22
  end
@@ -27,7 +27,7 @@ class LateralTestSQLServer < ActiveRecord::TestCase
27
27
  subselect = post.project(Arel.star).take(1).where(post[:author_id].eq(author[:id])).where(post[:id].eq(42))
28
28
 
29
29
  sql = author.project(Arel.star).where(author[:name].matches("David")).join(subselect.lateral.as("bar")).to_sql
30
- results = ActiveRecord::Base.connection.exec_query sql
30
+ results = ActiveRecord::Base.lease_connection.exec_query sql
31
31
 
32
32
  assert_equal sql, "SELECT * FROM [authors] CROSS APPLY (SELECT * FROM [posts] WHERE [posts].[author_id] = [authors].[id] AND [posts].[id] = 42 ORDER BY [posts].[id] ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) AS bar WHERE [authors].[name] LIKE N'David'"
33
33
  assert_equal results.length, 0
@@ -20,7 +20,7 @@ class MigrationTestSQLServer < ActiveRecord::TestCase
20
20
  it "not create a tables if error in migrations" do
21
21
  begin
22
22
  migrations_dir = File.join ARTest::SQLServer.migrations_root, "transaction_table"
23
- quietly { ActiveRecord::MigrationContext.new(migrations_dir, ActiveRecord::SchemaMigration).up }
23
+ quietly { ActiveRecord::MigrationContext.new(migrations_dir).up }
24
24
  rescue Exception => e
25
25
  assert_match %r|this and all later migrations canceled|, e.message
26
26
  end
@@ -115,4 +115,22 @@ class MigrationTestSQLServer < ActiveRecord::TestCase
115
115
  refute_includes schemas, { "name" => "some schema" }
116
116
  end
117
117
  end
118
+
119
+ describe 'creating stored procedure' do
120
+ it 'stored procedure contains inserts are created successfully' do
121
+ sql = <<-SQL
122
+ CREATE OR ALTER PROCEDURE do_some_task
123
+ AS
124
+ IF NOT EXISTS(SELECT * FROM sys.objects WHERE type = 'U' AND name = 'SomeTableName')
125
+ BEGIN
126
+ CREATE TABLE SomeTableName (SomeNum int PRIMARY KEY CLUSTERED);
127
+ INSERT INTO SomeTableName(SomeNum) VALUES(1);
128
+ END
129
+ SQL
130
+
131
+ assert_nothing_raised { connection.execute(sql) }
132
+ ensure
133
+ connection.execute("DROP PROCEDURE IF EXISTS dbo.do_some_task;")
134
+ end
135
+ end
118
136
  end