From b83fb847337b690ebbc96c55414157f71bbf8aa2 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Wed, 4 May 2016 00:46:38 -0500 Subject: Refactor connection handler ConnectionHandler will not have any knowlodge of AR models now, it will only know about the specs. Like that we can decouple the two, and allow the same model to use more than one connection. Historically, folks used to create abstract AR classes on the fly in order to have multiple connections for the same model, and override the connection methods. With this, now we can override the `specificiation_id` method in the model, to return a key, that will be used to find the connection_pool from the handler. --- .../abstract/connection_pool.rb | 57 ++++++++-------------- .../connection_specification.rb | 10 ++-- .../lib/active_record/connection_handling.rb | 34 ++++++++++--- activerecord/lib/active_record/core.rb | 2 +- .../connection_adapters/adapter_leasing_test.rb | 2 +- .../connection_adapters/connection_handler_test.rb | 44 +++++++++-------- .../connection_specification_test.rb | 2 +- activerecord/test/cases/connection_pool_test.rb | 16 +++--- activerecord/test/cases/multiple_db_test.rb | 4 +- 9 files changed, 88 insertions(+), 83 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index e389d818fd..67a2ac3050 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -829,9 +829,6 @@ module ActiveRecord @owner_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k| h[k] = Concurrent::Map.new(:initial_capacity => 2) end - @class_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k| - h[k] = Concurrent::Map.new - end end def connection_pool_list @@ -839,10 +836,8 @@ module ActiveRecord end alias :connection_pools :connection_pool_list - def establish_connection(owner, spec) - @class_to_pool.clear - raise RuntimeError, "Anonymous class is not allowed." unless owner.name - owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec) + def establish_connection(spec) + owner_to_pool[spec.id] = ConnectionAdapters::ConnectionPool.new(spec) end # Returns true if there are any active connections among the connection @@ -873,18 +868,18 @@ module ActiveRecord # active or defined connection: if it is the latter, it will be # opened and set as the active connection for the class it was defined # for (not necessarily the current class). - def retrieve_connection(klass) #:nodoc: - pool = retrieve_connection_pool(klass) - raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool + def retrieve_connection(spec_id) #:nodoc: + pool = retrieve_connection_pool(spec_id) + raise ConnectionNotEstablished, "No connection pool with id #{spec_id} found." unless pool conn = pool.connection - raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn + raise ConnectionNotEstablished, "No connection for #{spec_id} in connection pool" unless conn conn end # Returns true if a connection that's accessible to this class has # already been opened. - def connected?(klass) - conn = retrieve_connection_pool(klass) + def connected?(spec_id) + conn = retrieve_connection_pool(spec_id) conn && conn.connected? end @@ -892,9 +887,8 @@ module ActiveRecord # connection and the defined connection (if they exist). The result # can be used as an argument for establish_connection, for easily # re-establishing the connection. - def remove_connection(owner) - if pool = owner_to_pool.delete(owner.name) - @class_to_pool.clear + def remove_connection(spec_id) + if pool = owner_to_pool.delete(spec_id) pool.automatic_reconnect = false pool.disconnect! pool.spec.config @@ -910,15 +904,8 @@ module ActiveRecord # #fetch is significantly slower than #[]. So in the nil case, no caching will # take place, but that's ok since the nil case is not the common one that we wish # to optimise for. - def retrieve_connection_pool(klass) - class_to_pool[klass.name] ||= begin - until pool = pool_for(klass) - klass = klass.superclass - break unless klass <= Base - end - - class_to_pool[klass.name] = pool - end + def retrieve_connection_pool(spec_id) + pool_for(spec_id) end private @@ -927,28 +914,24 @@ module ActiveRecord @owner_to_pool[Process.pid] end - def class_to_pool - @class_to_pool[Process.pid] - end - - def pool_for(owner) - owner_to_pool.fetch(owner.name) { - if ancestor_pool = pool_from_any_process_for(owner) + def pool_for(spec_id) + owner_to_pool.fetch(spec_id) { + if ancestor_pool = pool_from_any_process_for(spec_id) # A connection was established in an ancestor process that must have # subsequently forked. We can't reuse the connection, but we can copy # the specification and establish a new connection with it. - establish_connection(owner, ancestor_pool.spec).tap do |pool| + establish_connection(ancestor_pool.spec).tap do |pool| pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache end else - owner_to_pool[owner.name] = nil + owner_to_pool[spec_id] = nil end } end - def pool_from_any_process_for(owner) - owner_to_pool = @owner_to_pool.values.find { |v| v[owner.name] } - owner_to_pool && owner_to_pool[owner.name] + def pool_from_any_process_for(spec_id) + owner_to_pool = @owner_to_pool.values.find { |v| v[spec_id] } + owner_to_pool && owner_to_pool[spec_id] end end end diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 4bc6447368..5a18e95bcd 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -3,10 +3,10 @@ require 'uri' module ActiveRecord module ConnectionAdapters class ConnectionSpecification #:nodoc: - attr_reader :config, :adapter_method + attr_reader :config, :adapter_method, :id - def initialize(config, adapter_method) - @config, @adapter_method = config, adapter_method + def initialize(id, config, adapter_method) + @config, @adapter_method, @id = config, adapter_method, id end def initialize_dup(original) @@ -164,7 +164,7 @@ module ActiveRecord # spec.config # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } # - def spec(config) + def spec(config, id = "primary") spec = resolve(config).symbolize_keys raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter) @@ -179,7 +179,7 @@ module ActiveRecord end adapter_method = "#{spec[:adapter]}_connection" - ConnectionSpecification.new(spec, adapter_method) + ConnectionSpecification.new(id, spec, adapter_method) end private diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index a8b3d03ba5..c54b8adc4e 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -45,16 +45,19 @@ module ActiveRecord # The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+ # may be returned on an error. def establish_connection(spec = nil) + raise RuntimeError, "Anonymous class is not allowed." unless name + spec ||= DEFAULT_ENV.call.to_sym resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations - spec = resolver.spec(spec) + spec = resolver.spec(spec, self == Base ? "primary" : name) + self.specification_id = spec.id unless respond_to?(spec.adapter_method) raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter" end remove_connection - connection_handler.establish_connection self, spec + connection_handler.establish_connection spec end class MergeAndResolveDefaultUrlConfig # :nodoc: @@ -87,6 +90,23 @@ module ActiveRecord retrieve_connection end + def specification_id=(value) + @specification_id = value + end + + def specification_id(fallback = true) + return @specification_id if defined?(@specification_id) + find_legacy_spec_id(self) if fallback + end + + def find_legacy_spec_id(klass) + return "primary" if klass == Base + if id = klass.specification_id(false) + return id + end + find_legacy_spec_id(klass.superclass) + end + def connection_id ActiveRecord::RuntimeRegistry.connection_id ||= Thread.current.object_id end @@ -106,20 +126,20 @@ module ActiveRecord end def connection_pool - connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished + connection_handler.retrieve_connection_pool(specification_id) or raise ConnectionNotEstablished end def retrieve_connection - connection_handler.retrieve_connection(self) + connection_handler.retrieve_connection(specification_id) end # Returns +true+ if Active Record is connected. def connected? - connection_handler.connected?(self) + connection_handler.connected?(specification_id) end - def remove_connection(klass = self) - connection_handler.remove_connection(klass) + def remove_connection(id = specification_id) + connection_handler.remove_connection(id) end def clear_cache! # :nodoc: diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 5d74631e32..291cabb4bb 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -257,7 +257,7 @@ module ActiveRecord # Returns the Arel engine. def arel_engine # :nodoc: @arel_engine ||= - if Base == self || connection_handler.retrieve_connection_pool(self) + if Base == self || connection_handler.retrieve_connection_pool(specification_id) self else superclass.arel_engine diff --git a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb index 580568c8ac..c7ca428ab7 100644 --- a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb +++ b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb @@ -37,7 +37,7 @@ module ActiveRecord end def test_close - pool = Pool.new(ConnectionSpecification.new({}, nil)) + pool = Pool.new(ConnectionSpecification.new("primary", {}, nil)) pool.insert_connection_for_test! @adapter @adapter.pool = pool diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index 9b1865e8bb..c3cdb29380 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -4,49 +4,51 @@ module ActiveRecord module ConnectionAdapters class ConnectionHandlerTest < ActiveRecord::TestCase def setup - @klass = Class.new(Base) { def self.name; 'klass'; end } - @subklass = Class.new(@klass) { def self.name; 'subklass'; end } - @handler = ConnectionHandler.new - @pool = @handler.establish_connection(@klass, Base.connection_pool.spec) + resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new Base.configurations + spec = resolver.spec(:arunit) + + @spec_id = "primary" + @pool = @handler.establish_connection(spec) end def test_retrieve_connection - assert @handler.retrieve_connection(@klass) + assert @handler.retrieve_connection(@spec_id) end def test_active_connections? assert !@handler.active_connections? - assert @handler.retrieve_connection(@klass) + assert @handler.retrieve_connection(@spec_id) assert @handler.active_connections? @handler.clear_active_connections! assert !@handler.active_connections? end - def test_retrieve_connection_pool_with_ar_base - assert_nil @handler.retrieve_connection_pool(ActiveRecord::Base) - end +# def test_retrieve_connection_pool_with_ar_base +# assert_nil @handler.retrieve_connection_pool(ActiveRecord::Base) +# end def test_retrieve_connection_pool - assert_not_nil @handler.retrieve_connection_pool(@klass) + assert_not_nil @handler.retrieve_connection_pool(@spec_id) end - def test_retrieve_connection_pool_uses_superclass_when_no_subclass_connection - assert_not_nil @handler.retrieve_connection_pool(@subklass) - end - - def test_retrieve_connection_pool_uses_superclass_pool_after_subclass_establish_and_remove - sub_pool = @handler.establish_connection(@subklass, Base.connection_pool.spec) - assert_same sub_pool, @handler.retrieve_connection_pool(@subklass) +# def test_retrieve_connection_pool_uses_superclass_when_no_subclass_connection +# assert_not_nil @handler.retrieve_connection_pool(@subklass) +# end - @handler.remove_connection @subklass - assert_same @pool, @handler.retrieve_connection_pool(@subklass) - end +# def test_retrieve_connection_pool_uses_superclass_pool_after_subclass_establish_and_remove +# sub_pool = @handler.establish_connection(@subklass, Base.connection_pool.spec) +# assert_same sub_pool, @handler.retrieve_connection_pool(@subklass) +# +# @handler.remove_connection @subklass +# assert_same @pool, @handler.retrieve_connection_pool(@subklass) +# end def test_connection_pools assert_equal([@pool], @handler.connection_pools) end + # TODO if Process.respond_to?(:fork) def test_connection_pool_per_pid object_id = ActiveRecord::Base.connection.object_id @@ -79,7 +81,7 @@ module ActiveRecord pid = fork { rd.close - pool = @handler.retrieve_connection_pool(@klass) + pool = @handler.retrieve_connection_pool(@spec_id) wr.write Marshal.dump pool.schema_cache.size wr.close exit! diff --git a/activerecord/test/cases/connection_adapters/connection_specification_test.rb b/activerecord/test/cases/connection_adapters/connection_specification_test.rb index ea2196cda2..d204fce59f 100644 --- a/activerecord/test/cases/connection_adapters/connection_specification_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_specification_test.rb @@ -4,7 +4,7 @@ module ActiveRecord module ConnectionAdapters class ConnectionSpecificationTest < ActiveRecord::TestCase def test_dup_deep_copy_config - spec = ConnectionSpecification.new({ :a => :b }, "bar") + spec = ConnectionSpecification.new("primary", { :a => :b }, "bar") assert_not_equal(spec.config.object_id, spec.dup.config.object_id) end end diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index efa3e0455e..55e90fd172 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -333,14 +333,14 @@ module ActiveRecord # make sure exceptions are thrown when establish_connection # is called with an anonymous class - def test_anonymous_class_exception - anonymous = Class.new(ActiveRecord::Base) - handler = ActiveRecord::Base.connection_handler - - assert_raises(RuntimeError) { - handler.establish_connection anonymous, nil - } - end +# def test_anonymous_class_exception +# anonymous = Class.new(ActiveRecord::Base) +# handler = ActiveRecord::Base.connection_handler +# +# assert_raises(RuntimeError) { +# handler.establish_connection anonymous, nil +# } +# end def test_pool_sets_connection_schema_cache connection = pool.checkout diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index af4183a601..e20ce20599 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -89,8 +89,8 @@ class MultipleDbTest < ActiveRecord::TestCase end def test_connection - assert_equal Entrant.arel_engine.connection, Bird.arel_engine.connection - assert_not_equal Entrant.arel_engine.connection, Course.arel_engine.connection + assert_equal Entrant.arel_engine.connection.object_id, Bird.arel_engine.connection.object_id + assert_not_equal Entrant.arel_engine.connection.object_id, Course.arel_engine.connection.object_id end unless in_memory_db? -- cgit v1.2.3 From 314cf0398c6b8a76ee9fe86ba8c2926d3b54ab28 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Wed, 4 May 2016 11:47:08 -0500 Subject: Rename method --- activerecord/lib/active_record/connection_handling.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index c54b8adc4e..8246c12ea8 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -96,15 +96,15 @@ module ActiveRecord def specification_id(fallback = true) return @specification_id if defined?(@specification_id) - find_legacy_spec_id(self) if fallback + find_parent_spec_id(self) if fallback end - def find_legacy_spec_id(klass) + def find_parent_spec_id(klass) return "primary" if klass == Base if id = klass.specification_id(false) return id end - find_legacy_spec_id(klass.superclass) + find_parent_spec_id(klass.superclass) end def connection_id -- cgit v1.2.3 From c1bc0d83def740648fdbed05fcc3283dcef1f07d Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Wed, 4 May 2016 12:00:21 -0500 Subject: Better specification_id method --- activerecord/lib/active_record/connection_handling.rb | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index 8246c12ea8..543992dcbf 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -49,6 +49,7 @@ module ActiveRecord spec ||= DEFAULT_ENV.call.to_sym resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations + # TODO: uses name on establish_connection, for backwards compatibility spec = resolver.spec(spec, self == Base ? "primary" : name) self.specification_id = spec.id @@ -94,17 +95,13 @@ module ActiveRecord @specification_id = value end - def specification_id(fallback = true) - return @specification_id if defined?(@specification_id) - find_parent_spec_id(self) if fallback - end - - def find_parent_spec_id(klass) - return "primary" if klass == Base - if id = klass.specification_id(false) - return id + # Return the specification id from this class otherwise look it up + # in the parent. + def specification_id + unless defined?(@specification_id) + @specification_id = self == Base ? "primary" : superclass.specification_id end - find_parent_spec_id(klass.superclass) + @specification_id end def connection_id -- cgit v1.2.3 From 79154a3281eb25a573dfcb5d5db31c3c481311f9 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Wed, 4 May 2016 14:05:31 -0500 Subject: Use spec key, when given as spec_id --- .../connection_specification.rb | 9 +++++- .../connection_adapters/connection_handler_test.rb | 34 ++++++++++------------ 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 5a18e95bcd..f8cdf3ca0c 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -164,7 +164,7 @@ module ActiveRecord # spec.config # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } # - def spec(config, id = "primary") + def spec(config, id = nil) spec = resolve(config).symbolize_keys raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter) @@ -179,6 +179,13 @@ module ActiveRecord end adapter_method = "#{spec[:adapter]}_connection" + + id ||= + if config.is_a?(Symbol) + config.to_s + else + "primary" + end ConnectionSpecification.new(id, spec, adapter_method) end diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index c3cdb29380..47e8486ded 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -6,10 +6,19 @@ module ActiveRecord def setup @handler = ConnectionHandler.new resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new Base.configurations - spec = resolver.spec(:arunit) - @spec_id = "primary" - @pool = @handler.establish_connection(spec) + @pool = @handler.establish_connection(resolver.spec(:arunit, @spec_id)) + end + + def test_establish_connection_uses_spec_id + config = {"readonly" => {"adapter" => 'sqlite3'}} + resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(config) + spec = resolver.spec(:readonly) + @handler.establish_connection(spec) + + assert_not_nil @handler.retrieve_connection_pool('readonly') + ensure + @handler.remove_connection('readonly') end def test_retrieve_connection @@ -24,31 +33,18 @@ module ActiveRecord assert !@handler.active_connections? end -# def test_retrieve_connection_pool_with_ar_base -# assert_nil @handler.retrieve_connection_pool(ActiveRecord::Base) -# end - def test_retrieve_connection_pool assert_not_nil @handler.retrieve_connection_pool(@spec_id) end -# def test_retrieve_connection_pool_uses_superclass_when_no_subclass_connection -# assert_not_nil @handler.retrieve_connection_pool(@subklass) -# end - -# def test_retrieve_connection_pool_uses_superclass_pool_after_subclass_establish_and_remove -# sub_pool = @handler.establish_connection(@subklass, Base.connection_pool.spec) -# assert_same sub_pool, @handler.retrieve_connection_pool(@subklass) -# -# @handler.remove_connection @subklass -# assert_same @pool, @handler.retrieve_connection_pool(@subklass) -# end + def test_retrieve_connection_pool_with_invalid_id + assert_nil @handler.retrieve_connection_pool("foo") + end def test_connection_pools assert_equal([@pool], @handler.connection_pools) end - # TODO if Process.respond_to?(:fork) def test_connection_pool_per_pid object_id = ActiveRecord::Base.connection.object_id -- cgit v1.2.3 From ac1c4e141b20c1067af2c2703db6e1b463b985da Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Wed, 4 May 2016 14:17:10 -0500 Subject: Add spec_id tests --- .../test/cases/connection_specification/resolver_test.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index 358b6ad537..36d1e059c7 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -116,6 +116,15 @@ module ActiveRecord "encoding" => "utf8" }, spec) end + def test_spec_id_on_key_lookup + spec = spec(:readonly, 'readonly' => {'adapter' => 'sqlite3'}) + assert_equal "readonly", spec.id + end + + def test_spec_id_with_inline_config + spec = spec({'adapter' => 'sqlite3'}) + assert_equal "primary", spec.id, "should default to primary id" + end end end end -- cgit v1.2.3 From 9872905336172df4f70cab9682b397b59deac1b8 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Wed, 4 May 2016 14:20:39 -0500 Subject: fix test --- activerecord/test/cases/connection_pool_test.rb | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 55e90fd172..a45ee281c7 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -333,14 +333,13 @@ module ActiveRecord # make sure exceptions are thrown when establish_connection # is called with an anonymous class -# def test_anonymous_class_exception -# anonymous = Class.new(ActiveRecord::Base) -# handler = ActiveRecord::Base.connection_handler -# -# assert_raises(RuntimeError) { -# handler.establish_connection anonymous, nil -# } -# end + def test_anonymous_class_exception + anonymous = Class.new(ActiveRecord::Base) + + assert_raises(RuntimeError) do + anonymous.establish_connection + end + end def test_pool_sets_connection_schema_cache connection = pool.checkout -- cgit v1.2.3 From c1ae7ec15ba01217d70e15da1b021c416097f3dd Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Wed, 4 May 2016 15:14:27 -0500 Subject: Test to swap connection at runtime --- activerecord/test/cases/multiple_db_test.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index e20ce20599..b7fc8710ca 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -24,6 +24,13 @@ class MultipleDbTest < ActiveRecord::TestCase assert_equal(ActiveRecord::Base.connection, Entrant.connection) end + def test_swapping_the_connection + old_spec_id, Course.specification_id = Course.specification_id, "primary" + assert_equal(Entrant.connection, Course.connection) + ensure + Course.specification_id = old_spec_id + end + def test_find c1 = Course.find(1) assert_equal "Ruby Development", c1.name -- cgit v1.2.3 From 40a8d2f3b530b61f032d7a9e0b85b392c54b573e Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Wed, 4 May 2016 16:58:50 -0500 Subject: Better code readability --- .../lib/active_record/connection_adapters/connection_specification.rb | 2 +- activerecord/lib/active_record/connection_handling.rb | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index f8cdf3ca0c..430937ae65 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -6,7 +6,7 @@ module ActiveRecord attr_reader :config, :adapter_method, :id def initialize(id, config, adapter_method) - @config, @adapter_method, @id = config, adapter_method, id + @id, @config, @adapter_method = id, config, adapter_method end def initialize_dup(original) diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index 543992dcbf..7d01c4ea48 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -91,9 +91,7 @@ module ActiveRecord retrieve_connection end - def specification_id=(value) - @specification_id = value - end + attr_writer :specification_id # Return the specification id from this class otherwise look it up # in the parent. -- cgit v1.2.3 From 7d9d076e736a554e865d1735b5bd2dd69d69209b Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Wed, 4 May 2016 17:13:06 -0500 Subject: inline retrive_conn_pool method --- .../connection_adapters/abstract/connection_pool.rb | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 67a2ac3050..3968b59b8f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -905,17 +905,7 @@ module ActiveRecord # take place, but that's ok since the nil case is not the common one that we wish # to optimise for. def retrieve_connection_pool(spec_id) - pool_for(spec_id) - end - - private - - def owner_to_pool - @owner_to_pool[Process.pid] - end - - def pool_for(spec_id) - owner_to_pool.fetch(spec_id) { + owner_to_pool.fetch(spec_id) do if ancestor_pool = pool_from_any_process_for(spec_id) # A connection was established in an ancestor process that must have # subsequently forked. We can't reuse the connection, but we can copy @@ -926,7 +916,13 @@ module ActiveRecord else owner_to_pool[spec_id] = nil end - } + end + end + + private + + def owner_to_pool + @owner_to_pool[Process.pid] end def pool_from_any_process_for(spec_id) -- cgit v1.2.3 From 34856ba9fa893bd1483ca5b08b65562cd5c02c58 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Thu, 5 May 2016 15:15:11 -0500 Subject: Retrive the right pool for db tasks --- activerecord/lib/active_record/tasks/database_tasks.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 9aea5b360b..6f5bb88d35 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -117,10 +117,10 @@ module ActiveRecord end def create_all - old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base) + old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.specification_id) each_local_configuration { |configuration| create configuration } if old_pool - ActiveRecord::Base.connection_handler.establish_connection(ActiveRecord::Base, old_pool.spec) + ActiveRecord::Base.connection_handler.establish_connection(old_pool.spec) end end -- cgit v1.2.3 From 598e7c9e2004b85146386571372423020ba7c52a Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Thu, 5 May 2016 15:25:08 -0500 Subject: s/specification_id/specification_name --- .../abstract/connection_pool.rb | 32 +++++++++++----------- .../connection_specification.rb | 12 ++++---- .../lib/active_record/connection_handling.rb | 22 +++++++-------- activerecord/lib/active_record/core.rb | 2 +- .../lib/active_record/tasks/database_tasks.rb | 2 +- .../connection_adapters/connection_handler_test.rb | 14 +++++----- .../connection_specification/resolver_test.rb | 8 +++--- activerecord/test/cases/multiple_db_test.rb | 4 +-- 8 files changed, 48 insertions(+), 48 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 3968b59b8f..0498737b20 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -837,7 +837,7 @@ module ActiveRecord alias :connection_pools :connection_pool_list def establish_connection(spec) - owner_to_pool[spec.id] = ConnectionAdapters::ConnectionPool.new(spec) + owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec) end # Returns true if there are any active connections among the connection @@ -868,18 +868,18 @@ module ActiveRecord # active or defined connection: if it is the latter, it will be # opened and set as the active connection for the class it was defined # for (not necessarily the current class). - def retrieve_connection(spec_id) #:nodoc: - pool = retrieve_connection_pool(spec_id) - raise ConnectionNotEstablished, "No connection pool with id #{spec_id} found." unless pool + def retrieve_connection(spec_name) #:nodoc: + pool = retrieve_connection_pool(spec_name) + raise ConnectionNotEstablished, "No connection pool with id #{spec_name} found." unless pool conn = pool.connection - raise ConnectionNotEstablished, "No connection for #{spec_id} in connection pool" unless conn + raise ConnectionNotEstablished, "No connection for #{spec_name} in connection pool" unless conn conn end # Returns true if a connection that's accessible to this class has # already been opened. - def connected?(spec_id) - conn = retrieve_connection_pool(spec_id) + def connected?(spec_name) + conn = retrieve_connection_pool(spec_name) conn && conn.connected? end @@ -887,8 +887,8 @@ module ActiveRecord # connection and the defined connection (if they exist). The result # can be used as an argument for establish_connection, for easily # re-establishing the connection. - def remove_connection(spec_id) - if pool = owner_to_pool.delete(spec_id) + def remove_connection(spec_name) + if pool = owner_to_pool.delete(spec_name) pool.automatic_reconnect = false pool.disconnect! pool.spec.config @@ -904,9 +904,9 @@ module ActiveRecord # #fetch is significantly slower than #[]. So in the nil case, no caching will # take place, but that's ok since the nil case is not the common one that we wish # to optimise for. - def retrieve_connection_pool(spec_id) - owner_to_pool.fetch(spec_id) do - if ancestor_pool = pool_from_any_process_for(spec_id) + def retrieve_connection_pool(spec_name) + owner_to_pool.fetch(spec_name) do + if ancestor_pool = pool_from_any_process_for(spec_name) # A connection was established in an ancestor process that must have # subsequently forked. We can't reuse the connection, but we can copy # the specification and establish a new connection with it. @@ -914,7 +914,7 @@ module ActiveRecord pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache end else - owner_to_pool[spec_id] = nil + owner_to_pool[spec_name] = nil end end end @@ -925,9 +925,9 @@ module ActiveRecord @owner_to_pool[Process.pid] end - def pool_from_any_process_for(spec_id) - owner_to_pool = @owner_to_pool.values.find { |v| v[spec_id] } - owner_to_pool && owner_to_pool[spec_id] + def pool_from_any_process_for(spec_name) + owner_to_pool = @owner_to_pool.values.find { |v| v[spec_name] } + owner_to_pool && owner_to_pool[spec_name] end end end diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 430937ae65..901c98b22b 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -3,10 +3,10 @@ require 'uri' module ActiveRecord module ConnectionAdapters class ConnectionSpecification #:nodoc: - attr_reader :config, :adapter_method, :id + attr_reader :name, :config, :adapter_method - def initialize(id, config, adapter_method) - @id, @config, @adapter_method = id, config, adapter_method + def initialize(name, config, adapter_method) + @name, @config, @adapter_method = name, config, adapter_method end def initialize_dup(original) @@ -164,7 +164,7 @@ module ActiveRecord # spec.config # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } # - def spec(config, id = nil) + def spec(config, name = nil) spec = resolve(config).symbolize_keys raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter) @@ -180,13 +180,13 @@ module ActiveRecord adapter_method = "#{spec[:adapter]}_connection" - id ||= + name ||= if config.is_a?(Symbol) config.to_s else "primary" end - ConnectionSpecification.new(id, spec, adapter_method) + ConnectionSpecification.new(name, spec, adapter_method) end private diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index 7d01c4ea48..f363a2d21b 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -51,7 +51,7 @@ module ActiveRecord resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations # TODO: uses name on establish_connection, for backwards compatibility spec = resolver.spec(spec, self == Base ? "primary" : name) - self.specification_id = spec.id + self.specification_name = spec.name unless respond_to?(spec.adapter_method) raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter" @@ -91,15 +91,15 @@ module ActiveRecord retrieve_connection end - attr_writer :specification_id + attr_writer :specification_name # Return the specification id from this class otherwise look it up # in the parent. - def specification_id - unless defined?(@specification_id) - @specification_id = self == Base ? "primary" : superclass.specification_id + def specification_name + unless defined?(@specification_name) + @specification_name = self == Base ? "primary" : superclass.specification_name end - @specification_id + @specification_name end def connection_id @@ -121,20 +121,20 @@ module ActiveRecord end def connection_pool - connection_handler.retrieve_connection_pool(specification_id) or raise ConnectionNotEstablished + connection_handler.retrieve_connection_pool(specification_name) or raise ConnectionNotEstablished end def retrieve_connection - connection_handler.retrieve_connection(specification_id) + connection_handler.retrieve_connection(specification_name) end # Returns +true+ if Active Record is connected. def connected? - connection_handler.connected?(specification_id) + connection_handler.connected?(specification_name) end - def remove_connection(id = specification_id) - connection_handler.remove_connection(id) + def remove_connection(name = specification_name) + connection_handler.remove_connection(name) end def clear_cache! # :nodoc: diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 291cabb4bb..4abe25e5b7 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -257,7 +257,7 @@ module ActiveRecord # Returns the Arel engine. def arel_engine # :nodoc: @arel_engine ||= - if Base == self || connection_handler.retrieve_connection_pool(specification_id) + if Base == self || connection_handler.retrieve_connection_pool(specification_name) self else superclass.arel_engine diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 6f5bb88d35..4fbc93496a 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -117,7 +117,7 @@ module ActiveRecord end def create_all - old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.specification_id) + old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.specification_name) each_local_configuration { |configuration| create configuration } if old_pool ActiveRecord::Base.connection_handler.establish_connection(old_pool.spec) diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index 47e8486ded..fc5ca8865b 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -6,11 +6,11 @@ module ActiveRecord def setup @handler = ConnectionHandler.new resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new Base.configurations - @spec_id = "primary" - @pool = @handler.establish_connection(resolver.spec(:arunit, @spec_id)) + @spec_name = "primary" + @pool = @handler.establish_connection(resolver.spec(:arunit, @spec_name)) end - def test_establish_connection_uses_spec_id + def test_establish_connection_uses_spec_name config = {"readonly" => {"adapter" => 'sqlite3'}} resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(config) spec = resolver.spec(:readonly) @@ -22,19 +22,19 @@ module ActiveRecord end def test_retrieve_connection - assert @handler.retrieve_connection(@spec_id) + assert @handler.retrieve_connection(@spec_name) end def test_active_connections? assert !@handler.active_connections? - assert @handler.retrieve_connection(@spec_id) + assert @handler.retrieve_connection(@spec_name) assert @handler.active_connections? @handler.clear_active_connections! assert !@handler.active_connections? end def test_retrieve_connection_pool - assert_not_nil @handler.retrieve_connection_pool(@spec_id) + assert_not_nil @handler.retrieve_connection_pool(@spec_name) end def test_retrieve_connection_pool_with_invalid_id @@ -77,7 +77,7 @@ module ActiveRecord pid = fork { rd.close - pool = @handler.retrieve_connection_pool(@spec_id) + pool = @handler.retrieve_connection_pool(@spec_name) wr.write Marshal.dump pool.schema_cache.size wr.close exit! diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index 36d1e059c7..3bddaf32ec 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -116,14 +116,14 @@ module ActiveRecord "encoding" => "utf8" }, spec) end - def test_spec_id_on_key_lookup + def test_spec_name_on_key_lookup spec = spec(:readonly, 'readonly' => {'adapter' => 'sqlite3'}) - assert_equal "readonly", spec.id + assert_equal "readonly", spec.name end - def test_spec_id_with_inline_config + def test_spec_name_with_inline_config spec = spec({'adapter' => 'sqlite3'}) - assert_equal "primary", spec.id, "should default to primary id" + assert_equal "primary", spec.name, "should default to primary id" end end end diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index b7fc8710ca..2838315bca 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -25,10 +25,10 @@ class MultipleDbTest < ActiveRecord::TestCase end def test_swapping_the_connection - old_spec_id, Course.specification_id = Course.specification_id, "primary" + old_spec_name, Course.specification_name = Course.specification_name, "primary" assert_equal(Entrant.connection, Course.connection) ensure - Course.specification_id = old_spec_id + Course.specification_name = old_spec_name end def test_find -- cgit v1.2.3 From 22a127c57baa533498124e1650d00fb780618ed2 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Fri, 6 May 2016 13:22:49 -0500 Subject: s/specification_name/connection_specification_name --- .../lib/active_record/connection_handling.rb | 20 ++++++++++---------- activerecord/lib/active_record/core.rb | 2 +- .../lib/active_record/tasks/database_tasks.rb | 2 +- activerecord/test/cases/multiple_db_test.rb | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index f363a2d21b..ba763149cc 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -51,7 +51,7 @@ module ActiveRecord resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations # TODO: uses name on establish_connection, for backwards compatibility spec = resolver.spec(spec, self == Base ? "primary" : name) - self.specification_name = spec.name + self.connection_specification_name = spec.name unless respond_to?(spec.adapter_method) raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter" @@ -91,15 +91,15 @@ module ActiveRecord retrieve_connection end - attr_writer :specification_name + attr_writer :connection_specification_name # Return the specification id from this class otherwise look it up # in the parent. - def specification_name - unless defined?(@specification_name) - @specification_name = self == Base ? "primary" : superclass.specification_name + def connection_specification_name + unless defined?(@connection_specification_name) + @connection_specification_name = self == Base ? "primary" : superclass.connection_specification_name end - @specification_name + @connection_specification_name end def connection_id @@ -121,19 +121,19 @@ module ActiveRecord end def connection_pool - connection_handler.retrieve_connection_pool(specification_name) or raise ConnectionNotEstablished + connection_handler.retrieve_connection_pool(connection_specification_name) or raise ConnectionNotEstablished end def retrieve_connection - connection_handler.retrieve_connection(specification_name) + connection_handler.retrieve_connection(connection_specification_name) end # Returns +true+ if Active Record is connected. def connected? - connection_handler.connected?(specification_name) + connection_handler.connected?(connection_specification_name) end - def remove_connection(name = specification_name) + def remove_connection(name = connection_specification_name) connection_handler.remove_connection(name) end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 4abe25e5b7..f936e865e4 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -257,7 +257,7 @@ module ActiveRecord # Returns the Arel engine. def arel_engine # :nodoc: @arel_engine ||= - if Base == self || connection_handler.retrieve_connection_pool(specification_name) + if Base == self || connection_handler.retrieve_connection_pool(connection_specification_name) self else superclass.arel_engine diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 4fbc93496a..0df46d54df 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -117,7 +117,7 @@ module ActiveRecord end def create_all - old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.specification_name) + old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name) each_local_configuration { |configuration| create configuration } if old_pool ActiveRecord::Base.connection_handler.establish_connection(old_pool.spec) diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index 2838315bca..a4fbf579a1 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -25,10 +25,10 @@ class MultipleDbTest < ActiveRecord::TestCase end def test_swapping_the_connection - old_spec_name, Course.specification_name = Course.specification_name, "primary" + old_spec_name, Course.connection_specification_name = Course.connection_specification_name, "primary" assert_equal(Entrant.connection, Course.connection) ensure - Course.specification_name = old_spec_name + Course.connection_specification_name = old_spec_name end def test_find -- cgit v1.2.3 From 36ce6aba980a176b03e675c157f51048985d4730 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Fri, 6 May 2016 15:11:48 -0500 Subject: Update docs for connection handler [skip ci] --- .../active_record/connection_adapters/abstract/connection_pool.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 0498737b20..4ba8ee2706 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -778,8 +778,7 @@ module ActiveRecord end # ConnectionHandler is a collection of ConnectionPool objects. It is used - # for keeping separate connection pools for Active Record models that connect - # to different databases. + # for keeping separate connection pools that connect to different databases. # # For example, suppose that you have 5 models, with the following hierarchy: # @@ -821,6 +820,10 @@ module ActiveRecord # ConnectionHandler accessible via ActiveRecord::Base.connection_handler. # All Active Record models use this handler to determine the connection pool that they # should use. + # + # The ConnectionHandler class is not coupled with the Active models, as it has no knowlodge + # about the model. The model, needs to pass a specification name to the handler, + # in order to lookup the correct connection pool. class ConnectionHandler def initialize # These caches are keyed by klass.name, NOT klass. Keying them by klass -- cgit v1.2.3