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
         |