activerecord 5.0.7.2 → 5.1.0.beta1

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.

Files changed (216) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +389 -2252
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/examples/performance.rb +28 -28
  6. data/examples/simple.rb +3 -3
  7. data/lib/active_record.rb +20 -20
  8. data/lib/active_record/aggregations.rb +244 -244
  9. data/lib/active_record/association_relation.rb +5 -5
  10. data/lib/active_record/associations.rb +1579 -1569
  11. data/lib/active_record/associations/alias_tracker.rb +1 -1
  12. data/lib/active_record/associations/association.rb +23 -15
  13. data/lib/active_record/associations/association_scope.rb +83 -81
  14. data/lib/active_record/associations/belongs_to_association.rb +0 -1
  15. data/lib/active_record/associations/builder/belongs_to.rb +16 -14
  16. data/lib/active_record/associations/builder/collection_association.rb +1 -2
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +27 -27
  18. data/lib/active_record/associations/collection_association.rb +74 -241
  19. data/lib/active_record/associations/collection_proxy.rb +144 -70
  20. data/lib/active_record/associations/has_many_association.rb +15 -19
  21. data/lib/active_record/associations/has_many_through_association.rb +12 -5
  22. data/lib/active_record/associations/has_one_association.rb +22 -28
  23. data/lib/active_record/associations/has_one_through_association.rb +5 -1
  24. data/lib/active_record/associations/join_dependency.rb +117 -115
  25. data/lib/active_record/associations/join_dependency/join_association.rb +16 -13
  26. data/lib/active_record/associations/join_dependency/join_base.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  28. data/lib/active_record/associations/preloader.rb +94 -94
  29. data/lib/active_record/associations/preloader/association.rb +87 -64
  30. data/lib/active_record/associations/preloader/belongs_to.rb +0 -2
  31. data/lib/active_record/associations/preloader/collection_association.rb +6 -6
  32. data/lib/active_record/associations/preloader/has_many.rb +0 -2
  33. data/lib/active_record/associations/preloader/singular_association.rb +6 -8
  34. data/lib/active_record/associations/preloader/through_association.rb +34 -41
  35. data/lib/active_record/associations/singular_association.rb +8 -25
  36. data/lib/active_record/associations/through_association.rb +3 -6
  37. data/lib/active_record/attribute.rb +98 -71
  38. data/lib/active_record/attribute/user_provided_default.rb +4 -2
  39. data/lib/active_record/attribute_assignment.rb +61 -61
  40. data/lib/active_record/attribute_decorators.rb +35 -13
  41. data/lib/active_record/attribute_methods.rb +56 -65
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -7
  43. data/lib/active_record/attribute_methods/dirty.rb +216 -34
  44. data/lib/active_record/attribute_methods/primary_key.rb +78 -73
  45. data/lib/active_record/attribute_methods/read.rb +39 -35
  46. data/lib/active_record/attribute_methods/serialization.rb +7 -7
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +35 -58
  48. data/lib/active_record/attribute_methods/write.rb +36 -30
  49. data/lib/active_record/attribute_mutation_tracker.rb +53 -10
  50. data/lib/active_record/attribute_set.rb +9 -6
  51. data/lib/active_record/attribute_set/builder.rb +41 -49
  52. data/lib/active_record/attribute_set/yaml_encoder.rb +41 -0
  53. data/lib/active_record/attributes.rb +21 -21
  54. data/lib/active_record/autosave_association.rb +13 -13
  55. data/lib/active_record/base.rb +24 -22
  56. data/lib/active_record/callbacks.rb +52 -14
  57. data/lib/active_record/coders/yaml_column.rb +9 -11
  58. data/lib/active_record/collection_cache_key.rb +6 -17
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +320 -278
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +1 -3
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -34
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -27
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +44 -57
  64. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +9 -19
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +78 -79
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +53 -41
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +99 -93
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -5
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +156 -128
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +424 -382
  71. data/lib/active_record/connection_adapters/column.rb +27 -5
  72. data/lib/active_record/connection_adapters/connection_specification.rb +128 -118
  73. data/lib/active_record/connection_adapters/mysql/column.rb +6 -31
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -43
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +22 -22
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +6 -12
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +49 -45
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +16 -19
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +49 -31
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +5 -6
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +24 -26
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -28
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -35
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +3 -3
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +22 -21
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +9 -9
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +5 -3
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -3
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +16 -16
  93. data/lib/active_record/connection_adapters/postgresql/oid/{rails_5_1_point.rb → legacy_point.rb} +9 -16
  94. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  95. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +28 -8
  97. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +28 -30
  98. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -1
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +51 -51
  100. data/lib/active_record/connection_adapters/postgresql/quoting.rb +38 -36
  101. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +37 -24
  103. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +19 -23
  104. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +161 -170
  105. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +4 -4
  106. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -7
  107. data/lib/active_record/connection_adapters/postgresql_adapter.rb +179 -152
  108. data/lib/active_record/connection_adapters/schema_cache.rb +16 -7
  109. data/lib/active_record/connection_adapters/sql_type_metadata.rb +3 -3
  110. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +1 -1
  111. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +16 -20
  112. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +1 -8
  113. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +28 -0
  114. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +17 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +187 -130
  116. data/lib/active_record/connection_adapters/statement_pool.rb +7 -7
  117. data/lib/active_record/connection_handling.rb +14 -26
  118. data/lib/active_record/core.rb +110 -93
  119. data/lib/active_record/counter_cache.rb +62 -13
  120. data/lib/active_record/define_callbacks.rb +20 -0
  121. data/lib/active_record/dynamic_matchers.rb +80 -79
  122. data/lib/active_record/enum.rb +8 -6
  123. data/lib/active_record/errors.rb +58 -15
  124. data/lib/active_record/explain.rb +1 -2
  125. data/lib/active_record/explain_registry.rb +1 -1
  126. data/lib/active_record/explain_subscriber.rb +7 -4
  127. data/lib/active_record/fixture_set/file.rb +11 -8
  128. data/lib/active_record/fixtures.rb +66 -53
  129. data/lib/active_record/gem_version.rb +3 -3
  130. data/lib/active_record/inheritance.rb +93 -79
  131. data/lib/active_record/integration.rb +7 -7
  132. data/lib/active_record/internal_metadata.rb +3 -16
  133. data/lib/active_record/legacy_yaml_adapter.rb +1 -1
  134. data/lib/active_record/locking/optimistic.rb +64 -56
  135. data/lib/active_record/locking/pessimistic.rb +10 -1
  136. data/lib/active_record/log_subscriber.rb +29 -29
  137. data/lib/active_record/migration.rb +155 -172
  138. data/lib/active_record/migration/command_recorder.rb +94 -94
  139. data/lib/active_record/migration/compatibility.rb +76 -37
  140. data/lib/active_record/migration/join_table.rb +6 -6
  141. data/lib/active_record/model_schema.rb +85 -119
  142. data/lib/active_record/nested_attributes.rb +200 -199
  143. data/lib/active_record/null_relation.rb +10 -33
  144. data/lib/active_record/persistence.rb +45 -38
  145. data/lib/active_record/query_cache.rb +4 -8
  146. data/lib/active_record/querying.rb +2 -3
  147. data/lib/active_record/railtie.rb +16 -17
  148. data/lib/active_record/railties/controller_runtime.rb +6 -2
  149. data/lib/active_record/railties/databases.rake +125 -140
  150. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  151. data/lib/active_record/readonly_attributes.rb +2 -2
  152. data/lib/active_record/reflection.rb +79 -96
  153. data/lib/active_record/relation.rb +72 -115
  154. data/lib/active_record/relation/batches.rb +87 -58
  155. data/lib/active_record/relation/batches/batch_enumerator.rb +1 -1
  156. data/lib/active_record/relation/calculations.rb +154 -160
  157. data/lib/active_record/relation/delegation.rb +30 -29
  158. data/lib/active_record/relation/finder_methods.rb +195 -226
  159. data/lib/active_record/relation/merger.rb +58 -62
  160. data/lib/active_record/relation/predicate_builder.rb +92 -89
  161. data/lib/active_record/relation/predicate_builder/array_handler.rb +7 -5
  162. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +23 -23
  163. data/lib/active_record/relation/predicate_builder/base_handler.rb +3 -1
  164. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +0 -8
  165. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +12 -10
  166. data/lib/active_record/relation/predicate_builder/range_handler.rb +0 -8
  167. data/lib/active_record/relation/query_attribute.rb +1 -1
  168. data/lib/active_record/relation/query_methods.rb +247 -295
  169. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  170. data/lib/active_record/relation/spawn_methods.rb +4 -5
  171. data/lib/active_record/relation/where_clause.rb +79 -65
  172. data/lib/active_record/relation/where_clause_factory.rb +47 -8
  173. data/lib/active_record/result.rb +29 -31
  174. data/lib/active_record/runtime_registry.rb +3 -3
  175. data/lib/active_record/sanitization.rb +182 -197
  176. data/lib/active_record/schema.rb +3 -3
  177. data/lib/active_record/schema_dumper.rb +14 -37
  178. data/lib/active_record/schema_migration.rb +3 -3
  179. data/lib/active_record/scoping.rb +9 -10
  180. data/lib/active_record/scoping/default.rb +87 -91
  181. data/lib/active_record/scoping/named.rb +16 -28
  182. data/lib/active_record/secure_token.rb +2 -2
  183. data/lib/active_record/statement_cache.rb +13 -15
  184. data/lib/active_record/store.rb +31 -32
  185. data/lib/active_record/suppressor.rb +2 -1
  186. data/lib/active_record/table_metadata.rb +9 -5
  187. data/lib/active_record/tasks/database_tasks.rb +72 -65
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +75 -72
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +53 -48
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +18 -16
  191. data/lib/active_record/timestamp.rb +39 -25
  192. data/lib/active_record/touch_later.rb +1 -2
  193. data/lib/active_record/transactions.rb +98 -110
  194. data/lib/active_record/type.rb +17 -13
  195. data/lib/active_record/type/adapter_specific_registry.rb +46 -42
  196. data/lib/active_record/type/decimal_without_scale.rb +9 -0
  197. data/lib/active_record/type/hash_lookup_type_map.rb +3 -3
  198. data/lib/active_record/type/serialized.rb +8 -8
  199. data/lib/active_record/type/text.rb +9 -0
  200. data/lib/active_record/type/time.rb +0 -1
  201. data/lib/active_record/type/type_map.rb +11 -15
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type_caster.rb +2 -2
  204. data/lib/active_record/type_caster/connection.rb +8 -6
  205. data/lib/active_record/type_caster/map.rb +3 -1
  206. data/lib/active_record/validations.rb +4 -4
  207. data/lib/active_record/validations/associated.rb +1 -1
  208. data/lib/active_record/validations/presence.rb +2 -2
  209. data/lib/active_record/validations/uniqueness.rb +8 -39
  210. data/lib/active_record/version.rb +1 -1
  211. data/lib/rails/generators/active_record.rb +4 -4
  212. data/lib/rails/generators/active_record/migration.rb +2 -2
  213. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -34
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -9
  215. metadata +22 -13
  216. data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
@@ -1,25 +1,26 @@
1
- require 'yaml'
2
- require 'active_support/benchmarkable'
3
- require 'active_support/dependencies'
4
- require 'active_support/descendants_tracker'
5
- require 'active_support/time'
6
- require 'active_support/core_ext/module/attribute_accessors'
7
- require 'active_support/core_ext/array/extract_options'
8
- require 'active_support/core_ext/hash/deep_merge'
9
- require 'active_support/core_ext/hash/slice'
10
- require 'active_support/core_ext/hash/transform_values'
11
- require 'active_support/core_ext/string/behavior'
12
- require 'active_support/core_ext/kernel/singleton_class'
13
- require 'active_support/core_ext/module/introspection'
14
- require 'active_support/core_ext/object/duplicable'
15
- require 'active_support/core_ext/class/subclasses'
16
- require 'active_record/attribute_decorators'
17
- require 'active_record/errors'
18
- require 'active_record/log_subscriber'
19
- require 'active_record/explain_subscriber'
20
- require 'active_record/relation/delegation'
21
- require 'active_record/attributes'
22
- require 'active_record/type_caster'
1
+ require "yaml"
2
+ require "active_support/benchmarkable"
3
+ require "active_support/dependencies"
4
+ require "active_support/descendants_tracker"
5
+ require "active_support/time"
6
+ require "active_support/core_ext/module/attribute_accessors"
7
+ require "active_support/core_ext/array/extract_options"
8
+ require "active_support/core_ext/hash/deep_merge"
9
+ require "active_support/core_ext/hash/slice"
10
+ require "active_support/core_ext/hash/transform_values"
11
+ require "active_support/core_ext/string/behavior"
12
+ require "active_support/core_ext/kernel/singleton_class"
13
+ require "active_support/core_ext/module/introspection"
14
+ require "active_support/core_ext/object/duplicable"
15
+ require "active_support/core_ext/class/subclasses"
16
+ require "active_record/attribute_decorators"
17
+ require "active_record/define_callbacks"
18
+ require "active_record/errors"
19
+ require "active_record/log_subscriber"
20
+ require "active_record/explain_subscriber"
21
+ require "active_record/relation/delegation"
22
+ require "active_record/attributes"
23
+ require "active_record/type_caster"
23
24
 
24
25
  module ActiveRecord #:nodoc:
25
26
  # = Active Record
@@ -303,6 +304,7 @@ module ActiveRecord #:nodoc:
303
304
  include AttributeDecorators
304
305
  include Locking::Optimistic
305
306
  include Locking::Pessimistic
307
+ include DefineCallbacks
306
308
  include AttributeMethods
307
309
  include Callbacks
308
310
  include Timestamp
@@ -225,6 +225,55 @@ module ActiveRecord
225
225
  #
226
226
  # This way, the +before_destroy+ gets executed before the <tt>dependent: :destroy</tt> is called, and the data is still available.
227
227
  #
228
+ # Also, there are cases when you want several callbacks of the same type to
229
+ # be executed in order.
230
+ #
231
+ # For example:
232
+ #
233
+ # class Topic
234
+ # has_many :children
235
+ #
236
+ # after_save :log_children
237
+ # after_save :do_something_else
238
+ #
239
+ # private
240
+ #
241
+ # def log_chidren
242
+ # # Child processing
243
+ # end
244
+ #
245
+ # def do_something_else
246
+ # # Something else
247
+ # end
248
+ # end
249
+ #
250
+ # In this case the +log_children+ gets executed before +do_something_else+.
251
+ # The same applies to all non-transactional callbacks.
252
+ #
253
+ # In case there are multiple transactional callbacks as seen below, the order
254
+ # is reversed.
255
+ #
256
+ # For example:
257
+ #
258
+ # class Topic
259
+ # has_many :children
260
+ #
261
+ # after_commit :log_children
262
+ # after_commit :do_something_else
263
+ #
264
+ # private
265
+ #
266
+ # def log_chidren
267
+ # # Child processing
268
+ # end
269
+ #
270
+ # def do_something_else
271
+ # # Something else
272
+ # end
273
+ # end
274
+ #
275
+ # In this case the +do_something_else+ gets executed before +log_children+.
276
+ #
228
277
  # == \Transactions
229
278
  #
230
279
  # The entire callback chain of a {#save}[rdoc-ref:Persistence#save], {#save!}[rdoc-ref:Persistence#save!],
@@ -265,17 +314,6 @@ module ActiveRecord
265
314
  :before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
266
315
  ]
267
316
 
268
- module ClassMethods # :nodoc:
269
- include ActiveModel::Callbacks
270
- end
271
-
272
- included do
273
- include ActiveModel::Validations::Callbacks
274
-
275
- define_model_callbacks :initialize, :find, :touch, :only => :after
276
- define_model_callbacks :save, :create, :update, :destroy
277
- end
278
-
279
317
  def destroy #:nodoc:
280
318
  @_destroy_callback_already_called ||= false
281
319
  return if @_destroy_callback_already_called
@@ -294,15 +332,15 @@ module ActiveRecord
294
332
 
295
333
  private
296
334
 
297
- def create_or_update(*) #:nodoc:
335
+ def create_or_update(*)
298
336
  _run_save_callbacks { super }
299
337
  end
300
338
 
301
- def _create_record #:nodoc:
339
+ def _create_record
302
340
  _run_create_callbacks { super }
303
341
  end
304
342
 
305
- def _update_record(*) #:nodoc:
343
+ def _update_record(*)
306
344
  _run_update_callbacks { super }
307
345
  end
308
346
  end
@@ -1,12 +1,12 @@
1
- require 'yaml'
1
+ require "yaml"
2
2
 
3
3
  module ActiveRecord
4
4
  module Coders # :nodoc:
5
5
  class YAMLColumn # :nodoc:
6
-
7
6
  attr_accessor :object_class
8
7
 
9
- def initialize(object_class = Object)
8
+ def initialize(attr_name, object_class = Object)
9
+ @attr_name = attr_name
10
10
  @object_class = object_class
11
11
  check_arity_of_constructor
12
12
  end
@@ -14,37 +14,35 @@ module ActiveRecord
14
14
  def dump(obj)
15
15
  return if obj.nil?
16
16
 
17
- assert_valid_value(obj)
17
+ assert_valid_value(obj, action: "dump")
18
18
  YAML.dump obj
19
19
  end
20
20
 
21
21
  def load(yaml)
22
22
  return object_class.new if object_class != Object && yaml.nil?
23
- return yaml unless yaml.is_a?(String) && yaml =~ /^---/
23
+ return yaml unless yaml.is_a?(String) && /^---/.match?(yaml)
24
24
  obj = YAML.load(yaml)
25
25
 
26
- assert_valid_value(obj)
26
+ assert_valid_value(obj, action: "load")
27
27
  obj ||= object_class.new if object_class != Object
28
28
 
29
29
  obj
30
30
  end
31
31
 
32
- def assert_valid_value(obj)
32
+ def assert_valid_value(obj, action:)
33
33
  unless obj.nil? || obj.is_a?(object_class)
34
34
  raise SerializationTypeMismatch,
35
- "Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
35
+ "can't #{action} `#{@attr_name}`: was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
36
36
  end
37
37
  end
38
38
 
39
39
  private
40
40
 
41
- def check_arity_of_constructor
42
- begin
41
+ def check_arity_of_constructor
43
42
  load(nil)
44
43
  rescue ArgumentError
45
44
  raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
46
45
  end
47
- end
48
46
  end
49
47
  end
50
48
  end
@@ -1,6 +1,5 @@
1
1
  module ActiveRecord
2
2
  module CollectionCacheKey
3
-
4
3
  def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
5
4
  query_signature = Digest::MD5.hexdigest(collection.to_sql)
6
5
  key = "#{collection.model_name.cache_key}/query-#{query_signature}"
@@ -8,27 +7,17 @@ module ActiveRecord
8
7
  if collection.loaded?
9
8
  size = collection.size
10
9
  if size > 0
11
- timestamp = collection.max_by(&timestamp_column)._read_attribute(timestamp_column)
10
+ timestamp = collection.max_by(&timestamp_column).public_send(timestamp_column)
12
11
  end
13
12
  else
14
13
  column_type = type_for_attribute(timestamp_column.to_s)
15
14
  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
15
 
31
- result = connection.select_one(arel, nil, query.bound_attributes)
16
+ query = collection
17
+ .unscope(:select)
18
+ .select("COUNT(*) AS #{connection.quote_column_name("size")}", "MAX(#{column}) AS timestamp")
19
+ .unscope(:order)
20
+ result = connection.select_one(query)
32
21
 
33
22
  if result.blank?
34
23
  size = 0
@@ -1,6 +1,6 @@
1
- require 'thread'
2
- require 'concurrent/map'
3
- require 'monitor'
1
+ require "thread"
2
+ require "concurrent/map"
3
+ require "monitor"
4
4
 
5
5
  module ActiveRecord
6
6
  # Raised when a connection could not be obtained within the connection
@@ -69,12 +69,12 @@ module ActiveRecord
69
69
  # threads, which can occur if a programmer forgets to close a
70
70
  # connection at the end of a thread or a thread dies unexpectedly.
71
71
  # Regardless of this setting, the Reaper will be invoked before every
72
- # blocking wait. (Default nil, which means don't schedule the Reaper).
72
+ # blocking wait. (Default +nil+, which means don't schedule the Reaper).
73
73
  #
74
74
  #--
75
75
  # Synchronization policy:
76
76
  # * all public methods can be called outside +synchronize+
77
- # * access to these i-vars needs to be in +synchronize+:
77
+ # * access to these instance variables needs to be in +synchronize+:
78
78
  # * @connections
79
79
  # * @now_connecting
80
80
  # * private methods that require being called in a +synchronize+ blocks
@@ -116,7 +116,7 @@ module ActiveRecord
116
116
  end
117
117
  end
118
118
 
119
- # If +element+ is in the queue, remove and return it, or nil.
119
+ # If +element+ is in the queue, remove and return it, or +nil+.
120
120
  def delete(element)
121
121
  synchronize do
122
122
  @queue.delete(element)
@@ -135,7 +135,7 @@ module ActiveRecord
135
135
  # If +timeout+ is not given, remove and return the head the
136
136
  # queue if the number of available elements is strictly
137
137
  # greater than the number of threads currently waiting (that
138
- # is, don't jump ahead in line). Otherwise, return nil.
138
+ # is, don't jump ahead in line). Otherwise, return +nil+.
139
139
  #
140
140
  # If +timeout+ is given, block if there is no element
141
141
  # available, waiting up to +timeout+ seconds for an element to
@@ -150,61 +150,61 @@ module ActiveRecord
150
150
 
151
151
  private
152
152
 
153
- def internal_poll(timeout)
154
- no_wait_poll || (timeout && wait_poll(timeout))
155
- end
153
+ def internal_poll(timeout)
154
+ no_wait_poll || (timeout && wait_poll(timeout))
155
+ end
156
156
 
157
- def synchronize(&block)
158
- @lock.synchronize(&block)
159
- end
157
+ def synchronize(&block)
158
+ @lock.synchronize(&block)
159
+ end
160
160
 
161
- # Test if the queue currently contains any elements.
162
- def any?
163
- !@queue.empty?
164
- end
161
+ # Test if the queue currently contains any elements.
162
+ def any?
163
+ !@queue.empty?
164
+ end
165
165
 
166
- # A thread can remove an element from the queue without
167
- # waiting if and only if the number of currently available
168
- # connections is strictly greater than the number of waiting
169
- # threads.
170
- def can_remove_no_wait?
171
- @queue.size > @num_waiting
172
- end
166
+ # A thread can remove an element from the queue without
167
+ # waiting if and only if the number of currently available
168
+ # connections is strictly greater than the number of waiting
169
+ # threads.
170
+ def can_remove_no_wait?
171
+ @queue.size > @num_waiting
172
+ end
173
173
 
174
- # Removes and returns the head of the queue if possible, or nil.
175
- def remove
176
- @queue.shift
177
- end
174
+ # Removes and returns the head of the queue if possible, or +nil+.
175
+ def remove
176
+ @queue.shift
177
+ end
178
178
 
179
- # Remove and return the head the queue if the number of
180
- # available elements is strictly greater than the number of
181
- # threads currently waiting. Otherwise, return nil.
182
- def no_wait_poll
183
- remove if can_remove_no_wait?
184
- end
179
+ # Remove and return the head the queue if the number of
180
+ # available elements is strictly greater than the number of
181
+ # threads currently waiting. Otherwise, return +nil+.
182
+ def no_wait_poll
183
+ remove if can_remove_no_wait?
184
+ end
185
185
 
186
- # Waits on the queue up to +timeout+ seconds, then removes and
187
- # returns the head of the queue.
188
- def wait_poll(timeout)
189
- @num_waiting += 1
186
+ # Waits on the queue up to +timeout+ seconds, then removes and
187
+ # returns the head of the queue.
188
+ def wait_poll(timeout)
189
+ @num_waiting += 1
190
190
 
191
- t0 = Time.now
192
- elapsed = 0
193
- loop do
194
- @cond.wait(timeout - elapsed)
191
+ t0 = Time.now
192
+ elapsed = 0
193
+ loop do
194
+ @cond.wait(timeout - elapsed)
195
195
 
196
- return remove if any?
196
+ return remove if any?
197
197
 
198
- elapsed = Time.now - t0
199
- if elapsed >= timeout
200
- msg = 'could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use' %
201
- [timeout, elapsed]
202
- raise ConnectionTimeoutError, msg
198
+ elapsed = Time.now - t0
199
+ if elapsed >= timeout
200
+ msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" %
201
+ [timeout, elapsed]
202
+ raise ConnectionTimeoutError, msg
203
+ end
203
204
  end
205
+ ensure
206
+ @num_waiting -= 1
204
207
  end
205
- ensure
206
- @num_waiting -= 1
207
- end
208
208
  end
209
209
 
210
210
  # Adds the ability to turn a basic fair FIFO queue into one
@@ -274,15 +274,15 @@ module ActiveRecord
274
274
  include BiasableQueue
275
275
 
276
276
  private
277
- def internal_poll(timeout)
278
- conn = super
279
- conn.lease if conn
280
- conn
281
- end
277
+ def internal_poll(timeout)
278
+ conn = super
279
+ conn.lease if conn
280
+ conn
281
+ end
282
282
  end
283
283
 
284
284
  # Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
285
- # A reaper instantiated with a nil frequency will never reap the
285
+ # A reaper instantiated with a +nil+ frequency will never reap the
286
286
  # connection pool.
287
287
  #
288
288
  # Configure the frequency by setting "reaping_frequency" in your
@@ -298,7 +298,7 @@ module ActiveRecord
298
298
  def run
299
299
  return unless frequency
300
300
  Thread.new(frequency, pool) { |t, p|
301
- while true
301
+ loop do
302
302
  sleep t
303
303
  p.reap
304
304
  end
@@ -330,17 +330,17 @@ module ActiveRecord
330
330
  # default max pool size to 5
331
331
  @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
332
332
 
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.
333
+ # This variable tracks the cache of threads mapped to reserved connections, with the
334
+ # sole purpose of speeding up the +connection+ method. It is not the authoritative
335
+ # registry of which thread owns which connection. Connection ownership is tracked by
336
+ # the +connection.owner+ attr on each +connection+ instance.
337
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
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
340
  # that case +conn.owner+ attr should be consulted.
341
341
  # Access and modification of +@thread_cached_conns+ does not require
342
342
  # synchronization.
343
- @thread_cached_conns = Concurrent::Map.new(:initial_capacity => @size)
343
+ @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
344
344
 
345
345
  @connections = []
346
346
  @automatic_reconnect = true
@@ -353,6 +353,16 @@ module ActiveRecord
353
353
  @threads_blocking_new_connections = 0
354
354
 
355
355
  @available = ConnectionLeasingQueue.new self
356
+
357
+ @lock_thread = false
358
+ end
359
+
360
+ def lock_thread=(lock_thread)
361
+ if lock_thread
362
+ @lock_thread = Thread.current
363
+ else
364
+ @lock_thread = nil
365
+ end
356
366
  end
357
367
 
358
368
  # Retrieve the connection associated with the current thread, or call
@@ -361,13 +371,13 @@ module ActiveRecord
361
371
  # #connection can be called any number of times; the connection is
362
372
  # held in a cache keyed by a thread.
363
373
  def connection
364
- @thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout
374
+ @thread_cached_conns[connection_cache_key(@lock_thread || Thread.current)] ||= checkout
365
375
  end
366
376
 
367
- # Is there an open connection that is being used for the current thread?
377
+ # Returns true if there is an open connection being used for the current thread.
368
378
  #
369
379
  # This method only works for connections that have been obtained through
370
- # #connection or #with_connection methods, connections obtained through
380
+ # #connection or #with_connection methods. Connections obtained through
371
381
  # #checkout will not be detected by #active_connection?
372
382
  def active_connection?
373
383
  @thread_cached_conns[connection_cache_key(Thread.current)]
@@ -429,9 +439,9 @@ module ActiveRecord
429
439
 
430
440
  # Disconnects all connections in the pool, and clears the pool.
431
441
  #
432
- # The pool first tries to gain ownership of all connections, if unable to
442
+ # The pool first tries to gain ownership of all connections. If unable to
433
443
  # do so within a timeout interval (default duration is
434
- # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool is forcefully
444
+ # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), then the pool is forcefully
435
445
  # disconnected without any regard for other connection owning threads.
436
446
  def disconnect!
437
447
  disconnect(false)
@@ -463,9 +473,9 @@ module ActiveRecord
463
473
  # Clears the cache which maps classes and re-connects connections that
464
474
  # require reloading.
465
475
  #
466
- # The pool first tries to gain ownership of all connections, if unable to
476
+ # The pool first tries to gain ownership of all connections. If unable to
467
477
  # do so within a timeout interval (default duration is
468
- # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool forcefully
478
+ # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), then the pool forcefully
469
479
  # clears the cache and reloads connections without any regard for other
470
480
  # connection owning threads.
471
481
  def clear_reloadable_connections!
@@ -519,20 +529,20 @@ module ActiveRecord
519
529
  @available.delete conn
520
530
 
521
531
  # @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
532
+ # conn, the pool was at its max size (@connections.size == @size).
533
+ # This would mean that any threads stuck waiting in the queue wouldn't
524
534
  # know they could checkout_new_connection, so let's do it for them.
525
535
  # Because condition-wait loop is encapsulated in the Queue class
526
536
  # (that in turn is oblivious to ConnectionPool implementation), threads
527
- # that are "stuck" there are helpless, they have no way of creating
537
+ # that are "stuck" there are helpless. They have no way of creating
528
538
  # new connections and are completely reliant on us feeding available
529
539
  # connections into the Queue.
530
540
  needs_new_connection = @available.any_waiting?
531
541
  end
532
542
 
533
543
  # 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
544
+ # would like not to hold the main mutex while checking out new connections.
545
+ # Thus there is some chance that needs_new_connection information is now
536
546
  # stale, we can live with that (bulk_make_new_connections will make
537
547
  # sure not to exceed the pool's @size limit).
538
548
  bulk_make_new_connections(1) if needs_new_connection
@@ -564,225 +574,243 @@ module ActiveRecord
564
574
  @available.num_waiting
565
575
  end
566
576
 
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
577
+ # Return connection pool's usage statistic
578
+ # Example:
579
+ #
580
+ # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
581
+ def stat
582
+ synchronize do
583
+ {
584
+ size: size,
585
+ connections: @connections.size,
586
+ busy: @connections.count { |c| c.in_use? && c.owner.alive? },
587
+ dead: @connections.count { |c| c.in_use? && !c.owner.alive? },
588
+ idle: @connections.count { |c| !c.in_use? },
589
+ waiting: num_waiting_in_queue,
590
+ checkout_timeout: checkout_timeout
591
+ }
597
592
  end
598
593
  end
599
594
 
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
595
+ private
596
+ #--
597
+ # this is unfortunately not concurrent
598
+ def bulk_make_new_connections(num_new_conns_needed)
599
+ num_new_conns_needed.times do
600
+ # try_to_checkout_new_connection will not exceed pool's @size limit
601
+ if new_conn = try_to_checkout_new_connection
602
+ # make the new_conn available to the starving threads stuck @available Queue
603
+ checkin(new_conn)
618
604
  end
619
605
  end
620
606
  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
607
 
627
- if raise_on_acquisition_timeout
628
- release_newly_checked_out = true
629
- raise
608
+ #--
609
+ # From the discussion on GitHub:
610
+ # https://github.com/rails/rails/pull/14938#commitcomment-6601951
611
+ # This hook-in method allows for easier monkey-patching fixes needed by
612
+ # JRuby users that use Fibers.
613
+ def connection_cache_key(thread)
614
+ thread
630
615
  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)}
616
+
617
+ # Take control of all existing connections so a "group" action such as
618
+ # reload/disconnect can be performed safely. It is no longer enough to
619
+ # wrap it in +synchronize+ because some pool's actions are allowed
620
+ # to be performed outside of the main +synchronize+ block.
621
+ def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
622
+ with_new_connections_blocked do
623
+ attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
624
+ yield
625
+ end
641
626
  end
642
- end
643
627
 
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"
628
+ def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
629
+ collected_conns = synchronize do
630
+ # account for our own connections
631
+ @connections.select { |conn| conn.owner == Thread.current }
632
+ end
653
633
 
654
- thread_report = []
655
- @connections.each do |conn|
656
- unless conn.owner == Thread.current
657
- thread_report << "#{conn} is owned by #{conn.owner}"
634
+ newly_checked_out = []
635
+ timeout_time = Time.now + (@checkout_timeout * 2)
636
+
637
+ @available.with_a_bias_for(Thread.current) do
638
+ loop do
639
+ synchronize do
640
+ return if collected_conns.size == @connections.size && @now_connecting == 0
641
+ remaining_timeout = timeout_time - Time.now
642
+ remaining_timeout = 0 if remaining_timeout < 0
643
+ conn = checkout_for_exclusive_access(remaining_timeout)
644
+ collected_conns << conn
645
+ newly_checked_out << conn
646
+ end
647
+ end
648
+ end
649
+ rescue ExclusiveConnectionTimeoutError
650
+ # <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any
651
+ # timeouts and are expected to just give up: we've obtained as many connections
652
+ # as possible, note that in a case like that we don't return any of the
653
+ # +newly_checked_out+ connections.
654
+
655
+ if raise_on_acquisition_timeout
656
+ release_newly_checked_out = true
657
+ raise
658
+ end
659
+ rescue Exception # if something else went wrong
660
+ # this can't be a "naked" rescue, because we have should return conns
661
+ # even for non-StandardErrors
662
+ release_newly_checked_out = true
663
+ raise
664
+ ensure
665
+ if release_newly_checked_out && newly_checked_out
666
+ # releasing only those conns that were checked out in this method, conns
667
+ # checked outside this method (before it was called) are not for us to release
668
+ newly_checked_out.each { |conn| checkin(conn) }
658
669
  end
659
670
  end
660
671
 
661
- msg << " (#{thread_report.join(', ')})" if thread_report.any?
672
+ #--
673
+ # Must be called in a synchronize block.
674
+ def checkout_for_exclusive_access(checkout_timeout)
675
+ checkout(checkout_timeout)
676
+ rescue ConnectionTimeoutError
677
+ # this block can't be easily moved into attempt_to_checkout_all_existing_connections's
678
+ # rescue block, because doing so would put it outside of synchronize section, without
679
+ # being in a critical section thread_report might become inaccurate
680
+ msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds"
681
+
682
+ thread_report = []
683
+ @connections.each do |conn|
684
+ unless conn.owner == Thread.current
685
+ thread_report << "#{conn} is owned by #{conn.owner}"
686
+ end
687
+ end
662
688
 
663
- raise ExclusiveConnectionTimeoutError, msg
664
- end
689
+ msg << " (#{thread_report.join(', ')})" if thread_report.any?
665
690
 
666
- def with_new_connections_blocked
667
- synchronize do
668
- @threads_blocking_new_connections += 1
691
+ raise ExclusiveConnectionTimeoutError, msg
669
692
  end
670
693
 
671
- yield
672
- ensure
673
- num_new_conns_required = 0
694
+ def with_new_connections_blocked
695
+ synchronize do
696
+ @threads_blocking_new_connections += 1
697
+ end
674
698
 
675
- synchronize do
676
- @threads_blocking_new_connections -= 1
699
+ yield
700
+ ensure
701
+ num_new_conns_required = 0
677
702
 
678
- if @threads_blocking_new_connections.zero?
679
- @available.clear
703
+ synchronize do
704
+ @threads_blocking_new_connections -= 1
680
705
 
681
- num_new_conns_required = num_waiting_in_queue
706
+ if @threads_blocking_new_connections.zero?
707
+ @available.clear
682
708
 
683
- @connections.each do |conn|
684
- next if conn.in_use?
709
+ num_new_conns_required = num_waiting_in_queue
710
+
711
+ @connections.each do |conn|
712
+ next if conn.in_use?
685
713
 
686
- @available.add conn
687
- num_new_conns_required -= 1
714
+ @available.add conn
715
+ num_new_conns_required -= 1
716
+ end
688
717
  end
689
718
  end
690
- end
691
719
 
692
- bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
693
- end
694
-
695
- # Acquire a connection by one of 1) immediately removing one
696
- # from the queue of available connections, 2) creating a new
697
- # connection if the pool is not at capacity, 3) waiting on the
698
- # queue for a connection to become available.
699
- #
700
- # Raises:
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
714
- conn
715
- else
716
- reap
717
- @available.poll(checkout_timeout)
720
+ bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
718
721
  end
719
- end
720
722
 
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)
725
- end
726
- alias_method :release, :remove_connection_from_thread_cache
723
+ # Acquire a connection by one of 1) immediately removing one
724
+ # from the queue of available connections, 2) creating a new
725
+ # connection if the pool is not at capacity, 3) waiting on the
726
+ # queue for a connection to become available.
727
+ #
728
+ # Raises:
729
+ # - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired
730
+ #
731
+ #--
732
+ # Implementation detail: the connection returned by +acquire_connection+
733
+ # will already be "+connection.lease+ -ed" to the current thread.
734
+ def acquire_connection(checkout_timeout)
735
+ # NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to
736
+ # +conn.lease+ the returned connection (and to do this in a +synchronized+
737
+ # section). This is not the cleanest implementation, as ideally we would
738
+ # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+
739
+ # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
740
+ # of the said methods and avoid an additional +synchronize+ overhead.
741
+ if conn = @available.poll || try_to_checkout_new_connection
742
+ conn
743
+ else
744
+ reap
745
+ @available.poll(checkout_timeout)
746
+ end
747
+ end
727
748
 
728
- def new_connection
729
- Base.send(spec.adapter_method, spec.config).tap do |conn|
730
- conn.schema_cache = schema_cache.dup if schema_cache
749
+ #--
750
+ # if owner_thread param is omitted, this must be called in synchronize block
751
+ def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
752
+ @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
731
753
  end
732
- end
754
+ alias_method :release, :remove_connection_from_thread_cache
733
755
 
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
756
+ def new_connection
757
+ Base.send(spec.adapter_method, spec.config).tap do |conn|
758
+ conn.schema_cache = schema_cache.dup if schema_cache
746
759
  end
747
760
  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
761
+
762
+ # If the pool is not at a +@size+ limit, establish new connection. Connecting
763
+ # to the DB is done outside main synchronized section.
764
+ #--
765
+ # Implementation constraint: a newly established connection returned by this
766
+ # method must be in the +.leased+ state.
767
+ def try_to_checkout_new_connection
768
+ # first in synchronized section check if establishing new conns is allowed
769
+ # and increment @now_connecting, to prevent overstepping this pool's @size
770
+ # constraint
771
+ do_checkout = synchronize do
772
+ if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
773
+ @now_connecting += 1
774
+ end
775
+ end
776
+ if do_checkout
777
+ begin
778
+ # if successfully incremented @now_connecting establish new connection
779
+ # outside of synchronized section
780
+ conn = checkout_new_connection
781
+ ensure
782
+ synchronize do
783
+ if conn
784
+ adopt_connection(conn)
785
+ # returned conn needs to be already leased
786
+ conn.lease
787
+ end
788
+ @now_connecting -= 1
759
789
  end
760
- @now_connecting -= 1
761
790
  end
762
791
  end
763
792
  end
764
- end
765
793
 
766
- def adopt_connection(conn)
767
- conn.pool = self
768
- @connections << conn
769
- end
794
+ def adopt_connection(conn)
795
+ conn.pool = self
796
+ @connections << conn
797
+ end
770
798
 
771
- def checkout_new_connection
772
- raise ConnectionNotEstablished unless @automatic_reconnect
773
- new_connection
774
- end
799
+ def checkout_new_connection
800
+ raise ConnectionNotEstablished unless @automatic_reconnect
801
+ new_connection
802
+ end
775
803
 
776
- def checkout_and_verify(c)
777
- c._run_checkout_callbacks do
778
- c.verify!
804
+ def checkout_and_verify(c)
805
+ c._run_checkout_callbacks do
806
+ c.verify!
807
+ end
808
+ c
809
+ rescue
810
+ remove c
811
+ c.disconnect!
812
+ raise
779
813
  end
780
- c
781
- rescue
782
- remove c
783
- c.disconnect!
784
- raise
785
- end
786
814
  end
787
815
 
788
816
  # ConnectionHandler is a collection of ConnectionPool objects. It is used
@@ -830,13 +858,13 @@ module ActiveRecord
830
858
  # should use.
831
859
  #
832
860
  # 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,
861
+ # about the model. The model needs to pass a specification name to the handler,
834
862
  # in order to lookup the correct connection pool.
835
863
  class ConnectionHandler
836
864
  def initialize
837
865
  # 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)
866
+ @owner_to_pool = Concurrent::Map.new(initial_capacity: 2) do |h, k|
867
+ h[k] = Concurrent::Map.new(initial_capacity: 2)
840
868
  end
841
869
  end
842
870
 
@@ -845,8 +873,26 @@ module ActiveRecord
845
873
  end
846
874
  alias :connection_pools :connection_pool_list
847
875
 
848
- def establish_connection(spec)
849
- owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
876
+ def establish_connection(config)
877
+ resolver = ConnectionSpecification::Resolver.new(Base.configurations)
878
+ spec = resolver.spec(config)
879
+
880
+ remove_connection(spec.name)
881
+
882
+ message_bus = ActiveSupport::Notifications.instrumenter
883
+ payload = {
884
+ connection_id: object_id
885
+ }
886
+ if spec
887
+ payload[:spec_name] = spec.name
888
+ payload[:config] = spec.config
889
+ end
890
+
891
+ message_bus.instrument("!connection.active_record", payload) do
892
+ owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
893
+ end
894
+
895
+ owner_to_pool[spec.name]
850
896
  end
851
897
 
852
898
  # Returns true if there are any active connections among the connection
@@ -879,9 +925,9 @@ module ActiveRecord
879
925
  # for (not necessarily the current class).
880
926
  def retrieve_connection(spec_name) #:nodoc:
881
927
  pool = retrieve_connection_pool(spec_name)
882
- raise ConnectionNotEstablished, "No connection pool with id #{spec_name} found." unless pool
928
+ raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found." unless pool
883
929
  conn = pool.connection
884
- raise ConnectionNotEstablished, "No connection for #{spec_name} in connection pool" unless conn
930
+ raise ConnectionNotEstablished, "No connection for '#{spec_name}' in connection pool" unless conn
885
931
  conn
886
932
  end
887
933
 
@@ -894,7 +940,7 @@ module ActiveRecord
894
940
 
895
941
  # Remove the connection for this class. This will close the active
896
942
  # connection and the defined connection (if they exist). The result
897
- # can be used as an argument for establish_connection, for easily
943
+ # can be used as an argument for #establish_connection, for easily
898
944
  # re-establishing the connection.
899
945
  def remove_connection(spec_name)
900
946
  if pool = owner_to_pool.delete(spec_name)
@@ -904,22 +950,18 @@ module ActiveRecord
904
950
  end
905
951
  end
906
952
 
907
- # Retrieving the connection pool happens a lot so we cache it in @class_to_pool.
953
+ # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool.
908
954
  # This makes retrieving the connection pool O(1) once the process is warm.
909
955
  # When a connection is established or removed, we invalidate the cache.
910
- #
911
- # Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil.
912
- # However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that
913
- # #fetch is significantly slower than #[]. So in the nil case, no caching will
914
- # take place, but that's ok since the nil case is not the common one that we wish
915
- # to optimise for.
916
956
  def retrieve_connection_pool(spec_name)
917
957
  owner_to_pool.fetch(spec_name) do
958
+ # Check if a connection was previously established in an ancestor process,
959
+ # which may have been forked.
918
960
  if ancestor_pool = pool_from_any_process_for(spec_name)
919
961
  # A connection was established in an ancestor process that must have
920
962
  # subsequently forked. We can't reuse the connection, but we can copy
921
963
  # the specification and establish a new connection with it.
922
- establish_connection(ancestor_pool.spec).tap do |pool|
964
+ establish_connection(ancestor_pool.spec.to_hash).tap do |pool|
923
965
  pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
924
966
  end
925
967
  else
@@ -930,14 +972,14 @@ module ActiveRecord
930
972
 
931
973
  private
932
974
 
933
- def owner_to_pool
934
- @owner_to_pool[Process.pid]
935
- end
975
+ def owner_to_pool
976
+ @owner_to_pool[Process.pid]
977
+ end
936
978
 
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]
940
- end
979
+ def pool_from_any_process_for(spec_name)
980
+ owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[spec_name] }
981
+ owner_to_pool && owner_to_pool[spec_name]
982
+ end
941
983
  end
942
984
  end
943
985
  end