activerecord-sqlserver-adapter 7.1.7 → 7.2.0

Sign up to get free protection for your applications and to get access to all the features.
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