activerecord 6.0.0 → 7.2.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 +996 -594
- data/MIT-LICENSE +1 -1
- data/README.rdoc +34 -34
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +22 -20
- data/lib/active_record/association_relation.rb +22 -12
- data/lib/active_record/associations/alias_tracker.rb +41 -30
- data/lib/active_record/associations/association.rb +106 -41
- data/lib/active_record/associations/association_scope.rb +30 -21
- data/lib/active_record/associations/belongs_to_association.rb +69 -14
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +20 -6
- data/lib/active_record/associations/builder/association.rb +39 -6
- data/lib/active_record/associations/builder/belongs_to.rb +47 -17
- data/lib/active_record/associations/builder/collection_association.rb +14 -6
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -10
- data/lib/active_record/associations/builder/has_many.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +13 -16
- data/lib/active_record/associations/builder/singular_association.rb +7 -3
- data/lib/active_record/associations/collection_association.rb +90 -53
- data/lib/active_record/associations/collection_proxy.rb +54 -19
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +21 -1
- data/lib/active_record/associations/has_many_association.rb +41 -10
- data/lib/active_record/associations/has_many_through_association.rb +29 -12
- data/lib/active_record/associations/has_one_association.rb +33 -9
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +41 -17
- data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
- data/lib/active_record/associations/join_dependency.rb +97 -54
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +237 -54
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +153 -0
- data/lib/active_record/associations/preloader/through_association.rb +51 -17
- data/lib/active_record/associations/preloader.rb +55 -121
- data/lib/active_record/associations/singular_association.rb +16 -4
- data/lib/active_record/associations/through_association.rb +26 -15
- data/lib/active_record/associations.rb +454 -440
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +11 -14
- data/lib/active_record/attribute_methods/before_type_cast.rb +36 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +75 -34
- data/lib/active_record/attribute_methods/primary_key.rb +53 -31
- data/lib/active_record/attribute_methods/query.rb +31 -22
- data/lib/active_record/attribute_methods/read.rb +16 -17
- data/lib/active_record/attribute_methods/serialization.rb +177 -35
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +18 -15
- data/lib/active_record/attribute_methods/write.rb +16 -28
- data/lib/active_record/attribute_methods.rb +227 -100
- data/lib/active_record/attributes.rb +94 -56
- data/lib/active_record/autosave_association.rb +119 -73
- data/lib/active_record/base.rb +31 -21
- data/lib/active_record/callbacks.rb +168 -55
- 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 -25
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +367 -565
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -57
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +277 -89
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +241 -69
- data/lib/active_record/connection_adapters/abstract/quoting.rb +122 -134
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +324 -72
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +17 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +611 -211
- data/lib/active_record/connection_adapters/abstract/transaction.rb +425 -82
- data/lib/active_record/connection_adapters/abstract_adapter.rb +698 -211
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +464 -239
- data/lib/active_record/connection_adapters/column.rb +28 -1
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +2 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +32 -137
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +90 -43
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +41 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +18 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +13 -4
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +53 -15
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +127 -63
- data/lib/active_record/connection_adapters/pool_config.rb +83 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +54 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +127 -100
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +9 -5
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -15
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -4
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +35 -8
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -4
- data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +139 -106
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -2
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +98 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +176 -4
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -118
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +585 -295
- data/lib/active_record/connection_adapters/schema_cache.rb +399 -60
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +99 -48
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +80 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +27 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +102 -24
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +425 -174
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -1
- 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 +176 -0
- data/lib/active_record/connection_handling.rb +243 -115
- data/lib/active_record/core.rb +481 -199
- data/lib/active_record/counter_cache.rb +69 -32
- data/lib/active_record/database_configurations/connection_url_resolver.rb +107 -0
- data/lib/active_record/database_configurations/database_config.rb +77 -10
- data/lib/active_record/database_configurations/hash_config.rb +148 -26
- data/lib/active_record/database_configurations/url_config.rb +44 -45
- data/lib/active_record/database_configurations.rb +190 -114
- data/lib/active_record/delegated_type.rb +279 -0
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +38 -0
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +5 -6
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +68 -0
- data/lib/active_record/encryption/configurable.rb +60 -0
- data/lib/active_record/encryption/context.rb +42 -0
- data/lib/active_record/encryption/contexts.rb +76 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +230 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +171 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +53 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +96 -0
- data/lib/active_record/encryption/null_encryptor.rb +25 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
- data/lib/active_record/encryption/scheme.rb +100 -0
- data/lib/active_record/encryption.rb +58 -0
- data/lib/active_record/enum.rb +224 -73
- data/lib/active_record/errors.rb +254 -36
- data/lib/active_record/explain.rb +30 -17
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/explain_subscriber.rb +2 -2
- data/lib/active_record/fixture_set/file.rb +22 -15
- data/lib/active_record/fixture_set/model_metadata.rb +15 -6
- data/lib/active_record/fixture_set/render_context.rb +3 -1
- data/lib/active_record/fixture_set/table_row.rb +88 -16
- data/lib/active_record/fixture_set/table_rows.rb +4 -5
- data/lib/active_record/fixtures.rb +229 -116
- data/lib/active_record/future_result.rb +178 -0
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +121 -48
- data/lib/active_record/insert_all.rb +178 -29
- data/lib/active_record/integration.rb +16 -14
- data/lib/active_record/internal_metadata.rb +132 -21
- data/lib/active_record/legacy_yaml_adapter.rb +3 -36
- data/lib/active_record/locking/optimistic.rb +64 -33
- data/lib/active_record/locking/pessimistic.rb +21 -8
- data/lib/active_record/log_subscriber.rb +61 -30
- 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/session.rb +3 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +19 -19
- data/lib/active_record/middleware/database_selector.rb +25 -13
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +160 -55
- data/lib/active_record/migration/compatibility.rb +286 -43
- 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/join_table.rb +1 -2
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +421 -193
- data/lib/active_record/model_schema.rb +217 -125
- data/lib/active_record/nested_attributes.rb +62 -27
- data/lib/active_record/no_touching.rb +4 -4
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +322 -319
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -15
- data/lib/active_record/query_logs.rb +193 -0
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +54 -14
- data/lib/active_record/railtie.rb +250 -72
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/controller_runtime.rb +25 -11
- data/lib/active_record/railties/databases.rake +312 -197
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +45 -3
- data/lib/active_record/reflection.rb +389 -146
- data/lib/active_record/relation/batches/batch_enumerator.rb +61 -16
- data/lib/active_record/relation/batches.rb +214 -73
- data/lib/active_record/relation/calculations.rb +379 -124
- data/lib/active_record/relation/delegation.rb +36 -23
- data/lib/active_record/relation/finder_methods.rb +159 -49
- data/lib/active_record/relation/from_clause.rb +5 -1
- data/lib/active_record/relation/merger.rb +41 -33
- data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -11
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -7
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +20 -13
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +79 -53
- data/lib/active_record/relation/query_attribute.rb +30 -12
- data/lib/active_record/relation/query_methods.rb +1156 -279
- data/lib/active_record/relation/record_fetch_warning.rb +12 -11
- data/lib/active_record/relation/spawn_methods.rb +10 -9
- data/lib/active_record/relation/where_clause.rb +100 -66
- data/lib/active_record/relation.rb +829 -194
- data/lib/active_record/result.rb +76 -56
- data/lib/active_record/runtime_registry.rb +71 -13
- data/lib/active_record/sanitization.rb +86 -47
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +140 -33
- data/lib/active_record/schema_migration.rb +74 -29
- data/lib/active_record/scoping/default.rb +73 -19
- data/lib/active_record/scoping/named.rb +10 -28
- data/lib/active_record/scoping.rb +65 -35
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +34 -8
- data/lib/active_record/serialization.rb +11 -4
- data/lib/active_record/signed_id.rb +138 -0
- data/lib/active_record/statement_cache.rb +26 -10
- data/lib/active_record/store.rb +19 -14
- data/lib/active_record/suppressor.rb +15 -17
- data/lib/active_record/table_metadata.rb +46 -36
- data/lib/active_record/tasks/database_tasks.rb +371 -205
- data/lib/active_record/tasks/mysql_database_tasks.rb +43 -36
- data/lib/active_record/tasks/postgresql_database_tasks.rb +54 -41
- data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -13
- data/lib/active_record/test_databases.rb +5 -4
- data/lib/active_record/test_fixtures.rb +189 -104
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +35 -25
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +31 -27
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +131 -99
- data/lib/active_record/translation.rb +3 -5
- data/lib/active_record/type/adapter_specific_registry.rb +33 -18
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +11 -6
- data/lib/active_record/type/time.rb +14 -0
- data/lib/active_record/type/type_map.rb +17 -21
- data/lib/active_record/type/unsigned_integer.rb +0 -1
- data/lib/active_record/type.rb +7 -2
- data/lib/active_record/type_caster/connection.rb +4 -5
- data/lib/active_record/type_caster/map.rb +8 -5
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +13 -8
- data/lib/active_record/validations/numericality.rb +36 -0
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +88 -18
- data/lib/active_record/validations.rb +15 -8
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +446 -40
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/attributes/attribute.rb +4 -8
- data/lib/arel/collectors/bind.rb +8 -1
- data/lib/arel/collectors/composite.rb +15 -0
- data/lib/arel/collectors/sql_string.rb +7 -0
- data/lib/arel/collectors/substitute_binds.rb +7 -0
- data/lib/arel/crud.rb +30 -22
- data/lib/arel/delete_manager.rb +23 -4
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/binary.rb +82 -9
- data/lib/arel/nodes/bind_param.rb +8 -0
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/casted.rb +22 -10
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +14 -13
- data/lib/arel/nodes/equality.rb +6 -9
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/grouping.rb +3 -0
- data/lib/arel/nodes/homogeneous_in.rb +68 -0
- data/lib/arel/nodes/in.rb +8 -1
- data/lib/arel/nodes/infix_operation.rb +13 -1
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/join_source.rb +1 -1
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
- data/lib/arel/nodes/node.rb +122 -11
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +16 -0
- data/lib/arel/nodes/table_alias.rb +11 -3
- data/lib/arel/nodes/unary.rb +0 -1
- data/lib/arel/nodes/update_statement.rb +11 -4
- data/lib/arel/nodes.rb +10 -3
- data/lib/arel/predications.rb +31 -28
- data/lib/arel/select_manager.rb +18 -9
- data/lib/arel/table.rb +21 -10
- data/lib/arel/tree_manager.rb +8 -15
- data/lib/arel/update_manager.rb +25 -5
- data/lib/arel/visitors/dot.rb +94 -90
- data/lib/arel/visitors/mysql.rb +34 -6
- data/lib/arel/visitors/postgresql.rb +5 -16
- data/lib/arel/visitors/sqlite.rb +25 -1
- data/lib/arel/visitors/to_sql.rb +227 -81
- data/lib/arel/visitors/visitor.rb +2 -3
- data/lib/arel/visitors.rb +0 -7
- data/lib/arel.rb +37 -15
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +6 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
- data/lib/rails/generators/active_record/migration.rb +9 -3
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +49 -4
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- metadata +117 -30
- data/lib/active_record/attribute_decorators.rb +0 -90
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/null_relation.rb +0 -68
- data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
- data/lib/active_record/relation/where_clause_factory.rb +0 -33
- data/lib/arel/attributes.rb +0 -22
- data/lib/arel/visitors/depth_first.rb +0 -204
- data/lib/arel/visitors/ibm_db.rb +0 -34
- data/lib/arel/visitors/informix.rb +0 -62
- data/lib/arel/visitors/mssql.rb +0 -157
- data/lib/arel/visitors/oracle.rb +0 -159
- data/lib/arel/visitors/oracle12.rb +0 -66
- data/lib/arel/visitors/where_sql.rb +0 -23
|
@@ -3,43 +3,57 @@
|
|
|
3
3
|
require "thread"
|
|
4
4
|
require "concurrent/map"
|
|
5
5
|
require "monitor"
|
|
6
|
-
require "weakref"
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
# acquisition timeout period: because max connections in pool
|
|
11
|
-
# are in use.
|
|
12
|
-
class ConnectionTimeoutError < ConnectionNotEstablished
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
# Raised when a pool was unable to get ahold of all its connections
|
|
16
|
-
# to perform a "group" action such as
|
|
17
|
-
# {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!]
|
|
18
|
-
# or {ActiveRecord::Base.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
|
|
19
|
-
class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
|
|
20
|
-
end
|
|
7
|
+
require "active_record/connection_adapters/abstract/connection_pool/queue"
|
|
8
|
+
require "active_record/connection_adapters/abstract/connection_pool/reaper"
|
|
21
9
|
|
|
10
|
+
module ActiveRecord
|
|
22
11
|
module ConnectionAdapters
|
|
23
12
|
module AbstractPool # :nodoc:
|
|
24
|
-
def get_schema_cache(connection)
|
|
25
|
-
@schema_cache ||= SchemaCache.new(connection)
|
|
26
|
-
@schema_cache.connection = connection
|
|
27
|
-
@schema_cache
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def set_schema_cache(cache)
|
|
31
|
-
@schema_cache = cache
|
|
32
|
-
end
|
|
33
13
|
end
|
|
34
14
|
|
|
35
15
|
class NullPool # :nodoc:
|
|
36
16
|
include ConnectionAdapters::AbstractPool
|
|
37
17
|
|
|
18
|
+
class NullConfig # :nodoc:
|
|
19
|
+
def method_missing(...)
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
NULL_CONFIG = NullConfig.new # :nodoc:
|
|
24
|
+
|
|
38
25
|
def initialize
|
|
39
|
-
|
|
26
|
+
super()
|
|
27
|
+
@mutex = Mutex.new
|
|
28
|
+
@server_version = nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def server_version(connection) # :nodoc:
|
|
32
|
+
@server_version || @mutex.synchronize { @server_version ||= connection.get_database_version }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def schema_reflection
|
|
36
|
+
SchemaReflection.new(nil)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def schema_cache; end
|
|
40
|
+
def connection_class; end
|
|
41
|
+
def query_cache; end
|
|
42
|
+
def checkin(_); end
|
|
43
|
+
def remove(_); end
|
|
44
|
+
def async_executor; end
|
|
45
|
+
|
|
46
|
+
def db_config
|
|
47
|
+
NULL_CONFIG
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def dirties_query_cache
|
|
51
|
+
true
|
|
40
52
|
end
|
|
41
53
|
end
|
|
42
54
|
|
|
55
|
+
# = Active Record Connection Pool
|
|
56
|
+
#
|
|
43
57
|
# Connection pool base class for managing Active Record database
|
|
44
58
|
# connections.
|
|
45
59
|
#
|
|
@@ -54,19 +68,17 @@ module ActiveRecord
|
|
|
54
68
|
# handle cases in which there are more threads than connections: if all
|
|
55
69
|
# connections have been checked out, and a thread tries to checkout a
|
|
56
70
|
# connection anyway, then ConnectionPool will wait until some other thread
|
|
57
|
-
# has checked in a connection.
|
|
71
|
+
# has checked in a connection, or the +checkout_timeout+ has expired.
|
|
58
72
|
#
|
|
59
73
|
# == Obtaining (checking out) a connection
|
|
60
74
|
#
|
|
61
75
|
# Connections can be obtained and used from a connection pool in several
|
|
62
76
|
# ways:
|
|
63
77
|
#
|
|
64
|
-
# 1. Simply use {ActiveRecord::Base.
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
# {ActiveRecord::Base.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
|
|
69
|
-
# This will be the default behavior for Active Record when used in conjunction with
|
|
78
|
+
# 1. Simply use {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling#lease_connection].
|
|
79
|
+
# When you're done with the connection(s) and wish it to be returned to the pool, you call
|
|
80
|
+
# {ActiveRecord::Base.connection_handler.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
|
|
81
|
+
# This is the default behavior for Active Record when used in conjunction with
|
|
70
82
|
# Action Pack's request handling cycle.
|
|
71
83
|
# 2. Manually check out a connection from the pool with
|
|
72
84
|
# {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
|
|
@@ -79,6 +91,12 @@ module ActiveRecord
|
|
|
79
91
|
# Connections in the pool are actually AbstractAdapter objects (or objects
|
|
80
92
|
# compatible with AbstractAdapter's interface).
|
|
81
93
|
#
|
|
94
|
+
# While a thread has a connection checked out from the pool using one of the
|
|
95
|
+
# above three methods, that connection will automatically be the one used
|
|
96
|
+
# by ActiveRecord queries executing on that thread. It is not required to
|
|
97
|
+
# explicitly pass the checked out connection to \Rails models or queries, for
|
|
98
|
+
# example.
|
|
99
|
+
#
|
|
82
100
|
# == Options
|
|
83
101
|
#
|
|
84
102
|
# There are several connection-pooling-related options that you can add to
|
|
@@ -101,297 +119,141 @@ module ActiveRecord
|
|
|
101
119
|
# * private methods that require being called in a +synchronize+ blocks
|
|
102
120
|
# are now explicitly documented
|
|
103
121
|
class ConnectionPool
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
# Test if any threads are currently waiting on the queue.
|
|
115
|
-
def any_waiting?
|
|
116
|
-
synchronize do
|
|
117
|
-
@num_waiting > 0
|
|
122
|
+
# Prior to 3.3.5, WeakKeyMap had a use after free bug
|
|
123
|
+
# https://bugs.ruby-lang.org/issues/20688
|
|
124
|
+
if ObjectSpace.const_defined?(:WeakKeyMap) && RUBY_VERSION >= "3.3.5"
|
|
125
|
+
WeakThreadKeyMap = ObjectSpace::WeakKeyMap
|
|
126
|
+
else
|
|
127
|
+
class WeakThreadKeyMap # :nodoc:
|
|
128
|
+
# FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
|
|
129
|
+
# but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
|
|
130
|
+
def initialize
|
|
131
|
+
@map = {}
|
|
118
132
|
end
|
|
119
|
-
end
|
|
120
133
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def num_waiting
|
|
124
|
-
synchronize do
|
|
125
|
-
@num_waiting
|
|
134
|
+
def clear
|
|
135
|
+
@map.clear
|
|
126
136
|
end
|
|
127
|
-
end
|
|
128
137
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
synchronize do
|
|
132
|
-
@queue.push element
|
|
133
|
-
@cond.signal
|
|
138
|
+
def [](key)
|
|
139
|
+
@map[key]
|
|
134
140
|
end
|
|
135
|
-
end
|
|
136
141
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
@queue.delete(element)
|
|
142
|
+
def []=(key, value)
|
|
143
|
+
@map.select! { |c, _| c&.alive? }
|
|
144
|
+
@map[key] = value
|
|
141
145
|
end
|
|
142
146
|
end
|
|
147
|
+
end
|
|
143
148
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
synchronize do
|
|
147
|
-
@queue.clear
|
|
148
|
-
end
|
|
149
|
-
end
|
|
149
|
+
class Lease # :nodoc:
|
|
150
|
+
attr_accessor :connection, :sticky
|
|
150
151
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
# queue if the number of available elements is strictly
|
|
155
|
-
# greater than the number of threads currently waiting (that
|
|
156
|
-
# is, don't jump ahead in line). Otherwise, return +nil+.
|
|
157
|
-
#
|
|
158
|
-
# If +timeout+ is given, block if there is no element
|
|
159
|
-
# available, waiting up to +timeout+ seconds for an element to
|
|
160
|
-
# become available.
|
|
161
|
-
#
|
|
162
|
-
# Raises:
|
|
163
|
-
# - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element
|
|
164
|
-
# becomes available within +timeout+ seconds,
|
|
165
|
-
def poll(timeout = nil)
|
|
166
|
-
synchronize { internal_poll(timeout) }
|
|
152
|
+
def initialize
|
|
153
|
+
@connection = nil
|
|
154
|
+
@sticky = nil
|
|
167
155
|
end
|
|
168
156
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def synchronize(&block)
|
|
176
|
-
@lock.synchronize(&block)
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
# Test if the queue currently contains any elements.
|
|
180
|
-
def any?
|
|
181
|
-
!@queue.empty?
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
# A thread can remove an element from the queue without
|
|
185
|
-
# waiting if and only if the number of currently available
|
|
186
|
-
# connections is strictly greater than the number of waiting
|
|
187
|
-
# threads.
|
|
188
|
-
def can_remove_no_wait?
|
|
189
|
-
@queue.size > @num_waiting
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
# Removes and returns the head of the queue if possible, or +nil+.
|
|
193
|
-
def remove
|
|
194
|
-
@queue.pop
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
# Remove and return the head the queue if the number of
|
|
198
|
-
# available elements is strictly greater than the number of
|
|
199
|
-
# threads currently waiting. Otherwise, return +nil+.
|
|
200
|
-
def no_wait_poll
|
|
201
|
-
remove if can_remove_no_wait?
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
# Waits on the queue up to +timeout+ seconds, then removes and
|
|
205
|
-
# returns the head of the queue.
|
|
206
|
-
def wait_poll(timeout)
|
|
207
|
-
@num_waiting += 1
|
|
208
|
-
|
|
209
|
-
t0 = Concurrent.monotonic_time
|
|
210
|
-
elapsed = 0
|
|
211
|
-
loop do
|
|
212
|
-
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
|
213
|
-
@cond.wait(timeout - elapsed)
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
return remove if any?
|
|
157
|
+
def release
|
|
158
|
+
conn = @connection
|
|
159
|
+
@connection = nil
|
|
160
|
+
@sticky = nil
|
|
161
|
+
conn
|
|
162
|
+
end
|
|
217
163
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
ensure
|
|
226
|
-
@num_waiting -= 1
|
|
164
|
+
def clear(connection)
|
|
165
|
+
if @connection == connection
|
|
166
|
+
@connection = nil
|
|
167
|
+
@sticky = nil
|
|
168
|
+
true
|
|
169
|
+
else
|
|
170
|
+
false
|
|
227
171
|
end
|
|
172
|
+
end
|
|
228
173
|
end
|
|
229
174
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
# semantics of condition variables guarantee that +broadcast+, +broadcast_on_biased+,
|
|
235
|
-
# +signal+ and +wait+ methods are only called while holding a lock
|
|
236
|
-
def initialize(lock, other_cond, preferred_thread)
|
|
237
|
-
@real_cond = lock.new_cond
|
|
238
|
-
@other_cond = other_cond
|
|
239
|
-
@preferred_thread = preferred_thread
|
|
240
|
-
@num_waiting_on_real_cond = 0
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
def broadcast
|
|
244
|
-
broadcast_on_biased
|
|
245
|
-
@other_cond.broadcast
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
def broadcast_on_biased
|
|
249
|
-
@num_waiting_on_real_cond = 0
|
|
250
|
-
@real_cond.broadcast
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
def signal
|
|
254
|
-
if @num_waiting_on_real_cond > 0
|
|
255
|
-
@num_waiting_on_real_cond -= 1
|
|
256
|
-
@real_cond
|
|
257
|
-
else
|
|
258
|
-
@other_cond
|
|
259
|
-
end.signal
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
def wait(timeout)
|
|
263
|
-
if Thread.current == @preferred_thread
|
|
264
|
-
@num_waiting_on_real_cond += 1
|
|
265
|
-
@real_cond
|
|
266
|
-
else
|
|
267
|
-
@other_cond
|
|
268
|
-
end.wait(timeout)
|
|
269
|
-
end
|
|
175
|
+
class LeaseRegistry # :nodoc:
|
|
176
|
+
def initialize
|
|
177
|
+
@mutex = Mutex.new
|
|
178
|
+
@map = WeakThreadKeyMap.new
|
|
270
179
|
end
|
|
271
180
|
|
|
272
|
-
def
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
synchronize do
|
|
276
|
-
previous_cond = @cond
|
|
277
|
-
@cond = new_cond = BiasedConditionVariable.new(@lock, @cond, thread)
|
|
278
|
-
end
|
|
279
|
-
yield
|
|
280
|
-
ensure
|
|
281
|
-
synchronize do
|
|
282
|
-
@cond = previous_cond if previous_cond
|
|
283
|
-
new_cond.broadcast_on_biased if new_cond # wake up any remaining sleepers
|
|
181
|
+
def [](context)
|
|
182
|
+
@mutex.synchronize do
|
|
183
|
+
@map[context] ||= Lease.new
|
|
284
184
|
end
|
|
285
185
|
end
|
|
286
|
-
end
|
|
287
186
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
# <tt>@lock</tt> as the main pool) so that a returned connection is already
|
|
292
|
-
# leased and there is no need to re-enter synchronized block.
|
|
293
|
-
class ConnectionLeasingQueue < Queue # :nodoc:
|
|
294
|
-
include BiasableQueue
|
|
295
|
-
|
|
296
|
-
private
|
|
297
|
-
def internal_poll(timeout)
|
|
298
|
-
conn = super
|
|
299
|
-
conn.lease if conn
|
|
300
|
-
conn
|
|
187
|
+
def clear
|
|
188
|
+
@mutex.synchronize do
|
|
189
|
+
@map.clear
|
|
301
190
|
end
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
# Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
|
|
305
|
-
# +pool+. A reaper instantiated with a zero frequency will never reap
|
|
306
|
-
# the connection pool.
|
|
307
|
-
#
|
|
308
|
-
# Configure the frequency by setting +reaping_frequency+ in your database
|
|
309
|
-
# yaml file (default 60 seconds).
|
|
310
|
-
class Reaper
|
|
311
|
-
attr_reader :pool, :frequency
|
|
312
|
-
|
|
313
|
-
def initialize(pool, frequency)
|
|
314
|
-
@pool = pool
|
|
315
|
-
@frequency = frequency
|
|
316
191
|
end
|
|
192
|
+
end
|
|
317
193
|
|
|
318
|
-
|
|
319
|
-
@pools = {}
|
|
320
|
-
|
|
194
|
+
module ExecutorHooks # :nodoc:
|
|
321
195
|
class << self
|
|
322
|
-
def
|
|
323
|
-
|
|
324
|
-
unless @pools.key?(frequency)
|
|
325
|
-
@pools[frequency] = []
|
|
326
|
-
spawn_thread(frequency)
|
|
327
|
-
end
|
|
328
|
-
@pools[frequency] << WeakRef.new(pool)
|
|
329
|
-
end
|
|
196
|
+
def run
|
|
197
|
+
# noop
|
|
330
198
|
end
|
|
331
199
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
@mutex.synchronize do
|
|
339
|
-
@pools[frequency].select!(&:weakref_alive?)
|
|
340
|
-
@pools[frequency].each do |p|
|
|
341
|
-
p.reap
|
|
342
|
-
p.flush
|
|
343
|
-
rescue WeakRef::RefError
|
|
344
|
-
end
|
|
345
|
-
end
|
|
200
|
+
def complete(_)
|
|
201
|
+
ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
|
|
202
|
+
if (connection = pool.active_connection?)
|
|
203
|
+
transaction = connection.current_transaction
|
|
204
|
+
if transaction.closed? || !transaction.joinable?
|
|
205
|
+
pool.release_connection
|
|
346
206
|
end
|
|
347
207
|
end
|
|
348
208
|
end
|
|
209
|
+
end
|
|
349
210
|
end
|
|
211
|
+
end
|
|
350
212
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
213
|
+
class << self
|
|
214
|
+
def install_executor_hooks(executor = ActiveSupport::Executor)
|
|
215
|
+
executor.register_hook(ExecutorHooks)
|
|
354
216
|
end
|
|
355
217
|
end
|
|
356
218
|
|
|
357
219
|
include MonitorMixin
|
|
358
|
-
|
|
220
|
+
prepend QueryCache::ConnectionPoolConfiguration
|
|
359
221
|
include ConnectionAdapters::AbstractPool
|
|
360
222
|
|
|
361
|
-
attr_accessor :automatic_reconnect, :checkout_timeout
|
|
362
|
-
attr_reader :
|
|
223
|
+
attr_accessor :automatic_reconnect, :checkout_timeout
|
|
224
|
+
attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
|
|
225
|
+
|
|
226
|
+
delegate :schema_reflection, :server_version, to: :pool_config
|
|
363
227
|
|
|
364
|
-
# Creates a new ConnectionPool object. +
|
|
228
|
+
# Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
|
|
365
229
|
# object which describes database connection information (e.g. adapter,
|
|
366
230
|
# host name, username, password, etc), as well as the maximum size for
|
|
367
231
|
# this ConnectionPool.
|
|
368
232
|
#
|
|
369
233
|
# The default ConnectionPool maximum size is 5.
|
|
370
|
-
def initialize(
|
|
234
|
+
def initialize(pool_config)
|
|
371
235
|
super()
|
|
372
236
|
|
|
373
|
-
@
|
|
374
|
-
|
|
375
|
-
@
|
|
376
|
-
|
|
377
|
-
@idle_timeout = @idle_timeout.to_f
|
|
378
|
-
@idle_timeout = nil if @idle_timeout <= 0
|
|
379
|
-
end
|
|
237
|
+
@pool_config = pool_config
|
|
238
|
+
@db_config = pool_config.db_config
|
|
239
|
+
@role = pool_config.role
|
|
240
|
+
@shard = pool_config.shard
|
|
380
241
|
|
|
381
|
-
|
|
382
|
-
@
|
|
242
|
+
@checkout_timeout = db_config.checkout_timeout
|
|
243
|
+
@idle_timeout = db_config.idle_timeout
|
|
244
|
+
@size = db_config.pool
|
|
383
245
|
|
|
384
246
|
# This variable tracks the cache of threads mapped to reserved connections, with the
|
|
385
247
|
# sole purpose of speeding up the +connection+ method. It is not the authoritative
|
|
386
248
|
# registry of which thread owns which connection. Connection ownership is tracked by
|
|
387
249
|
# the +connection.owner+ attr on each +connection+ instance.
|
|
388
250
|
# The invariant works like this: if there is mapping of <tt>thread => conn</tt>,
|
|
389
|
-
# then that +thread+ does indeed own that +conn+. However, an absence of
|
|
251
|
+
# then that +thread+ does indeed own that +conn+. However, an absence of such
|
|
390
252
|
# mapping does not mean that the +thread+ doesn't own the said connection. In
|
|
391
253
|
# that case +conn.owner+ attr should be consulted.
|
|
392
|
-
# Access and modification of <tt>@
|
|
254
|
+
# Access and modification of <tt>@leases</tt> does not require
|
|
393
255
|
# synchronization.
|
|
394
|
-
@
|
|
256
|
+
@leases = LeaseRegistry.new
|
|
395
257
|
|
|
396
258
|
@connections = []
|
|
397
259
|
@automatic_reconnect = true
|
|
@@ -404,72 +266,179 @@ module ActiveRecord
|
|
|
404
266
|
@threads_blocking_new_connections = 0
|
|
405
267
|
|
|
406
268
|
@available = ConnectionLeasingQueue.new self
|
|
269
|
+
@pinned_connection = nil
|
|
270
|
+
@pinned_connections_depth = 0
|
|
407
271
|
|
|
408
|
-
@
|
|
272
|
+
@async_executor = build_async_executor
|
|
409
273
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
@reaper = Reaper.new(self, reaping_frequency && reaping_frequency.to_f)
|
|
274
|
+
@schema_cache = nil
|
|
275
|
+
|
|
276
|
+
@reaper = Reaper.new(self, db_config.reaping_frequency)
|
|
414
277
|
@reaper.run
|
|
415
278
|
end
|
|
416
279
|
|
|
417
|
-
def
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
280
|
+
def inspect # :nodoc:
|
|
281
|
+
name_field = " name=#{db_config.name.inspect}" unless db_config.name == "primary"
|
|
282
|
+
shard_field = " shard=#{@shard.inspect}" unless @shard == :default
|
|
283
|
+
|
|
284
|
+
"#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def schema_cache
|
|
288
|
+
@schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def schema_reflection=(schema_reflection)
|
|
292
|
+
pool_config.schema_reflection = schema_reflection
|
|
293
|
+
@schema_cache = nil
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def migration_context # :nodoc:
|
|
297
|
+
MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def migrations_paths # :nodoc:
|
|
301
|
+
db_config.migrations_paths || Migrator.migrations_paths
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def schema_migration # :nodoc:
|
|
305
|
+
SchemaMigration.new(self)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def internal_metadata # :nodoc:
|
|
309
|
+
InternalMetadata.new(self)
|
|
423
310
|
end
|
|
424
311
|
|
|
425
312
|
# Retrieve the connection associated with the current thread, or call
|
|
426
313
|
# #checkout to obtain one if necessary.
|
|
427
314
|
#
|
|
428
|
-
# #
|
|
315
|
+
# #lease_connection can be called any number of times; the connection is
|
|
429
316
|
# held in a cache keyed by a thread.
|
|
317
|
+
def lease_connection
|
|
318
|
+
lease = connection_lease
|
|
319
|
+
lease.connection ||= checkout
|
|
320
|
+
lease.sticky = true
|
|
321
|
+
lease.connection
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def permanent_lease? # :nodoc:
|
|
325
|
+
connection_lease.sticky.nil?
|
|
326
|
+
end
|
|
327
|
+
|
|
430
328
|
def connection
|
|
431
|
-
|
|
329
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
|
330
|
+
ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
|
|
331
|
+
and will be removed in Rails 8.0. Use #lease_connection instead.
|
|
332
|
+
MSG
|
|
333
|
+
lease_connection
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def pin_connection!(lock_thread) # :nodoc:
|
|
337
|
+
@pinned_connection ||= (connection_lease&.connection || checkout)
|
|
338
|
+
@pinned_connections_depth += 1
|
|
339
|
+
|
|
340
|
+
# Any leased connection must be in @connections otherwise
|
|
341
|
+
# some methods like #connected? won't behave correctly
|
|
342
|
+
unless @connections.include?(@pinned_connection)
|
|
343
|
+
@connections << @pinned_connection
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
@pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
|
|
347
|
+
@pinned_connection.pinned = true
|
|
348
|
+
@pinned_connection.verify! # eagerly validate the connection
|
|
349
|
+
@pinned_connection.begin_transaction joinable: false, _lazy: false
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def unpin_connection! # :nodoc:
|
|
353
|
+
raise "There isn't a pinned connection #{object_id}" unless @pinned_connection
|
|
354
|
+
|
|
355
|
+
clean = true
|
|
356
|
+
@pinned_connection.lock.synchronize do
|
|
357
|
+
@pinned_connections_depth -= 1
|
|
358
|
+
connection = @pinned_connection
|
|
359
|
+
@pinned_connection = nil if @pinned_connections_depth.zero?
|
|
360
|
+
|
|
361
|
+
if connection.transaction_open?
|
|
362
|
+
connection.rollback_transaction
|
|
363
|
+
else
|
|
364
|
+
# Something committed or rolled back the transaction
|
|
365
|
+
clean = false
|
|
366
|
+
connection.reset!
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
if @pinned_connection.nil?
|
|
370
|
+
connection.pinned = false
|
|
371
|
+
connection.steal!
|
|
372
|
+
connection.lock_thread = nil
|
|
373
|
+
checkin(connection)
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
clean
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def connection_class # :nodoc:
|
|
381
|
+
pool_config.connection_class
|
|
432
382
|
end
|
|
433
383
|
|
|
434
384
|
# Returns true if there is an open connection being used for the current thread.
|
|
435
385
|
#
|
|
436
386
|
# This method only works for connections that have been obtained through
|
|
437
|
-
# #
|
|
387
|
+
# #lease_connection or #with_connection methods. Connections obtained through
|
|
438
388
|
# #checkout will not be detected by #active_connection?
|
|
439
389
|
def active_connection?
|
|
440
|
-
|
|
390
|
+
connection_lease.connection
|
|
441
391
|
end
|
|
392
|
+
alias_method :active_connection, :active_connection? # :nodoc:
|
|
442
393
|
|
|
443
394
|
# Signal that the thread is finished with the current connection.
|
|
444
395
|
# #release_connection releases the connection-thread association
|
|
445
396
|
# and returns the connection to the pool.
|
|
446
397
|
#
|
|
447
398
|
# This method only works for connections that have been obtained through
|
|
448
|
-
# #
|
|
399
|
+
# #lease_connection or #with_connection methods, connections obtained through
|
|
449
400
|
# #checkout will not be automatically released.
|
|
450
|
-
def release_connection(
|
|
451
|
-
if conn =
|
|
401
|
+
def release_connection(existing_lease = nil)
|
|
402
|
+
if conn = connection_lease.release
|
|
452
403
|
checkin conn
|
|
404
|
+
return true
|
|
405
|
+
end
|
|
406
|
+
false
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# Yields a connection from the connection pool to the block. If no connection
|
|
410
|
+
# is already checked out by the current thread, a connection will be checked
|
|
411
|
+
# out from the pool, yielded to the block, and then returned to the pool when
|
|
412
|
+
# the block is finished. If a connection has already been checked out on the
|
|
413
|
+
# current thread, such as via #lease_connection or #with_connection, that existing
|
|
414
|
+
# connection will be the one yielded and it will not be returned to the pool
|
|
415
|
+
# automatically at the end of the block; it is expected that such an existing
|
|
416
|
+
# connection will be properly returned to the pool by the code that checked
|
|
417
|
+
# it out.
|
|
418
|
+
def with_connection(prevent_permanent_checkout: false)
|
|
419
|
+
lease = connection_lease
|
|
420
|
+
sticky_was = lease.sticky
|
|
421
|
+
lease.sticky = false if prevent_permanent_checkout
|
|
422
|
+
|
|
423
|
+
if lease.connection
|
|
424
|
+
begin
|
|
425
|
+
yield lease.connection
|
|
426
|
+
ensure
|
|
427
|
+
lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
|
|
428
|
+
end
|
|
429
|
+
else
|
|
430
|
+
begin
|
|
431
|
+
yield lease.connection = checkout
|
|
432
|
+
ensure
|
|
433
|
+
lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
|
|
434
|
+
release_connection(lease) unless lease.sticky
|
|
435
|
+
end
|
|
453
436
|
end
|
|
454
437
|
end
|
|
455
438
|
|
|
456
|
-
# If a connection obtained through #connection or #with_connection methods
|
|
457
|
-
# already exists yield it to the block. If no such connection
|
|
458
|
-
# exists checkout a connection, yield it to the block, and checkin the
|
|
459
|
-
# connection when finished.
|
|
460
|
-
def with_connection
|
|
461
|
-
unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
|
|
462
|
-
conn = connection
|
|
463
|
-
fresh_connection = true
|
|
464
|
-
end
|
|
465
|
-
yield conn
|
|
466
|
-
ensure
|
|
467
|
-
release_connection if fresh_connection
|
|
468
|
-
end
|
|
469
|
-
|
|
470
439
|
# Returns true if a connection has already been opened.
|
|
471
440
|
def connected?
|
|
472
|
-
synchronize { @connections.any? }
|
|
441
|
+
synchronize { @connections.any?(&:connected?) }
|
|
473
442
|
end
|
|
474
443
|
|
|
475
444
|
# Returns an array containing the connections currently in the pool.
|
|
@@ -492,7 +461,7 @@ module ActiveRecord
|
|
|
492
461
|
# Raises:
|
|
493
462
|
# - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
|
|
494
463
|
# connections in the pool within a timeout interval (default duration is
|
|
495
|
-
# <tt>spec.
|
|
464
|
+
# <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
|
|
496
465
|
def disconnect(raise_on_acquisition_timeout = true)
|
|
497
466
|
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
|
|
498
467
|
synchronize do
|
|
@@ -504,6 +473,7 @@ module ActiveRecord
|
|
|
504
473
|
conn.disconnect!
|
|
505
474
|
end
|
|
506
475
|
@connections = []
|
|
476
|
+
@leases.clear
|
|
507
477
|
@available.clear
|
|
508
478
|
end
|
|
509
479
|
end
|
|
@@ -513,7 +483,7 @@ module ActiveRecord
|
|
|
513
483
|
#
|
|
514
484
|
# The pool first tries to gain ownership of all connections. If unable to
|
|
515
485
|
# do so within a timeout interval (default duration is
|
|
516
|
-
# <tt>spec.
|
|
486
|
+
# <tt>spec.db_config.checkout_timeout * 2</tt> seconds), then the pool is forcefully
|
|
517
487
|
# disconnected without any regard for other connection owning threads.
|
|
518
488
|
def disconnect!
|
|
519
489
|
disconnect(false)
|
|
@@ -526,21 +496,25 @@ module ActiveRecord
|
|
|
526
496
|
# See AbstractAdapter#discard!
|
|
527
497
|
def discard! # :nodoc:
|
|
528
498
|
synchronize do
|
|
529
|
-
return if
|
|
499
|
+
return if self.discarded?
|
|
530
500
|
@connections.each do |conn|
|
|
531
501
|
conn.discard!
|
|
532
502
|
end
|
|
533
|
-
@connections = @available = @
|
|
503
|
+
@connections = @available = @leases = nil
|
|
534
504
|
end
|
|
535
505
|
end
|
|
536
506
|
|
|
507
|
+
def discarded? # :nodoc:
|
|
508
|
+
@connections.nil?
|
|
509
|
+
end
|
|
510
|
+
|
|
537
511
|
# Clears the cache which maps classes and re-connects connections that
|
|
538
512
|
# require reloading.
|
|
539
513
|
#
|
|
540
514
|
# Raises:
|
|
541
515
|
# - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
|
|
542
516
|
# connections in the pool within a timeout interval (default duration is
|
|
543
|
-
# <tt>spec.
|
|
517
|
+
# <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
|
|
544
518
|
def clear_reloadable_connections(raise_on_acquisition_timeout = true)
|
|
545
519
|
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
|
|
546
520
|
synchronize do
|
|
@@ -562,7 +536,7 @@ module ActiveRecord
|
|
|
562
536
|
#
|
|
563
537
|
# The pool first tries to gain ownership of all connections. If unable to
|
|
564
538
|
# do so within a timeout interval (default duration is
|
|
565
|
-
# <tt>spec.
|
|
539
|
+
# <tt>spec.db_config.checkout_timeout * 2</tt> seconds), then the pool forcefully
|
|
566
540
|
# clears the cache and reloads connections without any regard for other
|
|
567
541
|
# connection owning threads.
|
|
568
542
|
def clear_reloadable_connections!
|
|
@@ -584,7 +558,26 @@ module ActiveRecord
|
|
|
584
558
|
# Raises:
|
|
585
559
|
# - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
|
|
586
560
|
def checkout(checkout_timeout = @checkout_timeout)
|
|
587
|
-
checkout_and_verify(acquire_connection(checkout_timeout))
|
|
561
|
+
return checkout_and_verify(acquire_connection(checkout_timeout)) unless @pinned_connection
|
|
562
|
+
|
|
563
|
+
@pinned_connection.lock.synchronize do
|
|
564
|
+
synchronize do
|
|
565
|
+
# The pinned connection may have been cleaned up before we synchronized, so check if it is still present
|
|
566
|
+
if @pinned_connection
|
|
567
|
+
@pinned_connection.verify!
|
|
568
|
+
|
|
569
|
+
# Any leased connection must be in @connections otherwise
|
|
570
|
+
# some methods like #connected? won't behave correctly
|
|
571
|
+
unless @connections.include?(@pinned_connection)
|
|
572
|
+
@connections << @pinned_connection
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
@pinned_connection
|
|
576
|
+
else
|
|
577
|
+
checkout_and_verify(acquire_connection(checkout_timeout))
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
end
|
|
588
581
|
end
|
|
589
582
|
|
|
590
583
|
# Check-in a database connection back into the pool, indicating that you
|
|
@@ -593,9 +586,11 @@ module ActiveRecord
|
|
|
593
586
|
# +conn+: an AbstractAdapter object, which was obtained by earlier by
|
|
594
587
|
# calling #checkout on this pool.
|
|
595
588
|
def checkin(conn)
|
|
589
|
+
return if @pinned_connection.equal?(conn)
|
|
590
|
+
|
|
596
591
|
conn.lock.synchronize do
|
|
597
592
|
synchronize do
|
|
598
|
-
|
|
593
|
+
connection_lease.clear(conn)
|
|
599
594
|
|
|
600
595
|
conn._run_checkin_callbacks do
|
|
601
596
|
conn.expire
|
|
@@ -642,6 +637,7 @@ module ActiveRecord
|
|
|
642
637
|
# or a thread dies unexpectedly.
|
|
643
638
|
def reap
|
|
644
639
|
stale_connections = synchronize do
|
|
640
|
+
return if self.discarded?
|
|
645
641
|
@connections.select do |conn|
|
|
646
642
|
conn.in_use? && !conn.owner.alive?
|
|
647
643
|
end.each do |conn|
|
|
@@ -666,6 +662,7 @@ module ActiveRecord
|
|
|
666
662
|
return if minimum_idle.nil?
|
|
667
663
|
|
|
668
664
|
idle_connections = synchronize do
|
|
665
|
+
return if self.discarded?
|
|
669
666
|
@connections.select do |conn|
|
|
670
667
|
!conn.in_use? && conn.seconds_idle >= minimum_idle
|
|
671
668
|
end.each do |conn|
|
|
@@ -692,8 +689,7 @@ module ActiveRecord
|
|
|
692
689
|
@available.num_waiting
|
|
693
690
|
end
|
|
694
691
|
|
|
695
|
-
#
|
|
696
|
-
# Example:
|
|
692
|
+
# Returns the connection pool's usage statistic.
|
|
697
693
|
#
|
|
698
694
|
# ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
|
|
699
695
|
def stat
|
|
@@ -710,7 +706,40 @@ module ActiveRecord
|
|
|
710
706
|
end
|
|
711
707
|
end
|
|
712
708
|
|
|
709
|
+
def schedule_query(future_result) # :nodoc:
|
|
710
|
+
@async_executor.post { future_result.execute_or_skip }
|
|
711
|
+
Thread.pass
|
|
712
|
+
end
|
|
713
|
+
|
|
714
|
+
def new_connection # :nodoc:
|
|
715
|
+
connection = db_config.new_connection
|
|
716
|
+
connection.pool = self
|
|
717
|
+
connection
|
|
718
|
+
rescue ConnectionNotEstablished => ex
|
|
719
|
+
raise ex.set_pool(self)
|
|
720
|
+
end
|
|
721
|
+
|
|
713
722
|
private
|
|
723
|
+
def connection_lease
|
|
724
|
+
@leases[ActiveSupport::IsolatedExecutionState.context]
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
def build_async_executor
|
|
728
|
+
case ActiveRecord.async_query_executor
|
|
729
|
+
when :multi_thread_pool
|
|
730
|
+
if @db_config.max_threads > 0
|
|
731
|
+
Concurrent::ThreadPoolExecutor.new(
|
|
732
|
+
min_threads: @db_config.min_threads,
|
|
733
|
+
max_threads: @db_config.max_threads,
|
|
734
|
+
max_queue: @db_config.max_queue,
|
|
735
|
+
fallback_policy: :caller_runs
|
|
736
|
+
)
|
|
737
|
+
end
|
|
738
|
+
when :global_thread_pool
|
|
739
|
+
ActiveRecord.global_thread_pool_async_query_executor
|
|
740
|
+
end
|
|
741
|
+
end
|
|
742
|
+
|
|
714
743
|
#--
|
|
715
744
|
# this is unfortunately not concurrent
|
|
716
745
|
def bulk_make_new_connections(num_new_conns_needed)
|
|
@@ -723,19 +752,6 @@ module ActiveRecord
|
|
|
723
752
|
end
|
|
724
753
|
end
|
|
725
754
|
|
|
726
|
-
#--
|
|
727
|
-
# From the discussion on GitHub:
|
|
728
|
-
# https://github.com/rails/rails/pull/14938#commitcomment-6601951
|
|
729
|
-
# This hook-in method allows for easier monkey-patching fixes needed by
|
|
730
|
-
# JRuby users that use Fibers.
|
|
731
|
-
def connection_cache_key(thread)
|
|
732
|
-
thread
|
|
733
|
-
end
|
|
734
|
-
|
|
735
|
-
def current_thread
|
|
736
|
-
@lock_thread || Thread.current
|
|
737
|
-
end
|
|
738
|
-
|
|
739
755
|
# Take control of all existing connections so a "group" action such as
|
|
740
756
|
# reload/disconnect can be performed safely. It is no longer enough to
|
|
741
757
|
# wrap it in +synchronize+ because some pool's actions are allowed
|
|
@@ -749,18 +765,21 @@ module ActiveRecord
|
|
|
749
765
|
|
|
750
766
|
def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
|
|
751
767
|
collected_conns = synchronize do
|
|
768
|
+
reap # No need to wait for dead owners
|
|
769
|
+
|
|
752
770
|
# account for our own connections
|
|
753
|
-
@connections.select { |conn| conn.owner ==
|
|
771
|
+
@connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
|
|
754
772
|
end
|
|
755
773
|
|
|
756
774
|
newly_checked_out = []
|
|
757
|
-
timeout_time =
|
|
775
|
+
timeout_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (@checkout_timeout * 2)
|
|
758
776
|
|
|
759
|
-
@available.with_a_bias_for(
|
|
777
|
+
@available.with_a_bias_for(ActiveSupport::IsolatedExecutionState.context) do
|
|
760
778
|
loop do
|
|
761
779
|
synchronize do
|
|
762
780
|
return if collected_conns.size == @connections.size && @now_connecting == 0
|
|
763
|
-
|
|
781
|
+
|
|
782
|
+
remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
764
783
|
remaining_timeout = 0 if remaining_timeout < 0
|
|
765
784
|
conn = checkout_for_exclusive_access(remaining_timeout)
|
|
766
785
|
collected_conns << conn
|
|
@@ -803,14 +822,14 @@ module ActiveRecord
|
|
|
803
822
|
|
|
804
823
|
thread_report = []
|
|
805
824
|
@connections.each do |conn|
|
|
806
|
-
unless conn.owner ==
|
|
825
|
+
unless conn.owner == ActiveSupport::IsolatedExecutionState.context
|
|
807
826
|
thread_report << "#{conn} is owned by #{conn.owner}"
|
|
808
827
|
end
|
|
809
828
|
end
|
|
810
829
|
|
|
811
830
|
msg << " (#{thread_report.join(', ')})" if thread_report.any?
|
|
812
831
|
|
|
813
|
-
raise ExclusiveConnectionTimeoutError,
|
|
832
|
+
raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self)
|
|
814
833
|
end
|
|
815
834
|
|
|
816
835
|
def with_new_connections_blocked
|
|
@@ -864,22 +883,26 @@ module ActiveRecord
|
|
|
864
883
|
conn
|
|
865
884
|
else
|
|
866
885
|
reap
|
|
867
|
-
|
|
886
|
+
# Retry after reaping, which may return an available connection,
|
|
887
|
+
# remove an inactive connection, or both
|
|
888
|
+
if conn = @available.poll || try_to_checkout_new_connection
|
|
889
|
+
conn
|
|
890
|
+
else
|
|
891
|
+
@available.poll(checkout_timeout)
|
|
892
|
+
end
|
|
868
893
|
end
|
|
894
|
+
rescue ConnectionTimeoutError => ex
|
|
895
|
+
raise ex.set_pool(self)
|
|
869
896
|
end
|
|
870
897
|
|
|
871
898
|
#--
|
|
872
899
|
# if owner_thread param is omitted, this must be called in synchronize block
|
|
873
900
|
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
alias_method :release, :remove_connection_from_thread_cache
|
|
877
|
-
|
|
878
|
-
def new_connection
|
|
879
|
-
Base.send(spec.adapter_method, spec.config).tap do |conn|
|
|
880
|
-
conn.check_version
|
|
901
|
+
if owner_thread
|
|
902
|
+
@leases[owner_thread].clear(conn)
|
|
881
903
|
end
|
|
882
904
|
end
|
|
905
|
+
alias_method :release, :remove_connection_from_thread_cache
|
|
883
906
|
|
|
884
907
|
# If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
|
|
885
908
|
# to the DB is done outside main synchronized section.
|
|
@@ -916,6 +939,12 @@ module ActiveRecord
|
|
|
916
939
|
def adopt_connection(conn)
|
|
917
940
|
conn.pool = self
|
|
918
941
|
@connections << conn
|
|
942
|
+
|
|
943
|
+
# We just created the first connection, it's time to load the schema
|
|
944
|
+
# cache if that wasn't eagerly done before
|
|
945
|
+
if @schema_cache.nil? && ActiveRecord.lazily_load_schema_cache
|
|
946
|
+
schema_cache.load!
|
|
947
|
+
end
|
|
919
948
|
end
|
|
920
949
|
|
|
921
950
|
def checkout_new_connection
|
|
@@ -925,241 +954,14 @@ module ActiveRecord
|
|
|
925
954
|
|
|
926
955
|
def checkout_and_verify(c)
|
|
927
956
|
c._run_checkout_callbacks do
|
|
928
|
-
c.
|
|
957
|
+
c.clean!
|
|
929
958
|
end
|
|
930
959
|
c
|
|
931
|
-
rescue
|
|
960
|
+
rescue Exception
|
|
932
961
|
remove c
|
|
933
962
|
c.disconnect!
|
|
934
963
|
raise
|
|
935
964
|
end
|
|
936
965
|
end
|
|
937
|
-
|
|
938
|
-
# ConnectionHandler is a collection of ConnectionPool objects. It is used
|
|
939
|
-
# for keeping separate connection pools that connect to different databases.
|
|
940
|
-
#
|
|
941
|
-
# For example, suppose that you have 5 models, with the following hierarchy:
|
|
942
|
-
#
|
|
943
|
-
# class Author < ActiveRecord::Base
|
|
944
|
-
# end
|
|
945
|
-
#
|
|
946
|
-
# class BankAccount < ActiveRecord::Base
|
|
947
|
-
# end
|
|
948
|
-
#
|
|
949
|
-
# class Book < ActiveRecord::Base
|
|
950
|
-
# establish_connection :library_db
|
|
951
|
-
# end
|
|
952
|
-
#
|
|
953
|
-
# class ScaryBook < Book
|
|
954
|
-
# end
|
|
955
|
-
#
|
|
956
|
-
# class GoodBook < Book
|
|
957
|
-
# end
|
|
958
|
-
#
|
|
959
|
-
# And a database.yml that looked like this:
|
|
960
|
-
#
|
|
961
|
-
# development:
|
|
962
|
-
# database: my_application
|
|
963
|
-
# host: localhost
|
|
964
|
-
#
|
|
965
|
-
# library_db:
|
|
966
|
-
# database: library
|
|
967
|
-
# host: some.library.org
|
|
968
|
-
#
|
|
969
|
-
# Your primary database in the development environment is "my_application"
|
|
970
|
-
# but the Book model connects to a separate database called "library_db"
|
|
971
|
-
# (this can even be a database on a different machine).
|
|
972
|
-
#
|
|
973
|
-
# Book, ScaryBook and GoodBook will all use the same connection pool to
|
|
974
|
-
# "library_db" while Author, BankAccount, and any other models you create
|
|
975
|
-
# will use the default connection pool to "my_application".
|
|
976
|
-
#
|
|
977
|
-
# The various connection pools are managed by a single instance of
|
|
978
|
-
# ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
|
|
979
|
-
# All Active Record models use this handler to determine the connection pool that they
|
|
980
|
-
# should use.
|
|
981
|
-
#
|
|
982
|
-
# The ConnectionHandler class is not coupled with the Active models, as it has no knowledge
|
|
983
|
-
# about the model. The model needs to pass a specification name to the handler,
|
|
984
|
-
# in order to look up the correct connection pool.
|
|
985
|
-
class ConnectionHandler
|
|
986
|
-
def self.create_owner_to_pool # :nodoc:
|
|
987
|
-
Concurrent::Map.new(initial_capacity: 2) do |h, k|
|
|
988
|
-
# Discard the parent's connection pools immediately; we have no need
|
|
989
|
-
# of them
|
|
990
|
-
discard_unowned_pools(h)
|
|
991
|
-
|
|
992
|
-
h[k] = Concurrent::Map.new(initial_capacity: 2)
|
|
993
|
-
end
|
|
994
|
-
end
|
|
995
|
-
|
|
996
|
-
def self.unowned_pool_finalizer(pid_map) # :nodoc:
|
|
997
|
-
lambda do |_|
|
|
998
|
-
discard_unowned_pools(pid_map)
|
|
999
|
-
end
|
|
1000
|
-
end
|
|
1001
|
-
|
|
1002
|
-
def self.discard_unowned_pools(pid_map) # :nodoc:
|
|
1003
|
-
pid_map.each do |pid, pools|
|
|
1004
|
-
pools.values.compact.each(&:discard!) unless pid == Process.pid
|
|
1005
|
-
end
|
|
1006
|
-
end
|
|
1007
|
-
|
|
1008
|
-
def initialize
|
|
1009
|
-
# These caches are keyed by spec.name (ConnectionSpecification#name).
|
|
1010
|
-
@owner_to_pool = ConnectionHandler.create_owner_to_pool
|
|
1011
|
-
|
|
1012
|
-
# Backup finalizer: if the forked child never needed a pool, the above
|
|
1013
|
-
# early discard has not occurred
|
|
1014
|
-
ObjectSpace.define_finalizer self, ConnectionHandler.unowned_pool_finalizer(@owner_to_pool)
|
|
1015
|
-
end
|
|
1016
|
-
|
|
1017
|
-
def prevent_writes # :nodoc:
|
|
1018
|
-
Thread.current[:prevent_writes]
|
|
1019
|
-
end
|
|
1020
|
-
|
|
1021
|
-
def prevent_writes=(prevent_writes) # :nodoc:
|
|
1022
|
-
Thread.current[:prevent_writes] = prevent_writes
|
|
1023
|
-
end
|
|
1024
|
-
|
|
1025
|
-
# Prevent writing to the database regardless of role.
|
|
1026
|
-
#
|
|
1027
|
-
# In some cases you may want to prevent writes to the database
|
|
1028
|
-
# even if you are on a database that can write. `while_preventing_writes`
|
|
1029
|
-
# will prevent writes to the database for the duration of the block.
|
|
1030
|
-
def while_preventing_writes(enabled = true)
|
|
1031
|
-
original, self.prevent_writes = self.prevent_writes, enabled
|
|
1032
|
-
yield
|
|
1033
|
-
ensure
|
|
1034
|
-
self.prevent_writes = original
|
|
1035
|
-
end
|
|
1036
|
-
|
|
1037
|
-
def connection_pool_list
|
|
1038
|
-
owner_to_pool.values.compact
|
|
1039
|
-
end
|
|
1040
|
-
alias :connection_pools :connection_pool_list
|
|
1041
|
-
|
|
1042
|
-
def establish_connection(config)
|
|
1043
|
-
resolver = ConnectionSpecification::Resolver.new(Base.configurations)
|
|
1044
|
-
spec = resolver.spec(config)
|
|
1045
|
-
|
|
1046
|
-
remove_connection(spec.name)
|
|
1047
|
-
|
|
1048
|
-
message_bus = ActiveSupport::Notifications.instrumenter
|
|
1049
|
-
payload = {
|
|
1050
|
-
connection_id: object_id
|
|
1051
|
-
}
|
|
1052
|
-
if spec
|
|
1053
|
-
payload[:spec_name] = spec.name
|
|
1054
|
-
payload[:config] = spec.config
|
|
1055
|
-
end
|
|
1056
|
-
|
|
1057
|
-
message_bus.instrument("!connection.active_record", payload) do
|
|
1058
|
-
owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
|
|
1059
|
-
end
|
|
1060
|
-
|
|
1061
|
-
owner_to_pool[spec.name]
|
|
1062
|
-
end
|
|
1063
|
-
|
|
1064
|
-
# Returns true if there are any active connections among the connection
|
|
1065
|
-
# pools that the ConnectionHandler is managing.
|
|
1066
|
-
def active_connections?
|
|
1067
|
-
connection_pool_list.any?(&:active_connection?)
|
|
1068
|
-
end
|
|
1069
|
-
|
|
1070
|
-
# Returns any connections in use by the current thread back to the pool,
|
|
1071
|
-
# and also returns connections to the pool cached by threads that are no
|
|
1072
|
-
# longer alive.
|
|
1073
|
-
def clear_active_connections!
|
|
1074
|
-
connection_pool_list.each(&:release_connection)
|
|
1075
|
-
end
|
|
1076
|
-
|
|
1077
|
-
# Clears the cache which maps classes.
|
|
1078
|
-
#
|
|
1079
|
-
# See ConnectionPool#clear_reloadable_connections! for details.
|
|
1080
|
-
def clear_reloadable_connections!
|
|
1081
|
-
connection_pool_list.each(&:clear_reloadable_connections!)
|
|
1082
|
-
end
|
|
1083
|
-
|
|
1084
|
-
def clear_all_connections!
|
|
1085
|
-
connection_pool_list.each(&:disconnect!)
|
|
1086
|
-
end
|
|
1087
|
-
|
|
1088
|
-
# Disconnects all currently idle connections.
|
|
1089
|
-
#
|
|
1090
|
-
# See ConnectionPool#flush! for details.
|
|
1091
|
-
def flush_idle_connections!
|
|
1092
|
-
connection_pool_list.each(&:flush!)
|
|
1093
|
-
end
|
|
1094
|
-
|
|
1095
|
-
# Locate the connection of the nearest super class. This can be an
|
|
1096
|
-
# active or defined connection: if it is the latter, it will be
|
|
1097
|
-
# opened and set as the active connection for the class it was defined
|
|
1098
|
-
# for (not necessarily the current class).
|
|
1099
|
-
def retrieve_connection(spec_name) #:nodoc:
|
|
1100
|
-
pool = retrieve_connection_pool(spec_name)
|
|
1101
|
-
|
|
1102
|
-
unless pool
|
|
1103
|
-
# multiple database application
|
|
1104
|
-
if ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler
|
|
1105
|
-
raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role."
|
|
1106
|
-
else
|
|
1107
|
-
raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found."
|
|
1108
|
-
end
|
|
1109
|
-
end
|
|
1110
|
-
|
|
1111
|
-
pool.connection
|
|
1112
|
-
end
|
|
1113
|
-
|
|
1114
|
-
# Returns true if a connection that's accessible to this class has
|
|
1115
|
-
# already been opened.
|
|
1116
|
-
def connected?(spec_name)
|
|
1117
|
-
pool = retrieve_connection_pool(spec_name)
|
|
1118
|
-
pool && pool.connected?
|
|
1119
|
-
end
|
|
1120
|
-
|
|
1121
|
-
# Remove the connection for this class. This will close the active
|
|
1122
|
-
# connection and the defined connection (if they exist). The result
|
|
1123
|
-
# can be used as an argument for #establish_connection, for easily
|
|
1124
|
-
# re-establishing the connection.
|
|
1125
|
-
def remove_connection(spec_name)
|
|
1126
|
-
if pool = owner_to_pool.delete(spec_name)
|
|
1127
|
-
pool.automatic_reconnect = false
|
|
1128
|
-
pool.disconnect!
|
|
1129
|
-
pool.spec.config
|
|
1130
|
-
end
|
|
1131
|
-
end
|
|
1132
|
-
|
|
1133
|
-
# Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool.
|
|
1134
|
-
# This makes retrieving the connection pool O(1) once the process is warm.
|
|
1135
|
-
# When a connection is established or removed, we invalidate the cache.
|
|
1136
|
-
def retrieve_connection_pool(spec_name)
|
|
1137
|
-
owner_to_pool.fetch(spec_name) do
|
|
1138
|
-
# Check if a connection was previously established in an ancestor process,
|
|
1139
|
-
# which may have been forked.
|
|
1140
|
-
if ancestor_pool = pool_from_any_process_for(spec_name)
|
|
1141
|
-
# A connection was established in an ancestor process that must have
|
|
1142
|
-
# subsequently forked. We can't reuse the connection, but we can copy
|
|
1143
|
-
# the specification and establish a new connection with it.
|
|
1144
|
-
establish_connection(ancestor_pool.spec.to_hash).tap do |pool|
|
|
1145
|
-
pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
|
|
1146
|
-
end
|
|
1147
|
-
else
|
|
1148
|
-
owner_to_pool[spec_name] = nil
|
|
1149
|
-
end
|
|
1150
|
-
end
|
|
1151
|
-
end
|
|
1152
|
-
|
|
1153
|
-
private
|
|
1154
|
-
|
|
1155
|
-
def owner_to_pool
|
|
1156
|
-
@owner_to_pool[Process.pid]
|
|
1157
|
-
end
|
|
1158
|
-
|
|
1159
|
-
def pool_from_any_process_for(spec_name)
|
|
1160
|
-
owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[spec_name] }
|
|
1161
|
-
owner_to_pool && owner_to_pool[spec_name]
|
|
1162
|
-
end
|
|
1163
|
-
end
|
|
1164
966
|
end
|
|
1165
967
|
end
|