activerecord-sqlserver-adapter-odbc-extended 8.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/.github/issue_template.md +22 -0
  3. data/.github/workflows/ci.yml +32 -0
  4. data/.gitignore +9 -0
  5. data/.rubocop.yml +69 -0
  6. data/CHANGELOG.md +5 -0
  7. data/CODE_OF_CONDUCT.md +132 -0
  8. data/Dockerfile.ci +14 -0
  9. data/Gemfile +26 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +104 -0
  12. data/RUNNING_UNIT_TESTS.md +38 -0
  13. data/Rakefile +45 -0
  14. data/VERSION +1 -0
  15. data/activerecord-sqlserver-adapter-odbc-extended.gemspec +34 -0
  16. data/compose.ci.yaml +15 -0
  17. data/lib/active_record/connection_adapters/extended_sqlserver_adapter.rb +204 -0
  18. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +41 -0
  19. data/lib/active_record/connection_adapters/sqlserver/odbc_database_statements.rb +234 -0
  20. data/lib/active_record/connection_adapters/sqlserver/type/binary_ext.rb +25 -0
  21. data/lib/activerecord-sqlserver-adapter-odbc-extended.rb +12 -0
  22. data/test/appveyor/dbsetup.ps1 +27 -0
  23. data/test/appveyor/dbsetup.sql +11 -0
  24. data/test/cases/active_schema_test_sqlserver.rb +127 -0
  25. data/test/cases/adapter_test_sqlserver.rb +648 -0
  26. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  27. data/test/cases/change_column_null_test_sqlserver.rb +44 -0
  28. data/test/cases/coerced_tests.rb +2796 -0
  29. data/test/cases/column_test_sqlserver.rb +848 -0
  30. data/test/cases/connection_test_sqlserver.rb +138 -0
  31. data/test/cases/dbconsole.rb +19 -0
  32. data/test/cases/disconnected_test_sqlserver.rb +42 -0
  33. data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
  34. data/test/cases/enum_test_sqlserver.rb +49 -0
  35. data/test/cases/execute_procedure_test_sqlserver.rb +57 -0
  36. data/test/cases/fetch_test_sqlserver.rb +88 -0
  37. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +72 -0
  38. data/test/cases/helper_sqlserver.rb +61 -0
  39. data/test/cases/migration_test_sqlserver.rb +144 -0
  40. data/test/cases/order_test_sqlserver.rb +153 -0
  41. data/test/cases/pessimistic_locking_test_sqlserver.rb +102 -0
  42. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  43. data/test/cases/rake_test_sqlserver.rb +198 -0
  44. data/test/cases/schema_dumper_test_sqlserver.rb +296 -0
  45. data/test/cases/schema_test_sqlserver.rb +111 -0
  46. data/test/cases/trigger_test_sqlserver.rb +51 -0
  47. data/test/cases/utils_test_sqlserver.rb +129 -0
  48. data/test/cases/uuid_test_sqlserver.rb +54 -0
  49. data/test/cases/view_test_sqlserver.rb +58 -0
  50. data/test/config.yml +38 -0
  51. data/test/debug.rb +16 -0
  52. data/test/fixtures/1px.gif +0 -0
  53. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  54. data/test/migrations/create_clients_and_change_column_null.rb +25 -0
  55. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
  56. data/test/models/sqlserver/alien.rb +5 -0
  57. data/test/models/sqlserver/booking.rb +5 -0
  58. data/test/models/sqlserver/composite_pk.rb +9 -0
  59. data/test/models/sqlserver/customers_view.rb +5 -0
  60. data/test/models/sqlserver/datatype.rb +5 -0
  61. data/test/models/sqlserver/datatype_migration.rb +10 -0
  62. data/test/models/sqlserver/dollar_table_name.rb +5 -0
  63. data/test/models/sqlserver/edge_schema.rb +13 -0
  64. data/test/models/sqlserver/fk_has_fk.rb +5 -0
  65. data/test/models/sqlserver/fk_has_pk.rb +5 -0
  66. data/test/models/sqlserver/natural_pk_data.rb +6 -0
  67. data/test/models/sqlserver/natural_pk_int_data.rb +5 -0
  68. data/test/models/sqlserver/no_pk_data.rb +5 -0
  69. data/test/models/sqlserver/object_default.rb +5 -0
  70. data/test/models/sqlserver/quoted_table.rb +9 -0
  71. data/test/models/sqlserver/quoted_view_1.rb +5 -0
  72. data/test/models/sqlserver/quoted_view_2.rb +5 -0
  73. data/test/models/sqlserver/sst_memory.rb +5 -0
  74. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  75. data/test/models/sqlserver/string_default.rb +5 -0
  76. data/test/models/sqlserver/string_defaults_big_view.rb +5 -0
  77. data/test/models/sqlserver/string_defaults_view.rb +5 -0
  78. data/test/models/sqlserver/table_with_spaces.rb +5 -0
  79. data/test/models/sqlserver/tinyint_pk.rb +5 -0
  80. data/test/models/sqlserver/trigger.rb +17 -0
  81. data/test/models/sqlserver/trigger_history.rb +5 -0
  82. data/test/models/sqlserver/upper.rb +5 -0
  83. data/test/models/sqlserver/uppered.rb +5 -0
  84. data/test/models/sqlserver/uuid.rb +5 -0
  85. data/test/schema/datatypes/2012.sql +56 -0
  86. data/test/schema/enable-in-memory-oltp.sql +81 -0
  87. data/test/schema/sqlserver_specific_schema.rb +363 -0
  88. data/test/support/coerceable_test_sqlserver.rb +55 -0
  89. data/test/support/connection_reflection.rb +32 -0
  90. data/test/support/core_ext/query_cache.rb +38 -0
  91. data/test/support/load_schema_sqlserver.rb +29 -0
  92. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
  93. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
  94. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
  95. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
  96. data/test/support/minitest_sqlserver.rb +3 -0
  97. data/test/support/paths_sqlserver.rb +50 -0
  98. data/test/support/query_assertions.rb +49 -0
  99. data/test/support/rake_helpers.rb +46 -0
  100. data/test/support/table_definition_sqlserver.rb +24 -0
  101. data/test/support/test_in_memory_oltp.rb +17 -0
  102. metadata +240 -0
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+ require "models/person"
5
+
6
+ class MigrationTestSQLServer < ActiveRecord::TestCase
7
+ describe "For transactions" do
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]
12
+ end
13
+
14
+ after do
15
+ @trans_tables.each do |table_name|
16
+ ActiveRecord::Migration.drop_table(table_name) if connection.tables.include?(table_name)
17
+ end
18
+ end
19
+
20
+ it "not create a tables if error in migrations" do
21
+ begin
22
+ migrations_dir = File.join ARTest::SQLServer.migrations_root, "transaction_table"
23
+ quietly { ActiveRecord::MigrationContext.new(migrations_dir).up }
24
+ rescue Exception => e
25
+ assert_match %r|this and all later migrations canceled|, e.message
26
+ end
27
+ _(connection.tables).wont_include @trans_test_table1
28
+ _(connection.tables).wont_include @trans_test_table2
29
+ end
30
+ end
31
+
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"]
35
+ assert_equal :integer, lock_version_column.type
36
+ assert lock_version_column.default.present?
37
+ assert_nothing_raised { connection.change_column "people", "lock_version", :string }
38
+ Person.reset_column_information
39
+ lock_version_column = Person.columns_hash["lock_version"]
40
+ assert_equal :string, lock_version_column.type
41
+ assert lock_version_column.default.nil?
42
+ assert_nothing_raised { connection.change_column "people", "lock_version", :integer }
43
+ Person.reset_column_information
44
+ end
45
+
46
+ it "not drop the default constraint if just renaming" do
47
+ find_default = lambda do
48
+ connection.execute_procedure(:sp_helpconstraint, "sst_string_defaults", "nomsg").flatten.select do |row|
49
+ row["constraint_type"] == "DEFAULT on column string_with_pretend_paren_three"
50
+ end.last
51
+ end
52
+ default_before = find_default.call
53
+ connection.change_column :sst_string_defaults, :string_with_pretend_paren_three, :string, limit: 255
54
+ default_after = find_default.call
55
+ assert default_after
56
+ assert_equal default_before["constraint_keys"], default_after["constraint_keys"]
57
+ end
58
+
59
+ it "change limit" do
60
+ assert_nothing_raised { connection.change_column :people, :lock_version, :integer, limit: 8 }
61
+ end
62
+
63
+ it 'change limit' do
64
+ assert_nothing_raised { connection.change_column :people, :lock_version, :integer, limit: 8 }
65
+ end
66
+
67
+ it 'change null and default' do
68
+ assert_nothing_raised { connection.change_column :people, :first_name, :text, null: true, default: nil }
69
+ end
70
+
71
+ it "change null and default" do
72
+ assert_nothing_raised { connection.change_column :people, :first_name, :text, null: true, default: nil }
73
+ end
74
+
75
+ it "change collation" do
76
+ assert_nothing_raised { connection.change_column :sst_string_collation, :string_with_collation, :varchar, collation: :SQL_Latin1_General_CP437_BIN }
77
+
78
+ SstStringCollation.reset_column_information
79
+ assert_equal "SQL_Latin1_General_CP437_BIN", SstStringCollation.columns_hash['string_with_collation'].collation
80
+ end
81
+ end
82
+
83
+ describe "#create_schema" do
84
+ it "creates a new schema" do
85
+ connection.create_schema("some schema")
86
+
87
+ schemas = connection.exec_query("select name from sys.schemas").to_a
88
+
89
+ assert_includes schemas, { "name" => "some schema" }
90
+ end
91
+
92
+ it "creates a new schema with an owner" do
93
+ connection.create_schema("some schema", :guest)
94
+
95
+ schemas = connection.exec_query("select name, principal_id from sys.schemas").to_a
96
+
97
+ assert_includes schemas, { "name" => "some schema", "principal_id" => 2 }
98
+ end
99
+ end
100
+
101
+ describe "#change_table_schema" do
102
+ before { connection.create_schema("foo") }
103
+
104
+ it "transfer the given table to the given schema" do
105
+ connection.change_table_schema("foo", "orders")
106
+
107
+ assert connection.data_source_exists?("foo.orders")
108
+ end
109
+ end
110
+
111
+ describe "#drop_schema" do
112
+ before { connection.create_schema("some schema") }
113
+
114
+ it "drops a schema" do
115
+ schemas = connection.exec_query("select name from sys.schemas").to_a
116
+
117
+ assert_includes schemas, { "name" => "some schema" }
118
+
119
+ connection.drop_schema("some schema")
120
+
121
+ schemas = connection.exec_query("select name from sys.schemas").to_a
122
+
123
+ refute_includes schemas, { "name" => "some schema" }
124
+ end
125
+ end
126
+
127
+ describe 'creating stored procedure' do
128
+ it 'stored procedure contains inserts are created successfully' do
129
+ sql = <<-SQL
130
+ CREATE OR ALTER PROCEDURE do_some_task
131
+ AS
132
+ IF NOT EXISTS(SELECT * FROM sys.objects WHERE type = 'U' AND name = 'SomeTableName')
133
+ BEGIN
134
+ CREATE TABLE SomeTableName (SomeNum int PRIMARY KEY CLUSTERED);
135
+ INSERT INTO SomeTableName(SomeNum) VALUES(1);
136
+ END
137
+ SQL
138
+
139
+ assert_nothing_raised { connection.execute(sql) }
140
+ ensure
141
+ connection.execute("DROP PROCEDURE IF EXISTS dbo.do_some_task;")
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+ require "models/post"
5
+
6
+ class OrderTestSQLServer < ActiveRecord::TestCase
7
+ fixtures :posts
8
+
9
+ it "not mangel complex order clauses" do
10
+ xyz_order = "CASE WHEN [title] LIKE N'XYZ%' THEN 0 ELSE 1 END"
11
+ xyz_post = Post.create title: "XYZ Post", body: "Test cased orders."
12
+ assert_equal xyz_post, Post.order(Arel.sql(xyz_order)).first
13
+ end
14
+
15
+ it "support column" do
16
+ order = "title"
17
+ post1 = Post.create title: "AAA Post", body: "Test cased orders."
18
+ assert_equal post1, Post.order(order).first
19
+ end
20
+
21
+ it "support column ASC" do
22
+ order = "title ASC"
23
+ post1 = Post.create title: "AAA Post", body: "Test cased orders."
24
+ assert_equal post1, Post.order(order).first
25
+ end
26
+
27
+ it "support column DESC" do
28
+ order = "title DESC"
29
+ post1 = Post.create title: "ZZZ Post", body: "Test cased orders."
30
+ assert_equal post1, Post.order(order).first
31
+ end
32
+
33
+ it "support column as symbol" do
34
+ order = :title
35
+ post1 = Post.create title: "AAA Post", body: "Test cased orders."
36
+ assert_equal post1, Post.order(order).first
37
+ end
38
+
39
+ it "support table and column" do
40
+ order = "posts.title"
41
+ post1 = Post.create title: "AAA Post", body: "Test cased orders."
42
+ assert_equal post1, Post.order(order).first
43
+ end
44
+
45
+ it "support quoted column" do
46
+ order = "[title]"
47
+ post1 = Post.create title: "AAA Post", body: "Test cased orders."
48
+ assert_equal post1, Post.order(Arel.sql(order)).first
49
+ end
50
+
51
+ it "support quoted table and column" do
52
+ order = "[posts].[title]"
53
+ post1 = Post.create title: "AAA Post", body: "Test cased orders."
54
+ assert_equal post1, Post.order(Arel.sql(order)).first
55
+ end
56
+
57
+ it "support primary: column, secondary: column" do
58
+ order = "title DESC, body"
59
+ post1 = Post.create title: "ZZZ Post", body: "Test cased orders."
60
+ post2 = Post.create title: "ZZZ Post", body: "ZZZ Test cased orders."
61
+ assert_equal post1, Post.order(order).first
62
+ assert_equal post2, Post.order(order).second
63
+ end
64
+
65
+ it "support primary: table and column, secondary: column" do
66
+ order = "posts.title DESC, body"
67
+ post1 = Post.create title: "ZZZ Post", body: "Test cased orders."
68
+ post2 = Post.create title: "ZZZ Post", body: "ZZZ Test cased orders."
69
+ assert_equal post1, Post.order(order).first
70
+ assert_equal post2, Post.order(order).second
71
+ end
72
+
73
+ it "support primary: case expression, secondary: column" do
74
+ order = "(CASE WHEN [title] LIKE N'ZZZ%' THEN title ELSE '' END) DESC, body"
75
+ post1 = Post.create title: "ZZZ Post", body: "Test cased orders."
76
+ post2 = Post.create title: "ZZZ Post", body: "ZZZ Test cased orders."
77
+ assert_equal post1, Post.order(Arel.sql(order)).first
78
+ assert_equal post2, Post.order(Arel.sql(order)).second
79
+ end
80
+
81
+ it "support primary: quoted table and column, secondary: case expresion" do
82
+ order = "[posts].[body] DESC, (CASE WHEN [title] LIKE N'ZZZ%' THEN title ELSE '' END) DESC"
83
+ post1 = Post.create title: "ZZZ Post", body: "ZZZ Test cased orders."
84
+ post2 = Post.create title: "ZZY Post", body: "ZZZ Test cased orders."
85
+ assert_equal post1, Post.order(Arel.sql(order)).first
86
+ assert_equal post2, Post.order(Arel.sql(order)).second
87
+ end
88
+
89
+ it "support inline function" do
90
+ order = "LEN(title)"
91
+ post1 = Post.create title: "A", body: "AAA Test cased orders."
92
+ assert_equal post1, Post.order(Arel.sql(order)).first
93
+ end
94
+
95
+ it "support inline function with parameters" do
96
+ order = "SUBSTRING(title, 1, 3)"
97
+ post1 = Post.create title: "AAA Post", body: "Test cased orders."
98
+ assert_equal post1, Post.order(Arel.sql(order)).first
99
+ end
100
+
101
+ it "support inline function with parameters DESC" do
102
+ order = "SUBSTRING(title, 1, 3) DESC"
103
+ post1 = Post.create title: "ZZZ Post", body: "Test cased orders."
104
+ assert_equal post1, Post.order(Arel.sql(order)).first
105
+ end
106
+
107
+ it "support primary: inline function, secondary: column" do
108
+ order = "LEN(title), body"
109
+ post1 = Post.create title: "A", body: "AAA Test cased orders."
110
+ post2 = Post.create title: "A", body: "Test cased orders."
111
+ assert_equal post1, Post.order(Arel.sql(order)).first
112
+ assert_equal post2, Post.order(Arel.sql(order)).second
113
+ end
114
+
115
+ it "support primary: inline function, secondary: column with direction" do
116
+ order = "LEN(title) ASC, body DESC"
117
+ post1 = Post.create title: "A", body: "ZZZ Test cased orders."
118
+ post2 = Post.create title: "A", body: "Test cased orders."
119
+ assert_equal post1, Post.order(Arel.sql(order)).first
120
+ assert_equal post2, Post.order(Arel.sql(order)).second
121
+ end
122
+
123
+ it "support primary: column, secondary: inline function" do
124
+ order = "body DESC, LEN(title)"
125
+ post1 = Post.create title: "Post", body: "ZZZ Test cased orders."
126
+ post2 = Post.create title: "Longer Post", body: "ZZZ Test cased orders."
127
+ assert_equal post1, Post.order(Arel.sql(order)).first
128
+ assert_equal post2, Post.order(Arel.sql(order)).second
129
+ end
130
+
131
+ it "support primary: case expression, secondary: inline function" do
132
+ order = "CASE WHEN [title] LIKE N'ZZZ%' THEN title ELSE '' END DESC, LEN(body) ASC"
133
+ post1 = Post.create title: "ZZZ Post", body: "Z"
134
+ post2 = Post.create title: "ZZZ Post", body: "Test cased orders."
135
+ assert_equal post1, Post.order(Arel.sql(order)).first
136
+ assert_equal post2, Post.order(Arel.sql(order)).second
137
+ end
138
+
139
+ it "support primary: inline function, secondary: case expression" do
140
+ order = "LEN(body), CASE WHEN [title] LIKE N'ZZZ%' THEN title ELSE '' END DESC"
141
+ post1 = Post.create title: "ZZZ Post", body: "Z"
142
+ post2 = Post.create title: "Post", body: "Z"
143
+ assert_equal post1, Post.order(Arel.sql(order)).first
144
+ assert_equal post2, Post.order(Arel.sql(order)).second
145
+ end
146
+
147
+ # Executing this kind of queries will raise "A column has been specified more than once in the order by list"
148
+ # This test shows that we don't do anything to prevent this
149
+ it "doesn't deduplicate semantically equal orders" do
150
+ sql = Post.order(:id).order("posts.id ASC").to_sql
151
+ assert_equal "SELECT [posts].* FROM [posts] ORDER BY [posts].[id] ASC, posts.id ASC", sql
152
+ end
153
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+ require "models/person"
5
+ require "models/reader"
6
+
7
+ class PessimisticLockingTestSQLServer < ActiveRecord::TestCase
8
+ fixtures :people, :readers
9
+
10
+ before do
11
+ Person.columns
12
+ Reader.columns
13
+ end
14
+
15
+ it "uses with updlock by default" do
16
+ assert_queries_match %r|SELECT \[people\]\.\* FROM \[people\] WITH\(UPDLOCK\)| do
17
+ _(Person.lock(true).to_a).must_equal Person.all.to_a
18
+ end
19
+ end
20
+
21
+ describe "For simple finds with default lock option" do
22
+ it "lock with simple find" do
23
+ assert_nothing_raised do
24
+ Person.transaction do
25
+ _(Person.lock(true).find(1)).must_equal Person.find(1)
26
+ end
27
+ end
28
+ end
29
+
30
+ it "lock with scoped find" do
31
+ assert_nothing_raised do
32
+ Person.transaction do
33
+ Person.lock(true).scoping do
34
+ _(Person.find(1)).must_equal Person.find(1)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ it "lock with eager find" do
41
+ assert_nothing_raised do
42
+ Person.transaction do
43
+ person = Person.lock(true).includes(:readers).find(1)
44
+ _(person).must_equal Person.find(1)
45
+ end
46
+ end
47
+ end
48
+
49
+ it "can add a custom lock directive" do
50
+ assert_queries_match %r|SELECT \[people\]\.\* FROM \[people\] WITH\(HOLDLOCK, ROWLOCK\)| do
51
+ Person.lock("WITH(HOLDLOCK, ROWLOCK)").load
52
+ end
53
+ end
54
+
55
+ describe "joining tables" do
56
+ it "joined tables use updlock by default" do
57
+ assert_queries_match %r|SELECT \[people\]\.\* FROM \[people\] WITH\(UPDLOCK\) INNER JOIN \[readers\] WITH\(UPDLOCK\)\s+ON \[readers\]\.\[person_id\] = \[people\]\.\[id\]| do
58
+ Person.lock(true).joins(:readers).load
59
+ end
60
+ end
61
+
62
+ it "joined tables can use custom lock directive" do
63
+ assert_queries_match %r|SELECT \[people\]\.\* FROM \[people\] WITH\(NOLOCK\) INNER JOIN \[readers\] WITH\(NOLOCK\)\s+ON \[readers\]\.\[person_id\] = \[people\]\.\[id\]| do
64
+ Person.lock("WITH(NOLOCK)").joins(:readers).load
65
+ end
66
+ end
67
+
68
+ it "left joined tables use updlock by default" do
69
+ assert_queries_match %r|SELECT \[people\]\.\* FROM \[people\] WITH\(UPDLOCK\) LEFT OUTER JOIN \[readers\] WITH\(UPDLOCK\)\s+ON \[readers\]\.\[person_id\] = \[people\]\.\[id\]| do
70
+ Person.lock(true).left_joins(:readers).load
71
+ end
72
+ end
73
+
74
+ it "left joined tables can use custom lock directive" do
75
+ assert_queries_match %r|SELECT \[people\]\.\* FROM \[people\] WITH\(NOLOCK\) LEFT OUTER JOIN \[readers\] WITH\(NOLOCK\)\s+ON \[readers\]\.\[person_id\] = \[people\]\.\[id\]| do
76
+ Person.lock("WITH(NOLOCK)").left_joins(:readers).load
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ describe "For paginated finds" do
83
+ before do
84
+ Person.delete_all
85
+ 20.times { |n| Person.create!(first_name: "Thing_#{n}") }
86
+ end
87
+
88
+ it "copes with eager loading un-locked paginated" do
89
+ eager_ids_sql = /SELECT\s+DISTINCT \[people\].\[id\] FROM \[people\] WITH\(UPDLOCK\) LEFT OUTER JOIN \[readers\] WITH\(UPDLOCK\)\s+ON \[readers\].\[person_id\] = \[people\].\[id\]\s+ORDER BY \[people\].\[id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY/
90
+ loader_sql = /SELECT.*FROM \[people\] WITH\(UPDLOCK\).*WHERE \[people\]\.\[id\] IN/
91
+
92
+ assert_queries_match(/#{eager_ids_sql}|#{loader_sql}/, count: 2) do
93
+ people = Person.lock(true).limit(5).offset(10).includes(:readers).references(:readers).to_a
94
+ _(people[0].first_name).must_equal "Thing_10"
95
+ _(people[1].first_name).must_equal "Thing_11"
96
+ _(people[2].first_name).must_equal "Thing_12"
97
+ _(people[3].first_name).must_equal "Thing_13"
98
+ _(people[4].first_name).must_equal "Thing_14"
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+ require "support/schema_dumping_helper"
5
+
6
+ class PrimaryKeyUuidTypeTest < ActiveRecord::TestCase
7
+ include SchemaDumpingHelper
8
+
9
+ self.use_transactional_tests = false
10
+
11
+ class Barcode < ActiveRecord::Base
12
+ end
13
+
14
+ setup do
15
+ @connection = ActiveRecord::Base.lease_connection
16
+ @connection.create_table(:barcodes, primary_key: "code", id: :uuid, force: true)
17
+ end
18
+
19
+ teardown do
20
+ @connection.drop_table(:barcodes, if_exists: true)
21
+ end
22
+
23
+ def test_any_type_primary_key
24
+ assert_equal "code", Barcode.primary_key
25
+
26
+ column = Barcode.column_for_attribute(Barcode.primary_key)
27
+ assert_not column.null
28
+ assert_equal :uuid, column.type
29
+ assert_not_predicate column, :is_identity?
30
+ assert_predicate column, :is_primary?
31
+ ensure
32
+ Barcode.reset_column_information
33
+ end
34
+
35
+ test "schema dump primary key includes default" do
36
+ schema = dump_table_schema "barcodes"
37
+ assert_match %r/create_table "barcodes", primary_key: "code", id: :uuid, default: -> { "newid\(\)" }/, schema
38
+ end
39
+ end
40
+
41
+ class PrimaryKeyIntegerTest < ActiveRecord::TestCase
42
+ include SchemaDumpingHelper
43
+
44
+ self.use_transactional_tests = false
45
+
46
+ class Barcode < ActiveRecord::Base
47
+ end
48
+
49
+ class Widget < ActiveRecord::Base
50
+ end
51
+
52
+ setup do
53
+ @connection = ActiveRecord::Base.lease_connection
54
+ end
55
+
56
+ teardown do
57
+ @connection.drop_table :barcodes, if_exists: true
58
+ @connection.drop_table :widgets, if_exists: true
59
+ end
60
+
61
+ test "integer primary key without default" do
62
+ @connection.create_table(:widgets, id: :integer, force: true)
63
+ column = @connection.columns(:widgets).find { |c| c.name == "id" }
64
+ assert_predicate column, :is_primary?
65
+ assert_predicate column, :is_identity?
66
+ assert_equal :integer, column.type
67
+ assert_not_predicate column, :bigint?
68
+
69
+ schema = dump_table_schema "widgets"
70
+ assert_match %r/create_table "widgets", id: :integer, force: :cascade do/, schema
71
+ end
72
+
73
+ test "bigint primary key without default" do
74
+ @connection.create_table(:widgets, id: :bigint, force: true)
75
+ column = @connection.columns(:widgets).find { |c| c.name == "id" }
76
+ assert_predicate column, :is_primary?
77
+ assert_predicate column, :is_identity?
78
+ assert_equal :integer, column.type
79
+ assert_predicate column, :bigint?
80
+
81
+ schema = dump_table_schema "widgets"
82
+ assert_match %r/create_table "widgets", force: :cascade do/, schema
83
+ end
84
+
85
+ test "don't set identity to integer and bigint when there is a default" do
86
+ @connection.create_table(:barcodes, id: :integer, default: nil, force: true)
87
+ @connection.create_table(:widgets, id: :bigint, default: nil, force: true)
88
+
89
+ column = @connection.columns(:widgets).find { |c| c.name == "id" }
90
+ assert_predicate column, :is_primary?
91
+ assert_not_predicate column, :is_identity?
92
+
93
+ schema = dump_table_schema "widgets"
94
+ assert_match %r/create_table "widgets", id: :bigint, default: nil, force: :cascade do/, schema
95
+
96
+ column = @connection.columns(:barcodes).find { |c| c.name == "id" }
97
+ assert_predicate column, :is_primary?
98
+ assert_not_predicate column, :is_identity?
99
+
100
+ schema = dump_table_schema "barcodes"
101
+ assert_match %r/create_table "barcodes", id: :integer, default: nil, force: :cascade do/, schema
102
+ end
103
+ end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+
5
+ class SQLServerRakeTest < ActiveRecord::TestCase
6
+ self.use_transactional_tests = false
7
+
8
+ cattr_accessor :azure_skip
9
+ self.azure_skip = connection_sqlserver_azure?
10
+
11
+ let(:db_tasks) { ActiveRecord::Tasks::DatabaseTasks }
12
+ let(:new_database) { "activerecord_unittest_tasks" }
13
+ let(:default_configuration) { ARTest.test_configuration_hashes["arunit"] }
14
+ let(:configuration) { default_configuration.merge("database" => new_database) }
15
+ let(:db_config) { ActiveRecord::Base.configurations.resolve(configuration) }
16
+
17
+ before { skip "on azure" if azure_skip }
18
+ before { disconnect! unless azure_skip }
19
+ after { reconnect unless azure_skip }
20
+
21
+ private
22
+
23
+ def disconnect!
24
+ connection.disconnect!
25
+ end
26
+
27
+ def reconnect
28
+ config = default_configuration
29
+ if connection_sqlserver_azure?
30
+ ActiveRecord::Base.establish_connection(config.merge("database" => "master"))
31
+ connection.drop_database(new_database) rescue nil
32
+ disconnect!
33
+ ActiveRecord::Base.establish_connection(config)
34
+ else
35
+ ActiveRecord::Base.establish_connection(config)
36
+ connection.drop_database(new_database) rescue nil
37
+ end
38
+ end
39
+ end
40
+
41
+ class SQLServerRakeCreateTest < SQLServerRakeTest
42
+ self.azure_skip = false
43
+
44
+ it "establishes connection to database after create " do
45
+ quietly { db_tasks.create configuration }
46
+ _(connection.current_database).must_equal(new_database)
47
+ end
48
+
49
+ it "creates database with default collation" do
50
+ quietly { db_tasks.create configuration }
51
+ _(connection.collation).must_equal "SQL_Latin1_General_CP1_CI_AS"
52
+ end
53
+
54
+ it "creates database with given collation" do
55
+ quietly { db_tasks.create configuration.merge("collation" => "Latin1_General_CI_AS") }
56
+ _(connection.collation).must_equal "Latin1_General_CI_AS"
57
+ end
58
+
59
+ it "prints error message when database exists" do
60
+ quietly { db_tasks.create configuration }
61
+ message = capture(:stderr) { db_tasks.create configuration }
62
+ _(message).must_match %r{activerecord_unittest_tasks.*already exists}
63
+ end
64
+ end
65
+
66
+ class SQLServerRakeDropTest < SQLServerRakeTest
67
+ self.azure_skip = false
68
+
69
+ it "drops database and uses master" do
70
+ quietly do
71
+ db_tasks.create configuration
72
+ db_tasks.drop configuration
73
+ end
74
+ _(connection.current_database).must_equal "master"
75
+ end
76
+
77
+ it "prints error message when database does not exist" do
78
+ message = capture(:stderr) { db_tasks.drop configuration.merge("database" => "doesnotexist") }
79
+ _(message).must_match %r{'doesnotexist' does not exist}
80
+ end
81
+ end
82
+
83
+ class SQLServerRakePurgeTest < SQLServerRakeTest
84
+ before do
85
+ quietly { db_tasks.create(configuration) }
86
+ connection.create_table :users, force: true do |t|
87
+ t.string :name, :email
88
+ t.timestamps null: false
89
+ end
90
+ end
91
+
92
+ it "clears active connections, drops database, and recreates with established connection" do
93
+ _(connection.current_database).must_equal(new_database)
94
+ _(connection.tables).must_include "users"
95
+ quietly { db_tasks.purge(configuration) }
96
+ _(connection.current_database).must_equal(new_database)
97
+ _(connection.tables).wont_include "users"
98
+ end
99
+ end
100
+
101
+ class SQLServerRakeCharsetTest < SQLServerRakeTest
102
+ before do
103
+ quietly { db_tasks.create(configuration) }
104
+ end
105
+
106
+ it "retrieves charset" do
107
+ _(db_tasks.charset(configuration)).must_equal "iso_1"
108
+ end
109
+ end
110
+
111
+ class SQLServerRakeCollationTest < SQLServerRakeTest
112
+ before do
113
+ quietly { db_tasks.create(configuration) }
114
+ end
115
+
116
+ it "retrieves collation" do
117
+ _(db_tasks.collation(configuration)).must_equal "SQL_Latin1_General_CP1_CI_AS"
118
+ end
119
+ end
120
+
121
+ class SQLServerRakeStructureDumpLoadTest < SQLServerRakeTest
122
+ let(:filename) { File.join ARTest::SQLServer.migrations_root, "structure.sql" }
123
+ let(:filedata) { File.read(filename) }
124
+
125
+ before do
126
+ quietly { db_tasks.create(configuration) }
127
+ connection.create_table :users, force: true do |t|
128
+ t.string :name, :email
129
+ t.text :background1
130
+ t.text_basic :background2
131
+ t.timestamps null: false
132
+ end
133
+ end
134
+
135
+ after do
136
+ FileUtils.rm_rf(filename)
137
+ end
138
+
139
+ it "dumps structure and accounts for defncopy oddities" do
140
+ skip "debug defncopy on windows later" if host_windows?
141
+
142
+ quietly { db_tasks.structure_dump configuration, filename }
143
+
144
+ _(filedata).wont_match %r{\AUSE.*\z}
145
+ _(filedata).wont_match %r{\AGO.*\z}
146
+ _(filedata).must_match %r{\[email\]\s+nvarchar\(4000\)}
147
+ _(filedata).must_match %r{\[background1\]\s+nvarchar\(max\)}
148
+ _(filedata).must_match %r{\[background2\]\s+text\s+}
149
+ end
150
+
151
+ it "can load dumped structure" do
152
+ skip "debug defncopy on windows later" if host_windows?
153
+
154
+ quietly { db_tasks.structure_dump configuration, filename }
155
+
156
+ _(filedata).must_match %r{CREATE TABLE \[dbo\]\.\[users\]}
157
+ db_tasks.purge(configuration)
158
+ _(connection.tables).wont_include "users"
159
+ db_tasks.load_schema db_config, :sql, filename
160
+ _(connection.tables).must_include "users"
161
+ end
162
+ end
163
+
164
+ class SQLServerRakeSchemaCacheDumpLoadTest < SQLServerRakeTest
165
+ let(:filename) { File.join ARTest::SQLServer.test_root_sqlserver, "schema_cache.yml" }
166
+ let(:filedata) { File.read(filename) }
167
+
168
+ before do
169
+ quietly { db_tasks.create(configuration) }
170
+
171
+ connection.create_table :users, force: true do |t|
172
+ t.string :name, null: false
173
+ end
174
+ end
175
+
176
+ after do
177
+ FileUtils.rm_rf(filename)
178
+ end
179
+
180
+ it "dumps schema cache with SQL Server metadata" do
181
+ quietly { db_tasks.dump_schema_cache connection, filename }
182
+
183
+ filedata = File.read(filename)
184
+ _schema_cache = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(filedata) : YAML.load(filedata)
185
+
186
+ col_id, col_name = connection.schema_cache.columns("users")
187
+
188
+ assert col_id.is_identity
189
+ assert col_id.is_primary
190
+ assert_equal col_id.ordinal_position, 1
191
+ assert_equal col_id.table_name, "users"
192
+
193
+ assert_not col_name.is_identity
194
+ assert_not col_name.is_primary
195
+ assert_equal col_name.ordinal_position, 2
196
+ assert_equal col_name.table_name, "users"
197
+ end
198
+ end