activerecord 7.0.8.7 → 7.2.2.1
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 +631 -1944
- data/MIT-LICENSE +1 -1
- data/README.rdoc +29 -29
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +35 -12
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +23 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +22 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +26 -14
- data/lib/active_record/associations/collection_proxy.rb +29 -11
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +21 -14
- data/lib/active_record/associations/has_many_through_association.rb +17 -7
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +33 -8
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +354 -485
- data/lib/active_record/attribute_assignment.rb +0 -4
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +45 -25
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +8 -7
- data/lib/active_record/attribute_methods/serialization.rb +131 -32
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +148 -33
- data/lib/active_record/attributes.rb +64 -50
- data/lib/active_record/autosave_association.rb +69 -37
- data/lib/active_record/base.rb +9 -5
- data/lib/active_record/callbacks.rb +11 -25
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
- data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
- data/lib/active_record/connection_adapters/pool_config.rb +20 -10
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
- data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
- data/lib/active_record/connection_adapters.rb +124 -1
- data/lib/active_record/connection_handling.rb +96 -104
- data/lib/active_record/core.rb +251 -176
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +26 -5
- data/lib/active_record/database_configurations/hash_config.rb +52 -34
- data/lib/active_record/database_configurations/url_config.rb +37 -12
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +39 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +45 -21
- data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +6 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +129 -28
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +167 -97
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +11 -8
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +18 -22
- data/lib/active_record/marshalling.rb +59 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +106 -8
- data/lib/active_record/migration/compatibility.rb +147 -5
- data/lib/active_record/migration/default_strategy.rb +22 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +234 -117
- data/lib/active_record/model_schema.rb +90 -102
- data/lib/active_record/nested_attributes.rb +48 -11
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +168 -339
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -25
- data/lib/active_record/query_logs.rb +92 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +33 -8
- data/lib/active_record/railtie.rb +129 -85
- data/lib/active_record/railties/controller_runtime.rb +22 -7
- data/lib/active_record/railties/databases.rake +145 -154
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +267 -69
- data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +250 -93
- data/lib/active_record/relation/delegation.rb +30 -19
- data/lib/active_record/relation/finder_methods.rb +93 -18
- data/lib/active_record/relation/merger.rb +6 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +28 -16
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +576 -107
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +5 -4
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +580 -90
- data/lib/active_record/result.rb +49 -48
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +70 -25
- data/lib/active_record/schema.rb +8 -7
- data/lib/active_record/schema_dumper.rb +63 -14
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +3 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +27 -6
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +190 -118
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_fixtures.rb +170 -155
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +31 -17
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +106 -24
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +61 -11
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +247 -33
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +17 -5
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +112 -34
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +21 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +54 -12
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -4,37 +4,83 @@ require "yaml"
|
|
4
4
|
|
5
5
|
module ActiveRecord
|
6
6
|
module Coders # :nodoc:
|
7
|
-
class YAMLColumn # :nodoc:
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
check_arity_of_constructor
|
14
|
-
end
|
7
|
+
class YAMLColumn < ColumnSerializer # :nodoc:
|
8
|
+
class SafeCoder
|
9
|
+
def initialize(permitted_classes: [], unsafe_load: nil)
|
10
|
+
@permitted_classes = permitted_classes
|
11
|
+
@unsafe_load = unsafe_load
|
12
|
+
end
|
15
13
|
|
16
|
-
|
17
|
-
|
14
|
+
if Gem::Version.new(Psych::VERSION) >= Gem::Version.new("5.1")
|
15
|
+
def dump(object)
|
16
|
+
if @unsafe_load.nil? ? ActiveRecord.use_yaml_unsafe_load : @unsafe_load
|
17
|
+
::YAML.dump(object)
|
18
|
+
else
|
19
|
+
::YAML.safe_dump(
|
20
|
+
object,
|
21
|
+
permitted_classes: @permitted_classes + ActiveRecord.yaml_column_permitted_classes,
|
22
|
+
aliases: true,
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
else
|
27
|
+
def dump(object)
|
28
|
+
YAML.dump(object)
|
29
|
+
end
|
30
|
+
end
|
18
31
|
|
19
|
-
|
20
|
-
|
32
|
+
if YAML.respond_to?(:unsafe_load)
|
33
|
+
def load(payload)
|
34
|
+
if @unsafe_load.nil? ? ActiveRecord.use_yaml_unsafe_load : @unsafe_load
|
35
|
+
YAML.unsafe_load(payload)
|
36
|
+
else
|
37
|
+
YAML.safe_load(
|
38
|
+
payload,
|
39
|
+
permitted_classes: @permitted_classes + ActiveRecord.yaml_column_permitted_classes,
|
40
|
+
aliases: true,
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
else
|
45
|
+
def load(payload)
|
46
|
+
if @unsafe_load.nil? ? ActiveRecord.use_yaml_unsafe_load : @unsafe_load
|
47
|
+
YAML.load(payload)
|
48
|
+
else
|
49
|
+
YAML.safe_load(
|
50
|
+
payload,
|
51
|
+
permitted_classes: @permitted_classes + ActiveRecord.yaml_column_permitted_classes,
|
52
|
+
aliases: true,
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
21
57
|
end
|
22
58
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
59
|
+
def initialize(attr_name, object_class = Object, permitted_classes: [], unsafe_load: nil)
|
60
|
+
super(
|
61
|
+
attr_name,
|
62
|
+
SafeCoder.new(permitted_classes: permitted_classes || [], unsafe_load: unsafe_load),
|
63
|
+
object_class,
|
64
|
+
)
|
65
|
+
check_arity_of_constructor
|
66
|
+
end
|
30
67
|
|
31
|
-
|
68
|
+
def init_with(coder) # :nodoc:
|
69
|
+
unless coder["coder"]
|
70
|
+
permitted_classes = coder["permitted_classes"] || []
|
71
|
+
unsafe_load = coder["unsafe_load"] || false
|
72
|
+
coder["coder"] = SafeCoder.new(permitted_classes: permitted_classes, unsafe_load: unsafe_load)
|
73
|
+
end
|
74
|
+
super(coder)
|
32
75
|
end
|
33
76
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
|
77
|
+
def coder
|
78
|
+
# This is to retain forward compatibility when loading records serialized with Marshal
|
79
|
+
# from a previous version of Rails.
|
80
|
+
@coder ||= begin
|
81
|
+
permitted_classes = defined?(@permitted_classes) ? @permitted_classes : []
|
82
|
+
unsafe_load = defined?(@unsafe_load) && @unsafe_load.nil?
|
83
|
+
SafeCoder.new(permitted_classes: permitted_classes, unsafe_load: unsafe_load)
|
38
84
|
end
|
39
85
|
end
|
40
86
|
|
@@ -44,24 +90,6 @@ module ActiveRecord
|
|
44
90
|
rescue ArgumentError
|
45
91
|
raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
|
46
92
|
end
|
47
|
-
|
48
|
-
if YAML.respond_to?(:unsafe_load)
|
49
|
-
def yaml_load(payload)
|
50
|
-
if ActiveRecord.use_yaml_unsafe_load
|
51
|
-
YAML.unsafe_load(payload)
|
52
|
-
else
|
53
|
-
YAML.safe_load(payload, permitted_classes: ActiveRecord.yaml_column_permitted_classes, aliases: true)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
else
|
57
|
-
def yaml_load(payload)
|
58
|
-
if ActiveRecord.use_yaml_unsafe_load
|
59
|
-
YAML.load(payload)
|
60
|
-
else
|
61
|
-
YAML.safe_load(payload, permitted_classes: ActiveRecord.yaml_column_permitted_classes, aliases: true)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
93
|
end
|
66
94
|
end
|
67
95
|
end
|
@@ -5,6 +5,8 @@ require "concurrent/map"
|
|
5
5
|
|
6
6
|
module ActiveRecord
|
7
7
|
module ConnectionAdapters
|
8
|
+
# = Active Record Connection Handler
|
9
|
+
#
|
8
10
|
# ConnectionHandler is a collection of ConnectionPool objects. It is used
|
9
11
|
# for keeping separate connection pools that connect to different databases.
|
10
12
|
#
|
@@ -53,10 +55,7 @@ module ActiveRecord
|
|
53
55
|
# about the model. The model needs to pass a connection specification name to the handler,
|
54
56
|
# in order to look up the correct connection pool.
|
55
57
|
class ConnectionHandler
|
56
|
-
|
57
|
-
private_constant :FINALIZER
|
58
|
-
|
59
|
-
class StringConnectionOwner # :nodoc:
|
58
|
+
class StringConnectionName # :nodoc:
|
60
59
|
attr_reader :name
|
61
60
|
|
62
61
|
def initialize(name)
|
@@ -73,11 +72,8 @@ module ActiveRecord
|
|
73
72
|
end
|
74
73
|
|
75
74
|
def initialize
|
76
|
-
# These caches are keyed by pool_config.
|
77
|
-
@
|
78
|
-
|
79
|
-
# Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred
|
80
|
-
ObjectSpace.define_finalizer self, FINALIZER
|
75
|
+
# These caches are keyed by pool_config.connection_name (PoolConfig#connection_name).
|
76
|
+
@connection_name_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
|
81
77
|
end
|
82
78
|
|
83
79
|
def prevent_writes # :nodoc:
|
@@ -88,138 +84,167 @@ module ActiveRecord
|
|
88
84
|
ActiveSupport::IsolatedExecutionState[:active_record_prevent_writes] = prevent_writes
|
89
85
|
end
|
90
86
|
|
91
|
-
# Prevent writing to the database regardless of role.
|
92
|
-
#
|
93
|
-
# In some cases you may want to prevent writes to the database
|
94
|
-
# even if you are on a database that can write. +while_preventing_writes+
|
95
|
-
# will prevent writes to the database for the duration of the block.
|
96
|
-
#
|
97
|
-
# This method does not provide the same protection as a readonly
|
98
|
-
# user and is meant to be a safeguard against accidental writes.
|
99
|
-
#
|
100
|
-
# See +READ_QUERY+ for the queries that are blocked by this
|
101
|
-
# method.
|
102
|
-
def while_preventing_writes(enabled = true)
|
103
|
-
unless ActiveRecord.legacy_connection_handling
|
104
|
-
raise NotImplementedError, "`while_preventing_writes` is only available on the connection_handler with legacy_connection_handling"
|
105
|
-
end
|
106
|
-
|
107
|
-
original, self.prevent_writes = self.prevent_writes, enabled
|
108
|
-
yield
|
109
|
-
ensure
|
110
|
-
self.prevent_writes = original
|
111
|
-
end
|
112
|
-
|
113
87
|
def connection_pool_names # :nodoc:
|
114
|
-
|
88
|
+
connection_name_to_pool_manager.keys
|
115
89
|
end
|
116
90
|
|
117
|
-
|
118
|
-
|
91
|
+
# Returns the pools for a connection handler and given role. If +:all+ is passed,
|
92
|
+
# all pools belonging to the connection handler will be returned.
|
93
|
+
def connection_pool_list(role = nil)
|
94
|
+
if role.nil? || role == :all
|
95
|
+
connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
|
96
|
+
else
|
97
|
+
connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
|
98
|
+
end
|
119
99
|
end
|
100
|
+
alias :connection_pools :connection_pool_list
|
101
|
+
|
102
|
+
def each_connection_pool(role = nil, &block) # :nodoc:
|
103
|
+
role = nil if role == :all
|
104
|
+
return enum_for(__method__, role) unless block_given?
|
120
105
|
|
121
|
-
|
122
|
-
|
106
|
+
connection_name_to_pool_manager.each_value do |manager|
|
107
|
+
manager.each_pool_config(role) do |pool_config|
|
108
|
+
yield pool_config.pool
|
109
|
+
end
|
110
|
+
end
|
123
111
|
end
|
124
|
-
alias :connection_pools :connection_pool_list
|
125
112
|
|
126
|
-
def establish_connection(config, owner_name: Base, role:
|
127
|
-
owner_name =
|
113
|
+
def establish_connection(config, owner_name: Base, role: Base.current_role, shard: Base.current_shard, clobber: false)
|
114
|
+
owner_name = determine_owner_name(owner_name, config)
|
128
115
|
|
129
116
|
pool_config = resolve_pool_config(config, owner_name, role, shard)
|
130
117
|
db_config = pool_config.db_config
|
131
118
|
|
132
|
-
|
133
|
-
# if the user calls `establish_connection :primary`.
|
134
|
-
if owner_to_pool_manager.key?(pool_config.connection_specification_name)
|
135
|
-
remove_connection_pool(pool_config.connection_specification_name, role: role, shard: shard)
|
136
|
-
end
|
119
|
+
pool_manager = set_pool_manager(pool_config.connection_name)
|
137
120
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
payload[:config] = db_config.configuration_hash
|
144
|
-
end
|
121
|
+
# If there is an existing pool with the same values as the pool_config
|
122
|
+
# don't remove the connection. Connections should only be removed if we are
|
123
|
+
# establishing a connection on a class that is already connected to a different
|
124
|
+
# configuration.
|
125
|
+
existing_pool_config = pool_manager.get_pool_config(role, shard)
|
145
126
|
|
146
|
-
if
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
127
|
+
if !clobber && existing_pool_config && existing_pool_config.db_config == db_config
|
128
|
+
# Update the pool_config's connection class if it differs. This is used
|
129
|
+
# for ensuring that ActiveRecord::Base and the primary_abstract_class use
|
130
|
+
# the same pool. Without this granular swapping will not work correctly.
|
131
|
+
if owner_name.primary_class? && (existing_pool_config.connection_class != owner_name)
|
132
|
+
existing_pool_config.connection_class = owner_name
|
133
|
+
end
|
153
134
|
|
154
|
-
|
155
|
-
|
135
|
+
existing_pool_config.pool
|
136
|
+
else
|
137
|
+
disconnect_pool_from_pool_manager(pool_manager, role, shard)
|
138
|
+
pool_manager.set_pool_config(role, shard, pool_config)
|
139
|
+
|
140
|
+
payload = {
|
141
|
+
connection_name: pool_config.connection_name,
|
142
|
+
role: role,
|
143
|
+
shard: shard,
|
144
|
+
config: db_config.configuration_hash
|
145
|
+
}
|
146
|
+
|
147
|
+
ActiveSupport::Notifications.instrumenter.instrument("!connection.active_record", payload) do
|
148
|
+
pool_config.pool
|
149
|
+
end
|
156
150
|
end
|
157
151
|
end
|
158
152
|
|
159
153
|
# Returns true if there are any active connections among the connection
|
160
154
|
# pools that the ConnectionHandler is managing.
|
161
|
-
def active_connections?(role =
|
162
|
-
|
155
|
+
def active_connections?(role = nil)
|
156
|
+
each_connection_pool(role).any?(&:active_connection?)
|
163
157
|
end
|
164
158
|
|
165
159
|
# Returns any connections in use by the current thread back to the pool,
|
166
160
|
# and also returns connections to the pool cached by threads that are no
|
167
161
|
# longer alive.
|
168
|
-
def clear_active_connections!(role =
|
169
|
-
|
162
|
+
def clear_active_connections!(role = nil)
|
163
|
+
each_connection_pool(role).each do |pool|
|
164
|
+
pool.release_connection
|
165
|
+
pool.disable_query_cache!
|
166
|
+
end
|
170
167
|
end
|
171
168
|
|
172
169
|
# Clears the cache which maps classes.
|
173
170
|
#
|
174
171
|
# See ConnectionPool#clear_reloadable_connections! for details.
|
175
|
-
def clear_reloadable_connections!(role =
|
176
|
-
|
172
|
+
def clear_reloadable_connections!(role = nil)
|
173
|
+
each_connection_pool(role).each(&:clear_reloadable_connections!)
|
177
174
|
end
|
178
175
|
|
179
|
-
def clear_all_connections!(role =
|
180
|
-
|
176
|
+
def clear_all_connections!(role = nil)
|
177
|
+
each_connection_pool(role).each(&:disconnect!)
|
181
178
|
end
|
182
179
|
|
183
180
|
# Disconnects all currently idle connections.
|
184
181
|
#
|
185
182
|
# See ConnectionPool#flush! for details.
|
186
|
-
def flush_idle_connections!(role =
|
187
|
-
|
183
|
+
def flush_idle_connections!(role = nil)
|
184
|
+
each_connection_pool(role).each(&:flush!)
|
188
185
|
end
|
189
186
|
|
190
187
|
# Locate the connection of the nearest super class. This can be an
|
191
188
|
# active or defined connection: if it is the latter, it will be
|
192
189
|
# opened and set as the active connection for the class it was defined
|
193
190
|
# for (not necessarily the current class).
|
194
|
-
def retrieve_connection(
|
195
|
-
pool = retrieve_connection_pool(
|
191
|
+
def retrieve_connection(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
|
192
|
+
pool = retrieve_connection_pool(connection_name, role: role, shard: shard, strict: true)
|
193
|
+
pool.lease_connection
|
194
|
+
end
|
195
|
+
|
196
|
+
# Returns true if a connection that's accessible to this class has
|
197
|
+
# already been opened.
|
198
|
+
def connected?(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
|
199
|
+
pool = retrieve_connection_pool(connection_name, role: role, shard: shard)
|
200
|
+
pool && pool.connected?
|
201
|
+
end
|
196
202
|
|
197
|
-
|
203
|
+
def remove_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
|
204
|
+
if pool_manager = get_pool_manager(connection_name)
|
205
|
+
disconnect_pool_from_pool_manager(pool_manager, role, shard)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Retrieving the connection pool happens a lot, so we cache it in @connection_name_to_pool_manager.
|
210
|
+
# This makes retrieving the connection pool O(1) once the process is warm.
|
211
|
+
# When a connection is established or removed, we invalidate the cache.
|
212
|
+
def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false)
|
213
|
+
pool = get_pool_manager(connection_name)&.get_pool_config(role, shard)&.pool
|
214
|
+
|
215
|
+
if strict && !pool
|
198
216
|
if shard != ActiveRecord::Base.default_shard
|
199
|
-
message = "No connection pool for '#{
|
200
|
-
elsif ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler
|
201
|
-
message = "No connection pool for '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role."
|
217
|
+
message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
|
202
218
|
elsif role != ActiveRecord::Base.default_role
|
203
|
-
message = "No connection pool for '#{
|
219
|
+
message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
|
204
220
|
else
|
205
|
-
message = "No connection pool for '#{
|
221
|
+
message = "No connection pool for '#{connection_name}' found."
|
206
222
|
end
|
207
223
|
|
208
224
|
raise ConnectionNotEstablished, message
|
209
225
|
end
|
210
226
|
|
211
|
-
pool
|
227
|
+
pool
|
212
228
|
end
|
213
229
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
pool
|
218
|
-
|
219
|
-
|
230
|
+
private
|
231
|
+
attr_reader :connection_name_to_pool_manager
|
232
|
+
|
233
|
+
# Returns the pool manager for a connection name / identifier.
|
234
|
+
def get_pool_manager(connection_name)
|
235
|
+
connection_name_to_pool_manager[connection_name]
|
236
|
+
end
|
237
|
+
|
238
|
+
# Get the existing pool manager or initialize and assign a new one.
|
239
|
+
def set_pool_manager(connection_name)
|
240
|
+
connection_name_to_pool_manager[connection_name] ||= PoolManager.new
|
241
|
+
end
|
242
|
+
|
243
|
+
def pool_managers
|
244
|
+
connection_name_to_pool_manager.values
|
245
|
+
end
|
220
246
|
|
221
|
-
|
222
|
-
if pool_manager = get_pool_manager(owner)
|
247
|
+
def disconnect_pool_from_pool_manager(pool_manager, role, shard)
|
223
248
|
pool_config = pool_manager.remove_pool_config(role, shard)
|
224
249
|
|
225
250
|
if pool_config
|
@@ -227,23 +252,6 @@ module ActiveRecord
|
|
227
252
|
pool_config.db_config
|
228
253
|
end
|
229
254
|
end
|
230
|
-
end
|
231
|
-
|
232
|
-
# Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool_manager.
|
233
|
-
# This makes retrieving the connection pool O(1) once the process is warm.
|
234
|
-
# When a connection is established or removed, we invalidate the cache.
|
235
|
-
def retrieve_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
|
236
|
-
pool_config = get_pool_manager(owner)&.get_pool_config(role, shard)
|
237
|
-
pool_config&.pool
|
238
|
-
end
|
239
|
-
|
240
|
-
private
|
241
|
-
attr_reader :owner_to_pool_manager
|
242
|
-
|
243
|
-
# Returns the pool manager for an owner.
|
244
|
-
def get_pool_manager(owner)
|
245
|
-
owner_to_pool_manager[owner]
|
246
|
-
end
|
247
255
|
|
248
256
|
# Returns an instance of PoolConfig for a given adapter.
|
249
257
|
# Accepts a hash one layer deep that contains all connection information.
|
@@ -255,37 +263,21 @@ module ActiveRecord
|
|
255
263
|
# pool_config.db_config.configuration_hash
|
256
264
|
# # => { host: "localhost", database: "foo", adapter: "sqlite3" }
|
257
265
|
#
|
258
|
-
def resolve_pool_config(config,
|
266
|
+
def resolve_pool_config(config, connection_name, role, shard)
|
259
267
|
db_config = Base.configurations.resolve(config)
|
260
|
-
|
268
|
+
db_config.validate!
|
261
269
|
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
|
270
|
+
ConnectionAdapters::PoolConfig.new(connection_name, db_config, role, shard)
|
271
|
+
end
|
262
272
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
# We couldn't require the adapter itself. Raise an exception that
|
271
|
-
# points out config typos and missing gems.
|
272
|
-
if e.path == path_to_adapter
|
273
|
-
# We can assume that a non-builtin adapter was specified, so it's
|
274
|
-
# either misspelled or missing from Gemfile.
|
275
|
-
raise LoadError, "Could not load the '#{db_config.adapter}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
|
276
|
-
|
277
|
-
# Bubbled up from the adapter require. Prefix the exception message
|
278
|
-
# with some guidance about how to address it and reraise.
|
279
|
-
else
|
280
|
-
raise LoadError, "Error loading the '#{db_config.adapter}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
|
281
|
-
end
|
282
|
-
end
|
283
|
-
|
284
|
-
unless ActiveRecord::Base.respond_to?(db_config.adapter_method)
|
285
|
-
raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
|
273
|
+
def determine_owner_name(owner_name, config)
|
274
|
+
if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
|
275
|
+
StringConnectionName.new(owner_name.to_s)
|
276
|
+
elsif config.is_a?(Symbol)
|
277
|
+
StringConnectionName.new(config.to_s)
|
278
|
+
else
|
279
|
+
owner_name
|
286
280
|
end
|
287
|
-
|
288
|
-
ConnectionAdapters::PoolConfig.new(owner_name, db_config, role, shard)
|
289
281
|
end
|
290
282
|
end
|
291
283
|
end
|
@@ -6,12 +6,14 @@ require "weakref"
|
|
6
6
|
module ActiveRecord
|
7
7
|
module ConnectionAdapters
|
8
8
|
class ConnectionPool
|
9
|
+
# = Active Record Connection Pool \Reaper
|
10
|
+
#
|
9
11
|
# Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
|
10
12
|
# +pool+. A reaper instantiated with a zero frequency will never reap
|
11
13
|
# the connection pool.
|
12
14
|
#
|
13
15
|
# Configure the frequency by setting +reaping_frequency+ in your database
|
14
|
-
#
|
16
|
+
# YAML file (default 60 seconds).
|
15
17
|
class Reaper
|
16
18
|
attr_reader :pool, :frequency
|
17
19
|
|
@@ -41,6 +43,7 @@ module ActiveRecord
|
|
41
43
|
# Advise multi-threaded app servers to ignore this thread for
|
42
44
|
# the purposes of fork safety warnings
|
43
45
|
Thread.current.thread_variable_set(:fork_safe, true)
|
46
|
+
Thread.current.name = "AR Pool Reaper"
|
44
47
|
running = true
|
45
48
|
while running
|
46
49
|
sleep t
|