activerecord-sqlserver-adapter 5.2.0 → 6.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/issue_template.md +23 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +29 -0
  6. data/.travis.yml +6 -8
  7. data/CHANGELOG.md +46 -11
  8. data/{Dockerfile → Dockerfile.ci} +2 -2
  9. data/Gemfile +48 -41
  10. data/Guardfile +9 -8
  11. data/README.md +9 -37
  12. data/RUNNING_UNIT_TESTS.md +3 -0
  13. data/Rakefile +14 -16
  14. data/VERSION +1 -1
  15. data/activerecord-sqlserver-adapter.gemspec +25 -14
  16. data/appveyor.yml +24 -17
  17. data/docker-compose.ci.yml +7 -5
  18. data/guides/RELEASING.md +11 -0
  19. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -4
  20. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +3 -4
  21. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +22 -2
  22. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +3 -3
  23. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
  24. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +44 -0
  25. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +36 -0
  26. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +28 -0
  27. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -0
  28. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +88 -44
  29. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +10 -12
  30. data/lib/active_record/connection_adapters/sqlserver/errors.rb +2 -3
  31. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +46 -8
  32. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +16 -5
  33. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -7
  34. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +197 -165
  35. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +8 -8
  36. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +4 -2
  37. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +3 -1
  38. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +2 -2
  39. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +43 -44
  40. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +7 -9
  41. data/lib/active_record/connection_adapters/sqlserver/type.rb +37 -35
  42. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +3 -3
  43. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -4
  44. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -3
  45. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +7 -4
  46. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +2 -2
  47. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +4 -3
  48. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +8 -8
  49. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -2
  50. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -2
  51. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -4
  52. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -3
  53. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -3
  54. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -1
  55. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -4
  56. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -3
  57. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -3
  58. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -4
  59. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -3
  60. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -2
  61. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -3
  62. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +6 -6
  63. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +8 -9
  64. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -3
  65. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -3
  66. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -4
  67. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -2
  68. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -3
  69. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -5
  70. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -4
  71. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +4 -3
  72. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -5
  73. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -4
  74. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -5
  75. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -4
  76. data/lib/active_record/connection_adapters/sqlserver/utils.rb +10 -11
  77. data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
  78. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +132 -92
  79. data/lib/active_record/connection_adapters/sqlserver_column.rb +9 -5
  80. data/lib/active_record/sqlserver_base.rb +9 -1
  81. data/lib/active_record/tasks/sqlserver_database_tasks.rb +28 -32
  82. data/lib/activerecord-sqlserver-adapter.rb +3 -1
  83. data/lib/arel/visitors/sqlserver.rb +58 -24
  84. data/lib/arel_sqlserver.rb +4 -2
  85. data/test/appveyor/dbsetup.ps1 +4 -4
  86. data/test/cases/adapter_test_sqlserver.rb +223 -180
  87. data/test/cases/change_column_null_test_sqlserver.rb +17 -15
  88. data/test/cases/coerced_tests.rb +654 -360
  89. data/test/cases/column_test_sqlserver.rb +635 -604
  90. data/test/cases/connection_test_sqlserver.rb +18 -21
  91. data/test/cases/execute_procedure_test_sqlserver.rb +20 -20
  92. data/test/cases/fetch_test_sqlserver.rb +17 -23
  93. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
  94. data/test/cases/helper_sqlserver.rb +20 -15
  95. data/test/cases/in_clause_test_sqlserver.rb +36 -0
  96. data/test/cases/index_test_sqlserver.rb +15 -15
  97. data/test/cases/json_test_sqlserver.rb +25 -25
  98. data/test/cases/migration_test_sqlserver.rb +30 -26
  99. data/test/cases/order_test_sqlserver.rb +53 -54
  100. data/test/cases/pessimistic_locking_test_sqlserver.rb +31 -37
  101. data/test/cases/rake_test_sqlserver.rb +44 -56
  102. data/test/cases/schema_dumper_test_sqlserver.rb +117 -112
  103. data/test/cases/schema_test_sqlserver.rb +20 -26
  104. data/test/cases/scratchpad_test_sqlserver.rb +4 -4
  105. data/test/cases/showplan_test_sqlserver.rb +32 -39
  106. data/test/cases/specific_schema_test_sqlserver.rb +75 -72
  107. data/test/cases/transaction_test_sqlserver.rb +27 -29
  108. data/test/cases/trigger_test_sqlserver.rb +18 -17
  109. data/test/cases/utils_test_sqlserver.rb +78 -78
  110. data/test/cases/uuid_test_sqlserver.rb +19 -20
  111. data/test/debug.rb +8 -6
  112. data/test/migrations/create_clients_and_change_column_null.rb +3 -1
  113. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +4 -4
  114. data/test/models/sqlserver/booking.rb +3 -1
  115. data/test/models/sqlserver/customers_view.rb +3 -1
  116. data/test/models/sqlserver/datatype.rb +2 -0
  117. data/test/models/sqlserver/datatype_migration.rb +2 -0
  118. data/test/models/sqlserver/dollar_table_name.rb +3 -1
  119. data/test/models/sqlserver/edge_schema.rb +3 -3
  120. data/test/models/sqlserver/fk_has_fk.rb +3 -1
  121. data/test/models/sqlserver/fk_has_pk.rb +3 -1
  122. data/test/models/sqlserver/natural_pk_data.rb +4 -2
  123. data/test/models/sqlserver/natural_pk_int_data.rb +3 -1
  124. data/test/models/sqlserver/no_pk_data.rb +3 -1
  125. data/test/models/sqlserver/object_default.rb +3 -1
  126. data/test/models/sqlserver/quoted_table.rb +4 -2
  127. data/test/models/sqlserver/quoted_view_1.rb +3 -1
  128. data/test/models/sqlserver/quoted_view_2.rb +3 -1
  129. data/test/models/sqlserver/sst_memory.rb +3 -1
  130. data/test/models/sqlserver/string_default.rb +3 -1
  131. data/test/models/sqlserver/string_defaults_big_view.rb +3 -1
  132. data/test/models/sqlserver/string_defaults_view.rb +3 -1
  133. data/test/models/sqlserver/tinyint_pk.rb +3 -1
  134. data/test/models/sqlserver/trigger.rb +4 -2
  135. data/test/models/sqlserver/trigger_history.rb +3 -1
  136. data/test/models/sqlserver/upper.rb +3 -1
  137. data/test/models/sqlserver/uppered.rb +3 -1
  138. data/test/models/sqlserver/uuid.rb +3 -1
  139. data/test/schema/datatypes/2012.sql +1 -0
  140. data/test/schema/sqlserver_specific_schema.rb +31 -21
  141. data/test/support/coerceable_test_sqlserver.rb +15 -9
  142. data/test/support/connection_reflection.rb +3 -2
  143. data/test/support/core_ext/query_cache.rb +4 -1
  144. data/test/support/load_schema_sqlserver.rb +5 -5
  145. data/test/support/minitest_sqlserver.rb +3 -1
  146. data/test/support/paths_sqlserver.rb +11 -11
  147. data/test/support/rake_helpers.rb +13 -10
  148. data/test/support/sql_counter_sqlserver.rb +3 -4
  149. data/test/support/test_in_memory_oltp.rb +9 -7
  150. metadata +23 -13
  151. data/BACKERS.md +0 -32
  152. data/circle.yml +0 -38
data/VERSION CHANGED
@@ -1 +1 @@
1
- 5.2.0
1
+ 6.0.1
@@ -1,21 +1,32 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "active_record/connection_adapters/sqlserver/version"
1
+ # frozen_string_literal: true
2
+
3
+ version = File.read(File.expand_path("VERSION", __dir__)).strip
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = 'activerecord-sqlserver-adapter'
7
- spec.version = ActiveRecord::ConnectionAdapters::SQLServer::Version::VERSION
6
+ spec.name = "activerecord-sqlserver-adapter"
8
7
  spec.platform = Gem::Platform::RUBY
9
- spec.license = 'MIT'
10
- spec.authors = ['Ken Collins', 'Anna Carey', 'Will Bond', 'Murray Steele', 'Shawn Balestracci', 'Joe Rafaniello', 'Tom Ward']
11
- spec.email = ['ken@metaskills.net', 'will@wbond.net']
12
- spec.homepage = 'http://github.com/rails-sqlserver/activerecord-sqlserver-adapter'
13
- spec.summary = 'ActiveRecord SQL Server Adapter.'
14
- spec.description = 'ActiveRecord SQL Server Adapter. SQL Server 2012 and upward.'
8
+ spec.version = version
9
+
10
+ spec.required_ruby_version = ">= 2.5.0"
11
+
12
+ spec.license = "MIT"
13
+ spec.authors = ["Ken Collins", "Anna Carey", "Will Bond", "Murray Steele", "Shawn Balestracci", "Joe Rafaniello", "Tom Ward"]
14
+ spec.email = ["ken@metaskills.net", "will@wbond.net"]
15
+ spec.homepage = "http://github.com/rails-sqlserver/activerecord-sqlserver-adapter"
16
+ spec.summary = "ActiveRecord SQL Server Adapter."
17
+ spec.description = "ActiveRecord SQL Server Adapter. SQL Server 2012 and upward."
18
+
19
+ spec.metadata = {
20
+ "bug_tracker_uri" => "https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues",
21
+ "changelog_uri" => "https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/v#{version}/CHANGELOG.md",
22
+ "source_code_uri" => "https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/v#{version}",
23
+ }
24
+
15
25
  spec.files = `git ls-files -z`.split("\x0")
16
26
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
27
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
- spec.require_paths = ['lib']
19
- spec.add_dependency 'activerecord', '~> 5.2.0'
20
- spec.add_dependency 'tiny_tds'
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "activerecord", "~> 6.0.0"
31
+ spec.add_dependency "tiny_tds"
21
32
  end
data/appveyor.yml CHANGED
@@ -1,12 +1,23 @@
1
+ image: Visual Studio 2017
2
+ skip_tags: true
3
+ clone_depth: 5
4
+ build: off
5
+ matrix:
6
+ fast_finish: true
7
+ allow_failures:
8
+ - ruby_version: "25"
9
+ - ruby_version: "26"
10
+ - ruby_version: "27"
11
+ - ruby_version: "27-x64"
12
+ services:
13
+ - mssql2014
14
+
1
15
  init:
2
16
  - SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
3
17
  - SET PATH=C:\MinGW\msys\1.0\bin;%PATH%
4
18
  - SET RAKEOPT=-rdevkit
5
- - SET TINYTDS_VERSION=2.1.0
6
- clone_depth: 5
7
- skip_tags: true
8
- matrix:
9
- fast_finish: true
19
+ - SET TINYTDS_VERSION=2.1.3.pre
20
+
10
21
  install:
11
22
  - ps: Update-AppveyorBuild -Version "$(Get-Content $env:appveyor_build_folder\VERSION).$env:appveyor_build_number"
12
23
  - ruby --version
@@ -14,26 +25,22 @@ install:
14
25
  - bundle install
15
26
  - gem uninstall bcrypt
16
27
  - gem install bcrypt --platform=ruby
17
- build: off
28
+
18
29
  test_script:
19
30
  - powershell -File "%APPVEYOR_BUILD_FOLDER%\test\appveyor\dbsetup.ps1"
20
31
  - timeout /t 4 /nobreak > NUL
21
- - ps: Start-Service 'MSSQL$SQL2014'
22
- - timeout /t 4 /nobreak > NUL
23
32
  - sqlcmd -S ".\SQL2014" -U sa -P Password12! -i %APPVEYOR_BUILD_FOLDER%\test\appveyor\dbsetup.sql
24
33
  - bundle exec rake test ACTIVERECORD_UNITTEST_DATASERVER="localhost\SQL2014"
25
- - ps: Stop-Service 'MSSQL$SQL2014'
26
- - ps: Start-Service 'MSSQL$SQL2012SP1'
27
- - timeout /t 4 /nobreak > NUL
28
- - sqlcmd -S ".\SQL2012SP1" -U sa -P Password12! -i %APPVEYOR_BUILD_FOLDER%\test\appveyor\dbsetup.sql
29
- - bundle exec rake test ACTIVERECORD_UNITTEST_DATASERVER="localhost\SQL2012SP1"
34
+
30
35
  environment:
31
36
  CI_AZURE_HOST:
32
37
  secure: VChrioaIWkf9iuuaSs4cryiA4honrADgZqNC0++begg=
33
38
  CI_AZURE_PASS:
34
39
  secure: cSQp8sk4urJYvq0utpsK+r7J+snJ2wpcdp8RdXJfB+w=
35
40
  matrix:
36
- - ruby_version: "23-x64"
37
- - ruby_version: "23"
38
- - ruby_version: "22-x64"
39
- - ruby_version: "22"
41
+ - ruby_version: "25-x64"
42
+ - ruby_version: "25"
43
+ - ruby_version: "26-x64"
44
+ - ruby_version: "26"
45
+ - ruby_version: "27-x64"
46
+ - ruby_version: "27"
@@ -1,11 +1,13 @@
1
1
  version: "2.2"
2
2
  services:
3
- database:
3
+ sqlserver:
4
4
  image: metaskills/mssql-server-linux-rails
5
5
  ci:
6
6
  environment:
7
- - ACTIVERECORD_UNITTEST_HOST=database
8
- build: .
9
- command: wait-for database:1433 -- bundle exec rake test
7
+ - ACTIVERECORD_UNITTEST_HOST=sqlserver
8
+ build:
9
+ context: .
10
+ dockerfile: Dockerfile.ci
11
+ command: wait-for sqlserver:1433 -- bundle exec rake test
10
12
  depends_on:
11
- - "database"
13
+ - "sqlserver"
@@ -0,0 +1,11 @@
1
+ # Releasing
2
+
3
+ ## Building locally
4
+
5
+ If you want to build the gem to test it locally run `bundle exec rake build`.
6
+
7
+ This command will build the gem in `pkg/activerecord-sqlserver-adapter-A.B.C.gem`, where `A.B.C` is the version in `VERSION` file.
8
+
9
+ ## Releasing to RubyGems
10
+
11
+ Run `bundle exec rake release` to build the gem locally and push the `gem` file to RubyGems.
@@ -1,13 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
4
6
  module CoreExt
5
7
  module ActiveRecord
6
-
7
8
  extend ActiveSupport::Concern
8
9
 
9
10
  module ClassMethods
10
-
11
11
  def execute_procedure(proc_name, *variables)
12
12
  if connection.respond_to?(:execute_procedure)
13
13
  connection.execute_procedure(proc_name, *variables)
@@ -15,9 +15,7 @@ module ActiveRecord
15
15
  []
16
16
  end
17
17
  end
18
-
19
18
  end
20
-
21
19
  end
22
20
  end
23
21
  end
@@ -1,12 +1,12 @@
1
- require 'active_record/attribute_methods'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/attribute_methods"
2
4
 
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  module SQLServer
6
8
  module CoreExt
7
9
  module AttributeMethods
8
-
9
-
10
10
  private
11
11
 
12
12
  def attributes_for_update(attribute_names)
@@ -15,7 +15,6 @@ module ActiveRecord
15
15
  column && column.respond_to?(:is_identity?) && column.is_identity?
16
16
  end
17
17
  end
18
-
19
18
  end
20
19
  end
21
20
  end
@@ -1,11 +1,31 @@
1
- require 'active_record/relation'
2
- require 'active_record/version'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/relation"
4
+ require "active_record/version"
3
5
 
4
6
  module ActiveRecord
5
7
  module ConnectionAdapters
6
8
  module SQLServer
7
9
  module CoreExt
8
10
  module Calculations
11
+ # Same as original except we don't perform PostgreSQL hack that removes ordering.
12
+ def calculate(operation, column_name)
13
+ if has_include?(column_name)
14
+ relation = apply_join_dependency
15
+
16
+ if operation.to_s.downcase == "count"
17
+ unless distinct_value || distinct_select?(column_name || select_for_count)
18
+ relation.distinct!
19
+ relation.select_values = [klass.primary_key || table[Arel.star]]
20
+ end
21
+ end
22
+
23
+ relation.calculate(operation, column_name)
24
+ else
25
+ perform_calculation(operation, column_name)
26
+ end
27
+ end
28
+
9
29
  private
10
30
 
11
31
  def build_count_subquery(relation, column_name, distinct)
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
4
6
  module CoreExt
5
7
  module Explain
6
-
7
- SQLSERVER_STATEMENT_PREFIX = 'EXEC sp_executesql '.freeze
8
+ SQLSERVER_STATEMENT_PREFIX = "EXEC sp_executesql "
8
9
  SQLSERVER_STATEMENT_REGEXP = /N'(.+)', N'(.+)', (.+)/
9
10
 
10
11
  def exec_explain(queries)
@@ -32,7 +33,6 @@ module ActiveRecord
32
33
 
33
34
  executesql
34
35
  end
35
-
36
36
  end
37
37
  end
38
38
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ActiveSupport.on_load(:active_record) do
2
4
  silence_warnings do
3
5
  # Already defined in Rails
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/relation"
4
+ require "active_record/version"
5
+
6
+ module ActiveRecord
7
+ module ConnectionAdapters
8
+ module SQLServer
9
+ module CoreExt
10
+ module FinderMethods
11
+ private
12
+
13
+ # Same as original except we order by values in distinct select if present.
14
+ def construct_relation_for_exists(conditions)
15
+ conditions = sanitize_forbidden_attributes(conditions)
16
+
17
+ if distinct_value && offset_value
18
+ if select_values.present?
19
+ relation = order(*select_values).limit!(1)
20
+ else
21
+ relation = except(:order).limit!(1)
22
+ end
23
+ else
24
+ relation = except(:select, :distinct, :order)._select!(::ActiveRecord::FinderMethods::ONE_AS_ONE).limit!(1)
25
+ end
26
+
27
+ case conditions
28
+ when Array, Hash
29
+ relation.where!(conditions) unless conditions.empty?
30
+ else
31
+ relation.where!(primary_key => conditions) unless conditions == :none
32
+ end
33
+
34
+ relation
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ ActiveSupport.on_load(:active_record) do
43
+ ActiveRecord::Relation.include(ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::FinderMethods)
44
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/associations/preloader"
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ module SQLServer
8
+ module CoreExt
9
+ module Preloader
10
+ private
11
+
12
+ def records_for(ids)
13
+ ids.each_slice(in_clause_length).flat_map do |slice|
14
+ scope.where(association_key_name => slice).load do |record|
15
+ # Processing only the first owner
16
+ # because the record is modified but not an owner
17
+ owner = owners_by_key[convert_key(record[association_key_name])].first
18
+ association = owner.association(reflection.name)
19
+ association.set_inverse_instance(record)
20
+ end.records
21
+ end
22
+ end
23
+
24
+ def in_clause_length
25
+ 10_000
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ ActiveSupport.on_load(:active_record) do
34
+ mod = ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::Preloader
35
+ ActiveRecord::Associations::Preloader::Association.prepend(mod)
36
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/relation"
4
+ require "active_record/version"
5
+
6
+ module ActiveRecord
7
+ module ConnectionAdapters
8
+ module SQLServer
9
+ module CoreExt
10
+ module QueryMethods
11
+ private
12
+
13
+ # Copy of original from Rails master.
14
+ # This patch can be removed when adapter supports Rails version greater than 6.0.2.2
15
+ def table_name_matches?(from)
16
+ table_name = Regexp.escape(table.name)
17
+ quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
18
+ /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ ActiveSupport.on_load(:active_record) do
27
+ ActiveRecord::Relation.include(ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::QueryMethods)
28
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
@@ -9,10 +11,12 @@ module ActiveRecord
9
11
  def column_name_length
10
12
  128
11
13
  end
14
+ deprecate :column_name_length
12
15
 
13
16
  def table_name_length
14
17
  128
15
18
  end
19
+ deprecate :table_name_length
16
20
 
17
21
  def index_name_length
18
22
  128
@@ -21,14 +25,17 @@ module ActiveRecord
21
25
  def columns_per_table
22
26
  1024
23
27
  end
28
+ deprecate :columns_per_table
24
29
 
25
30
  def indexes_per_table
26
31
  999
27
32
  end
33
+ deprecate :indexes_per_table
28
34
 
29
35
  def columns_per_multicolumn_index
30
36
  16
31
37
  end
38
+ deprecate :columns_per_multicolumn_index
32
39
 
33
40
  def in_clause_length
34
41
  10_000
@@ -37,10 +44,12 @@ module ActiveRecord
37
44
  def sql_query_length
38
45
  65_536 * 4_096
39
46
  end
47
+ deprecate :sql_query_length
40
48
 
41
49
  def joins_per_query
42
50
  256
43
51
  end
52
+ deprecate :joins_per_query
44
53
 
45
54
  private
46
55
 
@@ -1,9 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module SQLServer
4
6
  module DatabaseStatements
7
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :dbcc, :explain, :save, :select, :set, :rollback) # :nodoc:
8
+ private_constant :READ_QUERY
9
+
10
+ def write_query?(sql) # :nodoc:
11
+ !READ_QUERY.match?(sql)
12
+ end
5
13
 
6
14
  def execute(sql, name = nil)
15
+ if preventing_writes? && write_query?(sql)
16
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
17
+ end
18
+
19
+ materialize_transactions
20
+
7
21
  if id_insert_table_name = query_requires_identity_insert?(sql)
8
22
  with_identity_insert_enabled(id_insert_table_name) { do_execute(sql, name) }
9
23
  else
@@ -11,7 +25,13 @@ module ActiveRecord
11
25
  end
12
26
  end
13
27
 
14
- def exec_query(sql, name = 'SQL', binds = [], prepare: false)
28
+ def exec_query(sql, name = "SQL", binds = [], prepare: false)
29
+ if preventing_writes? && write_query?(sql)
30
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
31
+ end
32
+
33
+ materialize_transactions
34
+
15
35
  sp_executesql(sql, name, binds, prepare: prepare)
16
36
  end
17
37
 
@@ -24,17 +44,17 @@ module ActiveRecord
24
44
  end
25
45
 
26
46
  def exec_delete(sql, name, binds)
27
- sql = sql.dup << '; SELECT @@ROWCOUNT AS AffectedRows'
47
+ sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
28
48
  super(sql, name, binds).rows.first.first
29
49
  end
30
50
 
31
51
  def exec_update(sql, name, binds)
32
- sql = sql.dup << '; SELECT @@ROWCOUNT AS AffectedRows'
52
+ sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
33
53
  super(sql, name, binds).rows.first.first
34
54
  end
35
55
 
36
56
  def begin_db_transaction
37
- do_execute 'BEGIN TRANSACTION'
57
+ do_execute "BEGIN TRANSACTION"
38
58
  end
39
59
 
40
60
  def transaction_isolation_levels
@@ -51,11 +71,11 @@ module ActiveRecord
51
71
  end
52
72
 
53
73
  def commit_db_transaction
54
- do_execute 'COMMIT TRANSACTION'
74
+ do_execute "COMMIT TRANSACTION"
55
75
  end
56
76
 
57
77
  def exec_rollback_db_transaction
58
- do_execute 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION'
78
+ do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
59
79
  end
60
80
 
61
81
  include Savepoints
@@ -71,9 +91,11 @@ module ActiveRecord
71
91
  def release_savepoint(name = current_savepoint_name)
72
92
  end
73
93
 
74
- def case_sensitive_comparison(table, attribute, column, value)
94
+ def case_sensitive_comparison(attribute, value)
95
+ column = column_for_attribute(attribute)
96
+
75
97
  if column.collation && !column.case_sensitive?
76
- table[attribute].eq(Arel::Nodes::Bin.new(value))
98
+ attribute.eq(Arel::Nodes::Bin.new(value))
77
99
  else
78
100
  super
79
101
  end
@@ -89,12 +111,12 @@ module ActiveRecord
89
111
  end
90
112
  end
91
113
 
92
- table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name table}".dup }
93
- total_sql = Array.wrap(combine_multi_statements(table_deletes + fixture_inserts))
114
+ table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name table}" }
115
+ total_sqls = Array.wrap(table_deletes + fixture_inserts)
94
116
 
95
117
  disable_referential_integrity do
96
118
  transaction(requires_new: true) do
97
- total_sql.each do |sql|
119
+ total_sqls.each do |sql|
98
120
  execute sql, "Fixtures Load"
99
121
  yield if block_given?
100
122
  end
@@ -107,11 +129,6 @@ module ActiveRecord
107
129
  end
108
130
  private :can_perform_case_insensitive_comparison_for?
109
131
 
110
- def combine_multi_statements(total_sql)
111
- total_sql
112
- end
113
- private :combine_multi_statements
114
-
115
132
  def default_insert_value(column)
116
133
  if column.is_identity?
117
134
  table_name = quote(quote_table_name(column.table_name))
@@ -122,16 +139,29 @@ module ActiveRecord
122
139
  end
123
140
  private :default_insert_value
124
141
 
142
+ def build_insert_sql(insert) # :nodoc:
143
+ sql = +"INSERT #{insert.into}"
144
+
145
+ if returning = insert.send(:insert_all).returning
146
+ sql << " OUTPUT " << returning.map { |column| "INSERTED.#{quote_column_name(column)}" }.join(", ")
147
+ end
148
+
149
+ sql << " #{insert.values_list}"
150
+ sql
151
+ end
152
+
125
153
  # === SQLServer Specific ======================================== #
126
154
 
127
155
  def execute_procedure(proc_name, *variables)
156
+ materialize_transactions
157
+
128
158
  vars = if variables.any? && variables.first.is_a?(Hash)
129
159
  variables.first.map { |k, v| "@#{k} = #{quote(v)}" }
130
160
  else
131
161
  variables.map { |v| quote(v) }
132
- end.join(', ')
162
+ end.join(", ")
133
163
  sql = "EXEC #{proc_name} #{vars}".strip
134
- name = 'Execute Procedure'
164
+ name = "Execute Procedure"
135
165
  log(sql, name) do
136
166
  case @connection_options[:mode]
137
167
  when :dblib
@@ -156,20 +186,22 @@ module ActiveRecord
156
186
 
157
187
  def use_database(database = nil)
158
188
  return if sqlserver_azure?
189
+
159
190
  name = SQLServer::Utils.extract_identifiers(database || @connection_options[:database]).quoted
160
191
  do_execute "USE #{name}" unless name.blank?
161
192
  end
162
193
 
163
194
  def user_options
164
195
  return {} if sqlserver_azure?
165
- rows = select_rows('DBCC USEROPTIONS WITH NO_INFOMSGS', 'SCHEMA')
196
+
197
+ rows = select_rows("DBCC USEROPTIONS WITH NO_INFOMSGS", "SCHEMA")
166
198
  rows = rows.first if rows.size == 2 && rows.last.empty?
167
199
  rows.reduce(HashWithIndifferentAccess.new) do |values, row|
168
200
  if row.instance_of? Hash
169
- set_option = row.values[0].gsub(/\s+/, '_')
201
+ set_option = row.values[0].gsub(/\s+/, "_")
170
202
  user_value = row.values[1]
171
- elsif row.instance_of? Array
172
- set_option = row[0].gsub(/\s+/, '_')
203
+ elsif row.instance_of? Array
204
+ set_option = row[0].gsub(/\s+/, "_")
173
205
  user_value = row[1]
174
206
  end
175
207
  values[set_option] = user_value
@@ -179,9 +211,9 @@ module ActiveRecord
179
211
 
180
212
  def user_options_dateformat
181
213
  if sqlserver_azure?
182
- select_value 'SELECT [dateformat] FROM [sys].[syslanguages] WHERE [langid] = @@LANGID', 'SCHEMA'
214
+ select_value "SELECT [dateformat] FROM [sys].[syslanguages] WHERE [langid] = @@LANGID", "SCHEMA"
183
215
  else
184
- user_options['dateformat']
216
+ user_options["dateformat"]
185
217
  end
186
218
  end
187
219
 
@@ -196,43 +228,44 @@ module ActiveRecord
196
228
  WHEN 5 THEN 'SNAPSHOT' END AS [isolation_level]
197
229
  FROM [sys].[dm_exec_sessions]
198
230
  WHERE [session_id] = @@SPID).squish
199
- select_value sql, 'SCHEMA'
231
+ select_value sql, "SCHEMA"
200
232
  else
201
- user_options['isolation_level']
233
+ user_options["isolation_level"]
202
234
  end
203
235
  end
204
236
 
205
237
  def user_options_language
206
238
  if sqlserver_azure?
207
- select_value 'SELECT @@LANGUAGE AS [language]', 'SCHEMA'
239
+ select_value "SELECT @@LANGUAGE AS [language]", "SCHEMA"
208
240
  else
209
- user_options['language']
241
+ user_options["language"]
210
242
  end
211
243
  end
212
244
 
213
245
  def newid_function
214
- select_value 'SELECT NEWID()'
246
+ select_value "SELECT NEWID()"
215
247
  end
216
248
 
217
249
  def newsequentialid_function
218
- select_value 'SELECT NEWSEQUENTIALID()'
250
+ select_value "SELECT NEWSEQUENTIALID()"
219
251
  end
220
252
 
221
-
222
253
  protected
223
254
 
224
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
255
+ def sql_for_insert(sql, pk, binds)
225
256
  if pk.nil?
226
257
  table_name = query_requires_identity_insert?(sql)
227
258
  pk = primary_key(table_name)
228
259
  end
260
+
229
261
  sql = if pk && use_output_inserted? && !database_prefix_remote_server?
230
262
  quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
231
263
  table_name ||= get_table_name(sql)
232
264
  exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql)
265
+
233
266
  if exclude_output_inserted
234
- id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? 'bigint' : exclude_output_inserted
235
- <<-SQL.strip_heredoc
267
+ id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? "bigint" : exclude_output_inserted
268
+ <<~SQL.squish
236
269
  DECLARE @ssaIdInsertTable table (#{quoted_pk} #{id_sql_type});
237
270
  #{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk} INTO @ssaIdInsertTable"}
238
271
  SELECT CAST(#{quoted_pk} AS #{id_sql_type}) FROM @ssaIdInsertTable
@@ -256,7 +289,9 @@ module ActiveRecord
256
289
 
257
290
  # === SQLServer Specific (Executing) ============================ #
258
291
 
259
- def do_execute(sql, name = 'SQL')
292
+ def do_execute(sql, name = "SQL")
293
+ materialize_transactions
294
+
260
295
  log(sql, name) { raw_connection_do(sql) }
261
296
  end
262
297
 
@@ -282,11 +317,12 @@ module ActiveRecord
282
317
 
283
318
  def sp_executesql_sql_type(attr)
284
319
  return attr.type.sqlserver_type if attr.type.respond_to?(:sqlserver_type)
320
+
285
321
  case value = attr.value_for_database
286
322
  when Numeric
287
- value > 2_147_483_647 ? 'bigint'.freeze : 'int'.freeze
323
+ value > 2_147_483_647 ? "bigint".freeze : "int".freeze
288
324
  else
289
- 'nvarchar(max)'.freeze
325
+ "nvarchar(max)".freeze
290
326
  end
291
327
  end
292
328
 
@@ -301,16 +337,16 @@ module ActiveRecord
301
337
  end
302
338
 
303
339
  def sp_executesql_sql(sql, types, params, name)
304
- if name == 'EXPLAIN'
340
+ if name == "EXPLAIN"
305
341
  params.each.with_index do |param, index|
306
342
  substitute_at_finder = /(@#{index})(?=(?:[^']|'[^']*')*$)/ # Finds unquoted @n values.
307
343
  sql = sql.sub substitute_at_finder, param.to_s
308
344
  end
309
345
  else
310
- types = quote(types.join(', '))
311
- params = params.map.with_index{ |p, i| "@#{i} = #{p}" }.join(', ') # Only p is needed, but with @i helps explain regexp.
346
+ types = quote(types.join(", "))
347
+ params = params.map.with_index { |p, i| "@#{i} = #{p}" }.join(", ") # Only p is needed, but with @i helps explain regexp.
312
348
  sql = "EXEC sp_executesql #{quote(sql)}"
313
- sql << ", #{types}, #{params}" unless params.empty?
349
+ sql += ", #{types}, #{params}" unless params.empty?
314
350
  end
315
351
  sql
316
352
  end
@@ -318,7 +354,14 @@ module ActiveRecord
318
354
  def raw_connection_do(sql)
319
355
  case @connection_options[:mode]
320
356
  when :dblib
321
- @connection.execute(sql).do
357
+ result = @connection.execute(sql)
358
+
359
+ # TinyTDS returns false instead of raising an exception if connection fails.
360
+ # Getting around this by raising an exception ourselves while this PR
361
+ # https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
362
+ raise TinyTds::Error, "failed to execute statement" if result.is_a?(FalseClass)
363
+
364
+ result.do
322
365
  end
323
366
  ensure
324
367
  @update_sql = false
@@ -336,8 +379,10 @@ module ActiveRecord
336
379
 
337
380
  def exclude_output_inserted_table_name?(table_name, sql)
338
381
  return false unless exclude_output_inserted_table_names?
382
+
339
383
  table_name ||= get_table_name(sql)
340
384
  return false unless table_name
385
+
341
386
  self.class.exclude_output_inserted_table_names[table_name]
342
387
  end
343
388
 
@@ -366,7 +411,7 @@ module ActiveRecord
366
411
 
367
412
  # === SQLServer Specific (Selecting) ============================ #
368
413
 
369
- def raw_select(sql, name = 'SQL', binds = [], options = {})
414
+ def raw_select(sql, name = "SQL", binds = [], options = {})
370
415
  log(sql, name, binds) { _raw_select(sql, options) }
371
416
  end
372
417
 
@@ -414,7 +459,6 @@ module ActiveRecord
414
459
  end
415
460
  handle
416
461
  end
417
-
418
462
  end
419
463
  end
420
464
  end