activerecord-sqlserver-adapter 7.1.7 → 7.2.0
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 +4 -4
- data/.devcontainer/Dockerfile +3 -3
- data/.github/workflows/ci.yml +10 -4
- data/CHANGELOG.md +5 -99
- data/Gemfile +4 -4
- data/README.md +43 -19
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +2 -2
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +6 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +6 -5
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +7 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +6 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +14 -12
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +50 -32
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +44 -46
- data/lib/active_record/connection_adapters/sqlserver/savepoints.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +2 -5
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +38 -32
- data/lib/arel/visitors/sqlserver.rb +57 -12
- data/test/cases/active_schema_test_sqlserver.rb +6 -6
- data/test/cases/adapter_test_sqlserver.rb +17 -18
- data/test/cases/coerced_tests.rb +279 -167
- data/test/cases/disconnected_test_sqlserver.rb +9 -3
- data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +1 -1
- data/test/cases/enum_test_sqlserver.rb +1 -1
- data/test/cases/execute_procedure_test_sqlserver.rb +9 -5
- data/test/cases/helper_sqlserver.rb +11 -5
- data/test/cases/index_test_sqlserver.rb +8 -6
- data/test/cases/json_test_sqlserver.rb +1 -1
- data/test/cases/lateral_test_sqlserver.rb +2 -2
- data/test/cases/migration_test_sqlserver.rb +1 -1
- data/test/cases/optimizer_hints_test_sqlserver.rb +12 -12
- data/test/cases/pessimistic_locking_test_sqlserver.rb +8 -7
- data/test/cases/primary_keys_test_sqlserver.rb +2 -2
- data/test/cases/rake_test_sqlserver.rb +8 -4
- data/test/cases/schema_dumper_test_sqlserver.rb +4 -5
- data/test/cases/showplan_test_sqlserver.rb +7 -7
- data/test/cases/specific_schema_test_sqlserver.rb +17 -13
- data/test/cases/view_test_sqlserver.rb +1 -1
- data/test/schema/sqlserver_specific_schema.rb +4 -4
- data/test/support/connection_reflection.rb +1 -1
- data/test/support/core_ext/query_cache.rb +2 -2
- data/test/support/query_assertions.rb +49 -0
- data/test/support/table_definition_sqlserver.rb +24 -0
- data/test/support/test_in_memory_oltp.rb +2 -2
- metadata +12 -13
- data/lib/active_record/sqlserver_base.rb +0 -13
- data/test/cases/scratchpad_test_sqlserver.rb +0 -8
- data/test/support/sql_counter_sqlserver.rb +0 -14
@@ -7,7 +7,7 @@ class TestDisconnectedAdapter < ActiveRecord::TestCase
|
|
7
7
|
|
8
8
|
undef_method :setup
|
9
9
|
def setup
|
10
|
-
@connection = ActiveRecord::Base.
|
10
|
+
@connection = ActiveRecord::Base.lease_connection
|
11
11
|
end
|
12
12
|
|
13
13
|
teardown do
|
@@ -19,7 +19,10 @@ class TestDisconnectedAdapter < ActiveRecord::TestCase
|
|
19
19
|
test "execute procedure after disconnect reconnects" do
|
20
20
|
@connection.execute_procedure :sp_tables, "sst_datatypes"
|
21
21
|
@connection.disconnect!
|
22
|
-
|
22
|
+
|
23
|
+
assert_nothing_raised do
|
24
|
+
@connection.execute_procedure :sp_tables, "sst_datatypes"
|
25
|
+
end
|
23
26
|
end
|
24
27
|
|
25
28
|
test "execute query after disconnect reconnects" do
|
@@ -31,6 +34,9 @@ class TestDisconnectedAdapter < ActiveRecord::TestCase
|
|
31
34
|
|
32
35
|
@connection.exec_query sql, "TEST", binds
|
33
36
|
@connection.disconnect!
|
34
|
-
|
37
|
+
|
38
|
+
assert_nothing_raised do
|
39
|
+
@connection.exec_query sql, "TEST", binds
|
40
|
+
end
|
35
41
|
end
|
36
42
|
end
|
@@ -11,7 +11,7 @@ class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
|
|
11
11
|
# We Monkey patch Preloader to work with batches of 10_000 records.
|
12
12
|
# Expect: N Books queries + Citation query
|
13
13
|
expected_query_count = (Citation.count / in_clause_length.to_f).ceil + 1
|
14
|
-
|
14
|
+
assert_queries_count(expected_query_count) do
|
15
15
|
Citation.preload(:reference_of).to_a.size
|
16
16
|
end
|
17
17
|
end
|
@@ -42,12 +42,16 @@ class ExecuteProcedureTestSQLServer < ActiveRecord::TestCase
|
|
42
42
|
assert_equal date_base.change(usec: 0), date_proc.change(usec: 0)
|
43
43
|
end
|
44
44
|
|
45
|
+
def transaction_with_procedure_and_return
|
46
|
+
ActiveRecord::Base.transaction do
|
47
|
+
connection.execute_procedure("my_getutcdate")
|
48
|
+
return
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
45
52
|
it 'test deprecation with transaction return when executing procedure' do
|
46
|
-
|
47
|
-
|
48
|
-
connection.execute_procedure("my_getutcdate")
|
49
|
-
return
|
50
|
-
end
|
53
|
+
assert_not_deprecated(ActiveRecord.deprecator) do
|
54
|
+
transaction_with_procedure_and_return
|
51
55
|
end
|
52
56
|
end
|
53
57
|
end
|
@@ -7,21 +7,27 @@ require "pry"
|
|
7
7
|
require "support/core_ext/query_cache"
|
8
8
|
require "support/minitest_sqlserver"
|
9
9
|
require "support/test_in_memory_oltp"
|
10
|
+
require "support/table_definition_sqlserver"
|
10
11
|
require "cases/helper"
|
11
12
|
require "support/load_schema_sqlserver"
|
12
13
|
require "support/coerceable_test_sqlserver"
|
13
|
-
require "support/sql_counter_sqlserver"
|
14
14
|
require "support/connection_reflection"
|
15
|
+
require "support/query_assertions"
|
15
16
|
require "mocha/minitest"
|
16
17
|
|
18
|
+
module ActiveSupport
|
19
|
+
class TestCase < ::Minitest::Test
|
20
|
+
include ARTest::SQLServer::CoerceableTest
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
17
24
|
module ActiveRecord
|
18
25
|
class TestCase < ActiveSupport::TestCase
|
19
26
|
SQLServer = ActiveRecord::ConnectionAdapters::SQLServer
|
20
27
|
|
21
|
-
include ARTest::SQLServer::
|
22
|
-
|
23
|
-
ARTest::SQLServer::
|
24
|
-
ActiveSupport::Testing::Stream
|
28
|
+
include ARTest::SQLServer::ConnectionReflection,
|
29
|
+
ActiveSupport::Testing::Stream,
|
30
|
+
ARTest::SQLServer::QueryAssertions
|
25
31
|
|
26
32
|
let(:logger) { ActiveRecord::Base.logger }
|
27
33
|
|
@@ -19,29 +19,31 @@ class IndexTestSQLServer < ActiveRecord::TestCase
|
|
19
19
|
end
|
20
20
|
|
21
21
|
it "add index with order" do
|
22
|
-
|
22
|
+
assert_queries_match(/CREATE.*INDEX.*\(\[last_name\] DESC\)/i) do
|
23
23
|
connection.add_index "testings", ["last_name"], order: { last_name: :desc }
|
24
24
|
connection.remove_index "testings", ["last_name"]
|
25
25
|
end
|
26
|
-
|
26
|
+
assert_queries_match(/CREATE.*INDEX.*\(\[last_name\] DESC, \[first_name\]\)/i) do
|
27
27
|
connection.add_index "testings", ["last_name", "first_name"], order: { last_name: :desc }
|
28
28
|
connection.remove_index "testings", ["last_name", "first_name"]
|
29
29
|
end
|
30
|
-
|
30
|
+
assert_queries_match(/CREATE.*INDEX.*\(\[last_name\] DESC, \[first_name\] ASC\)/i) do
|
31
31
|
connection.add_index "testings", ["last_name", "first_name"], order: { last_name: :desc, first_name: :asc }
|
32
32
|
connection.remove_index "testings", ["last_name", "first_name"]
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
36
|
it "add index with where" do
|
37
|
-
|
37
|
+
assert_queries_match(/CREATE.*INDEX.*\(\[last_name\]\) WHERE \[first_name\] = N'john doe'/i) do
|
38
38
|
connection.add_index "testings", "last_name", where: "[first_name] = N'john doe'"
|
39
39
|
connection.remove_index "testings", "last_name"
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
43
|
it "add index with expression" do
|
44
|
-
|
45
|
-
|
44
|
+
assert_nothing_raised do
|
45
|
+
connection.execute "ALTER TABLE [testings] ADD [first_name_upper] AS UPPER([first_name])"
|
46
|
+
connection.add_index "testings", "first_name_upper"
|
47
|
+
end
|
46
48
|
end
|
47
49
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require "cases/helper_sqlserver"
|
4
4
|
|
5
|
-
if ActiveRecord::Base.
|
5
|
+
if ActiveRecord::Base.lease_connection.supports_json?
|
6
6
|
class JsonTestSQLServer < ActiveRecord::TestCase
|
7
7
|
before do
|
8
8
|
@o1 = SSTestDatatypeMigrationJson.create! json_col: { "a" => "a", "b" => "b", "c" => "c" }
|
@@ -16,7 +16,7 @@ class LateralTestSQLServer < ActiveRecord::TestCase
|
|
16
16
|
eq = Arel::Nodes::Equality.new(one, one)
|
17
17
|
|
18
18
|
sql = author.project(Arel.star).where(author[:name].matches("David")).outer_join(subselect.lateral.as("bar")).on(eq).to_sql
|
19
|
-
results = ActiveRecord::Base.
|
19
|
+
results = ActiveRecord::Base.lease_connection.exec_query sql
|
20
20
|
assert_equal sql, "SELECT * FROM [authors] OUTER APPLY (SELECT * FROM [posts] WHERE [posts].[author_id] = [authors].[id] AND [posts].[id] = 42 ORDER BY [posts].[id] ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) AS bar WHERE [authors].[name] LIKE N'David'"
|
21
21
|
assert_equal results.length, 1
|
22
22
|
end
|
@@ -27,7 +27,7 @@ class LateralTestSQLServer < ActiveRecord::TestCase
|
|
27
27
|
subselect = post.project(Arel.star).take(1).where(post[:author_id].eq(author[:id])).where(post[:id].eq(42))
|
28
28
|
|
29
29
|
sql = author.project(Arel.star).where(author[:name].matches("David")).join(subselect.lateral.as("bar")).to_sql
|
30
|
-
results = ActiveRecord::Base.
|
30
|
+
results = ActiveRecord::Base.lease_connection.exec_query sql
|
31
31
|
|
32
32
|
assert_equal sql, "SELECT * FROM [authors] CROSS APPLY (SELECT * FROM [posts] WHERE [posts].[author_id] = [authors].[id] AND [posts].[id] = 42 ORDER BY [posts].[id] ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY) AS bar WHERE [authors].[name] LIKE N'David'"
|
33
33
|
assert_equal results.length, 0
|
@@ -20,7 +20,7 @@ class MigrationTestSQLServer < ActiveRecord::TestCase
|
|
20
20
|
it "not create a tables if error in migrations" do
|
21
21
|
begin
|
22
22
|
migrations_dir = File.join ARTest::SQLServer.migrations_root, "transaction_table"
|
23
|
-
quietly { ActiveRecord::MigrationContext.new(migrations_dir
|
23
|
+
quietly { ActiveRecord::MigrationContext.new(migrations_dir).up }
|
24
24
|
rescue Exception => e
|
25
25
|
assert_match %r|this and all later migrations canceled|, e.message
|
26
26
|
end
|
@@ -7,29 +7,29 @@ class OptimizerHitsTestSQLServer < ActiveRecord::TestCase
|
|
7
7
|
fixtures :companies
|
8
8
|
|
9
9
|
it "apply optimizations" do
|
10
|
-
|
10
|
+
assert_queries_match(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
|
11
11
|
companies = Company.optimizer_hints("HASH GROUP")
|
12
12
|
companies = companies.distinct.select("firm_id")
|
13
|
-
assert_includes companies.explain, "| Hash Match | Aggregate |"
|
13
|
+
assert_includes companies.explain.inspect, "| Hash Match | Aggregate |"
|
14
14
|
end
|
15
15
|
|
16
|
-
|
16
|
+
assert_queries_match(%r{\ASELECT .+ FROM .+ OPTION \(ORDER GROUP\)\z}) do
|
17
17
|
companies = Company.optimizer_hints("ORDER GROUP")
|
18
18
|
companies = companies.distinct.select("firm_id")
|
19
|
-
assert_includes companies.explain, "| Stream Aggregate | Aggregate |"
|
19
|
+
assert_includes companies.explain.inspect, "| Stream Aggregate | Aggregate |"
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
23
|
it "apply multiple optimizations" do
|
24
|
-
|
24
|
+
assert_queries_match(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP, FAST 1\)\z}) do
|
25
25
|
companies = Company.optimizer_hints("HASH GROUP", "FAST 1")
|
26
26
|
companies = companies.distinct.select("firm_id")
|
27
|
-
assert_includes companies.explain, "| Hash Match | Flow Distinct |"
|
27
|
+
assert_includes companies.explain.inspect, "| Hash Match | Flow Distinct |"
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
31
|
it "support subqueries" do
|
32
|
-
|
32
|
+
assert_queries_match(%r{.*'SELECT COUNT\(count_column\) FROM \(SELECT .*\) subquery_for_count OPTION \(MAXDOP 2\)'.*}) do
|
33
33
|
companies = Company.optimizer_hints("MAXDOP 2")
|
34
34
|
companies = companies.select(:id).where(firm_id: [0, 1]).limit(3)
|
35
35
|
assert_equal 3, companies.count
|
@@ -37,25 +37,25 @@ class OptimizerHitsTestSQLServer < ActiveRecord::TestCase
|
|
37
37
|
end
|
38
38
|
|
39
39
|
it "sanitize values" do
|
40
|
-
|
40
|
+
assert_queries_match(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
|
41
41
|
companies = Company.optimizer_hints("OPTION (HASH GROUP)")
|
42
42
|
companies = companies.distinct.select("firm_id")
|
43
43
|
companies.to_a
|
44
44
|
end
|
45
45
|
|
46
|
-
|
46
|
+
assert_queries_match(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
|
47
47
|
companies = Company.optimizer_hints("OPTION(HASH GROUP)")
|
48
48
|
companies = companies.distinct.select("firm_id")
|
49
49
|
companies.to_a
|
50
50
|
end
|
51
51
|
|
52
|
-
|
52
|
+
assert_queries_match(%r{\ASELECT .+ FROM .+ OPTION \(TABLE HINT \(\[companies\], INDEX\(1\)\)\)\z}) do
|
53
53
|
companies = Company.optimizer_hints("OPTION(TABLE HINT ([companies], INDEX(1)))")
|
54
54
|
companies = companies.distinct.select("firm_id")
|
55
55
|
companies.to_a
|
56
56
|
end
|
57
57
|
|
58
|
-
|
58
|
+
assert_queries_match(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
|
59
59
|
companies = Company.optimizer_hints("Option(HASH GROUP)")
|
60
60
|
companies = companies.distinct.select("firm_id")
|
61
61
|
companies.to_a
|
@@ -63,7 +63,7 @@ class OptimizerHitsTestSQLServer < ActiveRecord::TestCase
|
|
63
63
|
end
|
64
64
|
|
65
65
|
it "skip optimization after unscope" do
|
66
|
-
|
66
|
+
assert_queries_match("SELECT DISTINCT [companies].[firm_id] FROM [companies]") do
|
67
67
|
companies = Company.optimizer_hints("HASH GROUP")
|
68
68
|
companies = companies.distinct.select("firm_id")
|
69
69
|
companies.unscope(:optimizer_hints).load
|
@@ -13,7 +13,7 @@ class PessimisticLockingTestSQLServer < ActiveRecord::TestCase
|
|
13
13
|
end
|
14
14
|
|
15
15
|
it "uses with updlock by default" do
|
16
|
-
|
16
|
+
assert_queries_match %r|SELECT \[people\]\.\* FROM \[people\] WITH\(UPDLOCK\)| do
|
17
17
|
_(Person.lock(true).to_a).must_equal Person.all.to_a
|
18
18
|
end
|
19
19
|
end
|
@@ -47,32 +47,32 @@ class PessimisticLockingTestSQLServer < ActiveRecord::TestCase
|
|
47
47
|
end
|
48
48
|
|
49
49
|
it "can add a custom lock directive" do
|
50
|
-
|
50
|
+
assert_queries_match %r|SELECT \[people\]\.\* FROM \[people\] WITH\(HOLDLOCK, ROWLOCK\)| do
|
51
51
|
Person.lock("WITH(HOLDLOCK, ROWLOCK)").load
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
55
|
describe "joining tables" do
|
56
56
|
it "joined tables use updlock by default" do
|
57
|
-
|
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
58
|
Person.lock(true).joins(:readers).load
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
62
|
it "joined tables can use custom lock directive" do
|
63
|
-
|
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
64
|
Person.lock("WITH(NOLOCK)").joins(:readers).load
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
68
|
it "left joined tables use updlock by default" do
|
69
|
-
|
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
70
|
Person.lock(true).left_joins(:readers).load
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
74
|
it "left joined tables can use custom lock directive" do
|
75
|
-
|
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
76
|
Person.lock("WITH(NOLOCK)").left_joins(:readers).load
|
77
77
|
end
|
78
78
|
end
|
@@ -88,7 +88,8 @@ class PessimisticLockingTestSQLServer < ActiveRecord::TestCase
|
|
88
88
|
it "copes with eager loading un-locked paginated" do
|
89
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
90
|
loader_sql = /SELECT.*FROM \[people\] WITH\(UPDLOCK\).*WHERE \[people\]\.\[id\] IN/
|
91
|
-
|
91
|
+
|
92
|
+
assert_queries_match(/#{eager_ids_sql}|#{loader_sql}/, count: 2) do
|
92
93
|
people = Person.lock(true).limit(5).offset(10).includes(:readers).references(:readers).to_a
|
93
94
|
_(people[0].first_name).must_equal "Thing_10"
|
94
95
|
_(people[1].first_name).must_equal "Thing_11"
|
@@ -12,7 +12,7 @@ class PrimaryKeyUuidTypeTest < ActiveRecord::TestCase
|
|
12
12
|
end
|
13
13
|
|
14
14
|
setup do
|
15
|
-
@connection = ActiveRecord::Base.
|
15
|
+
@connection = ActiveRecord::Base.lease_connection
|
16
16
|
@connection.create_table(:barcodes, primary_key: "code", id: :uuid, force: true)
|
17
17
|
end
|
18
18
|
|
@@ -50,7 +50,7 @@ class PrimaryKeyIntegerTest < ActiveRecord::TestCase
|
|
50
50
|
end
|
51
51
|
|
52
52
|
setup do
|
53
|
-
@connection = ActiveRecord::Base.
|
53
|
+
@connection = ActiveRecord::Base.lease_connection
|
54
54
|
end
|
55
55
|
|
56
56
|
teardown do
|
@@ -138,18 +138,22 @@ class SQLServerRakeStructureDumpLoadTest < SQLServerRakeTest
|
|
138
138
|
|
139
139
|
it "dumps structure and accounts for defncopy oddities" do
|
140
140
|
skip "debug defncopy on windows later" if host_windows?
|
141
|
+
|
141
142
|
quietly { db_tasks.structure_dump configuration, filename }
|
143
|
+
|
142
144
|
_(filedata).wont_match %r{\AUSE.*\z}
|
143
145
|
_(filedata).wont_match %r{\AGO.*\z}
|
144
|
-
_(filedata).must_match %r{email\s+nvarchar\(4000\)}
|
145
|
-
_(filedata).must_match %r{background1\s+nvarchar\(max\)}
|
146
|
-
_(filedata).must_match %r{background2\s+text\s+}
|
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+}
|
147
149
|
end
|
148
150
|
|
149
151
|
it "can load dumped structure" do
|
150
152
|
skip "debug defncopy on windows later" if host_windows?
|
153
|
+
|
151
154
|
quietly { db_tasks.structure_dump configuration, filename }
|
152
|
-
|
155
|
+
|
156
|
+
_(filedata).must_match %r{CREATE TABLE \[dbo\]\.\[users\]}
|
153
157
|
db_tasks.purge(configuration)
|
154
158
|
_(connection.tables).wont_include "users"
|
155
159
|
db_tasks.load_schema db_config, :sql, filename
|
@@ -6,7 +6,7 @@ require "stringio"
|
|
6
6
|
class SchemaDumperTestSQLServer < ActiveRecord::TestCase
|
7
7
|
before { all_tables }
|
8
8
|
|
9
|
-
let(:all_tables) { ActiveRecord::Base.
|
9
|
+
let(:all_tables) { ActiveRecord::Base.lease_connection.tables }
|
10
10
|
let(:schema) { @generated_schema }
|
11
11
|
|
12
12
|
it "sst_datatypes" do
|
@@ -162,7 +162,7 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
|
|
162
162
|
|
163
163
|
it "schemas are dumped and tables names only include non-default schema" do
|
164
164
|
stream = StringIO.new
|
165
|
-
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.
|
165
|
+
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, stream)
|
166
166
|
generated_schema = stream.string
|
167
167
|
|
168
168
|
# Only generate non-default schemas. Default schema is 'dbo'.
|
@@ -177,7 +177,7 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
|
|
177
177
|
# Only non-default schemas should be included in table names. Default schema is 'dbo'.
|
178
178
|
assert_includes generated_schema, 'create_table "accounts"'
|
179
179
|
assert_includes generated_schema, 'create_table "test.aliens"'
|
180
|
-
assert_includes generated_schema, 'create_table "test2.
|
180
|
+
assert_includes generated_schema, 'create_table "test2.sst_schema_test_multiple_schema"'
|
181
181
|
end
|
182
182
|
|
183
183
|
private
|
@@ -187,8 +187,7 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
|
|
187
187
|
ActiveRecord::SchemaDumper.ignore_tables = all_tables - table_names
|
188
188
|
|
189
189
|
stream = StringIO.new
|
190
|
-
ActiveRecord::SchemaDumper.
|
191
|
-
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
|
190
|
+
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, stream)
|
192
191
|
|
193
192
|
@generated_schema = stream.string
|
194
193
|
yield @generated_schema if block_given?
|
@@ -8,32 +8,32 @@ class ShowplanTestSQLServer < ActiveRecord::TestCase
|
|
8
8
|
|
9
9
|
describe "Unprepare previously prepared SQL" do
|
10
10
|
it "from simple statement" do
|
11
|
-
plan = Car.where(id: 1).explain
|
11
|
+
plan = Car.where(id: 1).explain.inspect
|
12
12
|
_(plan).must_include "SELECT [cars].* FROM [cars] WHERE [cars].[id] = 1"
|
13
13
|
_(plan).must_include "Clustered Index Seek", "make sure we do not showplan the sp_executesql"
|
14
14
|
end
|
15
15
|
|
16
16
|
it "from multiline statement" do
|
17
|
-
plan = Car.where("\n id = 1 \n").explain
|
17
|
+
plan = Car.where("\n id = 1 \n").explain.inspect
|
18
18
|
_(plan).must_include "SELECT [cars].* FROM [cars] WHERE (\n id = 1 \n)"
|
19
19
|
_(plan).must_include "Clustered Index Seek", "make sure we do not showplan the sp_executesql"
|
20
20
|
end
|
21
21
|
|
22
22
|
it "from prepared statement" do
|
23
|
-
plan = Car.where(name: ",").limit(1).explain
|
23
|
+
plan = Car.where(name: ",").limit(1).explain.inspect
|
24
24
|
_(plan).must_include "SELECT [cars].* FROM [cars] WHERE [cars].[name]"
|
25
25
|
_(plan).must_include "TOP EXPRESSION", "make sure we do not showplan the sp_executesql"
|
26
26
|
_(plan).must_include "Clustered Index Scan", "make sure we do not showplan the sp_executesql"
|
27
27
|
end
|
28
28
|
|
29
29
|
it "from array condition using index" do
|
30
|
-
plan = Car.where(id: [1, 2]).explain
|
30
|
+
plan = Car.where(id: [1, 2]).explain.inspect
|
31
31
|
_(plan).must_include "SELECT [cars].* FROM [cars] WHERE [cars].[id] IN (1, 2)"
|
32
32
|
_(plan).must_include "Clustered Index Seek", "make sure we do not showplan the sp_executesql"
|
33
33
|
end
|
34
34
|
|
35
35
|
it "from array condition" do
|
36
|
-
plan = Car.where(name: ["honda", "zyke"]).explain
|
36
|
+
plan = Car.where(name: ["honda", "zyke"]).explain.inspect
|
37
37
|
_(plan).must_include " SELECT [cars].* FROM [cars] WHERE [cars].[name] IN (N'honda', N'zyke')"
|
38
38
|
_(plan).must_include "Clustered Index Scan", "make sure we do not showplan the sp_executesql"
|
39
39
|
end
|
@@ -42,7 +42,7 @@ class ShowplanTestSQLServer < ActiveRecord::TestCase
|
|
42
42
|
describe "With SHOWPLAN_TEXT option" do
|
43
43
|
it "use simple table printer" do
|
44
44
|
with_showplan_option("SHOWPLAN_TEXT") do
|
45
|
-
plan = Car.where(id: 1).explain
|
45
|
+
plan = Car.where(id: 1).explain.inspect
|
46
46
|
_(plan).must_include "SELECT [cars].* FROM [cars] WHERE [cars].[id]"
|
47
47
|
_(plan).must_include "Clustered Index Seek", "make sure we do not showplan the sp_executesql"
|
48
48
|
end
|
@@ -52,7 +52,7 @@ class ShowplanTestSQLServer < ActiveRecord::TestCase
|
|
52
52
|
describe "With SHOWPLAN_XML option" do
|
53
53
|
it "show formatted xml" do
|
54
54
|
with_showplan_option("SHOWPLAN_XML") do
|
55
|
-
plan = Car.where(id: 1).explain
|
55
|
+
plan = Car.where(id: 1).explain.inspect
|
56
56
|
_(plan).must_include "ShowPlanXML"
|
57
57
|
end
|
58
58
|
end
|
@@ -6,8 +6,12 @@ class SpecificSchemaTestSQLServer < ActiveRecord::TestCase
|
|
6
6
|
after { SSTestEdgeSchema.delete_all }
|
7
7
|
|
8
8
|
it "handle dollar symbols" do
|
9
|
-
SSTestDollarTableName.
|
10
|
-
|
9
|
+
assert_difference("SSTestDollarTableName.count", 1) do
|
10
|
+
SSTestDollarTableName.create!
|
11
|
+
end
|
12
|
+
assert_nothing_raised do
|
13
|
+
SSTestDollarTableName.limit(20).offset(1)
|
14
|
+
end
|
11
15
|
end
|
12
16
|
|
13
17
|
it "models can use tinyint pk tables" do
|
@@ -93,7 +97,7 @@ class SpecificSchemaTestSQLServer < ActiveRecord::TestCase
|
|
93
97
|
|
94
98
|
it "use primary key for row table order in pagination sql" do
|
95
99
|
sql = /ORDER BY \[sst_natural_pk_data\]\.\[legacy_id\] ASC OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY/
|
96
|
-
|
100
|
+
assert_queries_match(sql) { SSTestNaturalPkData.limit(5).offset(5).load }
|
97
101
|
end
|
98
102
|
|
99
103
|
# Special quoted column
|
@@ -112,16 +116,16 @@ class SpecificSchemaTestSQLServer < ActiveRecord::TestCase
|
|
112
116
|
end
|
113
117
|
end
|
114
118
|
# Using ActiveRecord's quoted_id feature for objects.
|
115
|
-
|
116
|
-
|
119
|
+
assert_queries_match(/@0 = 'T'/) { SSTestDatatypeMigration.where(char_col: value.new).first }
|
120
|
+
assert_queries_match(/@0 = 'T'/) { SSTestDatatypeMigration.where(varchar_col: value.new).first }
|
117
121
|
# Using our custom char type data.
|
118
122
|
type = ActiveRecord::Type::SQLServer::Char
|
119
123
|
data = ActiveRecord::Type::SQLServer::Data
|
120
|
-
|
121
|
-
|
124
|
+
assert_queries_match(/@0 = 'T'/) { SSTestDatatypeMigration.where(char_col: data.new("T", type.new)).first }
|
125
|
+
assert_queries_match(/@0 = 'T'/) { SSTestDatatypeMigration.where(varchar_col: data.new("T", type.new)).first }
|
122
126
|
# Taking care of everything.
|
123
|
-
|
124
|
-
|
127
|
+
assert_queries_match(/@0 = 'T'/) { SSTestDatatypeMigration.where(char_col: "T").first }
|
128
|
+
assert_queries_match(/@0 = 'T'/) { SSTestDatatypeMigration.where(varchar_col: "T").first }
|
125
129
|
end
|
126
130
|
|
127
131
|
it "can update and hence properly quoted non-national char/varchar columns" do
|
@@ -159,15 +163,15 @@ class SpecificSchemaTestSQLServer < ActiveRecord::TestCase
|
|
159
163
|
|
160
164
|
it "returns a new id via connection newid_function" do
|
161
165
|
acceptable_uuid = ActiveRecord::ConnectionAdapters::SQLServer::Type::Uuid::ACCEPTABLE_UUID
|
162
|
-
db_uuid = ActiveRecord::Base.
|
166
|
+
db_uuid = ActiveRecord::Base.lease_connection.newid_function
|
163
167
|
_(db_uuid).must_match(acceptable_uuid)
|
164
168
|
end
|
165
169
|
|
166
170
|
# with similar table definition in two schemas
|
167
171
|
|
168
172
|
it "returns the correct primary columns" do
|
169
|
-
connection = ActiveRecord::Base.
|
170
|
-
assert_equal "field_1", connection.columns("test.
|
171
|
-
assert_equal "field_2", connection.columns("test2.
|
173
|
+
connection = ActiveRecord::Base.lease_connection
|
174
|
+
assert_equal "field_1", connection.columns("test.sst_schema_test_multiple_schema").detect(&:is_primary?).name
|
175
|
+
assert_equal "field_2", connection.columns("test2.sst_schema_test_multiple_schema").detect(&:is_primary?).name
|
172
176
|
end
|
173
177
|
end
|
@@ -308,17 +308,17 @@ ActiveRecord::Schema.define do
|
|
308
308
|
)
|
309
309
|
NATURALPKTABLESQLINOTHERSCHEMA
|
310
310
|
|
311
|
-
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '
|
311
|
+
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_schema_test_multiple_schema' and TABLE_SCHEMA = 'test') DROP TABLE test.sst_schema_test_multiple_schema"
|
312
312
|
execute <<-SCHEMATESTMULTIPLESCHEMA
|
313
|
-
CREATE TABLE test.
|
313
|
+
CREATE TABLE test.sst_schema_test_multiple_schema(
|
314
314
|
field_1 int NOT NULL PRIMARY KEY,
|
315
315
|
field_2 int,
|
316
316
|
)
|
317
317
|
SCHEMATESTMULTIPLESCHEMA
|
318
318
|
execute "IF NOT EXISTS(SELECT * FROM sys.schemas WHERE name = 'test2') EXEC sp_executesql N'CREATE SCHEMA test2'"
|
319
|
-
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '
|
319
|
+
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_schema_test_multiple_schema' and TABLE_SCHEMA = 'test2') DROP TABLE test2.sst_schema_test_multiple_schema"
|
320
320
|
execute <<-SCHEMATESTMULTIPLESCHEMA
|
321
|
-
CREATE TABLE test2.
|
321
|
+
CREATE TABLE test2.sst_schema_test_multiple_schema(
|
322
322
|
field_1 int,
|
323
323
|
field_2 int NOT NULL PRIMARY KEY,
|
324
324
|
)
|
@@ -22,8 +22,8 @@ module SqlIgnoredCache
|
|
22
22
|
# compromising cache outside tests.
|
23
23
|
def cache_sql(sql, name, binds)
|
24
24
|
result = super
|
25
|
-
|
26
|
-
@query_cache.delete_if do |cache_key, _v|
|
25
|
+
|
26
|
+
@query_cache.instance_variable_get(:@map).delete_if do |cache_key, _v|
|
27
27
|
# Query cache key generated by `sql` or `[sql, binds]`, so need to retrieve `sql` for both cases.
|
28
28
|
cache_key_sql = Array(cache_key).first
|
29
29
|
Regexp.union(IGNORED_SQL).match?(cache_key_sql)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module ARTest
|
2
|
+
module SQLServer
|
3
|
+
module QueryAssertions
|
4
|
+
def assert_queries_count(count = nil, include_schema: false, &block)
|
5
|
+
ActiveRecord::Base.lease_connection.materialize_transactions
|
6
|
+
|
7
|
+
counter = ActiveRecord::Assertions::QueryAssertions::SQLCounter.new
|
8
|
+
ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
|
9
|
+
result = _assert_nothing_raised_or_warn("assert_queries_count", &block)
|
10
|
+
queries = include_schema ? counter.log_all : counter.log
|
11
|
+
|
12
|
+
# Start of monkey-patch
|
13
|
+
queries = include_release_savepoint_placeholder_queries(queries)
|
14
|
+
# End of monkey-patch
|
15
|
+
|
16
|
+
if count
|
17
|
+
assert_equal count, queries.size, "#{queries.size} instead of #{count} queries were executed. Queries: #{queries.join("\n\n")}"
|
18
|
+
else
|
19
|
+
assert_operator queries.size, :>=, 1, "1 or more queries expected, but none were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
|
20
|
+
end
|
21
|
+
result
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Rails tests expect a save-point to be created and released. SQL Server does not release
|
28
|
+
# save-points and so the number of queries will be off. This monkey patch adds a placeholder queries
|
29
|
+
# to replace the missing save-point releases.
|
30
|
+
def include_release_savepoint_placeholder_queries(queries)
|
31
|
+
grouped_queries = [[]]
|
32
|
+
|
33
|
+
queries.each do |query|
|
34
|
+
if query =~ /SAVE TRANSACTION \S+/
|
35
|
+
grouped_queries << [query]
|
36
|
+
else
|
37
|
+
grouped_queries.last << query
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
grouped_queries.each do |group|
|
42
|
+
group.append "/* release savepoint placeholder for testing */" if group.first =~ /SAVE TRANSACTION \S+/
|
43
|
+
end
|
44
|
+
|
45
|
+
grouped_queries.flatten
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|