activerecord 7.2.3 → 8.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +612 -1055
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/association.rb +35 -11
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +1 -1
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +192 -24
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +16 -3
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/base.rb +1 -2
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +412 -88
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +137 -75
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -32
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +150 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +63 -52
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +139 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -14
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +102 -37
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +25 -2
- data/lib/active_record/core.rb +33 -17
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +67 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +8 -8
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encryptor.rb +28 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +33 -30
- data/lib/active_record/errors.rb +33 -9
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +14 -9
- data/lib/active_record/locking/optimistic.rb +8 -1
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -13
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +45 -12
- data/lib/active_record/migration/compatibility.rb +37 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +48 -42
- data/lib/active_record/model_schema.rb +38 -13
- data/lib/active_record/nested_attributes.rb +6 -6
- data/lib/active_record/persistence.rb +162 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +100 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +8 -8
- data/lib/active_record/railtie.rb +35 -30
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -38
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +53 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +147 -73
- data/lib/active_record/relation/calculations.rb +52 -40
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +40 -24
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +22 -7
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +140 -86
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +2 -9
- data/lib/active_record/relation.rb +107 -75
- data/lib/active_record/result.rb +109 -24
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +9 -6
- data/lib/active_record/schema_dumper.rb +18 -11
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +43 -15
- data/lib/active_record/statement_cache.rb +24 -20
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +6 -23
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +39 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +37 -16
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +13 -2
- data/lib/active_record/type/serialized.rb +16 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +84 -49
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/table.rb +3 -7
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +3 -21
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +16 -13
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -221,8 +221,10 @@ module ActiveRecord
|
|
|
221
221
|
if reflection.validate? && !method_defined?(validation_method)
|
|
222
222
|
if reflection.collection?
|
|
223
223
|
method = :validate_collection_association
|
|
224
|
+
elsif reflection.has_one?
|
|
225
|
+
method = :validate_has_one_association
|
|
224
226
|
else
|
|
225
|
-
method = :
|
|
227
|
+
method = :validate_belongs_to_association
|
|
226
228
|
end
|
|
227
229
|
|
|
228
230
|
define_non_cyclic_method(validation_method) { send(method, reflection) }
|
|
@@ -274,6 +276,16 @@ module ActiveRecord
|
|
|
274
276
|
new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave?
|
|
275
277
|
end
|
|
276
278
|
|
|
279
|
+
def validating_belongs_to_for?(association)
|
|
280
|
+
@validating_belongs_to_for ||= {}
|
|
281
|
+
@validating_belongs_to_for[association]
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def autosaving_belongs_to_for?(association)
|
|
285
|
+
@autosaving_belongs_to_for ||= {}
|
|
286
|
+
@autosaving_belongs_to_for[association]
|
|
287
|
+
end
|
|
288
|
+
|
|
277
289
|
private
|
|
278
290
|
def init_internals
|
|
279
291
|
super
|
|
@@ -313,11 +325,33 @@ module ActiveRecord
|
|
|
313
325
|
end
|
|
314
326
|
|
|
315
327
|
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
|
|
316
|
-
# turned on for the association.
|
|
317
|
-
def
|
|
328
|
+
# turned on for the has_one association.
|
|
329
|
+
def validate_has_one_association(reflection)
|
|
330
|
+
association = association_instance_get(reflection.name)
|
|
331
|
+
record = association && association.reader
|
|
332
|
+
return unless record && (record.changed_for_autosave? || custom_validation_context?)
|
|
333
|
+
|
|
334
|
+
inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name)
|
|
335
|
+
return if inverse_association && (record.validating_belongs_to_for?(inverse_association) ||
|
|
336
|
+
record.autosaving_belongs_to_for?(inverse_association))
|
|
337
|
+
|
|
338
|
+
association_valid?(association, record)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
|
|
342
|
+
# turned on for the belongs_to association.
|
|
343
|
+
def validate_belongs_to_association(reflection)
|
|
318
344
|
association = association_instance_get(reflection.name)
|
|
319
345
|
record = association && association.reader
|
|
320
|
-
|
|
346
|
+
return unless record && (record.changed_for_autosave? || custom_validation_context?)
|
|
347
|
+
|
|
348
|
+
begin
|
|
349
|
+
@validating_belongs_to_for ||= {}
|
|
350
|
+
@validating_belongs_to_for[association] = true
|
|
351
|
+
association_valid?(association, record)
|
|
352
|
+
ensure
|
|
353
|
+
@validating_belongs_to_for[association] = false
|
|
354
|
+
end
|
|
321
355
|
end
|
|
322
356
|
|
|
323
357
|
# Validate the associated records if <tt>:validate</tt> or
|
|
@@ -441,33 +475,34 @@ module ActiveRecord
|
|
|
441
475
|
return unless association && association.loaded?
|
|
442
476
|
|
|
443
477
|
record = association.load_target
|
|
478
|
+
return unless record && !record.destroyed?
|
|
444
479
|
|
|
445
|
-
|
|
446
|
-
autosave = reflection.options[:autosave]
|
|
447
|
-
|
|
448
|
-
if autosave && record.marked_for_destruction?
|
|
449
|
-
record.destroy
|
|
450
|
-
elsif autosave != false
|
|
451
|
-
primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
|
|
452
|
-
primary_key_value = primary_key.map { |key| _read_attribute(key) }
|
|
480
|
+
autosave = reflection.options[:autosave]
|
|
453
481
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
482
|
+
if autosave && record.marked_for_destruction?
|
|
483
|
+
record.destroy
|
|
484
|
+
elsif autosave != false
|
|
485
|
+
primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
|
|
486
|
+
primary_key_value = primary_key.map { |key| _read_attribute(key) }
|
|
487
|
+
return unless (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
|
|
458
488
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
end
|
|
463
|
-
association.set_inverse_instance(record)
|
|
464
|
-
end
|
|
489
|
+
unless reflection.through_reflection
|
|
490
|
+
foreign_key = Array(reflection.foreign_key)
|
|
491
|
+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
|
465
492
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
493
|
+
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
|
|
494
|
+
association_id = _read_attribute(primary_key)
|
|
495
|
+
record[foreign_key] = association_id unless record[foreign_key] == association_id
|
|
469
496
|
end
|
|
497
|
+
association.set_inverse_instance(record)
|
|
470
498
|
end
|
|
499
|
+
|
|
500
|
+
inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name)
|
|
501
|
+
return if inverse_association && record.autosaving_belongs_to_for?(inverse_association)
|
|
502
|
+
|
|
503
|
+
saved = record.save(validate: !autosave)
|
|
504
|
+
raise ActiveRecord::Rollback if !saved && autosave
|
|
505
|
+
saved
|
|
471
506
|
end
|
|
472
507
|
end
|
|
473
508
|
|
|
@@ -492,7 +527,6 @@ module ActiveRecord
|
|
|
492
527
|
return false unless reflection.inverse_of&.polymorphic?
|
|
493
528
|
|
|
494
529
|
class_name = record._read_attribute(reflection.inverse_of.foreign_type)
|
|
495
|
-
|
|
496
530
|
reflection.active_record.polymorphic_name != class_name
|
|
497
531
|
end
|
|
498
532
|
|
|
@@ -512,7 +546,15 @@ module ActiveRecord
|
|
|
512
546
|
foreign_key.each { |key| self[key] = nil }
|
|
513
547
|
record.destroy
|
|
514
548
|
elsif autosave != false
|
|
515
|
-
saved =
|
|
549
|
+
saved = if record.new_record? || (autosave && record.changed_for_autosave?)
|
|
550
|
+
begin
|
|
551
|
+
@autosaving_belongs_to_for ||= {}
|
|
552
|
+
@autosaving_belongs_to_for[association] = true
|
|
553
|
+
record.save(validate: !autosave)
|
|
554
|
+
ensure
|
|
555
|
+
@autosaving_belongs_to_for[association] = false
|
|
556
|
+
end
|
|
557
|
+
end
|
|
516
558
|
|
|
517
559
|
if association.updated?
|
|
518
560
|
primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
|
data/lib/active_record/base.rb
CHANGED
|
@@ -6,7 +6,7 @@ require "active_support/descendants_tracker"
|
|
|
6
6
|
require "active_support/time"
|
|
7
7
|
require "active_support/core_ext/class/subclasses"
|
|
8
8
|
require "active_record/log_subscriber"
|
|
9
|
-
require "active_record/
|
|
9
|
+
require "active_record/structured_event_subscriber"
|
|
10
10
|
require "active_record/relation/delegation"
|
|
11
11
|
require "active_record/attributes"
|
|
12
12
|
require "active_record/type_caster"
|
|
@@ -328,7 +328,6 @@ module ActiveRecord # :nodoc:
|
|
|
328
328
|
include TokenFor
|
|
329
329
|
include SignedId
|
|
330
330
|
include Suppressor
|
|
331
|
-
include Normalization
|
|
332
331
|
include Marshalling::Methods
|
|
333
332
|
|
|
334
333
|
self.param_delimiter = "_"
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_support/json"
|
|
4
|
+
|
|
3
5
|
module ActiveRecord
|
|
4
6
|
module Coders # :nodoc:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
class JSON # :nodoc:
|
|
8
|
+
DEFAULT_OPTIONS = { escape: false }.freeze
|
|
9
|
+
|
|
10
|
+
def initialize(options = nil)
|
|
11
|
+
@options = options ? DEFAULT_OPTIONS.merge(options) : DEFAULT_OPTIONS
|
|
12
|
+
@encoder = ActiveSupport::JSON::Encoding.json_encoder.new(options)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def dump(obj)
|
|
16
|
+
@encoder.encode(obj)
|
|
8
17
|
end
|
|
9
18
|
|
|
10
|
-
def
|
|
11
|
-
ActiveSupport::JSON.decode(json) unless json.blank?
|
|
19
|
+
def load(json)
|
|
20
|
+
ActiveSupport::JSON.decode(json, @options) unless json.blank?
|
|
12
21
|
end
|
|
13
22
|
end
|
|
14
23
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "thread"
|
|
4
3
|
require "concurrent/map"
|
|
5
4
|
|
|
6
5
|
module ActiveRecord
|
|
@@ -55,19 +54,22 @@ module ActiveRecord
|
|
|
55
54
|
# about the model. The model needs to pass a connection specification name to the handler,
|
|
56
55
|
# in order to look up the correct connection pool.
|
|
57
56
|
class ConnectionHandler
|
|
58
|
-
class
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def initialize(name)
|
|
57
|
+
class ConnectionDescriptor # :nodoc:
|
|
58
|
+
def initialize(name, primary = false)
|
|
62
59
|
@name = name
|
|
60
|
+
@primary = primary
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def name
|
|
64
|
+
primary_class? ? "ActiveRecord::Base" : @name
|
|
63
65
|
end
|
|
64
66
|
|
|
65
67
|
def primary_class?
|
|
66
|
-
|
|
68
|
+
@primary
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
def current_preventing_writes
|
|
70
|
-
|
|
72
|
+
ActiveRecord::Base.preventing_writes?(@name)
|
|
71
73
|
end
|
|
72
74
|
end
|
|
73
75
|
|
|
@@ -116,7 +118,7 @@ module ActiveRecord
|
|
|
116
118
|
pool_config = resolve_pool_config(config, owner_name, role, shard)
|
|
117
119
|
db_config = pool_config.db_config
|
|
118
120
|
|
|
119
|
-
pool_manager = set_pool_manager(pool_config.
|
|
121
|
+
pool_manager = set_pool_manager(pool_config.connection_descriptor)
|
|
120
122
|
|
|
121
123
|
# If there is an existing pool with the same values as the pool_config
|
|
122
124
|
# don't remove the connection. Connections should only be removed if we are
|
|
@@ -128,8 +130,8 @@ module ActiveRecord
|
|
|
128
130
|
# Update the pool_config's connection class if it differs. This is used
|
|
129
131
|
# for ensuring that ActiveRecord::Base and the primary_abstract_class use
|
|
130
132
|
# the same pool. Without this granular swapping will not work correctly.
|
|
131
|
-
if owner_name.primary_class? && (existing_pool_config.
|
|
132
|
-
existing_pool_config.
|
|
133
|
+
if owner_name.primary_class? && (existing_pool_config.connection_descriptor != owner_name)
|
|
134
|
+
existing_pool_config.connection_descriptor = owner_name
|
|
133
135
|
end
|
|
134
136
|
|
|
135
137
|
existing_pool_config.pool
|
|
@@ -138,7 +140,7 @@ module ActiveRecord
|
|
|
138
140
|
pool_manager.set_pool_config(role, shard, pool_config)
|
|
139
141
|
|
|
140
142
|
payload = {
|
|
141
|
-
connection_name: pool_config.
|
|
143
|
+
connection_name: pool_config.connection_descriptor.name,
|
|
142
144
|
role: role,
|
|
143
145
|
shard: shard,
|
|
144
146
|
config: db_config.configuration_hash
|
|
@@ -156,9 +158,7 @@ module ActiveRecord
|
|
|
156
158
|
each_connection_pool(role).any?(&:active_connection?)
|
|
157
159
|
end
|
|
158
160
|
|
|
159
|
-
# Returns any connections in use by the current thread back to the pool
|
|
160
|
-
# and also returns connections to the pool cached by threads that are no
|
|
161
|
-
# longer alive.
|
|
161
|
+
# Returns any connections in use by the current thread back to the pool.
|
|
162
162
|
def clear_active_connections!(role = nil)
|
|
163
163
|
each_connection_pool(role).each do |pool|
|
|
164
164
|
pool.release_connection
|
|
@@ -166,7 +166,7 @@ module ActiveRecord
|
|
|
166
166
|
end
|
|
167
167
|
end
|
|
168
168
|
|
|
169
|
-
# Clears
|
|
169
|
+
# Clears reloadable connection caches in all connection pools.
|
|
170
170
|
#
|
|
171
171
|
# See ConnectionPool#clear_reloadable_connections! for details.
|
|
172
172
|
def clear_reloadable_connections!(role = nil)
|
|
@@ -210,18 +210,25 @@ module ActiveRecord
|
|
|
210
210
|
# This makes retrieving the connection pool O(1) once the process is warm.
|
|
211
211
|
# When a connection is established or removed, we invalidate the cache.
|
|
212
212
|
def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false)
|
|
213
|
-
|
|
213
|
+
pool_manager = get_pool_manager(connection_name)
|
|
214
|
+
pool = pool_manager&.get_pool_config(role, shard)&.pool
|
|
214
215
|
|
|
215
216
|
if strict && !pool
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
217
|
+
selector = [
|
|
218
|
+
("'#{shard}' shard" unless shard == ActiveRecord::Base.default_shard),
|
|
219
|
+
("'#{role}' role" unless role == ActiveRecord::Base.default_role),
|
|
220
|
+
].compact.join(" and ")
|
|
221
|
+
|
|
222
|
+
selector = [
|
|
223
|
+
(connection_name unless connection_name == "ActiveRecord::Base"),
|
|
224
|
+
selector.presence,
|
|
225
|
+
].compact.join(" with ")
|
|
226
|
+
|
|
227
|
+
selector = " for #{selector}" if selector.present?
|
|
228
|
+
|
|
229
|
+
message = "No database connection defined#{selector}."
|
|
223
230
|
|
|
224
|
-
raise
|
|
231
|
+
raise ConnectionNotDefined.new(message, connection_name: connection_name, shard: shard, role: role)
|
|
225
232
|
end
|
|
226
233
|
|
|
227
234
|
pool
|
|
@@ -236,8 +243,8 @@ module ActiveRecord
|
|
|
236
243
|
end
|
|
237
244
|
|
|
238
245
|
# Get the existing pool manager or initialize and assign a new one.
|
|
239
|
-
def set_pool_manager(
|
|
240
|
-
connection_name_to_pool_manager[
|
|
246
|
+
def set_pool_manager(connection_descriptor)
|
|
247
|
+
connection_name_to_pool_manager[connection_descriptor.name] ||= PoolManager.new
|
|
241
248
|
end
|
|
242
249
|
|
|
243
250
|
def pool_managers
|
|
@@ -272,9 +279,9 @@ module ActiveRecord
|
|
|
272
279
|
|
|
273
280
|
def determine_owner_name(owner_name, config)
|
|
274
281
|
if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
|
|
275
|
-
|
|
282
|
+
ConnectionDescriptor.new(owner_name.to_s)
|
|
276
283
|
elsif config.is_a?(Symbol)
|
|
277
|
-
|
|
284
|
+
ConnectionDescriptor.new(config.to_s)
|
|
278
285
|
else
|
|
279
286
|
owner_name
|
|
280
287
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "thread"
|
|
4
3
|
require "monitor"
|
|
5
4
|
|
|
6
5
|
module ActiveRecord
|
|
@@ -41,6 +40,14 @@ module ActiveRecord
|
|
|
41
40
|
end
|
|
42
41
|
end
|
|
43
42
|
|
|
43
|
+
# Add +element+ to the back of the queue. Never blocks.
|
|
44
|
+
def add_back(element)
|
|
45
|
+
synchronize do
|
|
46
|
+
@queue.unshift element
|
|
47
|
+
@cond.signal
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
44
51
|
# If +element+ is in the queue, remove and return it, or +nil+.
|
|
45
52
|
def delete(element)
|
|
46
53
|
synchronize do
|
|
@@ -55,6 +62,13 @@ module ActiveRecord
|
|
|
55
62
|
end
|
|
56
63
|
end
|
|
57
64
|
|
|
65
|
+
# Number of elements in the queue.
|
|
66
|
+
def size
|
|
67
|
+
synchronize do
|
|
68
|
+
@queue.size
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
58
72
|
# Remove the head of the queue.
|
|
59
73
|
#
|
|
60
74
|
# If +timeout+ is not given, remove and return the head of the
|
|
@@ -115,9 +129,7 @@ module ActiveRecord
|
|
|
115
129
|
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
116
130
|
elapsed = 0
|
|
117
131
|
loop do
|
|
118
|
-
|
|
119
|
-
@cond.wait(timeout - elapsed)
|
|
120
|
-
end
|
|
132
|
+
@cond.wait(timeout - elapsed)
|
|
121
133
|
|
|
122
134
|
return remove if any?
|
|
123
135
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "thread"
|
|
4
3
|
require "weakref"
|
|
5
4
|
|
|
6
5
|
module ActiveRecord
|
|
@@ -8,12 +7,29 @@ module ActiveRecord
|
|
|
8
7
|
class ConnectionPool
|
|
9
8
|
# = Active Record Connection Pool \Reaper
|
|
10
9
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# the connection pool.
|
|
10
|
+
# The reaper is a singleton that exists in the background of the process
|
|
11
|
+
# and is responsible for general maintenance of all the connection pools.
|
|
14
12
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
13
|
+
# It will reclaim connections that are leased to now-dead threads,
|
|
14
|
+
# ensuring that a bad thread can't leak a pool slot forever. By definition,
|
|
15
|
+
# this involves touching currently-leased connections, but that is safe
|
|
16
|
+
# because the owning thread is known to be dead.
|
|
17
|
+
#
|
|
18
|
+
# Beyond that, it manages the health of available / unleased connections:
|
|
19
|
+
# * retiring connections that have been idle[1] for too long
|
|
20
|
+
# * creating occasional activity on inactive[1] connections
|
|
21
|
+
# * keeping the pool prepopulated up to its minimum size
|
|
22
|
+
# * proactively connecting to the target database from any pooled
|
|
23
|
+
# connections that had lazily deferred that step
|
|
24
|
+
# * resetting or replacing connections that are known to be broken
|
|
25
|
+
#
|
|
26
|
+
#
|
|
27
|
+
# [1]: "idle" and "inactive" here distinguish between connections that
|
|
28
|
+
# have not been requested by the application in a while (idle) and those
|
|
29
|
+
# that have not spoken to their remote server in a while (inactive). The
|
|
30
|
+
# former is a desirable opportunity to reduce our connection count
|
|
31
|
+
# (`idle_timeout`); the latter is a risk that the server or a firewall may
|
|
32
|
+
# drop a connection we still anticipate using (avoided by `keepalive`).
|
|
17
33
|
class Reaper
|
|
18
34
|
attr_reader :pool, :frequency
|
|
19
35
|
|
|
@@ -37,6 +53,15 @@ module ActiveRecord
|
|
|
37
53
|
end
|
|
38
54
|
end
|
|
39
55
|
|
|
56
|
+
def pools(refs = nil) # :nodoc:
|
|
57
|
+
refs ||= @mutex.synchronize { @pools.values.flatten(1) }
|
|
58
|
+
|
|
59
|
+
refs.filter_map do |ref|
|
|
60
|
+
ref.__getobj__ if ref.weakref_alive?
|
|
61
|
+
rescue WeakRef::RefError
|
|
62
|
+
end.select(&:maintainable?)
|
|
63
|
+
end
|
|
64
|
+
|
|
40
65
|
private
|
|
41
66
|
def spawn_thread(frequency)
|
|
42
67
|
Thread.new(frequency) do |t|
|
|
@@ -47,23 +72,36 @@ module ActiveRecord
|
|
|
47
72
|
running = true
|
|
48
73
|
while running
|
|
49
74
|
sleep t
|
|
75
|
+
|
|
76
|
+
refs = nil
|
|
77
|
+
|
|
50
78
|
@mutex.synchronize do
|
|
51
|
-
@pools[frequency]
|
|
52
|
-
pool.weakref_alive? && !pool.discarded?
|
|
53
|
-
end
|
|
79
|
+
refs = @pools[frequency]
|
|
54
80
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
p.flush
|
|
81
|
+
refs.select! do |pool|
|
|
82
|
+
pool.weakref_alive? && !pool.discarded?
|
|
58
83
|
rescue WeakRef::RefError
|
|
59
84
|
end
|
|
60
85
|
|
|
61
|
-
if
|
|
86
|
+
if refs.empty?
|
|
62
87
|
@pools.delete(frequency)
|
|
63
88
|
@threads.delete(frequency)
|
|
64
89
|
running = false
|
|
65
90
|
end
|
|
66
91
|
end
|
|
92
|
+
|
|
93
|
+
if running
|
|
94
|
+
pools(refs).each do |pool|
|
|
95
|
+
pool.reaper_lock do
|
|
96
|
+
pool.reap
|
|
97
|
+
pool.flush
|
|
98
|
+
pool.prepopulate
|
|
99
|
+
pool.retire_old_connections
|
|
100
|
+
pool.keep_alive
|
|
101
|
+
pool.preconnect
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
67
105
|
end
|
|
68
106
|
end
|
|
69
107
|
end
|