activerecord 7.0.0 → 7.1.2
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 +1701 -1039
- data/MIT-LICENSE +1 -1
- data/README.rdoc +18 -18
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +18 -3
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +17 -12
- data/lib/active_record/associations/collection_proxy.rb +22 -12
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +27 -17
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +20 -14
- data/lib/active_record/associations/preloader/association.rb +27 -6
- data/lib/active_record/associations/preloader/through_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +362 -236
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +52 -34
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +172 -69
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +110 -28
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +56 -10
- data/lib/active_record/base.rb +10 -5
- data/lib/active_record/callbacks.rb +16 -32
- 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 -34
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
- 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 +163 -29
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -131
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +513 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
- 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 +23 -144
- data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- 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 +4 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +372 -63
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +359 -197
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
- 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 +254 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +73 -96
- data/lib/active_record/core.rb +142 -153
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +9 -4
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/disable_joins_association_relation.rb +1 -1
- 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 +13 -14
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +8 -4
- data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +38 -22
- data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
- data/lib/active_record/encryption/encryptor.rb +7 -7
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
- 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/message.rb +1 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +4 -4
- data/lib/active_record/encryption/scheme.rb +20 -23
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +113 -29
- data/lib/active_record/errors.rb +108 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/explain_subscriber.rb +1 -1
- 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 +121 -73
- data/lib/active_record/future_result.rb +30 -5
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +10 -10
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/optimistic.rb +32 -18
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +39 -17
- data/lib/active_record/marshalling.rb +56 -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 +18 -13
- data/lib/active_record/middleware/shard_selector.rb +7 -5
- data/lib/active_record/migration/command_recorder.rb +108 -10
- data/lib/active_record/migration/compatibility.rb +158 -64
- data/lib/active_record/migration/default_strategy.rb +23 -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 +274 -117
- data/lib/active_record/model_schema.rb +86 -54
- data/lib/active_record/nested_attributes.rb +24 -6
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +200 -47
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +87 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +16 -3
- data/lib/active_record/railtie.rb +128 -62
- data/lib/active_record/railties/controller_runtime.rb +12 -8
- data/lib/active_record/railties/databases.rake +145 -146
- 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 +189 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +208 -83
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +430 -77
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +98 -41
- data/lib/active_record/result.rb +25 -9
- data/lib/active_record/runtime_registry.rb +10 -1
- data/lib/active_record/sanitization.rb +57 -16
- data/lib/active_record/schema.rb +36 -22
- data/lib/active_record/schema_dumper.rb +65 -23
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +20 -12
- data/lib/active_record/scoping/named.rb +2 -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/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +9 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +138 -107
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +123 -99
- data/lib/active_record/timestamp.rb +27 -15
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +39 -13
- data/lib/active_record/translation.rb +1 -1
- 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 +8 -4
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +3 -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 +50 -5
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +143 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +1 -1
- data/lib/arel/nodes/and.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/filter.rb +1 -1
- 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/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- 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 +51 -15
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -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
|
#
|
@@ -40,7 +42,7 @@ module ActiveRecord
|
|
40
42
|
# but the Book model connects to a separate database called "library_db"
|
41
43
|
# (this can even be a database on a different machine).
|
42
44
|
#
|
43
|
-
# Book, ScaryBook and GoodBook will all use the same connection pool to
|
45
|
+
# Book, ScaryBook, and GoodBook will all use the same connection pool to
|
44
46
|
# "library_db" while Author, BankAccount, and any other models you create
|
45
47
|
# will use the default connection pool to "my_application".
|
46
48
|
#
|
@@ -56,7 +58,7 @@ module ActiveRecord
|
|
56
58
|
FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
|
57
59
|
private_constant :FINALIZER
|
58
60
|
|
59
|
-
class
|
61
|
+
class StringConnectionName # :nodoc:
|
60
62
|
attr_reader :name
|
61
63
|
|
62
64
|
def initialize(name)
|
@@ -73,8 +75,8 @@ module ActiveRecord
|
|
73
75
|
end
|
74
76
|
|
75
77
|
def initialize
|
76
|
-
# These caches are keyed by pool_config.
|
77
|
-
@
|
78
|
+
# These caches are keyed by pool_config.connection_name (PoolConfig#connection_name).
|
79
|
+
@connection_name_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
|
78
80
|
|
79
81
|
# Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred
|
80
82
|
ObjectSpace.define_finalizer self, FINALIZER
|
@@ -88,121 +90,154 @@ module ActiveRecord
|
|
88
90
|
ActiveSupport::IsolatedExecutionState[:active_record_prevent_writes] = prevent_writes
|
89
91
|
end
|
90
92
|
|
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
93
|
def connection_pool_names # :nodoc:
|
114
|
-
|
94
|
+
connection_name_to_pool_manager.keys
|
115
95
|
end
|
116
96
|
|
117
97
|
def all_connection_pools
|
118
|
-
|
98
|
+
ActiveRecord.deprecator.warn(<<-MSG.squish)
|
99
|
+
The `all_connection_pools` method is deprecated in favor of `connection_pool_list`.
|
100
|
+
Call `connection_pool_list(:all)` to get the same behavior as `all_connection_pools`.
|
101
|
+
MSG
|
102
|
+
connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
|
119
103
|
end
|
120
104
|
|
121
|
-
|
122
|
-
|
105
|
+
# Returns the pools for a connection handler and given role. If +:all+ is passed,
|
106
|
+
# all pools belonging to the connection handler will be returned.
|
107
|
+
def connection_pool_list(role = nil)
|
108
|
+
if role.nil?
|
109
|
+
deprecation_for_pool_handling(__method__)
|
110
|
+
role = ActiveRecord::Base.current_role
|
111
|
+
connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
|
112
|
+
elsif role == :all
|
113
|
+
connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
|
114
|
+
else
|
115
|
+
connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
|
116
|
+
end
|
123
117
|
end
|
124
118
|
alias :connection_pools :connection_pool_list
|
125
119
|
|
126
|
-
def
|
127
|
-
|
120
|
+
def each_connection_pool(role = nil, &block) # :nodoc:
|
121
|
+
role = nil if role == :all
|
122
|
+
return enum_for(__method__, role) unless block_given?
|
123
|
+
|
124
|
+
connection_name_to_pool_manager.each_value do |manager|
|
125
|
+
manager.each_pool_config(role) do |pool_config|
|
126
|
+
yield pool_config.pool
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def establish_connection(config, owner_name: Base, role: Base.current_role, shard: Base.current_shard, clobber: false)
|
132
|
+
owner_name = determine_owner_name(owner_name, config)
|
128
133
|
|
129
134
|
pool_config = resolve_pool_config(config, owner_name, role, shard)
|
130
135
|
db_config = pool_config.db_config
|
131
136
|
|
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
|
137
|
+
pool_manager = set_pool_manager(pool_config.connection_name)
|
137
138
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
payload[:config] = db_config.configuration_hash
|
144
|
-
end
|
139
|
+
# If there is an existing pool with the same values as the pool_config
|
140
|
+
# don't remove the connection. Connections should only be removed if we are
|
141
|
+
# establishing a connection on a class that is already connected to a different
|
142
|
+
# configuration.
|
143
|
+
existing_pool_config = pool_manager.get_pool_config(role, shard)
|
145
144
|
|
146
|
-
if
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
145
|
+
if !clobber && existing_pool_config && existing_pool_config.db_config == db_config
|
146
|
+
# Update the pool_config's connection class if it differs. This is used
|
147
|
+
# for ensuring that ActiveRecord::Base and the primary_abstract_class use
|
148
|
+
# the same pool. Without this granular swapping will not work correctly.
|
149
|
+
if owner_name.primary_class? && (existing_pool_config.connection_class != owner_name)
|
150
|
+
existing_pool_config.connection_class = owner_name
|
151
|
+
end
|
153
152
|
|
154
|
-
|
155
|
-
|
153
|
+
existing_pool_config.pool
|
154
|
+
else
|
155
|
+
disconnect_pool_from_pool_manager(pool_manager, role, shard)
|
156
|
+
pool_manager.set_pool_config(role, shard, pool_config)
|
157
|
+
|
158
|
+
payload = {
|
159
|
+
connection_name: pool_config.connection_name,
|
160
|
+
role: role,
|
161
|
+
shard: shard,
|
162
|
+
config: db_config.configuration_hash
|
163
|
+
}
|
164
|
+
|
165
|
+
ActiveSupport::Notifications.instrumenter.instrument("!connection.active_record", payload) do
|
166
|
+
pool_config.pool
|
167
|
+
end
|
156
168
|
end
|
157
169
|
end
|
158
170
|
|
159
171
|
# Returns true if there are any active connections among the connection
|
160
172
|
# pools that the ConnectionHandler is managing.
|
161
|
-
def active_connections?(role =
|
162
|
-
|
173
|
+
def active_connections?(role = nil)
|
174
|
+
if role.nil?
|
175
|
+
deprecation_for_pool_handling(__method__)
|
176
|
+
role = ActiveRecord::Base.current_role
|
177
|
+
end
|
178
|
+
|
179
|
+
each_connection_pool(role).any?(&:active_connection?)
|
163
180
|
end
|
164
181
|
|
165
182
|
# Returns any connections in use by the current thread back to the pool,
|
166
183
|
# and also returns connections to the pool cached by threads that are no
|
167
184
|
# longer alive.
|
168
|
-
def clear_active_connections!(role =
|
169
|
-
|
185
|
+
def clear_active_connections!(role = nil)
|
186
|
+
if role.nil?
|
187
|
+
deprecation_for_pool_handling(__method__)
|
188
|
+
role = ActiveRecord::Base.current_role
|
189
|
+
end
|
190
|
+
|
191
|
+
each_connection_pool(role).each(&:release_connection)
|
170
192
|
end
|
171
193
|
|
172
194
|
# Clears the cache which maps classes.
|
173
195
|
#
|
174
196
|
# See ConnectionPool#clear_reloadable_connections! for details.
|
175
|
-
def clear_reloadable_connections!(role =
|
176
|
-
|
197
|
+
def clear_reloadable_connections!(role = nil)
|
198
|
+
if role.nil?
|
199
|
+
deprecation_for_pool_handling(__method__)
|
200
|
+
role = ActiveRecord::Base.current_role
|
201
|
+
end
|
202
|
+
|
203
|
+
each_connection_pool(role).each(&:clear_reloadable_connections!)
|
177
204
|
end
|
178
205
|
|
179
|
-
def clear_all_connections!(role =
|
180
|
-
|
206
|
+
def clear_all_connections!(role = nil)
|
207
|
+
if role.nil?
|
208
|
+
deprecation_for_pool_handling(__method__)
|
209
|
+
role = ActiveRecord::Base.current_role
|
210
|
+
end
|
211
|
+
|
212
|
+
each_connection_pool(role).each(&:disconnect!)
|
181
213
|
end
|
182
214
|
|
183
215
|
# Disconnects all currently idle connections.
|
184
216
|
#
|
185
217
|
# See ConnectionPool#flush! for details.
|
186
|
-
def flush_idle_connections!(role =
|
187
|
-
|
218
|
+
def flush_idle_connections!(role = nil)
|
219
|
+
if role.nil?
|
220
|
+
deprecation_for_pool_handling(__method__)
|
221
|
+
role = ActiveRecord::Base.current_role
|
222
|
+
end
|
223
|
+
|
224
|
+
each_connection_pool(role).each(&:flush!)
|
188
225
|
end
|
189
226
|
|
190
227
|
# Locate the connection of the nearest super class. This can be an
|
191
228
|
# active or defined connection: if it is the latter, it will be
|
192
229
|
# opened and set as the active connection for the class it was defined
|
193
230
|
# for (not necessarily the current class).
|
194
|
-
def retrieve_connection(
|
195
|
-
pool = retrieve_connection_pool(
|
231
|
+
def retrieve_connection(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
|
232
|
+
pool = retrieve_connection_pool(connection_name, role: role, shard: shard)
|
196
233
|
|
197
234
|
unless pool
|
198
235
|
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."
|
236
|
+
message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
|
202
237
|
elsif role != ActiveRecord::Base.default_role
|
203
|
-
message = "No connection pool for '#{
|
238
|
+
message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
|
204
239
|
else
|
205
|
-
message = "No connection pool for '#{
|
240
|
+
message = "No connection pool for '#{connection_name}' found."
|
206
241
|
end
|
207
242
|
|
208
243
|
raise ConnectionNotEstablished, message
|
@@ -213,36 +248,66 @@ module ActiveRecord
|
|
213
248
|
|
214
249
|
# Returns true if a connection that's accessible to this class has
|
215
250
|
# already been opened.
|
216
|
-
def connected?(
|
217
|
-
pool = retrieve_connection_pool(
|
251
|
+
def connected?(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
|
252
|
+
pool = retrieve_connection_pool(connection_name, role: role, shard: shard)
|
218
253
|
pool && pool.connected?
|
219
254
|
end
|
220
255
|
|
221
|
-
def remove_connection_pool(
|
222
|
-
if pool_manager = get_pool_manager(
|
223
|
-
|
224
|
-
|
225
|
-
if pool_config
|
226
|
-
pool_config.disconnect!
|
227
|
-
pool_config.db_config
|
228
|
-
end
|
256
|
+
def remove_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
|
257
|
+
if pool_manager = get_pool_manager(connection_name)
|
258
|
+
disconnect_pool_from_pool_manager(pool_manager, role, shard)
|
229
259
|
end
|
230
260
|
end
|
231
261
|
|
232
|
-
# Retrieving the connection pool happens a lot, so we cache it in @
|
262
|
+
# Retrieving the connection pool happens a lot, so we cache it in @connection_name_to_pool_manager.
|
233
263
|
# This makes retrieving the connection pool O(1) once the process is warm.
|
234
264
|
# When a connection is established or removed, we invalidate the cache.
|
235
|
-
def retrieve_connection_pool(
|
236
|
-
pool_config = get_pool_manager(
|
265
|
+
def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
|
266
|
+
pool_config = get_pool_manager(connection_name)&.get_pool_config(role, shard)
|
237
267
|
pool_config&.pool
|
238
268
|
end
|
239
269
|
|
240
270
|
private
|
241
|
-
attr_reader :
|
271
|
+
attr_reader :connection_name_to_pool_manager
|
242
272
|
|
243
|
-
# Returns the pool manager for
|
244
|
-
def get_pool_manager(
|
245
|
-
|
273
|
+
# Returns the pool manager for a connection name / identifier.
|
274
|
+
def get_pool_manager(connection_name)
|
275
|
+
connection_name_to_pool_manager[connection_name]
|
276
|
+
end
|
277
|
+
|
278
|
+
# Get the existing pool manager or initialize and assign a new one.
|
279
|
+
def set_pool_manager(connection_name)
|
280
|
+
connection_name_to_pool_manager[connection_name] ||= PoolManager.new
|
281
|
+
end
|
282
|
+
|
283
|
+
def pool_managers
|
284
|
+
connection_name_to_pool_manager.values
|
285
|
+
end
|
286
|
+
|
287
|
+
def deprecation_for_pool_handling(method)
|
288
|
+
roles = []
|
289
|
+
pool_managers.each do |pool_manager|
|
290
|
+
roles << pool_manager.role_names
|
291
|
+
end
|
292
|
+
|
293
|
+
if roles.flatten.uniq.count > 1
|
294
|
+
ActiveRecord.deprecator.warn(<<-MSG.squish)
|
295
|
+
`#{method}` currently only applies to connection pools in the current
|
296
|
+
role (`#{ActiveRecord::Base.current_role}`). In Rails 7.2, this method
|
297
|
+
will apply to all known pools, regardless of role. To affect only those
|
298
|
+
connections belonging to a specific role, pass the role name as an
|
299
|
+
argument. To switch to the new behavior, pass `:all` as the role name.
|
300
|
+
MSG
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def disconnect_pool_from_pool_manager(pool_manager, role, shard)
|
305
|
+
pool_config = pool_manager.remove_pool_config(role, shard)
|
306
|
+
|
307
|
+
if pool_config
|
308
|
+
pool_config.disconnect!
|
309
|
+
pool_config.db_config
|
310
|
+
end
|
246
311
|
end
|
247
312
|
|
248
313
|
# Returns an instance of PoolConfig for a given adapter.
|
@@ -255,7 +320,7 @@ module ActiveRecord
|
|
255
320
|
# pool_config.db_config.configuration_hash
|
256
321
|
# # => { host: "localhost", database: "foo", adapter: "sqlite3" }
|
257
322
|
#
|
258
|
-
def resolve_pool_config(config,
|
323
|
+
def resolve_pool_config(config, connection_name, role, shard)
|
259
324
|
db_config = Base.configurations.resolve(config)
|
260
325
|
|
261
326
|
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
|
@@ -285,7 +350,17 @@ module ActiveRecord
|
|
285
350
|
raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
|
286
351
|
end
|
287
352
|
|
288
|
-
ConnectionAdapters::PoolConfig.new(
|
353
|
+
ConnectionAdapters::PoolConfig.new(connection_name, db_config, role, shard)
|
354
|
+
end
|
355
|
+
|
356
|
+
def determine_owner_name(owner_name, config)
|
357
|
+
if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
|
358
|
+
StringConnectionName.new(owner_name.to_s)
|
359
|
+
elsif config.is_a?(Symbol)
|
360
|
+
StringConnectionName.new(config.to_s)
|
361
|
+
else
|
362
|
+
owner_name
|
363
|
+
end
|
289
364
|
end
|
290
365
|
end
|
291
366
|
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
|
|
@@ -10,35 +10,33 @@ require "active_record/connection_adapters/abstract/connection_pool/reaper"
|
|
10
10
|
module ActiveRecord
|
11
11
|
module ConnectionAdapters
|
12
12
|
module AbstractPool # :nodoc:
|
13
|
-
def get_schema_cache(connection)
|
14
|
-
self.schema_cache ||= SchemaCache.new(connection)
|
15
|
-
schema_cache.connection = connection
|
16
|
-
schema_cache
|
17
|
-
end
|
18
|
-
|
19
|
-
def set_schema_cache(cache)
|
20
|
-
self.schema_cache = cache
|
21
|
-
end
|
22
|
-
|
23
|
-
def lazily_set_schema_cache
|
24
|
-
return unless ActiveRecord.lazily_load_schema_cache
|
25
|
-
|
26
|
-
cache = SchemaCache.load_from(db_config.lazy_schema_cache_path)
|
27
|
-
set_schema_cache(cache)
|
28
|
-
end
|
29
13
|
end
|
30
14
|
|
31
15
|
class NullPool # :nodoc:
|
32
16
|
include ConnectionAdapters::AbstractPool
|
33
17
|
|
34
|
-
|
18
|
+
class NullConfig # :nodoc:
|
19
|
+
def method_missing(*)
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
NULL_CONFIG = NullConfig.new # :nodoc:
|
24
|
+
|
25
|
+
def schema_reflection
|
26
|
+
SchemaReflection.new(nil)
|
27
|
+
end
|
35
28
|
|
36
29
|
def connection_class; end
|
37
30
|
def checkin(_); end
|
38
31
|
def remove(_); end
|
39
32
|
def async_executor; end
|
33
|
+
def db_config
|
34
|
+
NULL_CONFIG
|
35
|
+
end
|
40
36
|
end
|
41
37
|
|
38
|
+
# = Active Record Connection Pool
|
39
|
+
#
|
42
40
|
# Connection pool base class for managing Active Record database
|
43
41
|
# connections.
|
44
42
|
#
|
@@ -53,7 +51,7 @@ module ActiveRecord
|
|
53
51
|
# handle cases in which there are more threads than connections: if all
|
54
52
|
# connections have been checked out, and a thread tries to checkout a
|
55
53
|
# connection anyway, then ConnectionPool will wait until some other thread
|
56
|
-
# has checked in a connection.
|
54
|
+
# has checked in a connection, or the +checkout_timeout+ has expired.
|
57
55
|
#
|
58
56
|
# == Obtaining (checking out) a connection
|
59
57
|
#
|
@@ -64,7 +62,7 @@ module ActiveRecord
|
|
64
62
|
# as with Active Record 2.1 and
|
65
63
|
# earlier (pre-connection-pooling). Eventually, when you're done with
|
66
64
|
# the connection(s) and wish it to be returned to the pool, you call
|
67
|
-
# {ActiveRecord::Base.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
|
65
|
+
# {ActiveRecord::Base.connection_handler.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
|
68
66
|
# This will be the default behavior for Active Record when used in conjunction with
|
69
67
|
# Action Pack's request handling cycle.
|
70
68
|
# 2. Manually check out a connection from the pool with
|
@@ -78,6 +76,12 @@ module ActiveRecord
|
|
78
76
|
# Connections in the pool are actually AbstractAdapter objects (or objects
|
79
77
|
# compatible with AbstractAdapter's interface).
|
80
78
|
#
|
79
|
+
# While a thread has a connection checked out from the pool using one of the
|
80
|
+
# above three methods, that connection will automatically be the one used
|
81
|
+
# by ActiveRecord queries executing on that thread. It is not required to
|
82
|
+
# explicitly pass the checked out connection to \Rails models or queries, for
|
83
|
+
# example.
|
84
|
+
#
|
81
85
|
# == Options
|
82
86
|
#
|
83
87
|
# There are several connection-pooling-related options that you can add to
|
@@ -105,11 +109,9 @@ module ActiveRecord
|
|
105
109
|
include ConnectionAdapters::AbstractPool
|
106
110
|
|
107
111
|
attr_accessor :automatic_reconnect, :checkout_timeout
|
108
|
-
attr_reader :db_config, :size, :reaper, :pool_config, :
|
112
|
+
attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
|
109
113
|
|
110
|
-
|
111
|
-
deprecate :connection_klass
|
112
|
-
delegate :schema_cache, :schema_cache=, to: :pool_config
|
114
|
+
delegate :schema_reflection, :schema_reflection=, to: :pool_config
|
113
115
|
|
114
116
|
# Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
|
115
117
|
# object which describes database connection information (e.g. adapter,
|
@@ -122,7 +124,6 @@ module ActiveRecord
|
|
122
124
|
|
123
125
|
@pool_config = pool_config
|
124
126
|
@db_config = pool_config.db_config
|
125
|
-
@connection_class = pool_config.connection_class
|
126
127
|
@role = pool_config.role
|
127
128
|
@shard = pool_config.shard
|
128
129
|
|
@@ -158,18 +159,20 @@ module ActiveRecord
|
|
158
159
|
|
159
160
|
@async_executor = build_async_executor
|
160
161
|
|
161
|
-
lazily_set_schema_cache
|
162
|
-
|
163
162
|
@reaper = Reaper.new(self, db_config.reaping_frequency)
|
164
163
|
@reaper.run
|
165
164
|
end
|
166
165
|
|
167
166
|
def lock_thread=(lock_thread)
|
168
167
|
if lock_thread
|
169
|
-
@lock_thread =
|
168
|
+
@lock_thread = ActiveSupport::IsolatedExecutionState.context
|
170
169
|
else
|
171
170
|
@lock_thread = nil
|
172
171
|
end
|
172
|
+
|
173
|
+
if (active_connection = @thread_cached_conns[connection_cache_key(current_thread)])
|
174
|
+
active_connection.lock_thread = @lock_thread
|
175
|
+
end
|
173
176
|
end
|
174
177
|
|
175
178
|
# Retrieve the connection associated with the current thread, or call
|
@@ -181,6 +184,12 @@ module ActiveRecord
|
|
181
184
|
@thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
|
182
185
|
end
|
183
186
|
|
187
|
+
def connection_class # :nodoc:
|
188
|
+
pool_config.connection_class
|
189
|
+
end
|
190
|
+
alias :connection_klass :connection_class
|
191
|
+
deprecate :connection_klass, deprecator: ActiveRecord.deprecator
|
192
|
+
|
184
193
|
# Returns true if there is an open connection being used for the current thread.
|
185
194
|
#
|
186
195
|
# This method only works for connections that have been obtained through
|
@@ -197,18 +206,23 @@ module ActiveRecord
|
|
197
206
|
# This method only works for connections that have been obtained through
|
198
207
|
# #connection or #with_connection methods, connections obtained through
|
199
208
|
# #checkout will not be automatically released.
|
200
|
-
def release_connection(owner_thread =
|
209
|
+
def release_connection(owner_thread = ActiveSupport::IsolatedExecutionState.context)
|
201
210
|
if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
|
202
211
|
checkin conn
|
203
212
|
end
|
204
213
|
end
|
205
214
|
|
206
|
-
#
|
207
|
-
# already
|
208
|
-
#
|
209
|
-
#
|
215
|
+
# Yields a connection from the connection pool to the block. If no connection
|
216
|
+
# is already checked out by the current thread, a connection will be checked
|
217
|
+
# out from the pool, yielded to the block, and then returned to the pool when
|
218
|
+
# the block is finished. If a connection has already been checked out on the
|
219
|
+
# current thread, such as via #connection or #with_connection, that existing
|
220
|
+
# connection will be the one yielded and it will not be returned to the pool
|
221
|
+
# automatically at the end of the block; it is expected that such an existing
|
222
|
+
# connection will be properly returned to the pool by the code that checked
|
223
|
+
# it out.
|
210
224
|
def with_connection
|
211
|
-
unless conn = @thread_cached_conns[connection_cache_key(
|
225
|
+
unless conn = @thread_cached_conns[connection_cache_key(ActiveSupport::IsolatedExecutionState.context)]
|
212
226
|
conn = connection
|
213
227
|
fresh_connection = true
|
214
228
|
end
|
@@ -338,7 +352,9 @@ module ActiveRecord
|
|
338
352
|
# Raises:
|
339
353
|
# - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
|
340
354
|
def checkout(checkout_timeout = @checkout_timeout)
|
341
|
-
checkout_and_verify(acquire_connection(checkout_timeout))
|
355
|
+
connection = checkout_and_verify(acquire_connection(checkout_timeout))
|
356
|
+
connection.lock_thread = @lock_thread
|
357
|
+
connection
|
342
358
|
end
|
343
359
|
|
344
360
|
# Check-in a database connection back into the pool, indicating that you
|
@@ -355,6 +371,7 @@ module ActiveRecord
|
|
355
371
|
conn.expire
|
356
372
|
end
|
357
373
|
|
374
|
+
conn.lock_thread = nil
|
358
375
|
@available.add conn
|
359
376
|
end
|
360
377
|
end
|
@@ -510,7 +527,7 @@ module ActiveRecord
|
|
510
527
|
end
|
511
528
|
|
512
529
|
def current_thread
|
513
|
-
@lock_thread ||
|
530
|
+
@lock_thread || ActiveSupport::IsolatedExecutionState.context
|
514
531
|
end
|
515
532
|
|
516
533
|
# Take control of all existing connections so a "group" action such as
|
@@ -527,13 +544,13 @@ module ActiveRecord
|
|
527
544
|
def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
|
528
545
|
collected_conns = synchronize do
|
529
546
|
# account for our own connections
|
530
|
-
@connections.select { |conn| conn.owner ==
|
547
|
+
@connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
|
531
548
|
end
|
532
549
|
|
533
550
|
newly_checked_out = []
|
534
551
|
timeout_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (@checkout_timeout * 2)
|
535
552
|
|
536
|
-
@available.with_a_bias_for(
|
553
|
+
@available.with_a_bias_for(ActiveSupport::IsolatedExecutionState.context) do
|
537
554
|
loop do
|
538
555
|
synchronize do
|
539
556
|
return if collected_conns.size == @connections.size && @now_connecting == 0
|
@@ -580,7 +597,7 @@ module ActiveRecord
|
|
580
597
|
|
581
598
|
thread_report = []
|
582
599
|
@connections.each do |conn|
|
583
|
-
unless conn.owner ==
|
600
|
+
unless conn.owner == ActiveSupport::IsolatedExecutionState.context
|
584
601
|
thread_report << "#{conn} is owned by #{conn.owner}"
|
585
602
|
end
|
586
603
|
end
|
@@ -653,9 +670,12 @@ module ActiveRecord
|
|
653
670
|
alias_method :release, :remove_connection_from_thread_cache
|
654
671
|
|
655
672
|
def new_connection
|
656
|
-
Base.public_send(db_config.adapter_method, db_config.configuration_hash)
|
657
|
-
|
658
|
-
|
673
|
+
connection = Base.public_send(db_config.adapter_method, db_config.configuration_hash)
|
674
|
+
connection.pool = self
|
675
|
+
connection.check_version
|
676
|
+
connection
|
677
|
+
rescue ConnectionNotEstablished => ex
|
678
|
+
raise ex.set_pool(self)
|
659
679
|
end
|
660
680
|
|
661
681
|
# If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
|
@@ -702,10 +722,10 @@ module ActiveRecord
|
|
702
722
|
|
703
723
|
def checkout_and_verify(c)
|
704
724
|
c._run_checkout_callbacks do
|
705
|
-
c.
|
725
|
+
c.clean!
|
706
726
|
end
|
707
727
|
c
|
708
|
-
rescue
|
728
|
+
rescue Exception
|
709
729
|
remove c
|
710
730
|
c.disconnect!
|
711
731
|
raise
|