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.
- checksums.yaml +7 -0
- data/.github/issue_template.md +22 -0
- data/.github/workflows/ci.yml +32 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +69 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/Dockerfile.ci +14 -0
- data/Gemfile +26 -0
- data/LICENSE.txt +21 -0
- data/README.md +104 -0
- data/RUNNING_UNIT_TESTS.md +38 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/activerecord-sqlserver-adapter-odbc-extended.gemspec +34 -0
- data/compose.ci.yaml +15 -0
- data/lib/active_record/connection_adapters/extended_sqlserver_adapter.rb +204 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +41 -0
- data/lib/active_record/connection_adapters/sqlserver/odbc_database_statements.rb +234 -0
- data/lib/active_record/connection_adapters/sqlserver/type/binary_ext.rb +25 -0
- data/lib/activerecord-sqlserver-adapter-odbc-extended.rb +12 -0
- data/test/appveyor/dbsetup.ps1 +27 -0
- data/test/appveyor/dbsetup.sql +11 -0
- data/test/cases/active_schema_test_sqlserver.rb +127 -0
- data/test/cases/adapter_test_sqlserver.rb +648 -0
- data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
- data/test/cases/change_column_null_test_sqlserver.rb +44 -0
- data/test/cases/coerced_tests.rb +2796 -0
- data/test/cases/column_test_sqlserver.rb +848 -0
- data/test/cases/connection_test_sqlserver.rb +138 -0
- data/test/cases/dbconsole.rb +19 -0
- data/test/cases/disconnected_test_sqlserver.rb +42 -0
- data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
- data/test/cases/enum_test_sqlserver.rb +49 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +57 -0
- data/test/cases/fetch_test_sqlserver.rb +88 -0
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +72 -0
- data/test/cases/helper_sqlserver.rb +61 -0
- data/test/cases/migration_test_sqlserver.rb +144 -0
- data/test/cases/order_test_sqlserver.rb +153 -0
- data/test/cases/pessimistic_locking_test_sqlserver.rb +102 -0
- data/test/cases/primary_keys_test_sqlserver.rb +103 -0
- data/test/cases/rake_test_sqlserver.rb +198 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +296 -0
- data/test/cases/schema_test_sqlserver.rb +111 -0
- data/test/cases/trigger_test_sqlserver.rb +51 -0
- data/test/cases/utils_test_sqlserver.rb +129 -0
- data/test/cases/uuid_test_sqlserver.rb +54 -0
- data/test/cases/view_test_sqlserver.rb +58 -0
- data/test/config.yml +38 -0
- data/test/debug.rb +16 -0
- data/test/fixtures/1px.gif +0 -0
- data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
- data/test/migrations/create_clients_and_change_column_null.rb +25 -0
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
- data/test/models/sqlserver/alien.rb +5 -0
- data/test/models/sqlserver/booking.rb +5 -0
- data/test/models/sqlserver/composite_pk.rb +9 -0
- data/test/models/sqlserver/customers_view.rb +5 -0
- data/test/models/sqlserver/datatype.rb +5 -0
- data/test/models/sqlserver/datatype_migration.rb +10 -0
- data/test/models/sqlserver/dollar_table_name.rb +5 -0
- data/test/models/sqlserver/edge_schema.rb +13 -0
- data/test/models/sqlserver/fk_has_fk.rb +5 -0
- data/test/models/sqlserver/fk_has_pk.rb +5 -0
- data/test/models/sqlserver/natural_pk_data.rb +6 -0
- data/test/models/sqlserver/natural_pk_int_data.rb +5 -0
- data/test/models/sqlserver/no_pk_data.rb +5 -0
- data/test/models/sqlserver/object_default.rb +5 -0
- data/test/models/sqlserver/quoted_table.rb +9 -0
- data/test/models/sqlserver/quoted_view_1.rb +5 -0
- data/test/models/sqlserver/quoted_view_2.rb +5 -0
- data/test/models/sqlserver/sst_memory.rb +5 -0
- data/test/models/sqlserver/sst_string_collation.rb +3 -0
- data/test/models/sqlserver/string_default.rb +5 -0
- data/test/models/sqlserver/string_defaults_big_view.rb +5 -0
- data/test/models/sqlserver/string_defaults_view.rb +5 -0
- data/test/models/sqlserver/table_with_spaces.rb +5 -0
- data/test/models/sqlserver/tinyint_pk.rb +5 -0
- data/test/models/sqlserver/trigger.rb +17 -0
- data/test/models/sqlserver/trigger_history.rb +5 -0
- data/test/models/sqlserver/upper.rb +5 -0
- data/test/models/sqlserver/uppered.rb +5 -0
- data/test/models/sqlserver/uuid.rb +5 -0
- data/test/schema/datatypes/2012.sql +56 -0
- data/test/schema/enable-in-memory-oltp.sql +81 -0
- data/test/schema/sqlserver_specific_schema.rb +363 -0
- data/test/support/coerceable_test_sqlserver.rb +55 -0
- data/test/support/connection_reflection.rb +32 -0
- data/test/support/core_ext/query_cache.rb +38 -0
- data/test/support/load_schema_sqlserver.rb +29 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
- data/test/support/minitest_sqlserver.rb +3 -0
- data/test/support/paths_sqlserver.rb +50 -0
- data/test/support/query_assertions.rb +49 -0
- data/test/support/rake_helpers.rb +46 -0
- data/test/support/table_definition_sqlserver.rb +24 -0
- data/test/support/test_in_memory_oltp.rb +17 -0
- 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
|