activerecord-sqlserver-adapter 5.2.1 → 6.0.2

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 (153) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/issue_template.md +23 -0
  4. data/.github/workflows/ci.yml +26 -0
  5. data/.gitignore +1 -0
  6. data/.rubocop.yml +29 -0
  7. data/CHANGELOG.md +58 -20
  8. data/{Dockerfile → Dockerfile.ci} +1 -1
  9. data/Gemfile +48 -41
  10. data/Guardfile +9 -8
  11. data/README.md +28 -31
  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 +5 -4
  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 +8 -7
  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 +6 -4
  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 +9 -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 +210 -163
  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 +38 -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/decimal_without_scale.rb +22 -0
  53. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -3
  54. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -3
  55. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -1
  56. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -4
  57. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -3
  58. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -3
  59. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -4
  60. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -3
  61. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -2
  62. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -3
  63. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +6 -6
  64. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +8 -9
  65. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -3
  66. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -3
  67. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -4
  68. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -2
  69. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -3
  70. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -5
  71. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -4
  72. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +4 -3
  73. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -5
  74. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -4
  75. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -5
  76. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -4
  77. data/lib/active_record/connection_adapters/sqlserver/utils.rb +10 -11
  78. data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
  79. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +145 -94
  80. data/lib/active_record/connection_adapters/sqlserver_column.rb +9 -5
  81. data/lib/active_record/sqlserver_base.rb +9 -1
  82. data/lib/active_record/tasks/sqlserver_database_tasks.rb +28 -32
  83. data/lib/activerecord-sqlserver-adapter.rb +3 -1
  84. data/lib/arel/visitors/sqlserver.rb +108 -34
  85. data/lib/arel_sqlserver.rb +4 -2
  86. data/test/appveyor/dbsetup.ps1 +4 -4
  87. data/test/cases/adapter_test_sqlserver.rb +246 -171
  88. data/test/cases/change_column_null_test_sqlserver.rb +14 -12
  89. data/test/cases/coerced_tests.rb +722 -381
  90. data/test/cases/column_test_sqlserver.rb +287 -285
  91. data/test/cases/connection_test_sqlserver.rb +17 -20
  92. data/test/cases/execute_procedure_test_sqlserver.rb +20 -20
  93. data/test/cases/fetch_test_sqlserver.rb +16 -22
  94. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
  95. data/test/cases/helper_sqlserver.rb +15 -15
  96. data/test/cases/in_clause_test_sqlserver.rb +36 -0
  97. data/test/cases/index_test_sqlserver.rb +15 -15
  98. data/test/cases/json_test_sqlserver.rb +25 -25
  99. data/test/cases/lateral_test_sqlserver.rb +35 -0
  100. data/test/cases/migration_test_sqlserver.rb +67 -27
  101. data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
  102. data/test/cases/order_test_sqlserver.rb +53 -54
  103. data/test/cases/pessimistic_locking_test_sqlserver.rb +27 -33
  104. data/test/cases/rake_test_sqlserver.rb +33 -45
  105. data/test/cases/schema_dumper_test_sqlserver.rb +115 -109
  106. data/test/cases/schema_test_sqlserver.rb +20 -26
  107. data/test/cases/scratchpad_test_sqlserver.rb +4 -4
  108. data/test/cases/showplan_test_sqlserver.rb +28 -35
  109. data/test/cases/specific_schema_test_sqlserver.rb +68 -65
  110. data/test/cases/transaction_test_sqlserver.rb +18 -20
  111. data/test/cases/trigger_test_sqlserver.rb +14 -13
  112. data/test/cases/utils_test_sqlserver.rb +70 -70
  113. data/test/cases/uuid_test_sqlserver.rb +13 -14
  114. data/test/debug.rb +8 -6
  115. data/test/migrations/create_clients_and_change_column_null.rb +3 -1
  116. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +4 -4
  117. data/test/models/sqlserver/booking.rb +3 -1
  118. data/test/models/sqlserver/customers_view.rb +3 -1
  119. data/test/models/sqlserver/datatype.rb +2 -0
  120. data/test/models/sqlserver/datatype_migration.rb +2 -0
  121. data/test/models/sqlserver/dollar_table_name.rb +3 -1
  122. data/test/models/sqlserver/edge_schema.rb +3 -3
  123. data/test/models/sqlserver/fk_has_fk.rb +3 -1
  124. data/test/models/sqlserver/fk_has_pk.rb +3 -1
  125. data/test/models/sqlserver/natural_pk_data.rb +4 -2
  126. data/test/models/sqlserver/natural_pk_int_data.rb +3 -1
  127. data/test/models/sqlserver/no_pk_data.rb +3 -1
  128. data/test/models/sqlserver/object_default.rb +3 -1
  129. data/test/models/sqlserver/quoted_table.rb +4 -2
  130. data/test/models/sqlserver/quoted_view_1.rb +3 -1
  131. data/test/models/sqlserver/quoted_view_2.rb +3 -1
  132. data/test/models/sqlserver/sst_memory.rb +3 -1
  133. data/test/models/sqlserver/string_default.rb +3 -1
  134. data/test/models/sqlserver/string_defaults_big_view.rb +3 -1
  135. data/test/models/sqlserver/string_defaults_view.rb +3 -1
  136. data/test/models/sqlserver/tinyint_pk.rb +3 -1
  137. data/test/models/sqlserver/trigger.rb +4 -2
  138. data/test/models/sqlserver/trigger_history.rb +3 -1
  139. data/test/models/sqlserver/upper.rb +3 -1
  140. data/test/models/sqlserver/uppered.rb +3 -1
  141. data/test/models/sqlserver/uuid.rb +3 -1
  142. data/test/schema/sqlserver_specific_schema.rb +31 -21
  143. data/test/support/coerceable_test_sqlserver.rb +15 -9
  144. data/test/support/connection_reflection.rb +3 -2
  145. data/test/support/core_ext/query_cache.rb +4 -1
  146. data/test/support/load_schema_sqlserver.rb +5 -5
  147. data/test/support/minitest_sqlserver.rb +3 -1
  148. data/test/support/paths_sqlserver.rb +11 -11
  149. data/test/support/rake_helpers.rb +13 -10
  150. data/test/support/sql_counter_sqlserver.rb +3 -4
  151. data/test/support/test_in_memory_oltp.rb +9 -7
  152. metadata +27 -12
  153. data/.travis.yml +0 -25
@@ -1,32 +1,32 @@
1
- require 'cases/helper_sqlserver'
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
2
4
 
3
5
  if ActiveRecord::Base.connection.supports_json?
4
- class JsonTestSQLServer < ActiveRecord::TestCase
6
+ class JsonTestSQLServer < ActiveRecord::TestCase
7
+ before do
8
+ @o1 = SSTestDatatypeMigrationJson.create! json_col: { "a" => "a", "b" => "b", "c" => "c" }
9
+ @o2 = SSTestDatatypeMigrationJson.create! json_col: { "a" => nil, "b" => "b", "c" => "c" }
10
+ @o3 = SSTestDatatypeMigrationJson.create! json_col: { "x" => 1, "y" => 2, "z" => 3 }
11
+ @o4 = SSTestDatatypeMigrationJson.create! json_col: { "array" => [1, 2, 3] }
12
+ @o5 = SSTestDatatypeMigrationJson.create! json_col: nil
13
+ end
5
14
 
6
- before do
7
- @o1 = SSTestDatatypeMigrationJson.create! json_col: { 'a' => 'a', 'b' => 'b', 'c' => 'c' }
8
- @o2 = SSTestDatatypeMigrationJson.create! json_col: { 'a' => nil, 'b' => 'b', 'c' => 'c' }
9
- @o3 = SSTestDatatypeMigrationJson.create! json_col: { 'x' => 1, 'y' => 2, 'z' => 3 }
10
- @o4 = SSTestDatatypeMigrationJson.create! json_col: { 'array' => [1, 2, 3] }
11
- @o5 = SSTestDatatypeMigrationJson.create! json_col: nil
12
- end
15
+ it "can return and save JSON data" do
16
+ _(SSTestDatatypeMigrationJson.find(@o1.id).json_col).must_equal({ "a" => "a", "b" => "b", "c" => "c" })
17
+ @o1.json_col = { "a" => "a" }
18
+ _(@o1.json_col).must_equal({ "a" => "a" })
19
+ @o1.save!
20
+ _(@o1.reload.json_col).must_equal({ "a" => "a" })
21
+ end
13
22
 
14
- it 'can return and save JSON data' do
15
- _(SSTestDatatypeMigrationJson.find(@o1.id).json_col).must_equal({ 'a' => 'a', 'b' => 'b', 'c' => 'c' })
16
- @o1.json_col = { 'a' => 'a' }
17
- _(@o1.json_col).must_equal({ 'a' => 'a' })
18
- @o1.save!
19
- _(@o1.reload.json_col).must_equal({ 'a' => 'a' })
20
- end
23
+ it "can use ISJSON function" do
24
+ _(SSTestDatatypeMigrationJson.where("ISJSON(json_col) > 0").count).must_equal 4
25
+ _(SSTestDatatypeMigrationJson.where("ISJSON(json_col) IS NULL").count).must_equal 1
26
+ end
21
27
 
22
- it 'can use ISJSON function' do
23
- _(SSTestDatatypeMigrationJson.where('ISJSON(json_col) > 0').count).must_equal 4
24
- _(SSTestDatatypeMigrationJson.where('ISJSON(json_col) IS NULL').count).must_equal 1
28
+ it "can use JSON_VALUE function" do
29
+ _(SSTestDatatypeMigrationJson.where("JSON_VALUE(json_col, '$.b') = 'b'").count).must_equal 2
30
+ end
25
31
  end
26
-
27
- it 'can use JSON_VALUE function' do
28
- _(SSTestDatatypeMigrationJson.where("JSON_VALUE(json_col, '$.b') = 'b'").count).must_equal 2
29
- end
30
-
31
- end
32
32
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+ require "models/post"
5
+ require "models/author"
6
+
7
+ class LateralTestSQLServer < ActiveRecord::TestCase
8
+ fixtures :posts, :authors
9
+
10
+ it 'uses OUTER APPLY for OUTER JOIN LATERAL' do
11
+ post = Arel::Table.new(:posts)
12
+ author = Arel::Table.new(:authors)
13
+ subselect = post.project(Arel.star).take(1).where(post[:author_id].eq(author[:id])).where(post[:id].eq(42))
14
+
15
+ one = Arel::Nodes::Quoted.new(1)
16
+ eq = Arel::Nodes::Equality.new(one, one)
17
+
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
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
+ assert_equal results.length, 1
22
+ end
23
+
24
+ it 'uses CROSS APPLY for INNER JOIN LATERAL' do
25
+ post = Arel::Table.new(:posts)
26
+ author = Arel::Table.new(:authors)
27
+ subselect = post.project(Arel.star).take(1).where(post[:author_id].eq(author[:id])).where(post[:id].eq(42))
28
+
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
31
+
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
+ assert_equal results.length, 0
34
+ end
35
+ end
@@ -1,14 +1,14 @@
1
- require 'cases/helper_sqlserver'
2
- require 'models/person'
1
+ # frozen_string_literal: true
3
2
 
4
- class MigrationTestSQLServer < ActiveRecord::TestCase
5
-
6
- describe 'For transactions' do
3
+ require "cases/helper_sqlserver"
4
+ require "models/person"
7
5
 
6
+ class MigrationTestSQLServer < ActiveRecord::TestCase
7
+ describe "For transactions" do
8
8
  before do
9
- @trans_test_table1 = 'sqlserver_trans_table1'
10
- @trans_test_table2 = 'sqlserver_trans_table2'
11
- @trans_tables = [@trans_test_table1,@trans_test_table2]
9
+ @trans_test_table1 = "sqlserver_trans_table1"
10
+ @trans_test_table2 = "sqlserver_trans_table2"
11
+ @trans_tables = [@trans_test_table1, @trans_test_table2]
12
12
  end
13
13
 
14
14
  after do
@@ -17,55 +17,95 @@ class MigrationTestSQLServer < ActiveRecord::TestCase
17
17
  end
18
18
  end
19
19
 
20
- it 'not create a tables if error in migrations' do
20
+ it "not create a tables if error in migrations" do
21
21
  begin
22
- migrations_dir = File.join ARTest::SQLServer.migrations_root, 'transaction_table'
23
- quietly { ActiveRecord::MigrationContext.new(migrations_dir).up }
22
+ migrations_dir = File.join ARTest::SQLServer.migrations_root, "transaction_table"
23
+ quietly { ActiveRecord::MigrationContext.new(migrations_dir, ActiveRecord::SchemaMigration).up }
24
24
  rescue Exception => e
25
25
  assert_match %r|this and all later migrations canceled|, e.message
26
26
  end
27
27
  _(connection.tables).wont_include @trans_test_table1
28
28
  _(connection.tables).wont_include @trans_test_table2
29
29
  end
30
-
31
30
  end
32
31
 
33
- describe 'For changing column' do
34
-
35
- it 'not raise exception when column contains default constraint' do
36
- lock_version_column = Person.columns_hash['lock_version']
32
+ describe "For changing column" do
33
+ it "not raise exception when column contains default constraint" do
34
+ lock_version_column = Person.columns_hash["lock_version"]
37
35
  assert_equal :integer, lock_version_column.type
38
36
  assert lock_version_column.default.present?
39
- assert_nothing_raised { connection.change_column 'people', 'lock_version', :string }
37
+ assert_nothing_raised { connection.change_column "people", "lock_version", :string }
40
38
  Person.reset_column_information
41
- lock_version_column = Person.columns_hash['lock_version']
39
+ lock_version_column = Person.columns_hash["lock_version"]
42
40
  assert_equal :string, lock_version_column.type
43
41
  assert lock_version_column.default.nil?
44
- assert_nothing_raised { connection.change_column 'people', 'lock_version', :integer }
42
+ assert_nothing_raised { connection.change_column "people", "lock_version", :integer }
45
43
  Person.reset_column_information
46
44
  end
47
45
 
48
- it 'not drop the default contraint if just renaming' do
46
+ it "not drop the default constraint if just renaming" do
49
47
  find_default = lambda do
50
- connection.execute_procedure(:sp_helpconstraint, 'sst_string_defaults', 'nomsg').select do |row|
51
- row['constraint_type'] == "DEFAULT on column string_with_pretend_paren_three"
48
+ connection.execute_procedure(:sp_helpconstraint, "sst_string_defaults", "nomsg").select do |row|
49
+ row["constraint_type"] == "DEFAULT on column string_with_pretend_paren_three"
52
50
  end.last
53
51
  end
54
52
  default_before = find_default.call
55
53
  connection.change_column :sst_string_defaults, :string_with_pretend_paren_three, :string, limit: 255
56
54
  default_after = find_default.call
57
55
  assert default_after
58
- assert_equal default_before['constraint_keys'], default_after['constraint_keys']
56
+ assert_equal default_before["constraint_keys"], default_after["constraint_keys"]
59
57
  end
60
-
61
- it 'change limit' do
58
+
59
+ it "change limit" do
62
60
  assert_nothing_raised { connection.change_column :people, :lock_version, :integer, limit: 8 }
63
61
  end
64
-
65
- it 'change null and default' do
62
+
63
+ it "change null and default" do
66
64
  assert_nothing_raised { connection.change_column :people, :first_name, :text, null: true, default: nil }
67
65
  end
66
+ end
67
+
68
+ describe "#create_schema" do
69
+ it "creates a new schema" do
70
+ connection.create_schema("some schema")
71
+
72
+ schemas = connection.exec_query("select name from sys.schemas").to_a
73
+
74
+ assert_includes schemas, { "name" => "some schema" }
75
+ end
76
+
77
+ it "creates a new schema with an owner" do
78
+ connection.create_schema("some schema", :guest)
79
+
80
+ schemas = connection.exec_query("select name, principal_id from sys.schemas").to_a
68
81
 
82
+ assert_includes schemas, { "name" => "some schema", "principal_id" => 2 }
83
+ end
69
84
  end
70
85
 
86
+ describe "#change_table_schema" do
87
+ before { connection.create_schema("foo") }
88
+
89
+ it "transfer the given table to the given schema" do
90
+ connection.change_table_schema("foo", "orders")
91
+
92
+ assert connection.data_source_exists?("foo.orders")
93
+ end
94
+ end
95
+
96
+ describe "#drop_schema" do
97
+ before { connection.create_schema("some schema") }
98
+
99
+ it "drops a schema" do
100
+ schemas = connection.exec_query("select name from sys.schemas").to_a
101
+
102
+ assert_includes schemas, { "name" => "some schema" }
103
+
104
+ connection.drop_schema("some schema")
105
+
106
+ schemas = connection.exec_query("select name from sys.schemas").to_a
107
+
108
+ refute_includes schemas, { "name" => "some schema" }
109
+ end
110
+ end
71
111
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+ require "models/company"
5
+
6
+ class OptimizerHitsTestSQLServer < ActiveRecord::TestCase
7
+ fixtures :companies
8
+
9
+ it "apply optimizations" do
10
+ assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
11
+ companies = Company.optimizer_hints("HASH GROUP")
12
+ companies = companies.distinct.select("firm_id")
13
+ assert_includes companies.explain, "| Hash Match | Aggregate |"
14
+ end
15
+
16
+ assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(ORDER GROUP\)\z}) do
17
+ companies = Company.optimizer_hints("ORDER GROUP")
18
+ companies = companies.distinct.select("firm_id")
19
+ assert_includes companies.explain, "| Stream Aggregate | Aggregate |"
20
+ end
21
+ end
22
+
23
+ it "apply multiple optimizations" do
24
+ assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP, FAST 1\)\z}) do
25
+ companies = Company.optimizer_hints("HASH GROUP", "FAST 1")
26
+ companies = companies.distinct.select("firm_id")
27
+ assert_includes companies.explain, "| Hash Match | Flow Distinct |"
28
+ end
29
+ end
30
+
31
+ it "support subqueries" do
32
+ assert_sql(%r{.*'SELECT COUNT\(count_column\) FROM \(SELECT .*\) subquery_for_count OPTION \(MAXDOP 2\)'.*}) do
33
+ companies = Company.optimizer_hints("MAXDOP 2")
34
+ companies = companies.select(:id).where(firm_id: [0, 1]).limit(3)
35
+ assert_equal 3, companies.count
36
+ end
37
+ end
38
+
39
+ it "sanitize values" do
40
+ assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
41
+ companies = Company.optimizer_hints("OPTION (HASH GROUP)")
42
+ companies = companies.distinct.select("firm_id")
43
+ companies.to_a
44
+ end
45
+
46
+ assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
47
+ companies = Company.optimizer_hints("OPTION(HASH GROUP)")
48
+ companies = companies.distinct.select("firm_id")
49
+ companies.to_a
50
+ end
51
+
52
+ assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(TABLE HINT \(\[companies\], INDEX\(1\)\)\)\z}) do
53
+ companies = Company.optimizer_hints("OPTION(TABLE HINT ([companies], INDEX(1)))")
54
+ companies = companies.distinct.select("firm_id")
55
+ companies.to_a
56
+ end
57
+
58
+ assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
59
+ companies = Company.optimizer_hints("Option(HASH GROUP)")
60
+ companies = companies.distinct.select("firm_id")
61
+ companies.to_a
62
+ end
63
+ end
64
+
65
+ it "skip optimization after unscope" do
66
+ assert_sql("SELECT DISTINCT [companies].[firm_id] FROM [companies]") do
67
+ companies = Company.optimizer_hints("HASH GROUP")
68
+ companies = companies.distinct.select("firm_id")
69
+ companies.unscope(:optimizer_hints).load
70
+ end
71
+ end
72
+ end
@@ -1,147 +1,146 @@
1
- require 'cases/helper_sqlserver'
2
- require 'models/post'
1
+ # frozen_string_literal: true
3
2
 
4
- class OrderTestSQLServer < ActiveRecord::TestCase
3
+ require "cases/helper_sqlserver"
4
+ require "models/post"
5
5
 
6
+ class OrderTestSQLServer < ActiveRecord::TestCase
6
7
  fixtures :posts
7
8
 
8
- it 'not mangel complex order clauses' do
9
+ it "not mangel complex order clauses" do
9
10
  xyz_order = "CASE WHEN [title] LIKE N'XYZ%' THEN 0 ELSE 1 END"
10
- xyz_post = Post.create title: 'XYZ Post', body: 'Test cased orders.'
11
+ xyz_post = Post.create title: "XYZ Post", body: "Test cased orders."
11
12
  assert_equal xyz_post, Post.order(Arel.sql(xyz_order)).first
12
13
  end
13
14
 
14
- it 'support column' do
15
+ it "support column" do
15
16
  order = "title"
16
- post1 = Post.create title: 'AAA Post', body: 'Test cased orders.'
17
+ post1 = Post.create title: "AAA Post", body: "Test cased orders."
17
18
  assert_equal post1, Post.order(order).first
18
19
  end
19
20
 
20
- it 'support column ASC' do
21
+ it "support column ASC" do
21
22
  order = "title ASC"
22
- post1 = Post.create title: 'AAA Post', body: 'Test cased orders.'
23
+ post1 = Post.create title: "AAA Post", body: "Test cased orders."
23
24
  assert_equal post1, Post.order(order).first
24
25
  end
25
26
 
26
- it 'support column DESC' do
27
+ it "support column DESC" do
27
28
  order = "title DESC"
28
- post1 = Post.create title: 'ZZZ Post', body: 'Test cased orders.'
29
+ post1 = Post.create title: "ZZZ Post", body: "Test cased orders."
29
30
  assert_equal post1, Post.order(order).first
30
31
  end
31
32
 
32
- it 'support column as symbol' do
33
+ it "support column as symbol" do
33
34
  order = :title
34
- post1 = Post.create title: 'AAA Post', body: 'Test cased orders.'
35
+ post1 = Post.create title: "AAA Post", body: "Test cased orders."
35
36
  assert_equal post1, Post.order(order).first
36
37
  end
37
38
 
38
- it 'support table and column' do
39
+ it "support table and column" do
39
40
  order = "posts.title"
40
- post1 = Post.create title: 'AAA Post', body: 'Test cased orders.'
41
+ post1 = Post.create title: "AAA Post", body: "Test cased orders."
41
42
  assert_equal post1, Post.order(order).first
42
43
  end
43
44
 
44
- it 'support quoted column' do
45
+ it "support quoted column" do
45
46
  order = "[title]"
46
- post1 = Post.create title: 'AAA Post', body: 'Test cased orders.'
47
+ post1 = Post.create title: "AAA Post", body: "Test cased orders."
47
48
  assert_equal post1, Post.order(Arel.sql(order)).first
48
49
  end
49
50
 
50
- it 'support quoted table and column' do
51
+ it "support quoted table and column" do
51
52
  order = "[posts].[title]"
52
- post1 = Post.create title: 'AAA Post', body: 'Test cased orders.'
53
+ post1 = Post.create title: "AAA Post", body: "Test cased orders."
53
54
  assert_equal post1, Post.order(Arel.sql(order)).first
54
55
  end
55
56
 
56
- it 'support primary: column, secondary: column' do
57
+ it "support primary: column, secondary: column" do
57
58
  order = "title DESC, body"
58
- post1 = Post.create title: 'ZZZ Post', body: 'Test cased orders.'
59
- post2 = Post.create title: 'ZZZ Post', body: 'ZZZ Test cased orders.'
59
+ post1 = Post.create title: "ZZZ Post", body: "Test cased orders."
60
+ post2 = Post.create title: "ZZZ Post", body: "ZZZ Test cased orders."
60
61
  assert_equal post1, Post.order(order).first
61
62
  assert_equal post2, Post.order(order).second
62
63
  end
63
64
 
64
- it 'support primary: table and column, secondary: column' do
65
+ it "support primary: table and column, secondary: column" do
65
66
  order = "posts.title DESC, body"
66
- post1 = Post.create title: 'ZZZ Post', body: 'Test cased orders.'
67
- post2 = Post.create title: 'ZZZ Post', body: 'ZZZ Test cased orders.'
67
+ post1 = Post.create title: "ZZZ Post", body: "Test cased orders."
68
+ post2 = Post.create title: "ZZZ Post", body: "ZZZ Test cased orders."
68
69
  assert_equal post1, Post.order(order).first
69
70
  assert_equal post2, Post.order(order).second
70
71
  end
71
72
 
72
- it 'support primary: case expression, secondary: column' do
73
+ it "support primary: case expression, secondary: column" do
73
74
  order = "(CASE WHEN [title] LIKE N'ZZZ%' THEN title ELSE '' END) DESC, body"
74
- post1 = Post.create title: 'ZZZ Post', body: 'Test cased orders.'
75
- post2 = Post.create title: 'ZZZ Post', body: 'ZZZ Test cased orders.'
75
+ post1 = Post.create title: "ZZZ Post", body: "Test cased orders."
76
+ post2 = Post.create title: "ZZZ Post", body: "ZZZ Test cased orders."
76
77
  assert_equal post1, Post.order(Arel.sql(order)).first
77
78
  assert_equal post2, Post.order(Arel.sql(order)).second
78
79
  end
79
80
 
80
- it 'support primary: quoted table and column, secondary: case expresion' do
81
+ it "support primary: quoted table and column, secondary: case expresion" do
81
82
  order = "[posts].[body] DESC, (CASE WHEN [title] LIKE N'ZZZ%' THEN title ELSE '' END) DESC"
82
- post1 = Post.create title: 'ZZZ Post', body: 'ZZZ Test cased orders.'
83
- post2 = Post.create title: 'ZZY Post', body: 'ZZZ Test cased orders.'
83
+ post1 = Post.create title: "ZZZ Post", body: "ZZZ Test cased orders."
84
+ post2 = Post.create title: "ZZY Post", body: "ZZZ Test cased orders."
84
85
  assert_equal post1, Post.order(Arel.sql(order)).first
85
86
  assert_equal post2, Post.order(Arel.sql(order)).second
86
87
  end
87
88
 
88
- it 'support inline function' do
89
+ it "support inline function" do
89
90
  order = "LEN(title)"
90
- post1 = Post.create title: 'A', body: 'AAA Test cased orders.'
91
+ post1 = Post.create title: "A", body: "AAA Test cased orders."
91
92
  assert_equal post1, Post.order(Arel.sql(order)).first
92
93
  end
93
94
 
94
- it 'support inline function with parameters' do
95
+ it "support inline function with parameters" do
95
96
  order = "SUBSTRING(title, 1, 3)"
96
- post1 = Post.create title: 'AAA Post', body: 'Test cased orders.'
97
+ post1 = Post.create title: "AAA Post", body: "Test cased orders."
97
98
  assert_equal post1, Post.order(Arel.sql(order)).first
98
99
  end
99
100
 
100
- it 'support inline function with parameters DESC' do
101
+ it "support inline function with parameters DESC" do
101
102
  order = "SUBSTRING(title, 1, 3) DESC"
102
- post1 = Post.create title: 'ZZZ Post', body: 'Test cased orders.'
103
+ post1 = Post.create title: "ZZZ Post", body: "Test cased orders."
103
104
  assert_equal post1, Post.order(Arel.sql(order)).first
104
105
  end
105
106
 
106
- it 'support primary: inline function, secondary: column' do
107
+ it "support primary: inline function, secondary: column" do
107
108
  order = "LEN(title), body"
108
- post1 = Post.create title: 'A', body: 'AAA Test cased orders.'
109
- post2 = Post.create title: 'A', body: 'Test cased orders.'
109
+ post1 = Post.create title: "A", body: "AAA Test cased orders."
110
+ post2 = Post.create title: "A", body: "Test cased orders."
110
111
  assert_equal post1, Post.order(Arel.sql(order)).first
111
112
  assert_equal post2, Post.order(Arel.sql(order)).second
112
113
  end
113
114
 
114
- it 'support primary: inline function, secondary: column with direction' do
115
+ it "support primary: inline function, secondary: column with direction" do
115
116
  order = "LEN(title) ASC, body DESC"
116
- post1 = Post.create title: 'A', body: 'ZZZ Test cased orders.'
117
- post2 = Post.create title: 'A', body: 'Test cased orders.'
117
+ post1 = Post.create title: "A", body: "ZZZ Test cased orders."
118
+ post2 = Post.create title: "A", body: "Test cased orders."
118
119
  assert_equal post1, Post.order(Arel.sql(order)).first
119
120
  assert_equal post2, Post.order(Arel.sql(order)).second
120
121
  end
121
122
 
122
- it 'support primary: column, secondary: inline function' do
123
+ it "support primary: column, secondary: inline function" do
123
124
  order = "body DESC, LEN(title)"
124
- post1 = Post.create title: 'Post', body: 'ZZZ Test cased orders.'
125
- post2 = Post.create title: 'Longer Post', body: 'ZZZ Test cased orders.'
125
+ post1 = Post.create title: "Post", body: "ZZZ Test cased orders."
126
+ post2 = Post.create title: "Longer Post", body: "ZZZ Test cased orders."
126
127
  assert_equal post1, Post.order(Arel.sql(order)).first
127
128
  assert_equal post2, Post.order(Arel.sql(order)).second
128
129
  end
129
130
 
130
- it 'support primary: case expression, secondary: inline function' do
131
+ it "support primary: case expression, secondary: inline function" do
131
132
  order = "CASE WHEN [title] LIKE N'ZZZ%' THEN title ELSE '' END DESC, LEN(body) ASC"
132
- post1 = Post.create title: 'ZZZ Post', body: 'Z'
133
- post2 = Post.create title: 'ZZZ Post', body: 'Test cased orders.'
133
+ post1 = Post.create title: "ZZZ Post", body: "Z"
134
+ post2 = Post.create title: "ZZZ Post", body: "Test cased orders."
134
135
  assert_equal post1, Post.order(Arel.sql(order)).first
135
136
  assert_equal post2, Post.order(Arel.sql(order)).second
136
137
  end
137
138
 
138
- it 'support primary: inline function, secondary: case expression' do
139
+ it "support primary: inline function, secondary: case expression" do
139
140
  order = "LEN(body), CASE WHEN [title] LIKE N'ZZZ%' THEN title ELSE '' END DESC"
140
- post1 = Post.create title: 'ZZZ Post', body: 'Z'
141
- post2 = Post.create title: 'Post', body: 'Z'
141
+ post1 = Post.create title: "ZZZ Post", body: "Z"
142
+ post2 = Post.create title: "Post", body: "Z"
142
143
  assert_equal post1, Post.order(Arel.sql(order)).first
143
144
  assert_equal post2, Post.order(Arel.sql(order)).second
144
145
  end
145
-
146
-
147
146
  end