activerecord 4.2.11.3 → 5.0.7.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1638 -1132
- data/MIT-LICENSE +2 -2
- data/README.rdoc +7 -8
- data/examples/performance.rb +2 -3
- data/examples/simple.rb +0 -1
- data/lib/active_record.rb +7 -2
- data/lib/active_record/aggregations.rb +34 -21
- data/lib/active_record/association_relation.rb +7 -4
- data/lib/active_record/associations.rb +347 -218
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +22 -10
- data/lib/active_record/associations/association_scope.rb +75 -104
- data/lib/active_record/associations/belongs_to_association.rb +21 -32
- data/lib/active_record/associations/builder/association.rb +28 -34
- data/lib/active_record/associations/builder/belongs_to.rb +43 -18
- data/lib/active_record/associations/builder/collection_association.rb +7 -19
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +16 -11
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +11 -6
- data/lib/active_record/associations/builder/singular_association.rb +13 -11
- data/lib/active_record/associations/collection_association.rb +85 -69
- data/lib/active_record/associations/collection_proxy.rb +104 -46
- data/lib/active_record/associations/foreign_association.rb +1 -1
- data/lib/active_record/associations/has_many_association.rb +21 -78
- data/lib/active_record/associations/has_many_through_association.rb +6 -47
- data/lib/active_record/associations/has_one_association.rb +12 -5
- data/lib/active_record/associations/join_dependency.rb +38 -22
- data/lib/active_record/associations/join_dependency/join_association.rb +15 -14
- data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
- data/lib/active_record/associations/preloader.rb +14 -4
- data/lib/active_record/associations/preloader/association.rb +52 -71
- data/lib/active_record/associations/preloader/collection_association.rb +0 -7
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/singular_association.rb +0 -1
- data/lib/active_record/associations/preloader/through_association.rb +36 -17
- data/lib/active_record/associations/singular_association.rb +13 -1
- data/lib/active_record/associations/through_association.rb +12 -4
- data/lib/active_record/attribute.rb +69 -19
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute_assignment.rb +19 -140
- data/lib/active_record/attribute_decorators.rb +6 -5
- data/lib/active_record/attribute_methods.rb +69 -44
- data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
- data/lib/active_record/attribute_methods/dirty.rb +46 -86
- data/lib/active_record/attribute_methods/primary_key.rb +16 -3
- data/lib/active_record/attribute_methods/query.rb +2 -2
- data/lib/active_record/attribute_methods/read.rb +31 -59
- data/lib/active_record/attribute_methods/serialization.rb +13 -16
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
- data/lib/active_record/attribute_methods/write.rb +13 -37
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set.rb +32 -3
- data/lib/active_record/attribute_set/builder.rb +42 -16
- data/lib/active_record/attributes.rb +199 -81
- data/lib/active_record/autosave_association.rb +54 -17
- data/lib/active_record/base.rb +32 -23
- data/lib/active_record/callbacks.rb +39 -43
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +20 -8
- data/lib/active_record/collection_cache_key.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +467 -189
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -62
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +39 -4
- data/lib/active_record/connection_adapters/abstract/quoting.rb +86 -13
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -188
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +407 -156
- data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -71
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +433 -399
- data/lib/active_record/connection_adapters/column.rb +28 -43
- data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +108 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +25 -166
- data/lib/active_record/connection_adapters/postgresql/column.rb +33 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -72
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +37 -57
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +13 -3
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +56 -19
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +250 -154
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +264 -170
- data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +151 -194
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +37 -14
- data/lib/active_record/core.rb +92 -108
- data/lib/active_record/counter_cache.rb +13 -24
- data/lib/active_record/dynamic_matchers.rb +1 -20
- data/lib/active_record/enum.rb +116 -76
- data/lib/active_record/errors.rb +87 -48
- data/lib/active_record/explain.rb +20 -9
- data/lib/active_record/explain_registry.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +26 -5
- data/lib/active_record/fixtures.rb +77 -41
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +32 -40
- data/lib/active_record/integration.rb +17 -14
- data/lib/active_record/internal_metadata.rb +56 -0
- data/lib/active_record/legacy_yaml_adapter.rb +18 -2
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +15 -15
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +48 -24
- data/lib/active_record/migration.rb +362 -111
- data/lib/active_record/migration/command_recorder.rb +59 -18
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/model_schema.rb +270 -73
- data/lib/active_record/nested_attributes.rb +58 -29
- data/lib/active_record/no_touching.rb +4 -0
- data/lib/active_record/null_relation.rb +16 -8
- data/lib/active_record/persistence.rb +152 -90
- data/lib/active_record/query_cache.rb +18 -23
- data/lib/active_record/querying.rb +12 -11
- data/lib/active_record/railtie.rb +23 -16
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +52 -41
- data/lib/active_record/readonly_attributes.rb +1 -1
- data/lib/active_record/reflection.rb +302 -115
- data/lib/active_record/relation.rb +187 -120
- data/lib/active_record/relation/batches.rb +141 -36
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/calculations.rb +92 -117
- data/lib/active_record/relation/delegation.rb +8 -20
- data/lib/active_record/relation/finder_methods.rb +173 -89
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +16 -42
- data/lib/active_record/relation/predicate_builder.rb +120 -107
- data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +308 -244
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +4 -7
- data/lib/active_record/relation/where_clause.rb +174 -0
- data/lib/active_record/relation/where_clause_factory.rb +38 -0
- data/lib/active_record/result.rb +11 -4
- data/lib/active_record/runtime_registry.rb +1 -1
- data/lib/active_record/sanitization.rb +105 -66
- data/lib/active_record/schema.rb +26 -22
- data/lib/active_record/schema_dumper.rb +54 -37
- data/lib/active_record/schema_migration.rb +11 -14
- data/lib/active_record/scoping.rb +34 -16
- data/lib/active_record/scoping/default.rb +28 -10
- data/lib/active_record/scoping/named.rb +59 -26
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +3 -5
- data/lib/active_record/statement_cache.rb +17 -15
- data/lib/active_record/store.rb +8 -3
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +69 -0
- data/lib/active_record/tasks/database_tasks.rb +66 -49
- data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
- data/lib/active_record/tasks/postgresql_database_tasks.rb +12 -3
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +20 -9
- data/lib/active_record/touch_later.rb +63 -0
- data/lib/active_record/transactions.rb +139 -57
- data/lib/active_record/type.rb +66 -17
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +2 -45
- data/lib/active_record/type/date_time.rb +2 -49
- data/lib/active_record/type/internal/abstract_json.rb +33 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +15 -14
- data/lib/active_record/type/time.rb +10 -16
- data/lib/active_record/type/type_map.rb +4 -4
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/validations.rb +33 -32
- data/lib/active_record/validations/absence.rb +23 -0
- data/lib/active_record/validations/associated.rb +10 -3
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +11 -12
- data/lib/active_record/validations/uniqueness.rb +33 -33
- data/lib/rails/generators/active_record/migration.rb +15 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -5
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
- data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +33 -16
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
- metadata +58 -34
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- data/lib/active_record/serializers/xml_serializer.rb +0 -193
- data/lib/active_record/type/big_integer.rb +0 -13
- data/lib/active_record/type/binary.rb +0 -50
- data/lib/active_record/type/boolean.rb +0 -31
- data/lib/active_record/type/decimal.rb +0 -64
- data/lib/active_record/type/decimal_without_scale.rb +0 -11
- data/lib/active_record/type/decorator.rb +0 -14
- data/lib/active_record/type/float.rb +0 -19
- data/lib/active_record/type/integer.rb +0 -59
- data/lib/active_record/type/mutable.rb +0 -16
- data/lib/active_record/type/numeric.rb +0 -36
- data/lib/active_record/type/string.rb +0 -40
- data/lib/active_record/type/text.rb +0 -11
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/unsigned_integer.rb +0 -15
- data/lib/active_record/type/value.rb +0 -110
data/lib/active_record/base.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
require 'yaml'
|
2
|
-
require 'set'
|
3
2
|
require 'active_support/benchmarkable'
|
4
3
|
require 'active_support/dependencies'
|
5
4
|
require 'active_support/descendants_tracker'
|
6
5
|
require 'active_support/time'
|
7
6
|
require 'active_support/core_ext/module/attribute_accessors'
|
8
|
-
require 'active_support/core_ext/class/delegating_attributes'
|
9
7
|
require 'active_support/core_ext/array/extract_options'
|
10
8
|
require 'active_support/core_ext/hash/deep_merge'
|
11
9
|
require 'active_support/core_ext/hash/slice'
|
@@ -15,13 +13,13 @@ require 'active_support/core_ext/kernel/singleton_class'
|
|
15
13
|
require 'active_support/core_ext/module/introspection'
|
16
14
|
require 'active_support/core_ext/object/duplicable'
|
17
15
|
require 'active_support/core_ext/class/subclasses'
|
18
|
-
require 'arel'
|
19
16
|
require 'active_record/attribute_decorators'
|
20
17
|
require 'active_record/errors'
|
21
18
|
require 'active_record/log_subscriber'
|
22
19
|
require 'active_record/explain_subscriber'
|
23
20
|
require 'active_record/relation/delegation'
|
24
21
|
require 'active_record/attributes'
|
22
|
+
require 'active_record/type_caster'
|
25
23
|
|
26
24
|
module ActiveRecord #:nodoc:
|
27
25
|
# = Active Record
|
@@ -133,14 +131,11 @@ module ActiveRecord #:nodoc:
|
|
133
131
|
# end
|
134
132
|
# end
|
135
133
|
#
|
136
|
-
# You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
|
137
|
-
# or <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
|
138
|
-
#
|
139
134
|
# == Attribute query methods
|
140
135
|
#
|
141
136
|
# In addition to the basic accessors, query methods are also automatically available on the Active Record object.
|
142
137
|
# Query methods allow you to test whether an attribute value is present.
|
143
|
-
#
|
138
|
+
# Additionally, when dealing with numeric values, a query method will return false if the value is zero.
|
144
139
|
#
|
145
140
|
# For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
|
146
141
|
# to determine whether the user has a name:
|
@@ -171,10 +166,11 @@ module ActiveRecord #:nodoc:
|
|
171
166
|
# <tt>Person.find_by_user_name(user_name)</tt>.
|
172
167
|
#
|
173
168
|
# It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
|
174
|
-
#
|
169
|
+
# ActiveRecord::RecordNotFound error if they do not return any records,
|
175
170
|
# like <tt>Person.find_by_last_name!</tt>.
|
176
171
|
#
|
177
|
-
# It's also possible to use multiple attributes in the same
|
172
|
+
# It's also possible to use multiple attributes in the same <tt>find_by_</tt> by separating them with
|
173
|
+
# "_and_".
|
178
174
|
#
|
179
175
|
# Person.find_by(user_name: user_name, password: password)
|
180
176
|
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
|
@@ -186,7 +182,8 @@ module ActiveRecord #:nodoc:
|
|
186
182
|
# == Saving arrays, hashes, and other non-mappable objects in text columns
|
187
183
|
#
|
188
184
|
# Active Record can serialize any object in text columns using YAML. To do so, you must
|
189
|
-
# specify this with a call to the class method
|
185
|
+
# specify this with a call to the class method
|
186
|
+
# {serialize}[rdoc-ref:AttributeMethods::Serialization::ClassMethods#serialize].
|
190
187
|
# This makes it possible to store arrays, hashes, and other non-mappable objects without doing
|
191
188
|
# any additional work.
|
192
189
|
#
|
@@ -226,39 +223,47 @@ module ActiveRecord #:nodoc:
|
|
226
223
|
#
|
227
224
|
# == Connection to multiple databases in different models
|
228
225
|
#
|
229
|
-
# Connections are usually created through
|
226
|
+
# Connections are usually created through
|
227
|
+
# {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] and retrieved
|
230
228
|
# by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this
|
231
229
|
# connection. But you can also set a class-specific connection. For example, if Course is an
|
232
230
|
# ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
|
233
231
|
# and Course and all of its subclasses will use this connection instead.
|
234
232
|
#
|
235
233
|
# This feature is implemented by keeping a connection pool in ActiveRecord::Base that is
|
236
|
-
# a
|
234
|
+
# a hash indexed by the class. If a connection is requested, the
|
235
|
+
# {ActiveRecord::Base.retrieve_connection}[rdoc-ref:ConnectionHandling#retrieve_connection] method
|
237
236
|
# will go up the class-hierarchy until a connection is found in the connection pool.
|
238
237
|
#
|
239
238
|
# == Exceptions
|
240
239
|
#
|
241
240
|
# * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
|
242
|
-
# * AdapterNotSpecified - The configuration hash used in
|
243
|
-
#
|
244
|
-
#
|
245
|
-
#
|
241
|
+
# * AdapterNotSpecified - The configuration hash used in
|
242
|
+
# {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection]
|
243
|
+
# didn't include an <tt>:adapter</tt> key.
|
244
|
+
# * AdapterNotFound - The <tt>:adapter</tt> key used in
|
245
|
+
# {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection]
|
246
|
+
# specified a non-existent adapter
|
246
247
|
# (or a bad spelling of an existing one).
|
247
248
|
# * AssociationTypeMismatch - The object assigned to the association wasn't of the type
|
248
249
|
# specified in the association definition.
|
249
250
|
# * AttributeAssignmentError - An error occurred while doing a mass assignment through the
|
250
|
-
#
|
251
|
+
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
|
251
252
|
# You can inspect the +attribute+ property of the exception object to determine which attribute
|
252
253
|
# triggered the error.
|
253
|
-
# * ConnectionNotEstablished - No connection has been established.
|
254
|
-
# before querying.
|
254
|
+
# * ConnectionNotEstablished - No connection has been established.
|
255
|
+
# Use {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] before querying.
|
255
256
|
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
|
256
|
-
#
|
257
|
+
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
|
258
|
+
# The +errors+ property of this exception contains an array of
|
257
259
|
# AttributeAssignmentError
|
258
260
|
# objects that should be inspected to determine which attributes triggered the errors.
|
259
|
-
# * RecordInvalid - raised by save! and
|
260
|
-
#
|
261
|
-
#
|
261
|
+
# * RecordInvalid - raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
|
262
|
+
# {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]
|
263
|
+
# when the record is invalid.
|
264
|
+
# * RecordNotFound - No record responded to the {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method.
|
265
|
+
# Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
|
266
|
+
# Some {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] calls do not raise this exception to signal
|
262
267
|
# nothing was found, please check its documentation for further details.
|
263
268
|
# * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
|
264
269
|
# * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
|
@@ -280,6 +285,7 @@ module ActiveRecord #:nodoc:
|
|
280
285
|
extend Explain
|
281
286
|
extend Enum
|
282
287
|
extend Delegation::DelegateCache
|
288
|
+
extend CollectionCacheKey
|
283
289
|
|
284
290
|
include Core
|
285
291
|
include Persistence
|
@@ -306,10 +312,13 @@ module ActiveRecord #:nodoc:
|
|
306
312
|
include NestedAttributes
|
307
313
|
include Aggregations
|
308
314
|
include Transactions
|
315
|
+
include TouchLater
|
309
316
|
include NoTouching
|
310
317
|
include Reflection
|
311
318
|
include Serialization
|
312
319
|
include Store
|
320
|
+
include SecureToken
|
321
|
+
include Suppressor
|
313
322
|
end
|
314
323
|
|
315
324
|
ActiveSupport.run_load_hooks(:active_record, Base)
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module ActiveRecord
|
2
|
-
# = Active Record Callbacks
|
2
|
+
# = Active Record \Callbacks
|
3
3
|
#
|
4
|
-
# Callbacks are hooks into the life cycle of an Active Record object that allow you to trigger logic
|
4
|
+
# \Callbacks are hooks into the life cycle of an Active Record object that allow you to trigger logic
|
5
5
|
# before or after an alteration of the object state. This can be used to make sure that associated and
|
6
|
-
# dependent objects are deleted when
|
7
|
-
# before they're validated (by overwriting +before_validation+).
|
8
|
-
# the
|
6
|
+
# dependent objects are deleted when {ActiveRecord::Base#destroy}[rdoc-ref:Persistence#destroy] is called (by overwriting +before_destroy+) or
|
7
|
+
# to massage attributes before they're validated (by overwriting +before_validation+).
|
8
|
+
# As an example of the callbacks initiated, consider the {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] call for a new record:
|
9
9
|
#
|
10
10
|
# * (-) <tt>save</tt>
|
11
11
|
# * (-) <tt>valid</tt>
|
@@ -20,7 +20,7 @@ module ActiveRecord
|
|
20
20
|
# * (7) <tt>after_commit</tt>
|
21
21
|
#
|
22
22
|
# Also, an <tt>after_rollback</tt> callback can be configured to be triggered whenever a rollback is issued.
|
23
|
-
# Check out
|
23
|
+
# Check out ActiveRecord::Transactions for more details about <tt>after_commit</tt> and
|
24
24
|
# <tt>after_rollback</tt>.
|
25
25
|
#
|
26
26
|
# Additionally, an <tt>after_touch</tt> callback is triggered whenever an
|
@@ -31,7 +31,7 @@ module ActiveRecord
|
|
31
31
|
# are instantiated as well.
|
32
32
|
#
|
33
33
|
# There are nineteen callbacks in total, which give you immense power to react and prepare for each state in the
|
34
|
-
# Active Record life cycle. The sequence for calling
|
34
|
+
# Active Record life cycle. The sequence for calling {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] for an existing record is similar,
|
35
35
|
# except that each <tt>_create</tt> callback is replaced by the corresponding <tt>_update</tt> callback.
|
36
36
|
#
|
37
37
|
# Examples:
|
@@ -53,9 +53,9 @@ module ActiveRecord
|
|
53
53
|
# end
|
54
54
|
#
|
55
55
|
# class Firm < ActiveRecord::Base
|
56
|
-
# #
|
57
|
-
# before_destroy { |record| Person.
|
58
|
-
# before_destroy { |record| Client.
|
56
|
+
# # Disables access to the system, for associated clients and people when the firm is destroyed
|
57
|
+
# before_destroy { |record| Person.where(firm_id: record.id).update_all(access: 'disabled') }
|
58
|
+
# before_destroy { |record| Client.where(client_of: record.id).update_all(access: 'disabled') }
|
59
59
|
# end
|
60
60
|
#
|
61
61
|
# == Inheritable callback queues
|
@@ -175,43 +175,30 @@ module ActiveRecord
|
|
175
175
|
# end
|
176
176
|
# end
|
177
177
|
#
|
178
|
-
# The callback macros usually accept a symbol for the method they're supposed to run, but you can also
|
179
|
-
# pass a "method string", which will then be evaluated within the binding of the callback. Example:
|
180
|
-
#
|
181
|
-
# class Topic < ActiveRecord::Base
|
182
|
-
# before_destroy 'self.class.delete_all "parent_id = #{id}"'
|
183
|
-
# end
|
184
|
-
#
|
185
|
-
# Notice that single quotes (') are used so the <tt>#{id}</tt> part isn't evaluated until the callback
|
186
|
-
# is triggered. Also note that these inline callbacks can be stacked just like the regular ones:
|
187
|
-
#
|
188
|
-
# class Topic < ActiveRecord::Base
|
189
|
-
# before_destroy 'self.class.delete_all "parent_id = #{id}"',
|
190
|
-
# 'puts "Evaluated after parents are destroyed"'
|
191
|
-
# end
|
192
|
-
#
|
193
178
|
# == <tt>before_validation*</tt> returning statements
|
194
179
|
#
|
195
|
-
# If the
|
196
|
-
# aborted and
|
197
|
-
# ActiveRecord::
|
180
|
+
# If the +before_validation+ callback throws +:abort+, the process will be
|
181
|
+
# aborted and {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will return +false+.
|
182
|
+
# If {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] is called it will raise an ActiveRecord::RecordInvalid exception.
|
183
|
+
# Nothing will be appended to the errors object.
|
198
184
|
#
|
199
185
|
# == Canceling callbacks
|
200
186
|
#
|
201
|
-
# If a <tt>before_*</tt> callback
|
202
|
-
# cancelled.
|
187
|
+
# If a <tt>before_*</tt> callback throws +:abort+, all the later callbacks and
|
188
|
+
# the associated action are cancelled.
|
203
189
|
# Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
|
204
190
|
# methods on the model, which are called last.
|
205
191
|
#
|
206
192
|
# == Ordering callbacks
|
207
193
|
#
|
208
194
|
# Sometimes the code needs that the callbacks execute in a specific order. For example, a +before_destroy+
|
209
|
-
# callback (+log_children+ in this case) should be executed before the children get destroyed by the
|
195
|
+
# callback (+log_children+ in this case) should be executed before the children get destroyed by the
|
196
|
+
# <tt>dependent: :destroy</tt> option.
|
210
197
|
#
|
211
198
|
# Let's look at the code below:
|
212
199
|
#
|
213
200
|
# class Topic < ActiveRecord::Base
|
214
|
-
# has_many :children, dependent: destroy
|
201
|
+
# has_many :children, dependent: :destroy
|
215
202
|
#
|
216
203
|
# before_destroy :log_children
|
217
204
|
#
|
@@ -222,10 +209,11 @@ module ActiveRecord
|
|
222
209
|
# end
|
223
210
|
#
|
224
211
|
# In this case, the problem is that when the +before_destroy+ callback is executed, the children are not available
|
225
|
-
# because the
|
212
|
+
# because the {ActiveRecord::Base#destroy}[rdoc-ref:Persistence#destroy] callback gets executed first.
|
213
|
+
# You can use the +prepend+ option on the +before_destroy+ callback to avoid this.
|
226
214
|
#
|
227
215
|
# class Topic < ActiveRecord::Base
|
228
|
-
# has_many :children, dependent: destroy
|
216
|
+
# has_many :children, dependent: :destroy
|
229
217
|
#
|
230
218
|
# before_destroy :log_children, prepend: true
|
231
219
|
#
|
@@ -235,23 +223,23 @@ module ActiveRecord
|
|
235
223
|
# end
|
236
224
|
# end
|
237
225
|
#
|
238
|
-
# This way, the +before_destroy+ gets executed before the <tt>dependent: destroy</tt> is called, and the data is still available.
|
226
|
+
# This way, the +before_destroy+ gets executed before the <tt>dependent: :destroy</tt> is called, and the data is still available.
|
239
227
|
#
|
240
|
-
# == Transactions
|
228
|
+
# == \Transactions
|
241
229
|
#
|
242
|
-
# The entire callback chain of a
|
243
|
-
# within a transaction. That includes <tt>after_*</tt> hooks.
|
244
|
-
# goes fine a COMMIT is executed once the chain has been completed.
|
230
|
+
# The entire callback chain of a {#save}[rdoc-ref:Persistence#save], {#save!}[rdoc-ref:Persistence#save!],
|
231
|
+
# or {#destroy}[rdoc-ref:Persistence#destroy] call runs within a transaction. That includes <tt>after_*</tt> hooks.
|
232
|
+
# If everything goes fine a COMMIT is executed once the chain has been completed.
|
245
233
|
#
|
246
234
|
# If a <tt>before_*</tt> callback cancels the action a ROLLBACK is issued. You
|
247
235
|
# can also trigger a ROLLBACK raising an exception in any of the callbacks,
|
248
236
|
# including <tt>after_*</tt> hooks. Note, however, that in that case the client
|
249
|
-
# needs to be aware of it because an ordinary
|
237
|
+
# needs to be aware of it because an ordinary {#save}[rdoc-ref:Persistence#save] will raise such exception
|
250
238
|
# instead of quietly returning +false+.
|
251
239
|
#
|
252
240
|
# == Debugging callbacks
|
253
241
|
#
|
254
|
-
# The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object.
|
242
|
+
# The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. Active Model \Callbacks support
|
255
243
|
# <tt>:before</tt>, <tt>:after</tt> and <tt>:around</tt> as values for the <tt>kind</tt> property. The <tt>kind</tt> property
|
256
244
|
# defines what part of the chain the callback runs in.
|
257
245
|
#
|
@@ -277,7 +265,7 @@ module ActiveRecord
|
|
277
265
|
:before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
|
278
266
|
]
|
279
267
|
|
280
|
-
module ClassMethods
|
268
|
+
module ClassMethods # :nodoc:
|
281
269
|
include ActiveModel::Callbacks
|
282
270
|
end
|
283
271
|
|
@@ -289,7 +277,15 @@ module ActiveRecord
|
|
289
277
|
end
|
290
278
|
|
291
279
|
def destroy #:nodoc:
|
280
|
+
@_destroy_callback_already_called ||= false
|
281
|
+
return if @_destroy_callback_already_called
|
282
|
+
@_destroy_callback_already_called = true
|
292
283
|
_run_destroy_callbacks { super }
|
284
|
+
rescue RecordNotDestroyed => e
|
285
|
+
@_association_destroy_exception = e
|
286
|
+
false
|
287
|
+
ensure
|
288
|
+
@_destroy_callback_already_called = false
|
293
289
|
end
|
294
290
|
|
295
291
|
def touch(*) #:nodoc:
|
@@ -298,7 +294,7 @@ module ActiveRecord
|
|
298
294
|
|
299
295
|
private
|
300
296
|
|
301
|
-
def create_or_update #:nodoc:
|
297
|
+
def create_or_update(*) #:nodoc:
|
302
298
|
_run_save_callbacks { super }
|
303
299
|
end
|
304
300
|
|
@@ -8,15 +8,13 @@ module ActiveRecord
|
|
8
8
|
|
9
9
|
def initialize(object_class = Object)
|
10
10
|
@object_class = object_class
|
11
|
+
check_arity_of_constructor
|
11
12
|
end
|
12
13
|
|
13
14
|
def dump(obj)
|
14
15
|
return if obj.nil?
|
15
16
|
|
16
|
-
|
17
|
-
raise SerializationTypeMismatch,
|
18
|
-
"Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
|
19
|
-
end
|
17
|
+
assert_valid_value(obj)
|
20
18
|
YAML.dump obj
|
21
19
|
end
|
22
20
|
|
@@ -25,14 +23,28 @@ module ActiveRecord
|
|
25
23
|
return yaml unless yaml.is_a?(String) && yaml =~ /^---/
|
26
24
|
obj = YAML.load(yaml)
|
27
25
|
|
28
|
-
|
29
|
-
raise SerializationTypeMismatch,
|
30
|
-
"Attribute was supposed to be a #{object_class}, but was a #{obj.class}"
|
31
|
-
end
|
26
|
+
assert_valid_value(obj)
|
32
27
|
obj ||= object_class.new if object_class != Object
|
33
28
|
|
34
29
|
obj
|
35
30
|
end
|
31
|
+
|
32
|
+
def assert_valid_value(obj)
|
33
|
+
unless obj.nil? || obj.is_a?(object_class)
|
34
|
+
raise SerializationTypeMismatch,
|
35
|
+
"Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def check_arity_of_constructor
|
42
|
+
begin
|
43
|
+
load(nil)
|
44
|
+
rescue ArgumentError
|
45
|
+
raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
|
46
|
+
end
|
47
|
+
end
|
36
48
|
end
|
37
49
|
end
|
38
50
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module CollectionCacheKey
|
3
|
+
|
4
|
+
def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
|
5
|
+
query_signature = Digest::MD5.hexdigest(collection.to_sql)
|
6
|
+
key = "#{collection.model_name.cache_key}/query-#{query_signature}"
|
7
|
+
|
8
|
+
if collection.loaded?
|
9
|
+
size = collection.size
|
10
|
+
if size > 0
|
11
|
+
timestamp = collection.max_by(×tamp_column)._read_attribute(timestamp_column)
|
12
|
+
end
|
13
|
+
else
|
14
|
+
column_type = type_for_attribute(timestamp_column.to_s)
|
15
|
+
column = "#{connection.quote_table_name(collection.table_name)}.#{connection.quote_column_name(timestamp_column)}"
|
16
|
+
select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
|
17
|
+
|
18
|
+
if collection.limit_value || collection.offset_value
|
19
|
+
query = collection.spawn
|
20
|
+
query.select_values = [column]
|
21
|
+
subquery_alias = "subquery_for_cache_key"
|
22
|
+
subquery_column = "#{subquery_alias}.#{timestamp_column}"
|
23
|
+
subquery = query.arel.as(subquery_alias)
|
24
|
+
arel = Arel::SelectManager.new(query.engine).project(select_values % subquery_column).from(subquery)
|
25
|
+
else
|
26
|
+
query = collection.unscope(:order)
|
27
|
+
query.select_values = [select_values % column]
|
28
|
+
arel = query.arel
|
29
|
+
end
|
30
|
+
|
31
|
+
result = connection.select_one(arel, nil, query.bound_attributes)
|
32
|
+
|
33
|
+
if result.blank?
|
34
|
+
size = 0
|
35
|
+
timestamp = nil
|
36
|
+
else
|
37
|
+
size = result["size"]
|
38
|
+
timestamp = column_type.deserialize(result["timestamp"])
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
if timestamp
|
44
|
+
"#{key}-#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
|
45
|
+
else
|
46
|
+
"#{key}-#{size}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
require 'thread'
|
2
|
-
require '
|
2
|
+
require 'concurrent/map'
|
3
3
|
require 'monitor'
|
4
|
-
require 'set'
|
5
|
-
require 'active_support/core_ext/string/filters'
|
6
4
|
|
7
5
|
module ActiveRecord
|
8
6
|
# Raised when a connection could not be obtained within the connection
|
@@ -11,6 +9,13 @@ module ActiveRecord
|
|
11
9
|
class ConnectionTimeoutError < ConnectionNotEstablished
|
12
10
|
end
|
13
11
|
|
12
|
+
# Raised when a pool was unable to get ahold of all its connections
|
13
|
+
# to perform a "group" action such as
|
14
|
+
# {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!]
|
15
|
+
# or {ActiveRecord::Base.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
|
16
|
+
class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
|
17
|
+
end
|
18
|
+
|
14
19
|
module ConnectionAdapters
|
15
20
|
# Connection pool base class for managing Active Record database
|
16
21
|
# connections.
|
@@ -33,17 +38,18 @@ module ActiveRecord
|
|
33
38
|
# Connections can be obtained and used from a connection pool in several
|
34
39
|
# ways:
|
35
40
|
#
|
36
|
-
# 1. Simply use ActiveRecord::Base.connection
|
41
|
+
# 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection]
|
42
|
+
# as with Active Record 2.1 and
|
37
43
|
# earlier (pre-connection-pooling). Eventually, when you're done with
|
38
44
|
# the connection(s) and wish it to be returned to the pool, you call
|
39
|
-
# ActiveRecord::Base.clear_active_connections
|
40
|
-
# default behavior for Active Record when used in conjunction with
|
45
|
+
# {ActiveRecord::Base.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
|
46
|
+
# This will be the default behavior for Active Record when used in conjunction with
|
41
47
|
# Action Pack's request handling cycle.
|
42
48
|
# 2. Manually check out a connection from the pool with
|
43
|
-
# ActiveRecord::Base.connection_pool.checkout. You are responsible for
|
49
|
+
# {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
|
44
50
|
# returning this connection to the pool when finished by calling
|
45
|
-
# ActiveRecord::Base.connection_pool.checkin(connection).
|
46
|
-
# 3. Use ActiveRecord::Base.connection_pool.with_connection(&block), which
|
51
|
+
# {ActiveRecord::Base.connection_pool.checkin(connection)}[rdoc-ref:#checkin].
|
52
|
+
# 3. Use {ActiveRecord::Base.connection_pool.with_connection(&block)}[rdoc-ref:#with_connection], which
|
47
53
|
# obtains a connection, yields it as the sole argument to the block,
|
48
54
|
# and returns it to the pool after the block completes.
|
49
55
|
#
|
@@ -64,6 +70,15 @@ module ActiveRecord
|
|
64
70
|
# connection at the end of a thread or a thread dies unexpectedly.
|
65
71
|
# Regardless of this setting, the Reaper will be invoked before every
|
66
72
|
# blocking wait. (Default nil, which means don't schedule the Reaper).
|
73
|
+
#
|
74
|
+
#--
|
75
|
+
# Synchronization policy:
|
76
|
+
# * all public methods can be called outside +synchronize+
|
77
|
+
# * access to these i-vars needs to be in +synchronize+:
|
78
|
+
# * @connections
|
79
|
+
# * @now_connecting
|
80
|
+
# * private methods that require being called in a +synchronize+ blocks
|
81
|
+
# are now explicitly documented
|
67
82
|
class ConnectionPool
|
68
83
|
# Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
|
69
84
|
# with which it shares a Monitor. But could be a generic Queue.
|
@@ -122,25 +137,23 @@ module ActiveRecord
|
|
122
137
|
# greater than the number of threads currently waiting (that
|
123
138
|
# is, don't jump ahead in line). Otherwise, return nil.
|
124
139
|
#
|
125
|
-
# If +timeout+ is given, block if
|
140
|
+
# If +timeout+ is given, block if there is no element
|
126
141
|
# available, waiting up to +timeout+ seconds for an element to
|
127
142
|
# become available.
|
128
143
|
#
|
129
144
|
# Raises:
|
130
|
-
# - ConnectionTimeoutError if +timeout+ is given and no element
|
131
|
-
# becomes available
|
145
|
+
# - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element
|
146
|
+
# becomes available within +timeout+ seconds,
|
132
147
|
def poll(timeout = nil)
|
133
|
-
synchronize
|
134
|
-
if timeout
|
135
|
-
no_wait_poll || wait_poll(timeout)
|
136
|
-
else
|
137
|
-
no_wait_poll
|
138
|
-
end
|
139
|
-
end
|
148
|
+
synchronize { internal_poll(timeout) }
|
140
149
|
end
|
141
150
|
|
142
151
|
private
|
143
152
|
|
153
|
+
def internal_poll(timeout)
|
154
|
+
no_wait_poll || (timeout && wait_poll(timeout))
|
155
|
+
end
|
156
|
+
|
144
157
|
def synchronize(&block)
|
145
158
|
@lock.synchronize(&block)
|
146
159
|
end
|
@@ -151,7 +164,7 @@ module ActiveRecord
|
|
151
164
|
end
|
152
165
|
|
153
166
|
# A thread can remove an element from the queue without
|
154
|
-
# waiting if
|
167
|
+
# waiting if and only if the number of currently available
|
155
168
|
# connections is strictly greater than the number of waiting
|
156
169
|
# threads.
|
157
170
|
def can_remove_no_wait?
|
@@ -184,7 +197,7 @@ module ActiveRecord
|
|
184
197
|
|
185
198
|
elapsed = Time.now - t0
|
186
199
|
if elapsed >= timeout
|
187
|
-
msg = 'could not obtain a
|
200
|
+
msg = 'could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use' %
|
188
201
|
[timeout, elapsed]
|
189
202
|
raise ConnectionTimeoutError, msg
|
190
203
|
end
|
@@ -194,6 +207,80 @@ module ActiveRecord
|
|
194
207
|
end
|
195
208
|
end
|
196
209
|
|
210
|
+
# Adds the ability to turn a basic fair FIFO queue into one
|
211
|
+
# biased to some thread.
|
212
|
+
module BiasableQueue # :nodoc:
|
213
|
+
class BiasedConditionVariable # :nodoc:
|
214
|
+
# semantics of condition variables guarantee that +broadcast+, +broadcast_on_biased+,
|
215
|
+
# +signal+ and +wait+ methods are only called while holding a lock
|
216
|
+
def initialize(lock, other_cond, preferred_thread)
|
217
|
+
@real_cond = lock.new_cond
|
218
|
+
@other_cond = other_cond
|
219
|
+
@preferred_thread = preferred_thread
|
220
|
+
@num_waiting_on_real_cond = 0
|
221
|
+
end
|
222
|
+
|
223
|
+
def broadcast
|
224
|
+
broadcast_on_biased
|
225
|
+
@other_cond.broadcast
|
226
|
+
end
|
227
|
+
|
228
|
+
def broadcast_on_biased
|
229
|
+
@num_waiting_on_real_cond = 0
|
230
|
+
@real_cond.broadcast
|
231
|
+
end
|
232
|
+
|
233
|
+
def signal
|
234
|
+
if @num_waiting_on_real_cond > 0
|
235
|
+
@num_waiting_on_real_cond -= 1
|
236
|
+
@real_cond
|
237
|
+
else
|
238
|
+
@other_cond
|
239
|
+
end.signal
|
240
|
+
end
|
241
|
+
|
242
|
+
def wait(timeout)
|
243
|
+
if Thread.current == @preferred_thread
|
244
|
+
@num_waiting_on_real_cond += 1
|
245
|
+
@real_cond
|
246
|
+
else
|
247
|
+
@other_cond
|
248
|
+
end.wait(timeout)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def with_a_bias_for(thread)
|
253
|
+
previous_cond = nil
|
254
|
+
new_cond = nil
|
255
|
+
synchronize do
|
256
|
+
previous_cond = @cond
|
257
|
+
@cond = new_cond = BiasedConditionVariable.new(@lock, @cond, thread)
|
258
|
+
end
|
259
|
+
yield
|
260
|
+
ensure
|
261
|
+
synchronize do
|
262
|
+
@cond = previous_cond if previous_cond
|
263
|
+
new_cond.broadcast_on_biased if new_cond # wake up any remaining sleepers
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Connections must be leased while holding the main pool mutex. This is
|
269
|
+
# an internal subclass that also +.leases+ returned connections while
|
270
|
+
# still in queue's critical section (queue synchronizes with the same
|
271
|
+
# +@lock+ as the main pool) so that a returned connection is already
|
272
|
+
# leased and there is no need to re-enter synchronized block.
|
273
|
+
class ConnectionLeasingQueue < Queue # :nodoc:
|
274
|
+
include BiasableQueue
|
275
|
+
|
276
|
+
private
|
277
|
+
def internal_poll(timeout)
|
278
|
+
conn = super
|
279
|
+
conn.lease if conn
|
280
|
+
conn
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
197
284
|
# Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
|
198
285
|
# A reaper instantiated with a nil frequency will never reap the
|
199
286
|
# connection pool.
|
@@ -220,8 +307,9 @@ module ActiveRecord
|
|
220
307
|
end
|
221
308
|
|
222
309
|
include MonitorMixin
|
310
|
+
include QueryCache::ConnectionPoolConfiguration
|
223
311
|
|
224
|
-
attr_accessor :automatic_reconnect, :checkout_timeout
|
312
|
+
attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
|
225
313
|
attr_reader :spec, :connections, :size, :reaper
|
226
314
|
|
227
315
|
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
|
@@ -242,56 +330,74 @@ module ActiveRecord
|
|
242
330
|
# default max pool size to 5
|
243
331
|
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
|
244
332
|
|
245
|
-
# The cache of reserved connections
|
246
|
-
|
333
|
+
# The cache of threads mapped to reserved connections, the sole purpose
|
334
|
+
# of the cache is to speed-up +connection+ method, it is not the authoritative
|
335
|
+
# registry of which thread owns which connection, that is tracked by
|
336
|
+
# +connection.owner+ attr on each +connection+ instance.
|
337
|
+
# The invariant works like this: if there is mapping of <tt>thread => conn</tt>,
|
338
|
+
# then that +thread+ does indeed own that +conn+, however an absence of a such
|
339
|
+
# mapping does not mean that the +thread+ doesn't own the said connection, in
|
340
|
+
# that case +conn.owner+ attr should be consulted.
|
341
|
+
# Access and modification of +@thread_cached_conns+ does not require
|
342
|
+
# synchronization.
|
343
|
+
@thread_cached_conns = Concurrent::Map.new(:initial_capacity => @size)
|
247
344
|
|
248
345
|
@connections = []
|
249
346
|
@automatic_reconnect = true
|
250
347
|
|
251
|
-
|
348
|
+
# Connection pool allows for concurrent (outside the main +synchronize+ section)
|
349
|
+
# establishment of new connections. This variable tracks the number of threads
|
350
|
+
# currently in the process of independently establishing connections to the DB.
|
351
|
+
@now_connecting = 0
|
352
|
+
|
353
|
+
@threads_blocking_new_connections = 0
|
354
|
+
|
355
|
+
@available = ConnectionLeasingQueue.new self
|
252
356
|
end
|
253
357
|
|
254
358
|
# Retrieve the connection associated with the current thread, or call
|
255
359
|
# #checkout to obtain one if necessary.
|
256
360
|
#
|
257
361
|
# #connection can be called any number of times; the connection is
|
258
|
-
# held in a
|
362
|
+
# held in a cache keyed by a thread.
|
259
363
|
def connection
|
260
|
-
|
261
|
-
# (ThreadSafe::Cache's lookups have volatile semantics)
|
262
|
-
@reserved_connections[current_connection_id] || synchronize do
|
263
|
-
@reserved_connections[current_connection_id] ||= checkout
|
264
|
-
end
|
364
|
+
@thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout
|
265
365
|
end
|
266
366
|
|
267
367
|
# Is there an open connection that is being used for the current thread?
|
368
|
+
#
|
369
|
+
# This method only works for connections that have been obtained through
|
370
|
+
# #connection or #with_connection methods, connections obtained through
|
371
|
+
# #checkout will not be detected by #active_connection?
|
268
372
|
def active_connection?
|
269
|
-
|
270
|
-
@reserved_connections.fetch(current_connection_id) {
|
271
|
-
return false
|
272
|
-
}.in_use?
|
273
|
-
end
|
373
|
+
@thread_cached_conns[connection_cache_key(Thread.current)]
|
274
374
|
end
|
275
375
|
|
276
376
|
# Signal that the thread is finished with the current connection.
|
277
377
|
# #release_connection releases the connection-thread association
|
278
378
|
# and returns the connection to the pool.
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
379
|
+
#
|
380
|
+
# This method only works for connections that have been obtained through
|
381
|
+
# #connection or #with_connection methods, connections obtained through
|
382
|
+
# #checkout will not be automatically released.
|
383
|
+
def release_connection(owner_thread = Thread.current)
|
384
|
+
if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
|
385
|
+
checkin conn
|
283
386
|
end
|
284
387
|
end
|
285
388
|
|
286
|
-
# If a connection
|
389
|
+
# If a connection obtained through #connection or #with_connection methods
|
390
|
+
# already exists yield it to the block. If no such connection
|
287
391
|
# exists checkout a connection, yield it to the block, and checkin the
|
288
392
|
# connection when finished.
|
289
393
|
def with_connection
|
290
|
-
|
291
|
-
|
292
|
-
|
394
|
+
unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
|
395
|
+
conn = connection
|
396
|
+
fresh_connection = true
|
397
|
+
end
|
398
|
+
yield conn
|
293
399
|
ensure
|
294
|
-
release_connection
|
400
|
+
release_connection if fresh_connection
|
295
401
|
end
|
296
402
|
|
297
403
|
# Returns true if a connection has already been opened.
|
@@ -300,36 +406,72 @@ module ActiveRecord
|
|
300
406
|
end
|
301
407
|
|
302
408
|
# Disconnects all connections in the pool, and clears the pool.
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
409
|
+
#
|
410
|
+
# Raises:
|
411
|
+
# - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
|
412
|
+
# connections in the pool within a timeout interval (default duration is
|
413
|
+
# <tt>spec.config[:checkout_timeout] * 2</tt> seconds).
|
414
|
+
def disconnect(raise_on_acquisition_timeout = true)
|
415
|
+
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
|
416
|
+
synchronize do
|
417
|
+
@connections.each do |conn|
|
418
|
+
if conn.in_use?
|
419
|
+
conn.steal!
|
420
|
+
checkin conn
|
421
|
+
end
|
422
|
+
conn.disconnect!
|
423
|
+
end
|
424
|
+
@connections = []
|
425
|
+
@available.clear
|
309
426
|
end
|
310
|
-
@connections = []
|
311
|
-
@available.clear
|
312
427
|
end
|
313
428
|
end
|
314
429
|
|
315
|
-
#
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
430
|
+
# Disconnects all connections in the pool, and clears the pool.
|
431
|
+
#
|
432
|
+
# The pool first tries to gain ownership of all connections, if unable to
|
433
|
+
# do so within a timeout interval (default duration is
|
434
|
+
# <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool is forcefully
|
435
|
+
# disconnected without any regard for other connection owning threads.
|
436
|
+
def disconnect!
|
437
|
+
disconnect(false)
|
438
|
+
end
|
439
|
+
|
440
|
+
# Clears the cache which maps classes and re-connects connections that
|
441
|
+
# require reloading.
|
442
|
+
#
|
443
|
+
# Raises:
|
444
|
+
# - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
|
445
|
+
# connections in the pool within a timeout interval (default duration is
|
446
|
+
# <tt>spec.config[:checkout_timeout] * 2</tt> seconds).
|
447
|
+
def clear_reloadable_connections(raise_on_acquisition_timeout = true)
|
448
|
+
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
|
449
|
+
synchronize do
|
450
|
+
@connections.each do |conn|
|
451
|
+
if conn.in_use?
|
452
|
+
conn.steal!
|
453
|
+
checkin conn
|
454
|
+
end
|
455
|
+
conn.disconnect! if conn.requires_reloading?
|
456
|
+
end
|
457
|
+
@connections.delete_if(&:requires_reloading?)
|
458
|
+
@available.clear
|
329
459
|
end
|
330
460
|
end
|
331
461
|
end
|
332
462
|
|
463
|
+
# Clears the cache which maps classes and re-connects connections that
|
464
|
+
# require reloading.
|
465
|
+
#
|
466
|
+
# The pool first tries to gain ownership of all connections, if unable to
|
467
|
+
# do so within a timeout interval (default duration is
|
468
|
+
# <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool forcefully
|
469
|
+
# clears the cache and reloads connections without any regard for other
|
470
|
+
# connection owning threads.
|
471
|
+
def clear_reloadable_connections!
|
472
|
+
clear_reloadable_connections(false)
|
473
|
+
end
|
474
|
+
|
333
475
|
# Check-out a database connection from the pool, indicating that you want
|
334
476
|
# to use it. You should call #checkin when you no longer need this.
|
335
477
|
#
|
@@ -343,70 +485,212 @@ module ActiveRecord
|
|
343
485
|
# Returns: an AbstractAdapter object.
|
344
486
|
#
|
345
487
|
# Raises:
|
346
|
-
# - ConnectionTimeoutError
|
347
|
-
def checkout
|
348
|
-
|
349
|
-
conn = acquire_connection
|
350
|
-
conn.lease
|
351
|
-
checkout_and_verify(conn)
|
352
|
-
end
|
488
|
+
# - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
|
489
|
+
def checkout(checkout_timeout = @checkout_timeout)
|
490
|
+
checkout_and_verify(acquire_connection(checkout_timeout))
|
353
491
|
end
|
354
492
|
|
355
493
|
# Check-in a database connection back into the pool, indicating that you
|
356
494
|
# no longer need this connection.
|
357
495
|
#
|
358
496
|
# +conn+: an AbstractAdapter object, which was obtained by earlier by
|
359
|
-
# calling
|
497
|
+
# calling #checkout on this pool.
|
360
498
|
def checkin(conn)
|
361
499
|
synchronize do
|
362
|
-
|
500
|
+
remove_connection_from_thread_cache conn
|
363
501
|
|
364
502
|
conn._run_checkin_callbacks do
|
365
503
|
conn.expire
|
366
504
|
end
|
367
505
|
|
368
|
-
release conn, owner
|
369
|
-
|
370
506
|
@available.add conn
|
371
507
|
end
|
372
508
|
end
|
373
509
|
|
374
|
-
# Remove a connection from the connection pool.
|
510
|
+
# Remove a connection from the connection pool. The connection will
|
375
511
|
# remain open and active but will no longer be managed by this pool.
|
376
512
|
def remove(conn)
|
513
|
+
needs_new_connection = false
|
514
|
+
|
377
515
|
synchronize do
|
516
|
+
remove_connection_from_thread_cache conn
|
517
|
+
|
378
518
|
@connections.delete conn
|
379
519
|
@available.delete conn
|
380
520
|
|
381
|
-
|
382
|
-
|
383
|
-
|
521
|
+
# @available.any_waiting? => true means that prior to removing this
|
522
|
+
# conn, the pool was at its max size (@connections.size == @size)
|
523
|
+
# this would mean that any threads stuck waiting in the queue wouldn't
|
524
|
+
# know they could checkout_new_connection, so let's do it for them.
|
525
|
+
# Because condition-wait loop is encapsulated in the Queue class
|
526
|
+
# (that in turn is oblivious to ConnectionPool implementation), threads
|
527
|
+
# that are "stuck" there are helpless, they have no way of creating
|
528
|
+
# new connections and are completely reliant on us feeding available
|
529
|
+
# connections into the Queue.
|
530
|
+
needs_new_connection = @available.any_waiting?
|
384
531
|
end
|
532
|
+
|
533
|
+
# This is intentionally done outside of the synchronized section as we
|
534
|
+
# would like not to hold the main mutex while checking out new connections,
|
535
|
+
# thus there is some chance that needs_new_connection information is now
|
536
|
+
# stale, we can live with that (bulk_make_new_connections will make
|
537
|
+
# sure not to exceed the pool's @size limit).
|
538
|
+
bulk_make_new_connections(1) if needs_new_connection
|
385
539
|
end
|
386
540
|
|
387
|
-
# Recover lost connections for the pool.
|
541
|
+
# Recover lost connections for the pool. A lost connection can occur if
|
388
542
|
# a programmer forgets to checkin a connection at the end of a thread
|
389
543
|
# or a thread dies unexpectedly.
|
390
544
|
def reap
|
391
545
|
stale_connections = synchronize do
|
392
546
|
@connections.select do |conn|
|
393
547
|
conn.in_use? && !conn.owner.alive?
|
548
|
+
end.each do |conn|
|
549
|
+
conn.steal!
|
394
550
|
end
|
395
551
|
end
|
396
552
|
|
397
553
|
stale_connections.each do |conn|
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
remove conn
|
404
|
-
end
|
554
|
+
if conn.active?
|
555
|
+
conn.reset!
|
556
|
+
checkin conn
|
557
|
+
else
|
558
|
+
remove conn
|
405
559
|
end
|
406
560
|
end
|
407
561
|
end
|
408
562
|
|
563
|
+
def num_waiting_in_queue # :nodoc:
|
564
|
+
@available.num_waiting
|
565
|
+
end
|
566
|
+
|
409
567
|
private
|
568
|
+
#--
|
569
|
+
# this is unfortunately not concurrent
|
570
|
+
def bulk_make_new_connections(num_new_conns_needed)
|
571
|
+
num_new_conns_needed.times do
|
572
|
+
# try_to_checkout_new_connection will not exceed pool's @size limit
|
573
|
+
if new_conn = try_to_checkout_new_connection
|
574
|
+
# make the new_conn available to the starving threads stuck @available Queue
|
575
|
+
checkin(new_conn)
|
576
|
+
end
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
#--
|
581
|
+
# From the discussion on GitHub:
|
582
|
+
# https://github.com/rails/rails/pull/14938#commitcomment-6601951
|
583
|
+
# This hook-in method allows for easier monkey-patching fixes needed by
|
584
|
+
# JRuby users that use Fibers.
|
585
|
+
def connection_cache_key(thread)
|
586
|
+
thread
|
587
|
+
end
|
588
|
+
|
589
|
+
# Take control of all existing connections so a "group" action such as
|
590
|
+
# reload/disconnect can be performed safely. It is no longer enough to
|
591
|
+
# wrap it in +synchronize+ because some pool's actions are allowed
|
592
|
+
# to be performed outside of the main +synchronize+ block.
|
593
|
+
def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
|
594
|
+
with_new_connections_blocked do
|
595
|
+
attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
|
596
|
+
yield
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
|
601
|
+
collected_conns = synchronize do
|
602
|
+
# account for our own connections
|
603
|
+
@connections.select {|conn| conn.owner == Thread.current}
|
604
|
+
end
|
605
|
+
|
606
|
+
newly_checked_out = []
|
607
|
+
timeout_time = Time.now + (@checkout_timeout * 2)
|
608
|
+
|
609
|
+
@available.with_a_bias_for(Thread.current) do
|
610
|
+
while true
|
611
|
+
synchronize do
|
612
|
+
return if collected_conns.size == @connections.size && @now_connecting == 0
|
613
|
+
remaining_timeout = timeout_time - Time.now
|
614
|
+
remaining_timeout = 0 if remaining_timeout < 0
|
615
|
+
conn = checkout_for_exclusive_access(remaining_timeout)
|
616
|
+
collected_conns << conn
|
617
|
+
newly_checked_out << conn
|
618
|
+
end
|
619
|
+
end
|
620
|
+
end
|
621
|
+
rescue ExclusiveConnectionTimeoutError
|
622
|
+
# <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any
|
623
|
+
# timeouts and are expected to just give up: we've obtained as many connections
|
624
|
+
# as possible, note that in a case like that we don't return any of the
|
625
|
+
# +newly_checked_out+ connections.
|
626
|
+
|
627
|
+
if raise_on_acquisition_timeout
|
628
|
+
release_newly_checked_out = true
|
629
|
+
raise
|
630
|
+
end
|
631
|
+
rescue Exception # if something else went wrong
|
632
|
+
# this can't be a "naked" rescue, because we have should return conns
|
633
|
+
# even for non-StandardErrors
|
634
|
+
release_newly_checked_out = true
|
635
|
+
raise
|
636
|
+
ensure
|
637
|
+
if release_newly_checked_out && newly_checked_out
|
638
|
+
# releasing only those conns that were checked out in this method, conns
|
639
|
+
# checked outside this method (before it was called) are not for us to release
|
640
|
+
newly_checked_out.each {|conn| checkin(conn)}
|
641
|
+
end
|
642
|
+
end
|
643
|
+
|
644
|
+
#--
|
645
|
+
# Must be called in a synchronize block.
|
646
|
+
def checkout_for_exclusive_access(checkout_timeout)
|
647
|
+
checkout(checkout_timeout)
|
648
|
+
rescue ConnectionTimeoutError
|
649
|
+
# this block can't be easily moved into attempt_to_checkout_all_existing_connections's
|
650
|
+
# rescue block, because doing so would put it outside of synchronize section, without
|
651
|
+
# being in a critical section thread_report might become inaccurate
|
652
|
+
msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds"
|
653
|
+
|
654
|
+
thread_report = []
|
655
|
+
@connections.each do |conn|
|
656
|
+
unless conn.owner == Thread.current
|
657
|
+
thread_report << "#{conn} is owned by #{conn.owner}"
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
msg << " (#{thread_report.join(', ')})" if thread_report.any?
|
662
|
+
|
663
|
+
raise ExclusiveConnectionTimeoutError, msg
|
664
|
+
end
|
665
|
+
|
666
|
+
def with_new_connections_blocked
|
667
|
+
synchronize do
|
668
|
+
@threads_blocking_new_connections += 1
|
669
|
+
end
|
670
|
+
|
671
|
+
yield
|
672
|
+
ensure
|
673
|
+
num_new_conns_required = 0
|
674
|
+
|
675
|
+
synchronize do
|
676
|
+
@threads_blocking_new_connections -= 1
|
677
|
+
|
678
|
+
if @threads_blocking_new_connections.zero?
|
679
|
+
@available.clear
|
680
|
+
|
681
|
+
num_new_conns_required = num_waiting_in_queue
|
682
|
+
|
683
|
+
@connections.each do |conn|
|
684
|
+
next if conn.in_use?
|
685
|
+
|
686
|
+
@available.add conn
|
687
|
+
num_new_conns_required -= 1
|
688
|
+
end
|
689
|
+
end
|
690
|
+
end
|
691
|
+
|
692
|
+
bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
|
693
|
+
end
|
410
694
|
|
411
695
|
# Acquire a connection by one of 1) immediately removing one
|
412
696
|
# from the queue of available connections, 2) creating a new
|
@@ -414,41 +698,79 @@ module ActiveRecord
|
|
414
698
|
# queue for a connection to become available.
|
415
699
|
#
|
416
700
|
# Raises:
|
417
|
-
# - ConnectionTimeoutError if a connection could not be acquired
|
418
|
-
|
419
|
-
|
701
|
+
# - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired
|
702
|
+
#
|
703
|
+
#--
|
704
|
+
# Implementation detail: the connection returned by +acquire_connection+
|
705
|
+
# will already be "+connection.lease+ -ed" to the current thread.
|
706
|
+
def acquire_connection(checkout_timeout)
|
707
|
+
# NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to
|
708
|
+
# +conn.lease+ the returned connection (and to do this in a +synchronized+
|
709
|
+
# section), this is not the cleanest implementation, as ideally we would
|
710
|
+
# <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+
|
711
|
+
# and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
|
712
|
+
# of the said methods and avoid an additional +synchronize+ overhead.
|
713
|
+
if conn = @available.poll || try_to_checkout_new_connection
|
420
714
|
conn
|
421
|
-
elsif @connections.size < @size
|
422
|
-
checkout_new_connection
|
423
715
|
else
|
424
716
|
reap
|
425
|
-
@available.poll(
|
717
|
+
@available.poll(checkout_timeout)
|
426
718
|
end
|
427
719
|
end
|
428
720
|
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
@reserved_connections.delete thread_id
|
434
|
-
end
|
721
|
+
#--
|
722
|
+
# if owner_thread param is omitted, this must be called in synchronize block
|
723
|
+
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
|
724
|
+
@thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
|
435
725
|
end
|
726
|
+
alias_method :release, :remove_connection_from_thread_cache
|
436
727
|
|
437
728
|
def new_connection
|
438
|
-
Base.send(spec.adapter_method, spec.config)
|
729
|
+
Base.send(spec.adapter_method, spec.config).tap do |conn|
|
730
|
+
conn.schema_cache = schema_cache.dup if schema_cache
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
734
|
+
# If the pool is not at a +@size+ limit, establish new connection. Connecting
|
735
|
+
# to the DB is done outside main synchronized section.
|
736
|
+
#--
|
737
|
+
# Implementation constraint: a newly established connection returned by this
|
738
|
+
# method must be in the +.leased+ state.
|
739
|
+
def try_to_checkout_new_connection
|
740
|
+
# first in synchronized section check if establishing new conns is allowed
|
741
|
+
# and increment @now_connecting, to prevent overstepping this pool's @size
|
742
|
+
# constraint
|
743
|
+
do_checkout = synchronize do
|
744
|
+
if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
|
745
|
+
@now_connecting += 1
|
746
|
+
end
|
747
|
+
end
|
748
|
+
if do_checkout
|
749
|
+
begin
|
750
|
+
# if successfully incremented @now_connecting establish new connection
|
751
|
+
# outside of synchronized section
|
752
|
+
conn = checkout_new_connection
|
753
|
+
ensure
|
754
|
+
synchronize do
|
755
|
+
if conn
|
756
|
+
adopt_connection(conn)
|
757
|
+
# returned conn needs to be already leased
|
758
|
+
conn.lease
|
759
|
+
end
|
760
|
+
@now_connecting -= 1
|
761
|
+
end
|
762
|
+
end
|
763
|
+
end
|
439
764
|
end
|
440
765
|
|
441
|
-
def
|
442
|
-
|
766
|
+
def adopt_connection(conn)
|
767
|
+
conn.pool = self
|
768
|
+
@connections << conn
|
443
769
|
end
|
444
770
|
|
445
771
|
def checkout_new_connection
|
446
772
|
raise ConnectionNotEstablished unless @automatic_reconnect
|
447
|
-
|
448
|
-
c = new_connection
|
449
|
-
c.pool = self
|
450
|
-
@connections << c
|
451
|
-
c
|
773
|
+
new_connection
|
452
774
|
end
|
453
775
|
|
454
776
|
def checkout_and_verify(c)
|
@@ -464,8 +786,7 @@ module ActiveRecord
|
|
464
786
|
end
|
465
787
|
|
466
788
|
# ConnectionHandler is a collection of ConnectionPool objects. It is used
|
467
|
-
# for keeping separate connection pools
|
468
|
-
# to different databases.
|
789
|
+
# for keeping separate connection pools that connect to different databases.
|
469
790
|
#
|
470
791
|
# For example, suppose that you have 5 models, with the following hierarchy:
|
471
792
|
#
|
@@ -507,36 +828,25 @@ module ActiveRecord
|
|
507
828
|
# ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
|
508
829
|
# All Active Record models use this handler to determine the connection pool that they
|
509
830
|
# should use.
|
831
|
+
#
|
832
|
+
# The ConnectionHandler class is not coupled with the Active models, as it has no knowlodge
|
833
|
+
# about the model. The model, needs to pass a specification name to the handler,
|
834
|
+
# in order to lookup the correct connection pool.
|
510
835
|
class ConnectionHandler
|
511
836
|
def initialize
|
512
|
-
# These caches are keyed by
|
513
|
-
|
514
|
-
|
515
|
-
@owner_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
|
516
|
-
h[k] = ThreadSafe::Cache.new(:initial_capacity => 2)
|
517
|
-
end
|
518
|
-
@class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
|
519
|
-
h[k] = ThreadSafe::Cache.new
|
837
|
+
# These caches are keyed by spec.name (ConnectionSpecification#name).
|
838
|
+
@owner_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k|
|
839
|
+
h[k] = Concurrent::Map.new(:initial_capacity => 2)
|
520
840
|
end
|
521
841
|
end
|
522
842
|
|
523
843
|
def connection_pool_list
|
524
844
|
owner_to_pool.values.compact
|
525
845
|
end
|
846
|
+
alias :connection_pools :connection_pool_list
|
526
847
|
|
527
|
-
def
|
528
|
-
|
529
|
-
In the next release, this will return the same as `#connection_pool_list`.
|
530
|
-
(An array of pools, rather than a hash mapping specs to pools.)
|
531
|
-
MSG
|
532
|
-
|
533
|
-
Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
|
534
|
-
end
|
535
|
-
|
536
|
-
def establish_connection(owner, spec)
|
537
|
-
@class_to_pool.clear
|
538
|
-
raise RuntimeError, "Anonymous class is not allowed." unless owner.name
|
539
|
-
owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)
|
848
|
+
def establish_connection(spec)
|
849
|
+
owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
|
540
850
|
end
|
541
851
|
|
542
852
|
# Returns true if there are any active connections among the connection
|
@@ -553,6 +863,8 @@ module ActiveRecord
|
|
553
863
|
end
|
554
864
|
|
555
865
|
# Clears the cache which maps classes.
|
866
|
+
#
|
867
|
+
# See ConnectionPool#clear_reloadable_connections! for details.
|
556
868
|
def clear_reloadable_connections!
|
557
869
|
connection_pool_list.each(&:clear_reloadable_connections!)
|
558
870
|
end
|
@@ -565,18 +877,18 @@ module ActiveRecord
|
|
565
877
|
# active or defined connection: if it is the latter, it will be
|
566
878
|
# opened and set as the active connection for the class it was defined
|
567
879
|
# for (not necessarily the current class).
|
568
|
-
def retrieve_connection(
|
569
|
-
pool = retrieve_connection_pool(
|
570
|
-
raise ConnectionNotEstablished, "No connection pool
|
880
|
+
def retrieve_connection(spec_name) #:nodoc:
|
881
|
+
pool = retrieve_connection_pool(spec_name)
|
882
|
+
raise ConnectionNotEstablished, "No connection pool with id #{spec_name} found." unless pool
|
571
883
|
conn = pool.connection
|
572
|
-
raise ConnectionNotEstablished, "No connection for #{
|
884
|
+
raise ConnectionNotEstablished, "No connection for #{spec_name} in connection pool" unless conn
|
573
885
|
conn
|
574
886
|
end
|
575
887
|
|
576
888
|
# Returns true if a connection that's accessible to this class has
|
577
889
|
# already been opened.
|
578
|
-
def connected?(
|
579
|
-
conn = retrieve_connection_pool(
|
890
|
+
def connected?(spec_name)
|
891
|
+
conn = retrieve_connection_pool(spec_name)
|
580
892
|
conn && conn.connected?
|
581
893
|
end
|
582
894
|
|
@@ -584,9 +896,8 @@ module ActiveRecord
|
|
584
896
|
# connection and the defined connection (if they exist). The result
|
585
897
|
# can be used as an argument for establish_connection, for easily
|
586
898
|
# re-establishing the connection.
|
587
|
-
def remove_connection(
|
588
|
-
if pool = owner_to_pool.delete(
|
589
|
-
@class_to_pool.clear
|
899
|
+
def remove_connection(spec_name)
|
900
|
+
if pool = owner_to_pool.delete(spec_name)
|
590
901
|
pool.automatic_reconnect = false
|
591
902
|
pool.disconnect!
|
592
903
|
pool.spec.config
|
@@ -602,63 +913,30 @@ module ActiveRecord
|
|
602
913
|
# #fetch is significantly slower than #[]. So in the nil case, no caching will
|
603
914
|
# take place, but that's ok since the nil case is not the common one that we wish
|
604
915
|
# to optimise for.
|
605
|
-
def retrieve_connection_pool(
|
606
|
-
|
607
|
-
|
608
|
-
klass = klass.superclass
|
609
|
-
break unless klass <= Base
|
610
|
-
end
|
611
|
-
|
612
|
-
class_to_pool[klass.name] = pool
|
613
|
-
end
|
614
|
-
end
|
615
|
-
|
616
|
-
private
|
617
|
-
|
618
|
-
def owner_to_pool
|
619
|
-
@owner_to_pool[Process.pid]
|
620
|
-
end
|
621
|
-
|
622
|
-
def class_to_pool
|
623
|
-
@class_to_pool[Process.pid]
|
624
|
-
end
|
625
|
-
|
626
|
-
def pool_for(owner)
|
627
|
-
owner_to_pool.fetch(owner.name) {
|
628
|
-
if ancestor_pool = pool_from_any_process_for(owner)
|
916
|
+
def retrieve_connection_pool(spec_name)
|
917
|
+
owner_to_pool.fetch(spec_name) do
|
918
|
+
if ancestor_pool = pool_from_any_process_for(spec_name)
|
629
919
|
# A connection was established in an ancestor process that must have
|
630
920
|
# subsequently forked. We can't reuse the connection, but we can copy
|
631
921
|
# the specification and establish a new connection with it.
|
632
|
-
establish_connection
|
922
|
+
establish_connection(ancestor_pool.spec).tap do |pool|
|
923
|
+
pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
|
924
|
+
end
|
633
925
|
else
|
634
|
-
owner_to_pool[
|
926
|
+
owner_to_pool[spec_name] = nil
|
635
927
|
end
|
636
|
-
|
928
|
+
end
|
637
929
|
end
|
638
930
|
|
639
|
-
|
640
|
-
owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[owner.name] }
|
641
|
-
owner_to_pool && owner_to_pool[owner.name]
|
642
|
-
end
|
643
|
-
end
|
931
|
+
private
|
644
932
|
|
645
|
-
|
646
|
-
|
647
|
-
@app = app
|
933
|
+
def owner_to_pool
|
934
|
+
@owner_to_pool[Process.pid]
|
648
935
|
end
|
649
936
|
|
650
|
-
def
|
651
|
-
|
652
|
-
|
653
|
-
response = @app.call(env)
|
654
|
-
response[2] = ::Rack::BodyProxy.new(response[2]) do
|
655
|
-
ActiveRecord::Base.clear_active_connections! unless testing
|
656
|
-
end
|
657
|
-
|
658
|
-
response
|
659
|
-
rescue Exception
|
660
|
-
ActiveRecord::Base.clear_active_connections! unless testing
|
661
|
-
raise
|
937
|
+
def pool_from_any_process_for(spec_name)
|
938
|
+
owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[spec_name] }
|
939
|
+
owner_to_pool && owner_to_pool[spec_name]
|
662
940
|
end
|
663
941
|
end
|
664
942
|
end
|