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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +3 -3
  3. data/.github/workflows/ci.yml +10 -4
  4. data/CHANGELOG.md +5 -99
  5. data/Gemfile +4 -4
  6. data/README.md +43 -19
  7. data/VERSION +1 -1
  8. data/activerecord-sqlserver-adapter.gemspec +2 -2
  9. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +6 -4
  10. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +6 -5
  11. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +7 -4
  12. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +6 -4
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +14 -12
  14. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +50 -32
  15. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +44 -46
  16. data/lib/active_record/connection_adapters/sqlserver/savepoints.rb +2 -0
  17. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +1 -1
  18. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +2 -5
  19. data/lib/active_record/tasks/sqlserver_database_tasks.rb +38 -32
  20. data/lib/arel/visitors/sqlserver.rb +57 -12
  21. data/test/cases/active_schema_test_sqlserver.rb +6 -6
  22. data/test/cases/adapter_test_sqlserver.rb +17 -18
  23. data/test/cases/coerced_tests.rb +279 -167
  24. data/test/cases/disconnected_test_sqlserver.rb +9 -3
  25. data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +1 -1
  26. data/test/cases/enum_test_sqlserver.rb +1 -1
  27. data/test/cases/execute_procedure_test_sqlserver.rb +9 -5
  28. data/test/cases/helper_sqlserver.rb +11 -5
  29. data/test/cases/index_test_sqlserver.rb +8 -6
  30. data/test/cases/json_test_sqlserver.rb +1 -1
  31. data/test/cases/lateral_test_sqlserver.rb +2 -2
  32. data/test/cases/migration_test_sqlserver.rb +1 -1
  33. data/test/cases/optimizer_hints_test_sqlserver.rb +12 -12
  34. data/test/cases/pessimistic_locking_test_sqlserver.rb +8 -7
  35. data/test/cases/primary_keys_test_sqlserver.rb +2 -2
  36. data/test/cases/rake_test_sqlserver.rb +8 -4
  37. data/test/cases/schema_dumper_test_sqlserver.rb +4 -5
  38. data/test/cases/showplan_test_sqlserver.rb +7 -7
  39. data/test/cases/specific_schema_test_sqlserver.rb +17 -13
  40. data/test/cases/view_test_sqlserver.rb +1 -1
  41. data/test/schema/sqlserver_specific_schema.rb +4 -4
  42. data/test/support/connection_reflection.rb +1 -1
  43. data/test/support/core_ext/query_cache.rb +2 -2
  44. data/test/support/query_assertions.rb +49 -0
  45. data/test/support/table_definition_sqlserver.rb +24 -0
  46. data/test/support/test_in_memory_oltp.rb +2 -2
  47. metadata +12 -13
  48. data/lib/active_record/sqlserver_base.rb +0 -13
  49. data/test/cases/scratchpad_test_sqlserver.rb +0 -8
  50. 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.connection
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
- @connection.execute_procedure :sp_tables, "sst_datatypes"
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
- @connection.exec_query sql, "TEST", binds
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
- assert_queries(expected_query_count) do
14
+ assert_queries_count(expected_query_count) do
15
15
  Citation.preload(:reference_of).to_a.size
16
16
  end
17
17
  end
@@ -13,7 +13,7 @@ class EnumTestSQLServer < ActiveRecord::TestCase
13
13
  Class.new(ActiveRecord::Base) do
14
14
  self.table_name = 'sst_datatypes'
15
15
 
16
- enum col_name => { alpha: "A", beta: "B" }
16
+ enum col_name, { alpha: "A", beta: "B" }
17
17
  end
18
18
  end
19
19
 
@@ -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
- assert_deprecated(ActiveRecord.deprecator) do
47
- ActiveRecord::Base.transaction do
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::CoerceableTest,
22
- ARTest::SQLServer::ConnectionReflection,
23
- ARTest::SQLServer::SqlCounterSqlserver,
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
- assert_sql(/CREATE.*INDEX.*\(\[last_name\] DESC\)/i) do
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
- assert_sql(/CREATE.*INDEX.*\(\[last_name\] DESC, \[first_name\]\)/i) do
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
- assert_sql(/CREATE.*INDEX.*\(\[last_name\] DESC, \[first_name\] ASC\)/i) do
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
- assert_sql(/CREATE.*INDEX.*\(\[last_name\]\) WHERE \[first_name\] = N'john doe'/i) do
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
- connection.execute "ALTER TABLE [testings] ADD [first_name_upper] AS UPPER([first_name])"
45
- connection.add_index "testings", "first_name_upper"
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.connection.supports_json?
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.connection.exec_query sql
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.connection.exec_query sql
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, ActiveRecord::SchemaMigration).up }
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
- assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
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
- assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(ORDER GROUP\)\z}) do
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
- assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP, FAST 1\)\z}) do
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
- assert_sql(%r{.*'SELECT COUNT\(count_column\) FROM \(SELECT .*\) subquery_for_count OPTION \(MAXDOP 2\)'.*}) do
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
- assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
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
- assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
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
- assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(TABLE HINT \(\[companies\], INDEX\(1\)\)\)\z}) do
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
- assert_sql(%r{\ASELECT .+ FROM .+ OPTION \(HASH GROUP\)\z}) do
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
- assert_sql("SELECT DISTINCT [companies].[firm_id] FROM [companies]") do
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
- assert_sql %r|SELECT \[people\]\.\* FROM \[people\] WITH\(UPDLOCK\)| do
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
- assert_sql %r|SELECT \[people\]\.\* FROM \[people\] WITH\(HOLDLOCK, ROWLOCK\)| do
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
- assert_sql %r|SELECT \[people\]\.\* FROM \[people\] WITH\(UPDLOCK\) INNER JOIN \[readers\] WITH\(UPDLOCK\)\s+ON \[readers\]\.\[person_id\] = \[people\]\.\[id\]| 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
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
- assert_sql %r|SELECT \[people\]\.\* FROM \[people\] WITH\(NOLOCK\) INNER JOIN \[readers\] WITH\(NOLOCK\)\s+ON \[readers\]\.\[person_id\] = \[people\]\.\[id\]| 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
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
- assert_sql %r|SELECT \[people\]\.\* FROM \[people\] WITH\(UPDLOCK\) LEFT OUTER JOIN \[readers\] WITH\(UPDLOCK\)\s+ON \[readers\]\.\[person_id\] = \[people\]\.\[id\]| 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
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
- assert_sql %r|SELECT \[people\]\.\* FROM \[people\] WITH\(NOLOCK\) LEFT OUTER JOIN \[readers\] WITH\(NOLOCK\)\s+ON \[readers\]\.\[person_id\] = \[people\]\.\[id\]| 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
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
- assert_sql(eager_ids_sql, loader_sql) do
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.connection
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.connection
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
- _(filedata).must_match %r{CREATE TABLE dbo\.users}
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.connection.tables }
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.connection, stream)
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.sst_schema_test_mulitple_schema"'
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.ignore_tables = all_tables - table_names
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.create!
10
- SSTestDollarTableName.limit(20).offset(1)
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
- assert_sql(sql) { SSTestNaturalPkData.limit(5).offset(5).load }
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
- assert_sql(/@0 = 'T'/) { SSTestDatatypeMigration.where(char_col: value.new).first }
116
- assert_sql(/@0 = 'T'/) { SSTestDatatypeMigration.where(varchar_col: value.new).first }
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
- assert_sql(/@0 = 'T'/) { SSTestDatatypeMigration.where(char_col: data.new("T", type.new)).first }
121
- assert_sql(/@0 = 'T'/) { SSTestDatatypeMigration.where(varchar_col: data.new("T", type.new)).first }
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
- assert_sql(/@0 = 'T'/) { SSTestDatatypeMigration.where(char_col: "T").first }
124
- assert_sql(/@0 = 'T'/) { SSTestDatatypeMigration.where(varchar_col: "T").first }
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.connection.newid_function
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.connection
170
- assert_equal "field_1", connection.columns("test.sst_schema_test_mulitple_schema").detect(&:is_primary?).name
171
- assert_equal "field_2", connection.columns("test2.sst_schema_test_mulitple_schema").detect(&:is_primary?).name
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
@@ -3,7 +3,7 @@
3
3
  require "cases/helper_sqlserver"
4
4
 
5
5
  class ViewTestSQLServer < ActiveRecord::TestCase
6
- let(:connection) { ActiveRecord::Base.connection }
6
+ let(:connection) { ActiveRecord::Base.lease_connection }
7
7
 
8
8
  describe 'view with default values' do
9
9
  before do
@@ -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 = 'sst_schema_test_mulitple_schema' and TABLE_SCHEMA = 'test') DROP TABLE test.sst_schema_test_mulitple_schema"
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.sst_schema_test_mulitple_schema(
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 = 'sst_schema_test_mulitple_schema' and TABLE_SCHEMA = 'test2') DROP TABLE test2.sst_schema_test_mulitple_schema"
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.sst_schema_test_mulitple_schema(
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
  )
@@ -8,7 +8,7 @@ module ARTest
8
8
  included { extend ConnectionReflection }
9
9
 
10
10
  def connection
11
- ActiveRecord::Base.connection
11
+ ActiveRecord::Base.lease_connection
12
12
  end
13
13
 
14
14
  def connection_options
@@ -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