connection_handlers_multi_db_test.rb 19.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# frozen_string_literal: true

require "cases/helper"
require "models/person"

module ActiveRecord
  module ConnectionAdapters
    class ConnectionHandlersMultiDbTest < ActiveRecord::TestCase
      self.use_transactional_tests = false

      fixtures :people

      def setup
        @handlers = { writing: ConnectionHandler.new, reading: ConnectionHandler.new }
        @rw_handler = @handlers[:writing]
        @ro_handler = @handlers[:reading]
17
        @owner_name = "ActiveRecord::Base"
18
        db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
19 20
        @rw_pool = @handlers[:writing].establish_connection(db_config)
        @ro_pool = @handlers[:reading].establish_connection(db_config)
21 22 23
      end

      def teardown
24
        clean_up_connection_handler
25 26
      end

27 28 29 30 31
      class SecondaryBase < ActiveRecord::Base
        self.abstract_class = true
      end

      class MultiConnectionTestModel < SecondaryBase
32 33 34 35 36 37
      end

      def test_multiple_connection_handlers_works_in_a_threaded_environment
        tf_writing = Tempfile.open "test_writing"
        tf_reading = Tempfile.open "test_reading"

38 39
        # We need to use a role for reading not named reading, otherwise we'll prevent writes
        # and won't be able to write to the second connection.
40
        SecondaryBase.connects_to database: { writing: { database: tf_writing.path, adapter: "sqlite3" }, secondary: { database: tf_reading.path, adapter: "sqlite3" } }
41

42 43
        MultiConnectionTestModel.connection.execute("CREATE TABLE `multi_connection_test_models` (connection_role VARCHAR (255))")
        MultiConnectionTestModel.connection.execute("INSERT INTO multi_connection_test_models VALUES ('writing')")
44

45
        ActiveRecord::Base.connected_to(role: :secondary) do
46 47
          MultiConnectionTestModel.connection.execute("CREATE TABLE `multi_connection_test_models` (connection_role VARCHAR (255))")
          MultiConnectionTestModel.connection.execute("INSERT INTO multi_connection_test_models VALUES ('reading')")
48 49 50 51 52 53 54 55 56 57 58
        end

        read_latch = Concurrent::CountDownLatch.new
        write_latch = Concurrent::CountDownLatch.new

        MultiConnectionTestModel.connection

        thread = Thread.new do
          MultiConnectionTestModel.connection

          write_latch.wait
59
          assert_equal "writing", MultiConnectionTestModel.connection.select_value("SELECT connection_role from multi_connection_test_models")
60 61 62
          read_latch.count_down
        end

63
        ActiveRecord::Base.connected_to(role: :secondary) do
64
          write_latch.count_down
65
          assert_equal "reading", MultiConnectionTestModel.connection.select_value("SELECT connection_role from multi_connection_test_models")
66 67 68 69 70 71 72 73 74 75 76
          read_latch.wait
        end

        thread.join
      ensure
        tf_reading.close
        tf_reading.unlink
        tf_writing.close
        tf_writing.unlink
      end

77 78 79
      def test_loading_relations_with_multi_db_connection_handlers
        # We need to use a role for reading not named reading, otherwise we'll prevent writes
        # and won't be able to write to the second connection.
80
        SecondaryBase.connects_to database: { writing: { database: ":memory:", adapter: "sqlite3" }, secondary: { database: ":memory:", adapter: "sqlite3" } }
81 82 83 84 85 86 87 88 89 90

        relation = ActiveRecord::Base.connected_to(role: :secondary) do
          MultiConnectionTestModel.connection.execute("CREATE TABLE `multi_connection_test_models` (connection_role VARCHAR (255))")
          MultiConnectionTestModel.create!(connection_role: "reading")
          MultiConnectionTestModel.where(connection_role: "reading")
        end

        assert_equal "reading", relation.first.connection_role
      end

91 92 93 94 95 96
      unless in_memory_db?
        def test_establish_connection_using_3_levels_config
          previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"

          config = {
            "default_env" => {
97 98
              "readonly" => { "adapter" => "sqlite3", "database" => "test/db/readonly.sqlite3", "replica" => true },
              "default"  => { "adapter" => "sqlite3", "database" => "test/db/primary.sqlite3" }
99 100 101 102
            }
          }
          @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config

103
          ActiveRecord::Base.connects_to(database: { writing: :default, reading: :readonly })
104

105
          assert_not_nil pool = ActiveRecord::Base.connection_handlers[:writing].retrieve_connection_pool("ActiveRecord::Base")
106
          assert_equal "test/db/primary.sqlite3", pool.db_config.database
107
          assert_equal "default", pool.db_config.name
108

109
          assert_not_nil pool = ActiveRecord::Base.connection_handlers[:reading].retrieve_connection_pool("ActiveRecord::Base")
110
          assert_equal "test/db/readonly.sqlite3", pool.db_config.database
111
          assert_equal "readonly", pool.db_config.name
112 113 114 115 116 117 118 119 120 121 122
        ensure
          ActiveRecord::Base.configurations = @prev_configs
          ActiveRecord::Base.establish_connection(:arunit)
          ENV["RAILS_ENV"] = previous_env
        end

        def test_switching_connections_via_handler
          previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"

          config = {
            "default_env" => {
123 124
              "readonly" => { "adapter" => "sqlite3", "database" => "test/db/readonly.sqlite3" },
              "primary"  => { "adapter" => "sqlite3", "database" => "test/db/primary.sqlite3" }
125 126 127 128 129 130 131 132 133
            }
          }
          @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config

          ActiveRecord::Base.connects_to(database: { writing: :primary, reading: :readonly })

          ActiveRecord::Base.connected_to(role: :reading) do
            @ro_handler = ActiveRecord::Base.connection_handler
            assert_equal ActiveRecord::Base.connection_handler, ActiveRecord::Base.connection_handlers[:reading]
134
            assert_equal :reading, ActiveRecord::Base.current_role
J
John Hawthorn 已提交
135 136
            assert ActiveRecord::Base.connected_to?(role: :reading)
            assert_not ActiveRecord::Base.connected_to?(role: :writing)
137
            assert_predicate ActiveRecord::Base.connection, :preventing_writes?
138 139 140 141 142
          end

          ActiveRecord::Base.connected_to(role: :writing) do
            assert_equal ActiveRecord::Base.connection_handler, ActiveRecord::Base.connection_handlers[:writing]
            assert_not_equal @ro_handler, ActiveRecord::Base.connection_handler
143
            assert_equal :writing, ActiveRecord::Base.current_role
J
John Hawthorn 已提交
144 145
            assert ActiveRecord::Base.connected_to?(role: :writing)
            assert_not ActiveRecord::Base.connected_to?(role: :reading)
146
            assert_not_predicate ActiveRecord::Base.connection, :preventing_writes?
147 148 149 150 151 152 153
          end
        ensure
          ActiveRecord::Base.configurations = @prev_configs
          ActiveRecord::Base.establish_connection(:arunit)
          ENV["RAILS_ENV"] = previous_env
        end

154 155 156 157 158
        def test_establish_connection_using_3_levels_config_with_non_default_handlers
          previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"

          config = {
            "default_env" => {
159 160
              "readonly" => { "adapter" => "sqlite3", "database" => "test/db/readonly.sqlite3" },
              "primary"  => { "adapter" => "sqlite3", "database" => "test/db/primary.sqlite3" }
161 162 163 164 165 166
            }
          }
          @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config

          ActiveRecord::Base.connects_to(database: { default: :primary, readonly: :readonly })

167
          assert_not_nil pool = ActiveRecord::Base.connection_handlers[:default].retrieve_connection_pool("ActiveRecord::Base")
168
          assert_equal "test/db/primary.sqlite3", pool.db_config.database
169

170
          assert_not_nil pool = ActiveRecord::Base.connection_handlers[:readonly].retrieve_connection_pool("ActiveRecord::Base")
171
          assert_equal "test/db/readonly.sqlite3", pool.db_config.database
172 173 174 175 176 177
        ensure
          ActiveRecord::Base.configurations = @prev_configs
          ActiveRecord::Base.establish_connection(:arunit)
          ENV["RAILS_ENV"] = previous_env
        end

178 179 180 181
        def test_switching_connections_with_database_url
          previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
          previous_url, ENV["DATABASE_URL"] = ENV["DATABASE_URL"], "postgres://localhost/foo"

182 183 184
          ActiveRecord::Base.connects_to(database: { writing: "postgres://localhost/bar" })
          assert_equal :writing, ActiveRecord::Base.current_role
          assert ActiveRecord::Base.connected_to?(role: :writing)
J
John Hawthorn 已提交
185

186 187
          handler = ActiveRecord::Base.connection_handler
          assert_equal handler, ActiveRecord::Base.connection_handlers[:writing]
188

189
          assert_not_nil pool = handler.retrieve_connection_pool("ActiveRecord::Base")
190
          assert_equal({ adapter: "postgresql", database: "bar", host: "localhost" }, pool.db_config.configuration_hash)
191 192 193 194 195 196 197 198
        ensure
          ActiveRecord::Base.establish_connection(:arunit)
          ENV["RAILS_ENV"] = previous_env
          ENV["DATABASE_URL"] = previous_url
        end

        def test_switching_connections_with_database_config_hash
          previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
199
          config = { adapter: "sqlite3", database: "test/db/readonly.sqlite3" }
200

201 202 203
          ActiveRecord::Base.connects_to(database: { writing: config })
          assert_equal :writing, ActiveRecord::Base.current_role
          assert ActiveRecord::Base.connected_to?(role: :writing)
J
John Hawthorn 已提交
204

205 206
          handler = ActiveRecord::Base.connection_handler
          assert_equal handler, ActiveRecord::Base.connection_handlers[:writing]
207

208
          assert_not_nil pool = handler.retrieve_connection_pool("ActiveRecord::Base")
209
          assert_equal(config, pool.db_config.configuration_hash)
210 211 212 213 214
        ensure
          ActiveRecord::Base.establish_connection(:arunit)
          ENV["RAILS_ENV"] = previous_env
        end

215 216
        def test_switching_connections_with_database_and_role_raises
          error = assert_raises(ArgumentError) do
217 218 219
            assert_deprecated do
              ActiveRecord::Base.connected_to(database: :readonly, role: :writing) { }
            end
220
          end
221
          assert_equal "`connected_to` cannot accept a `database` argument with any other arguments.", error.message
222 223
        end

E
eileencodes 已提交
224 225 226 227 228 229 230 231
        def test_database_argument_is_deprecated
          assert_deprecated do
            ActiveRecord::Base.connected_to(database: { writing: { adapter: "sqlite3", database: "test/db/primary.sqlite3" } }) { }
          end
        ensure
          ActiveRecord::Base.establish_connection(:arunit)
        end

232 233 234 235
        def test_switching_connections_without_database_and_role_raises
          error = assert_raises(ArgumentError) do
            ActiveRecord::Base.connected_to { }
          end
236
          assert_equal "must provide a `shard` and/or `role`.", error.message
237 238
        end

239
        def test_switching_connections_with_database_symbol_uses_default_role
240 241
          previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"

242 243
          config = {
            "default_env" => {
244 245
              "animals" => { adapter: "sqlite3", database: "test/db/animals.sqlite3" },
              "primary" => { adapter: "sqlite3", database: "test/db/primary.sqlite3" }
246 247 248 249
            }
          }
          @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config

250 251 252
          ActiveRecord::Base.connects_to(database: { writing: :animals })
          assert_equal :writing, ActiveRecord::Base.current_role
          assert ActiveRecord::Base.connected_to?(role: :writing)
253

254 255
          handler = ActiveRecord::Base.connection_handler
          assert_equal handler, ActiveRecord::Base.connection_handlers[:writing]
256

257
          assert_not_nil pool = handler.retrieve_connection_pool("ActiveRecord::Base")
258
          assert_equal(config["default_env"]["animals"], pool.db_config.configuration_hash)
259 260 261 262 263 264 265 266 267 268 269
        ensure
          ActiveRecord::Base.configurations = @prev_configs
          ActiveRecord::Base.establish_connection(:arunit)
          ENV["RAILS_ENV"] = previous_env
        end

        def test_switching_connections_with_database_hash_uses_passed_role_and_database
          previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"

          config = {
            "default_env" => {
270 271
              "animals" => { adapter: "sqlite3", database: "test/db/animals.sqlite3" },
              "primary" => { adapter: "sqlite3", database: "test/db/primary.sqlite3" }
272 273 274 275
            }
          }
          @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config

276 277 278
          ActiveRecord::Base.connects_to(database: { writing: :primary })
          assert_equal :writing, ActiveRecord::Base.current_role
          assert ActiveRecord::Base.connected_to?(role: :writing)
J
John Hawthorn 已提交
279

280 281
          handler = ActiveRecord::Base.connection_handler
          assert_equal handler, ActiveRecord::Base.connection_handlers[:writing]
282

283
          assert_not_nil pool = handler.retrieve_connection_pool("ActiveRecord::Base")
284
          assert_equal(config["default_env"]["primary"], pool.db_config.configuration_hash)
285
        ensure
286
          ActiveRecord::Base.configurations = @prev_configs
287 288 289 290
          ActiveRecord::Base.establish_connection(:arunit)
          ENV["RAILS_ENV"] = previous_env
        end

291 292
        def test_connects_to_with_single_configuration
          config = {
293
            "development" => { "adapter" => "sqlite3", "database" => "test/db/primary.sqlite3" },
294 295 296 297 298 299 300
          }
          @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config

          ActiveRecord::Base.connects_to database: { writing: :development }

          assert_equal 1, ActiveRecord::Base.connection_handlers.size
          assert_equal ActiveRecord::Base.connection_handler, ActiveRecord::Base.connection_handlers[:writing]
301
          assert_equal :writing, ActiveRecord::Base.current_role
J
John Hawthorn 已提交
302
          assert ActiveRecord::Base.connected_to?(role: :writing)
303 304 305 306 307 308 309
        ensure
          ActiveRecord::Base.configurations = @prev_configs
          ActiveRecord::Base.establish_connection(:arunit)
        end

        def test_connects_to_using_top_level_key_in_two_level_config
          config = {
310 311
            "development" => { "adapter" => "sqlite3", "database" => "test/db/primary.sqlite3" },
            "development_readonly" => { "adapter" => "sqlite3", "database" => "test/db/readonly.sqlite3" }
312 313 314 315 316
          }
          @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config

          ActiveRecord::Base.connects_to database: { writing: :development, reading: :development_readonly }

317
          assert_not_nil pool = ActiveRecord::Base.connection_handlers[:reading].retrieve_connection_pool("ActiveRecord::Base")
318
          assert_equal "test/db/readonly.sqlite3", pool.db_config.database
319 320 321 322
        ensure
          ActiveRecord::Base.configurations = @prev_configs
          ActiveRecord::Base.establish_connection(:arunit)
        end
323 324 325

        def test_connects_to_returns_array_of_established_connections
          config = {
326 327
            "development" => { "adapter" => "sqlite3", "database" => "test/db/primary.sqlite3" },
            "development_readonly" => { "adapter" => "sqlite3", "database" => "test/db/readonly.sqlite3" }
328 329 330 331 332 333 334
          }
          @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config

          result = ActiveRecord::Base.connects_to database: { writing: :development, reading: :development_readonly }

          assert_equal(
            [
335 336
              ActiveRecord::Base.connection_handlers[:writing].retrieve_connection_pool("ActiveRecord::Base"),
              ActiveRecord::Base.connection_handlers[:reading].retrieve_connection_pool("ActiveRecord::Base")
337 338 339 340 341 342 343
            ],
            result
          )
        ensure
          ActiveRecord::Base.configurations = @prev_configs
          ActiveRecord::Base.establish_connection(:arunit)
        end
344 345 346 347 348 349 350 351
      end

      def test_connection_pools
        assert_equal([@rw_pool], @handlers[:writing].connection_pools)
        assert_equal([@ro_pool], @handlers[:reading].connection_pools)
      end

      def test_retrieve_connection
352 353
        assert @rw_handler.retrieve_connection(@owner_name)
        assert @ro_handler.retrieve_connection(@owner_name)
354 355 356 357 358 359
      end

      def test_active_connections?
        assert_not_predicate @rw_handler, :active_connections?
        assert_not_predicate @ro_handler, :active_connections?

360 361
        assert @rw_handler.retrieve_connection(@owner_name)
        assert @ro_handler.retrieve_connection(@owner_name)
362 363 364 365 366 367 368 369 370 371 372 373

        assert_predicate @rw_handler, :active_connections?
        assert_predicate @ro_handler, :active_connections?

        @rw_handler.clear_active_connections!
        assert_not_predicate @rw_handler, :active_connections?

        @ro_handler.clear_active_connections!
        assert_not_predicate @ro_handler, :active_connections?
      end

      def test_retrieve_connection_pool
374 375
        assert_not_nil @rw_handler.retrieve_connection_pool(@owner_name)
        assert_not_nil @ro_handler.retrieve_connection_pool(@owner_name)
376 377 378 379 380 381
      end

      def test_retrieve_connection_pool_with_invalid_id
        assert_nil @rw_handler.retrieve_connection_pool("foo")
        assert_nil @ro_handler.retrieve_connection_pool("foo")
      end
382 383 384 385 386 387

      def test_connection_handlers_are_per_thread_and_not_per_fiber
        ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler, reading: ActiveRecord::ConnectionAdapters::ConnectionHandler.new }

        reading_handler = ActiveRecord::Base.connection_handlers[:reading]

388
        reading = ActiveRecord::Base.connected_to(role: :reading) do
389 390 391 392 393 394
          Person.connection_handler
        end

        assert_not_equal reading, ActiveRecord::Base.connection_handler
        assert_equal reading, reading_handler
      ensure
395
        clean_up_connection_handler
396 397 398 399 400 401 402 403 404 405 406 407 408
      end

      def test_connection_handlers_swapping_connections_in_fiber
        original_handlers = ActiveRecord::Base.connection_handlers

        ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler, reading: ActiveRecord::ConnectionAdapters::ConnectionHandler.new }

        reading_handler = ActiveRecord::Base.connection_handlers[:reading]

        enum = Enumerator.new do |r|
          r << ActiveRecord::Base.connection_handler
        end

409
        reading = ActiveRecord::Base.connected_to(role: :reading) do
410 411 412 413 414 415 416
          enum.next
        end

        assert_equal reading, reading_handler
      ensure
        ActiveRecord::Base.connection_handlers = original_handlers
      end
417 418

      def test_calling_connected_to_on_a_non_existent_handler_raises
419
        error = assert_raises ActiveRecord::ConnectionNotEstablished do
420
          ActiveRecord::Base.connected_to(role: :reading) do
421
            Person.first
422 423 424
          end
        end

425
        assert_equal "No connection pool for 'ActiveRecord::Base' found for the 'reading' role.", error.message
426
      end
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444

      def test_default_handlers_are_writing_and_reading
        assert_equal :writing, ActiveRecord::Base.writing_role
        assert_equal :reading, ActiveRecord::Base.reading_role
      end

      def test_an_application_can_change_the_default_handlers
        old_writing = ActiveRecord::Base.writing_role
        old_reading = ActiveRecord::Base.reading_role
        ActiveRecord::Base.writing_role = :default
        ActiveRecord::Base.reading_role = :readonly

        assert_equal :default, ActiveRecord::Base.writing_role
        assert_equal :readonly, ActiveRecord::Base.reading_role
      ensure
        ActiveRecord::Base.writing_role = old_writing
        ActiveRecord::Base.reading_role = old_reading
      end
445 446 447
    end
  end
end