activerecord 5.0.7.2 → 6.1.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +829 -2015
- data/MIT-LICENSE +3 -1
- data/README.rdoc +11 -9
- data/examples/performance.rb +31 -29
- data/examples/simple.rb +5 -3
- data/lib/active_record.rb +37 -29
- data/lib/active_record/aggregations.rb +249 -247
- data/lib/active_record/association_relation.rb +30 -18
- data/lib/active_record/associations.rb +1714 -1596
- data/lib/active_record/associations/alias_tracker.rb +36 -42
- data/lib/active_record/associations/association.rb +143 -68
- data/lib/active_record/associations/association_scope.rb +98 -94
- data/lib/active_record/associations/belongs_to_association.rb +76 -46
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -12
- data/lib/active_record/associations/builder/association.rb +27 -28
- data/lib/active_record/associations/builder/belongs_to.rb +52 -60
- data/lib/active_record/associations/builder/collection_association.rb +12 -22
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +40 -62
- data/lib/active_record/associations/builder/has_many.rb +10 -2
- data/lib/active_record/associations/builder/has_one.rb +35 -2
- data/lib/active_record/associations/builder/singular_association.rb +5 -1
- data/lib/active_record/associations/collection_association.rb +104 -259
- data/lib/active_record/associations/collection_proxy.rb +169 -125
- data/lib/active_record/associations/foreign_association.rb +22 -0
- data/lib/active_record/associations/has_many_association.rb +46 -31
- data/lib/active_record/associations/has_many_through_association.rb +66 -46
- data/lib/active_record/associations/has_one_association.rb +71 -52
- data/lib/active_record/associations/has_one_through_association.rb +20 -11
- data/lib/active_record/associations/join_dependency.rb +169 -180
- data/lib/active_record/associations/join_dependency/join_association.rb +53 -79
- data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
- data/lib/active_record/associations/preloader.rb +97 -104
- data/lib/active_record/associations/preloader/association.rb +109 -97
- data/lib/active_record/associations/preloader/through_association.rb +77 -76
- data/lib/active_record/associations/singular_association.rb +12 -45
- data/lib/active_record/associations/through_association.rb +27 -15
- data/lib/active_record/attribute_assignment.rb +55 -60
- data/lib/active_record/attribute_methods.rb +111 -141
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -9
- data/lib/active_record/attribute_methods/dirty.rb +172 -112
- data/lib/active_record/attribute_methods/primary_key.rb +88 -91
- data/lib/active_record/attribute_methods/query.rb +6 -8
- data/lib/active_record/attribute_methods/read.rb +18 -50
- data/lib/active_record/attribute_methods/serialization.rb +38 -10
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -66
- data/lib/active_record/attribute_methods/write.rb +25 -32
- data/lib/active_record/attributes.rb +69 -31
- data/lib/active_record/autosave_association.rb +102 -66
- data/lib/active_record/base.rb +16 -25
- data/lib/active_record/callbacks.rb +202 -43
- data/lib/active_record/coders/json.rb +2 -0
- data/lib/active_record/coders/yaml_column.rb +11 -12
- data/lib/active_record/connection_adapters.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +661 -375
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +14 -38
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +269 -105
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +54 -35
- data/lib/active_record/connection_adapters/abstract/quoting.rb +137 -93
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +155 -113
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -162
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +68 -80
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +591 -259
- data/lib/active_record/connection_adapters/abstract/transaction.rb +229 -91
- data/lib/active_record/connection_adapters/abstract_adapter.rb +392 -244
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +457 -582
- data/lib/active_record/connection_adapters/column.rb +55 -13
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +8 -31
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +135 -49
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +24 -23
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -20
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +79 -49
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +66 -56
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +70 -36
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +268 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +20 -12
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +74 -37
- data/lib/active_record/connection_adapters/pool_config.rb +63 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +39 -28
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +70 -101
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid.rb +26 -21
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +22 -11
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +6 -5
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -6
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -4
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +19 -18
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -5
- data/lib/active_record/connection_adapters/postgresql/oid/{json.rb → oid.rb} +6 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +30 -9
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -30
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +18 -4
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +98 -38
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +21 -27
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +80 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +147 -105
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +34 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +426 -324
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +32 -23
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -6
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +418 -293
- data/lib/active_record/connection_adapters/schema_cache.rb +135 -18
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +22 -7
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +3 -1
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +72 -18
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +170 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +282 -290
- data/lib/active_record/connection_adapters/statement_pool.rb +9 -8
- data/lib/active_record/connection_handling.rb +287 -45
- data/lib/active_record/core.rb +385 -181
- data/lib/active_record/counter_cache.rb +60 -28
- data/lib/active_record/database_configurations.rb +272 -0
- data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
- data/lib/active_record/database_configurations/database_config.rb +80 -0
- data/lib/active_record/database_configurations/hash_config.rb +96 -0
- data/lib/active_record/database_configurations/url_config.rb +53 -0
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/dynamic_matchers.rb +87 -87
- data/lib/active_record/enum.rb +122 -47
- data/lib/active_record/errors.rb +153 -22
- data/lib/active_record/explain.rb +13 -8
- data/lib/active_record/explain_registry.rb +3 -1
- data/lib/active_record/explain_subscriber.rb +9 -4
- data/lib/active_record/fixture_set/file.rb +20 -22
- data/lib/active_record/fixture_set/model_metadata.rb +32 -0
- data/lib/active_record/fixture_set/render_context.rb +17 -0
- data/lib/active_record/fixture_set/table_row.rb +152 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +246 -507
- data/lib/active_record/gem_version.rb +6 -4
- data/lib/active_record/inheritance.rb +168 -95
- data/lib/active_record/insert_all.rb +208 -0
- data/lib/active_record/integration.rb +114 -25
- data/lib/active_record/internal_metadata.rb +30 -24
- data/lib/active_record/legacy_yaml_adapter.rb +11 -5
- data/lib/active_record/locking/optimistic.rb +81 -85
- data/lib/active_record/locking/pessimistic.rb +22 -6
- data/lib/active_record/log_subscriber.rb +68 -31
- data/lib/active_record/middleware/database_selector.rb +77 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
- data/lib/active_record/migration.rb +439 -342
- data/lib/active_record/migration/command_recorder.rb +152 -98
- data/lib/active_record/migration/compatibility.rb +229 -60
- data/lib/active_record/migration/join_table.rb +8 -7
- data/lib/active_record/model_schema.rb +230 -122
- data/lib/active_record/nested_attributes.rb +213 -203
- data/lib/active_record/no_touching.rb +11 -2
- data/lib/active_record/null_relation.rb +12 -34
- data/lib/active_record/persistence.rb +471 -97
- data/lib/active_record/query_cache.rb +23 -12
- data/lib/active_record/querying.rb +43 -25
- data/lib/active_record/railtie.rb +155 -43
- data/lib/active_record/railties/console_sandbox.rb +2 -0
- data/lib/active_record/railties/controller_runtime.rb +34 -33
- data/lib/active_record/railties/databases.rake +507 -195
- data/lib/active_record/readonly_attributes.rb +9 -4
- data/lib/active_record/reflection.rb +245 -269
- data/lib/active_record/relation.rb +475 -324
- data/lib/active_record/relation/batches.rb +125 -72
- data/lib/active_record/relation/batches/batch_enumerator.rb +28 -10
- data/lib/active_record/relation/calculations.rb +267 -171
- data/lib/active_record/relation/delegation.rb +73 -69
- data/lib/active_record/relation/finder_methods.rb +238 -248
- data/lib/active_record/relation/from_clause.rb +7 -9
- data/lib/active_record/relation/merger.rb +95 -77
- data/lib/active_record/relation/predicate_builder.rb +109 -110
- data/lib/active_record/relation/predicate_builder/array_handler.rb +22 -17
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +6 -4
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +55 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +7 -18
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
- data/lib/active_record/relation/query_attribute.rb +33 -2
- data/lib/active_record/relation/query_methods.rb +654 -374
- data/lib/active_record/relation/record_fetch_warning.rb +8 -6
- data/lib/active_record/relation/spawn_methods.rb +15 -14
- data/lib/active_record/relation/where_clause.rb +171 -109
- data/lib/active_record/result.rb +88 -51
- data/lib/active_record/runtime_registry.rb +5 -3
- data/lib/active_record/sanitization.rb +73 -100
- data/lib/active_record/schema.rb +7 -14
- data/lib/active_record/schema_dumper.rb +101 -69
- data/lib/active_record/schema_migration.rb +16 -12
- data/lib/active_record/scoping.rb +20 -20
- data/lib/active_record/scoping/default.rb +92 -95
- data/lib/active_record/scoping/named.rb +39 -30
- data/lib/active_record/secure_token.rb +19 -9
- data/lib/active_record/serialization.rb +7 -3
- data/lib/active_record/signed_id.rb +116 -0
- data/lib/active_record/statement_cache.rb +80 -29
- data/lib/active_record/store.rb +122 -42
- data/lib/active_record/suppressor.rb +6 -3
- data/lib/active_record/table_metadata.rb +51 -39
- data/lib/active_record/tasks/database_tasks.rb +332 -115
- data/lib/active_record/tasks/mysql_database_tasks.rb +66 -104
- data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -56
- data/lib/active_record/tasks/sqlite_database_tasks.rb +40 -19
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +246 -0
- data/lib/active_record/timestamp.rb +70 -38
- data/lib/active_record/touch_later.rb +26 -24
- data/lib/active_record/transactions.rb +121 -184
- data/lib/active_record/translation.rb +3 -1
- data/lib/active_record/type.rb +29 -17
- data/lib/active_record/type/adapter_specific_registry.rb +44 -48
- data/lib/active_record/type/date.rb +2 -0
- data/lib/active_record/type/date_time.rb +2 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
- data/lib/active_record/type/internal/timezone.rb +2 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +20 -9
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +12 -1
- data/lib/active_record/type/type_map.rb +14 -17
- data/lib/active_record/type/unsigned_integer.rb +16 -0
- data/lib/active_record/type_caster.rb +4 -2
- data/lib/active_record/type_caster/connection.rb +17 -13
- data/lib/active_record/type_caster/map.rb +10 -6
- data/lib/active_record/validations.rb +8 -5
- data/lib/active_record/validations/absence.rb +2 -0
- data/lib/active_record/validations/associated.rb +4 -3
- data/lib/active_record/validations/length.rb +2 -0
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/presence.rb +4 -2
- data/lib/active_record/validations/uniqueness.rb +52 -45
- data/lib/active_record/version.rb +3 -1
- data/lib/arel.rb +54 -0
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +41 -0
- data/lib/arel/collectors/bind.rb +29 -0
- data/lib/arel/collectors/composite.rb +39 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +27 -0
- data/lib/arel/collectors/substitute_binds.rb +35 -0
- data/lib/arel/crud.rb +42 -0
- data/lib/arel/delete_manager.rb +18 -0
- data/lib/arel/errors.rb +9 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +49 -0
- data/lib/arel/insert_manager.rb +49 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes.rb +70 -0
- data/lib/arel/nodes/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +126 -0
- data/lib/arel/nodes/bind_param.rb +44 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +62 -0
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/count.rb +12 -0
- data/lib/arel/nodes/delete_statement.rb +45 -0
- data/lib/arel/nodes/descending.rb +23 -0
- data/lib/arel/nodes/equality.rb +15 -0
- data/lib/arel/nodes/extract.rb +24 -0
- data/lib/arel/nodes/false.rb +16 -0
- data/lib/arel/nodes/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +44 -0
- data/lib/arel/nodes/grouping.rb +11 -0
- data/lib/arel/nodes/homogeneous_in.rb +72 -0
- data/lib/arel/nodes/in.rb +15 -0
- data/lib/arel/nodes/infix_operation.rb +92 -0
- data/lib/arel/nodes/inner_join.rb +8 -0
- data/lib/arel/nodes/insert_statement.rb +37 -0
- data/lib/arel/nodes/join_source.rb +20 -0
- data/lib/arel/nodes/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/node.rb +51 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/outer_join.rb +8 -0
- data/lib/arel/nodes/over.rb +15 -0
- data/lib/arel/nodes/regexp.rb +16 -0
- data/lib/arel/nodes/right_outer_join.rb +8 -0
- data/lib/arel/nodes/select_core.rb +67 -0
- data/lib/arel/nodes/select_statement.rb +41 -0
- data/lib/arel/nodes/sql_literal.rb +19 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +31 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +44 -0
- data/lib/arel/nodes/unary_operation.rb +20 -0
- data/lib/arel/nodes/unqualified_column.rb +22 -0
- data/lib/arel/nodes/update_statement.rb +41 -0
- data/lib/arel/nodes/values_list.rb +9 -0
- data/lib/arel/nodes/window.rb +126 -0
- data/lib/arel/nodes/with.rb +11 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +250 -0
- data/lib/arel/select_manager.rb +270 -0
- data/lib/arel/table.rb +118 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors.rb +13 -0
- data/lib/arel/visitors/dot.rb +308 -0
- data/lib/arel/visitors/mysql.rb +93 -0
- data/lib/arel/visitors/postgresql.rb +120 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +899 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/rails/generators/active_record.rb +7 -5
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
- data/lib/rails/generators/active_record/{model/templates/application_record.rb → application_record/templates/application_record.rb.tt} +0 -0
- data/lib/rails/generators/active_record/migration.rb +22 -3
- data/lib/rails/generators/active_record/migration/migration_generator.rb +38 -35
- data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +3 -1
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +7 -5
- data/lib/rails/generators/active_record/model/model_generator.rb +41 -25
- 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 → model.rb.tt} +10 -1
- data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
- metadata +141 -57
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
- data/lib/active_record/associations/preloader/collection_association.rb +0 -17
- data/lib/active_record/associations/preloader/has_many.rb +0 -17
- data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
- data/lib/active_record/associations/preloader/has_one.rb +0 -15
- data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
- data/lib/active_record/associations/preloader/singular_association.rb +0 -20
- data/lib/active_record/attribute.rb +0 -213
- data/lib/active_record/attribute/user_provided_default.rb +0 -28
- data/lib/active_record/attribute_decorators.rb +0 -67
- data/lib/active_record/attribute_mutation_tracker.rb +0 -70
- data/lib/active_record/attribute_set.rb +0 -110
- data/lib/active_record/attribute_set/builder.rb +0 -132
- data/lib/active_record/collection_cache_key.rb +0 -50
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -263
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -22
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +0 -50
- data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +0 -88
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -17
- data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -57
- data/lib/active_record/relation/where_clause_factory.rb +0 -38
- data/lib/active_record/type/internal/abstract_json.rb +0 -33
@@ -1,12 +1,14 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
2
4
|
|
3
5
|
module ActiveRecord
|
4
6
|
module Coders # :nodoc:
|
5
7
|
class YAMLColumn # :nodoc:
|
6
|
-
|
7
8
|
attr_accessor :object_class
|
8
9
|
|
9
|
-
def initialize(object_class = Object)
|
10
|
+
def initialize(attr_name, object_class = Object)
|
11
|
+
@attr_name = attr_name
|
10
12
|
@object_class = object_class
|
11
13
|
check_arity_of_constructor
|
12
14
|
end
|
@@ -14,37 +16,34 @@ module ActiveRecord
|
|
14
16
|
def dump(obj)
|
15
17
|
return if obj.nil?
|
16
18
|
|
17
|
-
assert_valid_value(obj)
|
19
|
+
assert_valid_value(obj, action: "dump")
|
18
20
|
YAML.dump obj
|
19
21
|
end
|
20
22
|
|
21
23
|
def load(yaml)
|
22
24
|
return object_class.new if object_class != Object && yaml.nil?
|
23
|
-
return yaml unless yaml.is_a?(String) && yaml
|
25
|
+
return yaml unless yaml.is_a?(String) && yaml.start_with?("---")
|
24
26
|
obj = YAML.load(yaml)
|
25
27
|
|
26
|
-
assert_valid_value(obj)
|
28
|
+
assert_valid_value(obj, action: "load")
|
27
29
|
obj ||= object_class.new if object_class != Object
|
28
30
|
|
29
31
|
obj
|
30
32
|
end
|
31
33
|
|
32
|
-
def assert_valid_value(obj)
|
34
|
+
def assert_valid_value(obj, action:)
|
33
35
|
unless obj.nil? || obj.is_a?(object_class)
|
34
36
|
raise SerializationTypeMismatch,
|
35
|
-
"
|
37
|
+
"can't #{action} `#{@attr_name}`: was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
39
41
|
private
|
40
|
-
|
41
|
-
def check_arity_of_constructor
|
42
|
-
begin
|
42
|
+
def check_arity_of_constructor
|
43
43
|
load(nil)
|
44
44
|
rescue ArgumentError
|
45
45
|
raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
|
46
46
|
end
|
47
|
-
end
|
48
47
|
end
|
49
48
|
end
|
50
49
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
extend ActiveSupport::Autoload
|
6
|
+
|
7
|
+
eager_autoload do
|
8
|
+
autoload :AbstractAdapter
|
9
|
+
end
|
10
|
+
|
11
|
+
autoload :Column
|
12
|
+
autoload :PoolConfig
|
13
|
+
autoload :PoolManager
|
14
|
+
autoload :LegacyPoolManager
|
15
|
+
|
16
|
+
autoload_at "active_record/connection_adapters/abstract/schema_definitions" do
|
17
|
+
autoload :IndexDefinition
|
18
|
+
autoload :ColumnDefinition
|
19
|
+
autoload :ChangeColumnDefinition
|
20
|
+
autoload :ForeignKeyDefinition
|
21
|
+
autoload :CheckConstraintDefinition
|
22
|
+
autoload :TableDefinition
|
23
|
+
autoload :Table
|
24
|
+
autoload :AlterTable
|
25
|
+
autoload :ReferenceDefinition
|
26
|
+
end
|
27
|
+
|
28
|
+
autoload_at "active_record/connection_adapters/abstract/connection_pool" do
|
29
|
+
autoload :ConnectionHandler
|
30
|
+
end
|
31
|
+
|
32
|
+
autoload_under "abstract" do
|
33
|
+
autoload :SchemaStatements
|
34
|
+
autoload :DatabaseStatements
|
35
|
+
autoload :DatabaseLimits
|
36
|
+
autoload :Quoting
|
37
|
+
autoload :ConnectionPool
|
38
|
+
autoload :QueryCache
|
39
|
+
autoload :Savepoints
|
40
|
+
end
|
41
|
+
|
42
|
+
autoload_at "active_record/connection_adapters/abstract/transaction" do
|
43
|
+
autoload :TransactionManager
|
44
|
+
autoload :NullTransaction
|
45
|
+
autoload :RealTransaction
|
46
|
+
autoload :SavepointTransaction
|
47
|
+
autoload :TransactionState
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,22 +1,34 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thread"
|
4
|
+
require "concurrent/map"
|
5
|
+
require "monitor"
|
6
|
+
require "weakref"
|
4
7
|
|
5
8
|
module ActiveRecord
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
module ConnectionAdapters
|
10
|
+
module AbstractPool # :nodoc:
|
11
|
+
def get_schema_cache(connection)
|
12
|
+
self.schema_cache ||= SchemaCache.new(connection)
|
13
|
+
schema_cache.connection = connection
|
14
|
+
schema_cache
|
15
|
+
end
|
11
16
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
def set_schema_cache(cache)
|
18
|
+
self.schema_cache = cache
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class NullPool # :nodoc:
|
23
|
+
include ConnectionAdapters::AbstractPool
|
24
|
+
|
25
|
+
attr_accessor :schema_cache
|
26
|
+
|
27
|
+
def owner_name
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
end
|
18
31
|
|
19
|
-
module ConnectionAdapters
|
20
32
|
# Connection pool base class for managing Active Record database
|
21
33
|
# connections.
|
22
34
|
#
|
@@ -61,30 +73,25 @@ module ActiveRecord
|
|
61
73
|
# There are several connection-pooling-related options that you can add to
|
62
74
|
# your database connection configuration:
|
63
75
|
#
|
64
|
-
# * +pool+: number
|
65
|
-
# * +
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
# Regardless of this setting, the Reaper will be invoked before every
|
72
|
-
# blocking wait. (Default nil, which means don't schedule the Reaper).
|
76
|
+
# * +pool+: maximum number of connections the pool may manage (default 5).
|
77
|
+
# * +idle_timeout+: number of seconds that a connection will be kept
|
78
|
+
# unused in the pool before it is automatically disconnected (default
|
79
|
+
# 300 seconds). Set this to zero to keep connections forever.
|
80
|
+
# * +checkout_timeout+: number of seconds to wait for a connection to
|
81
|
+
# become available before giving up and raising a timeout error (default
|
82
|
+
# 5 seconds).
|
73
83
|
#
|
74
84
|
#--
|
75
85
|
# Synchronization policy:
|
76
86
|
# * all public methods can be called outside +synchronize+
|
77
|
-
# * access to these
|
87
|
+
# * access to these instance variables needs to be in +synchronize+:
|
78
88
|
# * @connections
|
79
89
|
# * @now_connecting
|
80
90
|
# * private methods that require being called in a +synchronize+ blocks
|
81
91
|
# are now explicitly documented
|
82
92
|
class ConnectionPool
|
83
|
-
# Threadsafe, fair,
|
84
|
-
# with which it shares a Monitor.
|
85
|
-
#
|
86
|
-
# The Queue in stdlib's 'thread' could replace this class except
|
87
|
-
# stdlib's doesn't support waiting with a timeout.
|
93
|
+
# Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool
|
94
|
+
# with which it shares a Monitor.
|
88
95
|
class Queue
|
89
96
|
def initialize(lock = Monitor.new)
|
90
97
|
@lock = lock
|
@@ -116,7 +123,7 @@ module ActiveRecord
|
|
116
123
|
end
|
117
124
|
end
|
118
125
|
|
119
|
-
# If +element+ is in the queue, remove and return it, or nil
|
126
|
+
# If +element+ is in the queue, remove and return it, or +nil+.
|
120
127
|
def delete(element)
|
121
128
|
synchronize do
|
122
129
|
@queue.delete(element)
|
@@ -132,10 +139,10 @@ module ActiveRecord
|
|
132
139
|
|
133
140
|
# Remove the head of the queue.
|
134
141
|
#
|
135
|
-
# If +timeout+ is not given, remove and return the head the
|
142
|
+
# If +timeout+ is not given, remove and return the head of the
|
136
143
|
# queue if the number of available elements is strictly
|
137
144
|
# greater than the number of threads currently waiting (that
|
138
|
-
# is, don't jump ahead in line). Otherwise, return nil
|
145
|
+
# is, don't jump ahead in line). Otherwise, return +nil+.
|
139
146
|
#
|
140
147
|
# If +timeout+ is given, block if there is no element
|
141
148
|
# available, waiting up to +timeout+ seconds for an element to
|
@@ -149,62 +156,63 @@ module ActiveRecord
|
|
149
156
|
end
|
150
157
|
|
151
158
|
private
|
159
|
+
def internal_poll(timeout)
|
160
|
+
no_wait_poll || (timeout && wait_poll(timeout))
|
161
|
+
end
|
152
162
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
def synchronize(&block)
|
158
|
-
@lock.synchronize(&block)
|
159
|
-
end
|
163
|
+
def synchronize(&block)
|
164
|
+
@lock.synchronize(&block)
|
165
|
+
end
|
160
166
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
167
|
+
# Test if the queue currently contains any elements.
|
168
|
+
def any?
|
169
|
+
!@queue.empty?
|
170
|
+
end
|
165
171
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
172
|
+
# A thread can remove an element from the queue without
|
173
|
+
# waiting if and only if the number of currently available
|
174
|
+
# connections is strictly greater than the number of waiting
|
175
|
+
# threads.
|
176
|
+
def can_remove_no_wait?
|
177
|
+
@queue.size > @num_waiting
|
178
|
+
end
|
173
179
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
180
|
+
# Removes and returns the head of the queue if possible, or +nil+.
|
181
|
+
def remove
|
182
|
+
@queue.pop
|
183
|
+
end
|
178
184
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
+
# Remove and return the head of the queue if the number of
|
186
|
+
# available elements is strictly greater than the number of
|
187
|
+
# threads currently waiting. Otherwise, return +nil+.
|
188
|
+
def no_wait_poll
|
189
|
+
remove if can_remove_no_wait?
|
190
|
+
end
|
185
191
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
192
|
+
# Waits on the queue up to +timeout+ seconds, then removes and
|
193
|
+
# returns the head of the queue.
|
194
|
+
def wait_poll(timeout)
|
195
|
+
@num_waiting += 1
|
190
196
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
197
|
+
t0 = Concurrent.monotonic_time
|
198
|
+
elapsed = 0
|
199
|
+
loop do
|
200
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
201
|
+
@cond.wait(timeout - elapsed)
|
202
|
+
end
|
195
203
|
|
196
|
-
|
204
|
+
return remove if any?
|
197
205
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
206
|
+
elapsed = Concurrent.monotonic_time - t0
|
207
|
+
if elapsed >= timeout
|
208
|
+
msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" %
|
209
|
+
[timeout, elapsed]
|
210
|
+
raise ConnectionTimeoutError, msg
|
211
|
+
end
|
203
212
|
end
|
213
|
+
ensure
|
214
|
+
@num_waiting -= 1
|
204
215
|
end
|
205
|
-
ensure
|
206
|
-
@num_waiting -= 1
|
207
|
-
end
|
208
216
|
end
|
209
217
|
|
210
218
|
# Adds the ability to turn a basic fair FIFO queue into one
|
@@ -268,25 +276,25 @@ module ActiveRecord
|
|
268
276
|
# Connections must be leased while holding the main pool mutex. This is
|
269
277
|
# an internal subclass that also +.leases+ returned connections while
|
270
278
|
# still in queue's critical section (queue synchronizes with the same
|
271
|
-
#
|
279
|
+
# <tt>@lock</tt> as the main pool) so that a returned connection is already
|
272
280
|
# leased and there is no need to re-enter synchronized block.
|
273
281
|
class ConnectionLeasingQueue < Queue # :nodoc:
|
274
282
|
include BiasableQueue
|
275
283
|
|
276
284
|
private
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
285
|
+
def internal_poll(timeout)
|
286
|
+
conn = super
|
287
|
+
conn.lease if conn
|
288
|
+
conn
|
289
|
+
end
|
282
290
|
end
|
283
291
|
|
284
|
-
# Every +frequency+ seconds, the reaper will call +reap+
|
285
|
-
# A reaper instantiated with a
|
286
|
-
# connection pool.
|
292
|
+
# Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
|
293
|
+
# +pool+. A reaper instantiated with a zero frequency will never reap
|
294
|
+
# the connection pool.
|
287
295
|
#
|
288
|
-
# Configure the frequency by setting
|
289
|
-
#
|
296
|
+
# Configure the frequency by setting +reaping_frequency+ in your database
|
297
|
+
# yaml file (default 60 seconds).
|
290
298
|
class Reaper
|
291
299
|
attr_reader :pool, :frequency
|
292
300
|
|
@@ -295,52 +303,95 @@ module ActiveRecord
|
|
295
303
|
@frequency = frequency
|
296
304
|
end
|
297
305
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
306
|
+
@mutex = Mutex.new
|
307
|
+
@pools = {}
|
308
|
+
@threads = {}
|
309
|
+
|
310
|
+
class << self
|
311
|
+
def register_pool(pool, frequency) # :nodoc:
|
312
|
+
@mutex.synchronize do
|
313
|
+
unless @threads[frequency]&.alive?
|
314
|
+
@threads[frequency] = spawn_thread(frequency)
|
315
|
+
end
|
316
|
+
@pools[frequency] ||= []
|
317
|
+
@pools[frequency] << WeakRef.new(pool)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
private
|
322
|
+
def spawn_thread(frequency)
|
323
|
+
Thread.new(frequency) do |t|
|
324
|
+
# Advise multi-threaded app servers to ignore this thread for
|
325
|
+
# the purposes of fork safety warnings
|
326
|
+
Thread.current.thread_variable_set(:fork_safe, true)
|
327
|
+
running = true
|
328
|
+
while running
|
329
|
+
sleep t
|
330
|
+
@mutex.synchronize do
|
331
|
+
@pools[frequency].select! do |pool|
|
332
|
+
pool.weakref_alive? && !pool.discarded?
|
333
|
+
end
|
334
|
+
|
335
|
+
@pools[frequency].each do |p|
|
336
|
+
p.reap
|
337
|
+
p.flush
|
338
|
+
rescue WeakRef::RefError
|
339
|
+
end
|
340
|
+
|
341
|
+
if @pools[frequency].empty?
|
342
|
+
@pools.delete(frequency)
|
343
|
+
@threads.delete(frequency)
|
344
|
+
running = false
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
304
349
|
end
|
305
|
-
|
350
|
+
end
|
351
|
+
|
352
|
+
def run
|
353
|
+
return unless frequency && frequency > 0
|
354
|
+
self.class.register_pool(pool, frequency)
|
306
355
|
end
|
307
356
|
end
|
308
357
|
|
309
358
|
include MonitorMixin
|
310
359
|
include QueryCache::ConnectionPoolConfiguration
|
360
|
+
include ConnectionAdapters::AbstractPool
|
361
|
+
|
362
|
+
attr_accessor :automatic_reconnect, :checkout_timeout
|
363
|
+
attr_reader :db_config, :size, :reaper, :pool_config, :owner_name
|
311
364
|
|
312
|
-
|
313
|
-
attr_reader :spec, :connections, :size, :reaper
|
365
|
+
delegate :schema_cache, :schema_cache=, to: :pool_config
|
314
366
|
|
315
|
-
# Creates a new ConnectionPool object. +
|
367
|
+
# Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
|
316
368
|
# object which describes database connection information (e.g. adapter,
|
317
369
|
# host name, username, password, etc), as well as the maximum size for
|
318
370
|
# this ConnectionPool.
|
319
371
|
#
|
320
372
|
# The default ConnectionPool maximum size is 5.
|
321
|
-
def initialize(
|
373
|
+
def initialize(pool_config)
|
322
374
|
super()
|
323
375
|
|
324
|
-
@
|
376
|
+
@pool_config = pool_config
|
377
|
+
@db_config = pool_config.db_config
|
378
|
+
@owner_name = pool_config.connection_specification_name
|
325
379
|
|
326
|
-
@checkout_timeout =
|
327
|
-
@
|
328
|
-
@
|
329
|
-
|
330
|
-
# default max pool size to 5
|
331
|
-
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
|
380
|
+
@checkout_timeout = db_config.checkout_timeout
|
381
|
+
@idle_timeout = db_config.idle_timeout
|
382
|
+
@size = db_config.pool
|
332
383
|
|
333
|
-
#
|
334
|
-
#
|
335
|
-
# registry of which thread owns which connection
|
336
|
-
# +connection.owner+ attr on each +connection+ instance.
|
384
|
+
# This variable tracks the cache of threads mapped to reserved connections, with the
|
385
|
+
# sole purpose of speeding up the +connection+ method. It is not the authoritative
|
386
|
+
# registry of which thread owns which connection. Connection ownership is tracked by
|
387
|
+
# the +connection.owner+ attr on each +connection+ instance.
|
337
388
|
# The invariant works like this: if there is mapping of <tt>thread => conn</tt>,
|
338
|
-
# then that +thread+ does indeed own that +conn
|
339
|
-
# mapping does not mean that the +thread+ doesn't own the said connection
|
389
|
+
# then that +thread+ does indeed own that +conn+. However, an absence of such
|
390
|
+
# mapping does not mean that the +thread+ doesn't own the said connection. In
|
340
391
|
# that case +conn.owner+ attr should be consulted.
|
341
|
-
# Access and modification of
|
392
|
+
# Access and modification of <tt>@thread_cached_conns</tt> does not require
|
342
393
|
# synchronization.
|
343
|
-
@thread_cached_conns = Concurrent::Map.new(:
|
394
|
+
@thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
|
344
395
|
|
345
396
|
@connections = []
|
346
397
|
@automatic_reconnect = true
|
@@ -353,6 +404,19 @@ module ActiveRecord
|
|
353
404
|
@threads_blocking_new_connections = 0
|
354
405
|
|
355
406
|
@available = ConnectionLeasingQueue.new self
|
407
|
+
|
408
|
+
@lock_thread = false
|
409
|
+
|
410
|
+
@reaper = Reaper.new(self, db_config.reaping_frequency)
|
411
|
+
@reaper.run
|
412
|
+
end
|
413
|
+
|
414
|
+
def lock_thread=(lock_thread)
|
415
|
+
if lock_thread
|
416
|
+
@lock_thread = Thread.current
|
417
|
+
else
|
418
|
+
@lock_thread = nil
|
419
|
+
end
|
356
420
|
end
|
357
421
|
|
358
422
|
# Retrieve the connection associated with the current thread, or call
|
@@ -361,16 +425,16 @@ module ActiveRecord
|
|
361
425
|
# #connection can be called any number of times; the connection is
|
362
426
|
# held in a cache keyed by a thread.
|
363
427
|
def connection
|
364
|
-
@thread_cached_conns[connection_cache_key(
|
428
|
+
@thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
|
365
429
|
end
|
366
430
|
|
367
|
-
#
|
431
|
+
# Returns true if there is an open connection being used for the current thread.
|
368
432
|
#
|
369
433
|
# This method only works for connections that have been obtained through
|
370
|
-
# #connection or #with_connection methods
|
434
|
+
# #connection or #with_connection methods. Connections obtained through
|
371
435
|
# #checkout will not be detected by #active_connection?
|
372
436
|
def active_connection?
|
373
|
-
@thread_cached_conns[connection_cache_key(
|
437
|
+
@thread_cached_conns[connection_cache_key(current_thread)]
|
374
438
|
end
|
375
439
|
|
376
440
|
# Signal that the thread is finished with the current connection.
|
@@ -405,12 +469,27 @@ module ActiveRecord
|
|
405
469
|
synchronize { @connections.any? }
|
406
470
|
end
|
407
471
|
|
472
|
+
# Returns an array containing the connections currently in the pool.
|
473
|
+
# Access to the array does not require synchronization on the pool because
|
474
|
+
# the array is newly created and not retained by the pool.
|
475
|
+
#
|
476
|
+
# However; this method bypasses the ConnectionPool's thread-safe connection
|
477
|
+
# access pattern. A returned connection may be owned by another thread,
|
478
|
+
# unowned, or by happen-stance owned by the calling thread.
|
479
|
+
#
|
480
|
+
# Calling methods on a connection without ownership is subject to the
|
481
|
+
# thread-safety guarantees of the underlying method. Many of the methods
|
482
|
+
# on connection adapter classes are inherently multi-thread unsafe.
|
483
|
+
def connections
|
484
|
+
synchronize { @connections.dup }
|
485
|
+
end
|
486
|
+
|
408
487
|
# Disconnects all connections in the pool, and clears the pool.
|
409
488
|
#
|
410
489
|
# Raises:
|
411
490
|
# - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
|
412
491
|
# connections in the pool within a timeout interval (default duration is
|
413
|
-
# <tt>spec.
|
492
|
+
# <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
|
414
493
|
def disconnect(raise_on_acquisition_timeout = true)
|
415
494
|
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
|
416
495
|
synchronize do
|
@@ -429,21 +508,40 @@ module ActiveRecord
|
|
429
508
|
|
430
509
|
# Disconnects all connections in the pool, and clears the pool.
|
431
510
|
#
|
432
|
-
# The pool first tries to gain ownership of all connections
|
511
|
+
# The pool first tries to gain ownership of all connections. If unable to
|
433
512
|
# do so within a timeout interval (default duration is
|
434
|
-
# <tt>spec.
|
513
|
+
# <tt>spec.db_config.checkout_timeout * 2</tt> seconds), then the pool is forcefully
|
435
514
|
# disconnected without any regard for other connection owning threads.
|
436
515
|
def disconnect!
|
437
516
|
disconnect(false)
|
438
517
|
end
|
439
518
|
|
519
|
+
# Discards all connections in the pool (even if they're currently
|
520
|
+
# leased!), along with the pool itself. Any further interaction with the
|
521
|
+
# pool (except #spec and #schema_cache) is undefined.
|
522
|
+
#
|
523
|
+
# See AbstractAdapter#discard!
|
524
|
+
def discard! # :nodoc:
|
525
|
+
synchronize do
|
526
|
+
return if self.discarded?
|
527
|
+
@connections.each do |conn|
|
528
|
+
conn.discard!
|
529
|
+
end
|
530
|
+
@connections = @available = @thread_cached_conns = nil
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
def discarded? # :nodoc:
|
535
|
+
@connections.nil?
|
536
|
+
end
|
537
|
+
|
440
538
|
# Clears the cache which maps classes and re-connects connections that
|
441
539
|
# require reloading.
|
442
540
|
#
|
443
541
|
# Raises:
|
444
542
|
# - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
|
445
543
|
# connections in the pool within a timeout interval (default duration is
|
446
|
-
# <tt>spec.
|
544
|
+
# <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
|
447
545
|
def clear_reloadable_connections(raise_on_acquisition_timeout = true)
|
448
546
|
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
|
449
547
|
synchronize do
|
@@ -463,9 +561,9 @@ module ActiveRecord
|
|
463
561
|
# Clears the cache which maps classes and re-connects connections that
|
464
562
|
# require reloading.
|
465
563
|
#
|
466
|
-
# The pool first tries to gain ownership of all connections
|
564
|
+
# The pool first tries to gain ownership of all connections. If unable to
|
467
565
|
# do so within a timeout interval (default duration is
|
468
|
-
# <tt>spec.
|
566
|
+
# <tt>spec.db_config.checkout_timeout * 2</tt> seconds), then the pool forcefully
|
469
567
|
# clears the cache and reloads connections without any regard for other
|
470
568
|
# connection owning threads.
|
471
569
|
def clear_reloadable_connections!
|
@@ -496,14 +594,16 @@ module ActiveRecord
|
|
496
594
|
# +conn+: an AbstractAdapter object, which was obtained by earlier by
|
497
595
|
# calling #checkout on this pool.
|
498
596
|
def checkin(conn)
|
499
|
-
synchronize do
|
500
|
-
|
597
|
+
conn.lock.synchronize do
|
598
|
+
synchronize do
|
599
|
+
remove_connection_from_thread_cache conn
|
501
600
|
|
502
|
-
|
503
|
-
|
504
|
-
|
601
|
+
conn._run_checkin_callbacks do
|
602
|
+
conn.expire
|
603
|
+
end
|
505
604
|
|
506
|
-
|
605
|
+
@available.add conn
|
606
|
+
end
|
507
607
|
end
|
508
608
|
end
|
509
609
|
|
@@ -519,20 +619,20 @@ module ActiveRecord
|
|
519
619
|
@available.delete conn
|
520
620
|
|
521
621
|
# @available.any_waiting? => true means that prior to removing this
|
522
|
-
# conn, the pool was at its max size (@connections.size == @size)
|
523
|
-
#
|
622
|
+
# conn, the pool was at its max size (@connections.size == @size).
|
623
|
+
# This would mean that any threads stuck waiting in the queue wouldn't
|
524
624
|
# know they could checkout_new_connection, so let's do it for them.
|
525
625
|
# Because condition-wait loop is encapsulated in the Queue class
|
526
626
|
# (that in turn is oblivious to ConnectionPool implementation), threads
|
527
|
-
# that are "stuck" there are helpless
|
627
|
+
# that are "stuck" there are helpless. They have no way of creating
|
528
628
|
# new connections and are completely reliant on us feeding available
|
529
629
|
# connections into the Queue.
|
530
630
|
needs_new_connection = @available.any_waiting?
|
531
631
|
end
|
532
632
|
|
533
633
|
# This is intentionally done outside of the synchronized section as we
|
534
|
-
# would like not to hold the main mutex while checking out new connections
|
535
|
-
#
|
634
|
+
# would like not to hold the main mutex while checking out new connections.
|
635
|
+
# Thus there is some chance that needs_new_connection information is now
|
536
636
|
# stale, we can live with that (bulk_make_new_connections will make
|
537
637
|
# sure not to exceed the pool's @size limit).
|
538
638
|
bulk_make_new_connections(1) if needs_new_connection
|
@@ -543,6 +643,7 @@ module ActiveRecord
|
|
543
643
|
# or a thread dies unexpectedly.
|
544
644
|
def reap
|
545
645
|
stale_connections = synchronize do
|
646
|
+
return if self.discarded?
|
546
647
|
@connections.select do |conn|
|
547
648
|
conn.in_use? && !conn.owner.alive?
|
548
649
|
end.each do |conn|
|
@@ -560,229 +661,281 @@ module ActiveRecord
|
|
560
661
|
end
|
561
662
|
end
|
562
663
|
|
664
|
+
# Disconnect all connections that have been idle for at least
|
665
|
+
# +minimum_idle+ seconds. Connections currently checked out, or that were
|
666
|
+
# checked in less than +minimum_idle+ seconds ago, are unaffected.
|
667
|
+
def flush(minimum_idle = @idle_timeout)
|
668
|
+
return if minimum_idle.nil?
|
669
|
+
|
670
|
+
idle_connections = synchronize do
|
671
|
+
return if self.discarded?
|
672
|
+
@connections.select do |conn|
|
673
|
+
!conn.in_use? && conn.seconds_idle >= minimum_idle
|
674
|
+
end.each do |conn|
|
675
|
+
conn.lease
|
676
|
+
|
677
|
+
@available.delete conn
|
678
|
+
@connections.delete conn
|
679
|
+
end
|
680
|
+
end
|
681
|
+
|
682
|
+
idle_connections.each do |conn|
|
683
|
+
conn.disconnect!
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
# Disconnect all currently idle connections. Connections currently checked
|
688
|
+
# out are unaffected.
|
689
|
+
def flush!
|
690
|
+
reap
|
691
|
+
flush(-1)
|
692
|
+
end
|
693
|
+
|
563
694
|
def num_waiting_in_queue # :nodoc:
|
564
695
|
@available.num_waiting
|
565
696
|
end
|
566
697
|
|
567
|
-
|
568
|
-
|
569
|
-
#
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
# https://github.com/rails/rails/pull/14938#commitcomment-6601951
|
583
|
-
# This hook-in method allows for easier monkey-patching fixes needed by
|
584
|
-
# JRuby users that use Fibers.
|
585
|
-
def connection_cache_key(thread)
|
586
|
-
thread
|
587
|
-
end
|
588
|
-
|
589
|
-
# Take control of all existing connections so a "group" action such as
|
590
|
-
# reload/disconnect can be performed safely. It is no longer enough to
|
591
|
-
# wrap it in +synchronize+ because some pool's actions are allowed
|
592
|
-
# to be performed outside of the main +synchronize+ block.
|
593
|
-
def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
|
594
|
-
with_new_connections_blocked do
|
595
|
-
attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
|
596
|
-
yield
|
698
|
+
# Return connection pool's usage statistic
|
699
|
+
# Example:
|
700
|
+
#
|
701
|
+
# ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
|
702
|
+
def stat
|
703
|
+
synchronize do
|
704
|
+
{
|
705
|
+
size: size,
|
706
|
+
connections: @connections.size,
|
707
|
+
busy: @connections.count { |c| c.in_use? && c.owner.alive? },
|
708
|
+
dead: @connections.count { |c| c.in_use? && !c.owner.alive? },
|
709
|
+
idle: @connections.count { |c| !c.in_use? },
|
710
|
+
waiting: num_waiting_in_queue,
|
711
|
+
checkout_timeout: checkout_timeout
|
712
|
+
}
|
597
713
|
end
|
598
714
|
end
|
599
715
|
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
@available.with_a_bias_for(Thread.current) do
|
610
|
-
while true
|
611
|
-
synchronize do
|
612
|
-
return if collected_conns.size == @connections.size && @now_connecting == 0
|
613
|
-
remaining_timeout = timeout_time - Time.now
|
614
|
-
remaining_timeout = 0 if remaining_timeout < 0
|
615
|
-
conn = checkout_for_exclusive_access(remaining_timeout)
|
616
|
-
collected_conns << conn
|
617
|
-
newly_checked_out << conn
|
716
|
+
private
|
717
|
+
#--
|
718
|
+
# this is unfortunately not concurrent
|
719
|
+
def bulk_make_new_connections(num_new_conns_needed)
|
720
|
+
num_new_conns_needed.times do
|
721
|
+
# try_to_checkout_new_connection will not exceed pool's @size limit
|
722
|
+
if new_conn = try_to_checkout_new_connection
|
723
|
+
# make the new_conn available to the starving threads stuck @available Queue
|
724
|
+
checkin(new_conn)
|
618
725
|
end
|
619
726
|
end
|
620
727
|
end
|
621
|
-
rescue ExclusiveConnectionTimeoutError
|
622
|
-
# <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any
|
623
|
-
# timeouts and are expected to just give up: we've obtained as many connections
|
624
|
-
# as possible, note that in a case like that we don't return any of the
|
625
|
-
# +newly_checked_out+ connections.
|
626
728
|
|
627
|
-
|
628
|
-
|
629
|
-
|
729
|
+
#--
|
730
|
+
# From the discussion on GitHub:
|
731
|
+
# https://github.com/rails/rails/pull/14938#commitcomment-6601951
|
732
|
+
# This hook-in method allows for easier monkey-patching fixes needed by
|
733
|
+
# JRuby users that use Fibers.
|
734
|
+
def connection_cache_key(thread)
|
735
|
+
thread
|
630
736
|
end
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
release_newly_checked_out = true
|
635
|
-
raise
|
636
|
-
ensure
|
637
|
-
if release_newly_checked_out && newly_checked_out
|
638
|
-
# releasing only those conns that were checked out in this method, conns
|
639
|
-
# checked outside this method (before it was called) are not for us to release
|
640
|
-
newly_checked_out.each {|conn| checkin(conn)}
|
737
|
+
|
738
|
+
def current_thread
|
739
|
+
@lock_thread || Thread.current
|
641
740
|
end
|
642
|
-
end
|
643
741
|
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
742
|
+
# Take control of all existing connections so a "group" action such as
|
743
|
+
# reload/disconnect can be performed safely. It is no longer enough to
|
744
|
+
# wrap it in +synchronize+ because some pool's actions are allowed
|
745
|
+
# to be performed outside of the main +synchronize+ block.
|
746
|
+
def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
|
747
|
+
with_new_connections_blocked do
|
748
|
+
attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
|
749
|
+
yield
|
750
|
+
end
|
751
|
+
end
|
653
752
|
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
753
|
+
def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
|
754
|
+
collected_conns = synchronize do
|
755
|
+
# account for our own connections
|
756
|
+
@connections.select { |conn| conn.owner == Thread.current }
|
757
|
+
end
|
758
|
+
|
759
|
+
newly_checked_out = []
|
760
|
+
timeout_time = Concurrent.monotonic_time + (@checkout_timeout * 2)
|
761
|
+
|
762
|
+
@available.with_a_bias_for(Thread.current) do
|
763
|
+
loop do
|
764
|
+
synchronize do
|
765
|
+
return if collected_conns.size == @connections.size && @now_connecting == 0
|
766
|
+
remaining_timeout = timeout_time - Concurrent.monotonic_time
|
767
|
+
remaining_timeout = 0 if remaining_timeout < 0
|
768
|
+
conn = checkout_for_exclusive_access(remaining_timeout)
|
769
|
+
collected_conns << conn
|
770
|
+
newly_checked_out << conn
|
771
|
+
end
|
772
|
+
end
|
773
|
+
end
|
774
|
+
rescue ExclusiveConnectionTimeoutError
|
775
|
+
# <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any
|
776
|
+
# timeouts and are expected to just give up: we've obtained as many connections
|
777
|
+
# as possible, note that in a case like that we don't return any of the
|
778
|
+
# +newly_checked_out+ connections.
|
779
|
+
|
780
|
+
if raise_on_acquisition_timeout
|
781
|
+
release_newly_checked_out = true
|
782
|
+
raise
|
783
|
+
end
|
784
|
+
rescue Exception # if something else went wrong
|
785
|
+
# this can't be a "naked" rescue, because we have should return conns
|
786
|
+
# even for non-StandardErrors
|
787
|
+
release_newly_checked_out = true
|
788
|
+
raise
|
789
|
+
ensure
|
790
|
+
if release_newly_checked_out && newly_checked_out
|
791
|
+
# releasing only those conns that were checked out in this method, conns
|
792
|
+
# checked outside this method (before it was called) are not for us to release
|
793
|
+
newly_checked_out.each { |conn| checkin(conn) }
|
658
794
|
end
|
659
795
|
end
|
660
796
|
|
661
|
-
|
797
|
+
#--
|
798
|
+
# Must be called in a synchronize block.
|
799
|
+
def checkout_for_exclusive_access(checkout_timeout)
|
800
|
+
checkout(checkout_timeout)
|
801
|
+
rescue ConnectionTimeoutError
|
802
|
+
# this block can't be easily moved into attempt_to_checkout_all_existing_connections's
|
803
|
+
# rescue block, because doing so would put it outside of synchronize section, without
|
804
|
+
# being in a critical section thread_report might become inaccurate
|
805
|
+
msg = +"could not obtain ownership of all database connections in #{checkout_timeout} seconds"
|
806
|
+
|
807
|
+
thread_report = []
|
808
|
+
@connections.each do |conn|
|
809
|
+
unless conn.owner == Thread.current
|
810
|
+
thread_report << "#{conn} is owned by #{conn.owner}"
|
811
|
+
end
|
812
|
+
end
|
662
813
|
|
663
|
-
|
664
|
-
end
|
814
|
+
msg << " (#{thread_report.join(', ')})" if thread_report.any?
|
665
815
|
|
666
|
-
|
667
|
-
synchronize do
|
668
|
-
@threads_blocking_new_connections += 1
|
816
|
+
raise ExclusiveConnectionTimeoutError, msg
|
669
817
|
end
|
670
818
|
|
671
|
-
|
672
|
-
|
673
|
-
|
819
|
+
def with_new_connections_blocked
|
820
|
+
synchronize do
|
821
|
+
@threads_blocking_new_connections += 1
|
822
|
+
end
|
674
823
|
|
675
|
-
|
676
|
-
|
824
|
+
yield
|
825
|
+
ensure
|
826
|
+
num_new_conns_required = 0
|
677
827
|
|
678
|
-
|
679
|
-
@
|
828
|
+
synchronize do
|
829
|
+
@threads_blocking_new_connections -= 1
|
680
830
|
|
681
|
-
|
831
|
+
if @threads_blocking_new_connections.zero?
|
832
|
+
@available.clear
|
682
833
|
|
683
|
-
|
684
|
-
next if conn.in_use?
|
834
|
+
num_new_conns_required = num_waiting_in_queue
|
685
835
|
|
686
|
-
@
|
687
|
-
|
836
|
+
@connections.each do |conn|
|
837
|
+
next if conn.in_use?
|
838
|
+
|
839
|
+
@available.add conn
|
840
|
+
num_new_conns_required -= 1
|
841
|
+
end
|
688
842
|
end
|
689
843
|
end
|
690
|
-
end
|
691
844
|
|
692
|
-
|
693
|
-
end
|
694
|
-
|
695
|
-
# Acquire a connection by one of 1) immediately removing one
|
696
|
-
# from the queue of available connections, 2) creating a new
|
697
|
-
# connection if the pool is not at capacity, 3) waiting on the
|
698
|
-
# queue for a connection to become available.
|
699
|
-
#
|
700
|
-
# Raises:
|
701
|
-
# - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired
|
702
|
-
#
|
703
|
-
#--
|
704
|
-
# Implementation detail: the connection returned by +acquire_connection+
|
705
|
-
# will already be "+connection.lease+ -ed" to the current thread.
|
706
|
-
def acquire_connection(checkout_timeout)
|
707
|
-
# NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to
|
708
|
-
# +conn.lease+ the returned connection (and to do this in a +synchronized+
|
709
|
-
# section), this is not the cleanest implementation, as ideally we would
|
710
|
-
# <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+
|
711
|
-
# and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
|
712
|
-
# of the said methods and avoid an additional +synchronize+ overhead.
|
713
|
-
if conn = @available.poll || try_to_checkout_new_connection
|
714
|
-
conn
|
715
|
-
else
|
716
|
-
reap
|
717
|
-
@available.poll(checkout_timeout)
|
845
|
+
bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
|
718
846
|
end
|
719
|
-
end
|
720
847
|
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
848
|
+
# Acquire a connection by one of 1) immediately removing one
|
849
|
+
# from the queue of available connections, 2) creating a new
|
850
|
+
# connection if the pool is not at capacity, 3) waiting on the
|
851
|
+
# queue for a connection to become available.
|
852
|
+
#
|
853
|
+
# Raises:
|
854
|
+
# - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired
|
855
|
+
#
|
856
|
+
#--
|
857
|
+
# Implementation detail: the connection returned by +acquire_connection+
|
858
|
+
# will already be "+connection.lease+ -ed" to the current thread.
|
859
|
+
def acquire_connection(checkout_timeout)
|
860
|
+
# NOTE: we rely on <tt>@available.poll</tt> and +try_to_checkout_new_connection+ to
|
861
|
+
# +conn.lease+ the returned connection (and to do this in a +synchronized+
|
862
|
+
# section). This is not the cleanest implementation, as ideally we would
|
863
|
+
# <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to <tt>@available.poll</tt>
|
864
|
+
# and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
|
865
|
+
# of the said methods and avoid an additional +synchronize+ overhead.
|
866
|
+
if conn = @available.poll || try_to_checkout_new_connection
|
867
|
+
conn
|
868
|
+
else
|
869
|
+
reap
|
870
|
+
@available.poll(checkout_timeout)
|
871
|
+
end
|
872
|
+
end
|
727
873
|
|
728
|
-
|
729
|
-
|
730
|
-
|
874
|
+
#--
|
875
|
+
# if owner_thread param is omitted, this must be called in synchronize block
|
876
|
+
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
|
877
|
+
@thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
|
731
878
|
end
|
732
|
-
|
879
|
+
alias_method :release, :remove_connection_from_thread_cache
|
733
880
|
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
# Implementation constraint: a newly established connection returned by this
|
738
|
-
# method must be in the +.leased+ state.
|
739
|
-
def try_to_checkout_new_connection
|
740
|
-
# first in synchronized section check if establishing new conns is allowed
|
741
|
-
# and increment @now_connecting, to prevent overstepping this pool's @size
|
742
|
-
# constraint
|
743
|
-
do_checkout = synchronize do
|
744
|
-
if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
|
745
|
-
@now_connecting += 1
|
881
|
+
def new_connection
|
882
|
+
Base.public_send(db_config.adapter_method, db_config.configuration_hash).tap do |conn|
|
883
|
+
conn.check_version
|
746
884
|
end
|
747
885
|
end
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
886
|
+
|
887
|
+
# If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
|
888
|
+
# to the DB is done outside main synchronized section.
|
889
|
+
#--
|
890
|
+
# Implementation constraint: a newly established connection returned by this
|
891
|
+
# method must be in the +.leased+ state.
|
892
|
+
def try_to_checkout_new_connection
|
893
|
+
# first in synchronized section check if establishing new conns is allowed
|
894
|
+
# and increment @now_connecting, to prevent overstepping this pool's @size
|
895
|
+
# constraint
|
896
|
+
do_checkout = synchronize do
|
897
|
+
if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
|
898
|
+
@now_connecting += 1
|
899
|
+
end
|
900
|
+
end
|
901
|
+
if do_checkout
|
902
|
+
begin
|
903
|
+
# if successfully incremented @now_connecting establish new connection
|
904
|
+
# outside of synchronized section
|
905
|
+
conn = checkout_new_connection
|
906
|
+
ensure
|
907
|
+
synchronize do
|
908
|
+
if conn
|
909
|
+
adopt_connection(conn)
|
910
|
+
# returned conn needs to be already leased
|
911
|
+
conn.lease
|
912
|
+
end
|
913
|
+
@now_connecting -= 1
|
759
914
|
end
|
760
|
-
@now_connecting -= 1
|
761
915
|
end
|
762
916
|
end
|
763
917
|
end
|
764
|
-
end
|
765
918
|
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
919
|
+
def adopt_connection(conn)
|
920
|
+
conn.pool = self
|
921
|
+
@connections << conn
|
922
|
+
end
|
770
923
|
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
924
|
+
def checkout_new_connection
|
925
|
+
raise ConnectionNotEstablished unless @automatic_reconnect
|
926
|
+
new_connection
|
927
|
+
end
|
775
928
|
|
776
|
-
|
777
|
-
|
778
|
-
|
929
|
+
def checkout_and_verify(c)
|
930
|
+
c._run_checkout_callbacks do
|
931
|
+
c.verify!
|
932
|
+
end
|
933
|
+
c
|
934
|
+
rescue
|
935
|
+
remove c
|
936
|
+
c.disconnect!
|
937
|
+
raise
|
779
938
|
end
|
780
|
-
c
|
781
|
-
rescue
|
782
|
-
remove c
|
783
|
-
c.disconnect!
|
784
|
-
raise
|
785
|
-
end
|
786
939
|
end
|
787
940
|
|
788
941
|
# ConnectionHandler is a collection of ConnectionPool objects. It is used
|
@@ -797,7 +950,7 @@ module ActiveRecord
|
|
797
950
|
# end
|
798
951
|
#
|
799
952
|
# class Book < ActiveRecord::Base
|
800
|
-
# establish_connection
|
953
|
+
# establish_connection :library_db
|
801
954
|
# end
|
802
955
|
#
|
803
956
|
# class ScaryBook < Book
|
@@ -829,115 +982,248 @@ module ActiveRecord
|
|
829
982
|
# All Active Record models use this handler to determine the connection pool that they
|
830
983
|
# should use.
|
831
984
|
#
|
832
|
-
# The ConnectionHandler class is not coupled with the Active models, as it has no
|
833
|
-
# about the model. The model
|
834
|
-
# in order to
|
985
|
+
# The ConnectionHandler class is not coupled with the Active models, as it has no knowledge
|
986
|
+
# about the model. The model needs to pass a connection specification name to the handler,
|
987
|
+
# in order to look up the correct connection pool.
|
835
988
|
class ConnectionHandler
|
989
|
+
FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
|
990
|
+
private_constant :FINALIZER
|
991
|
+
|
836
992
|
def initialize
|
837
|
-
# These caches are keyed by
|
838
|
-
@
|
839
|
-
|
993
|
+
# These caches are keyed by pool_config.connection_specification_name (PoolConfig#connection_specification_name).
|
994
|
+
@owner_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
|
995
|
+
|
996
|
+
# Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred
|
997
|
+
ObjectSpace.define_finalizer self, FINALIZER
|
998
|
+
end
|
999
|
+
|
1000
|
+
def prevent_writes # :nodoc:
|
1001
|
+
Thread.current[:prevent_writes]
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
def prevent_writes=(prevent_writes) # :nodoc:
|
1005
|
+
Thread.current[:prevent_writes] = prevent_writes
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
# Prevent writing to the database regardless of role.
|
1009
|
+
#
|
1010
|
+
# In some cases you may want to prevent writes to the database
|
1011
|
+
# even if you are on a database that can write. `while_preventing_writes`
|
1012
|
+
# will prevent writes to the database for the duration of the block.
|
1013
|
+
#
|
1014
|
+
# This method does not provide the same protection as a readonly
|
1015
|
+
# user and is meant to be a safeguard against accidental writes.
|
1016
|
+
#
|
1017
|
+
# See `READ_QUERY` for the queries that are blocked by this
|
1018
|
+
# method.
|
1019
|
+
def while_preventing_writes(enabled = true)
|
1020
|
+
unless ActiveRecord::Base.legacy_connection_handling
|
1021
|
+
raise NotImplementedError, "`while_preventing_writes` is only available on the connection_handler with legacy_connection_handling"
|
840
1022
|
end
|
1023
|
+
|
1024
|
+
original, self.prevent_writes = self.prevent_writes, enabled
|
1025
|
+
yield
|
1026
|
+
ensure
|
1027
|
+
self.prevent_writes = original
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
def connection_pool_names # :nodoc:
|
1031
|
+
owner_to_pool_manager.keys
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
def all_connection_pools
|
1035
|
+
owner_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
|
841
1036
|
end
|
842
1037
|
|
843
|
-
def connection_pool_list
|
844
|
-
|
1038
|
+
def connection_pool_list(role = ActiveRecord::Base.current_role)
|
1039
|
+
owner_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
|
845
1040
|
end
|
846
1041
|
alias :connection_pools :connection_pool_list
|
847
1042
|
|
848
|
-
def establish_connection(
|
849
|
-
|
1043
|
+
def establish_connection(config, owner_name: Base.name, role: ActiveRecord::Base.current_role, shard: Base.current_shard)
|
1044
|
+
owner_name = config.to_s if config.is_a?(Symbol)
|
1045
|
+
|
1046
|
+
pool_config = resolve_pool_config(config, owner_name)
|
1047
|
+
db_config = pool_config.db_config
|
1048
|
+
|
1049
|
+
# Protects the connection named `ActiveRecord::Base` from being removed
|
1050
|
+
# if the user calls `establish_connection :primary`.
|
1051
|
+
if owner_to_pool_manager.key?(pool_config.connection_specification_name)
|
1052
|
+
remove_connection_pool(pool_config.connection_specification_name, role: role, shard: shard)
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
message_bus = ActiveSupport::Notifications.instrumenter
|
1056
|
+
payload = {}
|
1057
|
+
if pool_config
|
1058
|
+
payload[:spec_name] = pool_config.connection_specification_name
|
1059
|
+
payload[:shard] = shard
|
1060
|
+
payload[:config] = db_config.configuration_hash
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
if ActiveRecord::Base.legacy_connection_handling
|
1064
|
+
owner_to_pool_manager[pool_config.connection_specification_name] ||= LegacyPoolManager.new
|
1065
|
+
else
|
1066
|
+
owner_to_pool_manager[pool_config.connection_specification_name] ||= PoolManager.new
|
1067
|
+
end
|
1068
|
+
pool_manager = get_pool_manager(pool_config.connection_specification_name)
|
1069
|
+
pool_manager.set_pool_config(role, shard, pool_config)
|
1070
|
+
|
1071
|
+
message_bus.instrument("!connection.active_record", payload) do
|
1072
|
+
pool_config.pool
|
1073
|
+
end
|
850
1074
|
end
|
851
1075
|
|
852
1076
|
# Returns true if there are any active connections among the connection
|
853
1077
|
# pools that the ConnectionHandler is managing.
|
854
|
-
def active_connections?
|
855
|
-
connection_pool_list.any?(&:active_connection?)
|
1078
|
+
def active_connections?(role = ActiveRecord::Base.current_role)
|
1079
|
+
connection_pool_list(role).any?(&:active_connection?)
|
856
1080
|
end
|
857
1081
|
|
858
1082
|
# Returns any connections in use by the current thread back to the pool,
|
859
1083
|
# and also returns connections to the pool cached by threads that are no
|
860
1084
|
# longer alive.
|
861
|
-
def clear_active_connections!
|
862
|
-
connection_pool_list.each(&:release_connection)
|
1085
|
+
def clear_active_connections!(role = ActiveRecord::Base.current_role)
|
1086
|
+
connection_pool_list(role).each(&:release_connection)
|
863
1087
|
end
|
864
1088
|
|
865
1089
|
# Clears the cache which maps classes.
|
866
1090
|
#
|
867
1091
|
# See ConnectionPool#clear_reloadable_connections! for details.
|
868
|
-
def clear_reloadable_connections!
|
869
|
-
connection_pool_list.each(&:clear_reloadable_connections!)
|
1092
|
+
def clear_reloadable_connections!(role = ActiveRecord::Base.current_role)
|
1093
|
+
connection_pool_list(role).each(&:clear_reloadable_connections!)
|
1094
|
+
end
|
1095
|
+
|
1096
|
+
def clear_all_connections!(role = ActiveRecord::Base.current_role)
|
1097
|
+
connection_pool_list(role).each(&:disconnect!)
|
870
1098
|
end
|
871
1099
|
|
872
|
-
|
873
|
-
|
1100
|
+
# Disconnects all currently idle connections.
|
1101
|
+
#
|
1102
|
+
# See ConnectionPool#flush! for details.
|
1103
|
+
def flush_idle_connections!(role = ActiveRecord::Base.current_role)
|
1104
|
+
connection_pool_list(role).each(&:flush!)
|
874
1105
|
end
|
875
1106
|
|
876
1107
|
# Locate the connection of the nearest super class. This can be an
|
877
1108
|
# active or defined connection: if it is the latter, it will be
|
878
1109
|
# opened and set as the active connection for the class it was defined
|
879
1110
|
# for (not necessarily the current class).
|
880
|
-
def retrieve_connection(spec_name)
|
881
|
-
pool = retrieve_connection_pool(spec_name)
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
1111
|
+
def retrieve_connection(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
|
1112
|
+
pool = retrieve_connection_pool(spec_name, role: role, shard: shard)
|
1113
|
+
|
1114
|
+
unless pool
|
1115
|
+
if shard != ActiveRecord::Base.default_shard
|
1116
|
+
message = "No connection pool for '#{spec_name}' found for the '#{shard}' shard."
|
1117
|
+
elsif ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler
|
1118
|
+
message = "No connection pool for '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role."
|
1119
|
+
elsif role != ActiveRecord::Base.default_role
|
1120
|
+
message = "No connection pool for '#{spec_name}' found for the '#{role}' role."
|
1121
|
+
else
|
1122
|
+
message = "No connection pool for '#{spec_name}' found."
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
raise ConnectionNotEstablished, message
|
1126
|
+
end
|
1127
|
+
|
1128
|
+
pool.connection
|
886
1129
|
end
|
887
1130
|
|
888
1131
|
# Returns true if a connection that's accessible to this class has
|
889
1132
|
# already been opened.
|
890
|
-
def connected?(spec_name)
|
891
|
-
|
892
|
-
|
1133
|
+
def connected?(spec_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
|
1134
|
+
pool = retrieve_connection_pool(spec_name, role: role, shard: shard)
|
1135
|
+
pool && pool.connected?
|
893
1136
|
end
|
894
1137
|
|
895
1138
|
# Remove the connection for this class. This will close the active
|
896
1139
|
# connection and the defined connection (if they exist). The result
|
897
|
-
# can be used as an argument for establish_connection, for easily
|
1140
|
+
# can be used as an argument for #establish_connection, for easily
|
898
1141
|
# re-establishing the connection.
|
899
|
-
def remove_connection(
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
1142
|
+
def remove_connection(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
|
1143
|
+
remove_connection_pool(owner, role: role, shard: shard)&.configuration_hash
|
1144
|
+
end
|
1145
|
+
deprecate remove_connection: "Use #remove_connection_pool, which now returns a DatabaseConfig object instead of a Hash"
|
1146
|
+
|
1147
|
+
def remove_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
|
1148
|
+
if pool_manager = get_pool_manager(owner)
|
1149
|
+
pool_config = pool_manager.remove_pool_config(role, shard)
|
1150
|
+
|
1151
|
+
if pool_config
|
1152
|
+
pool_config.disconnect!
|
1153
|
+
pool_config.db_config
|
1154
|
+
end
|
904
1155
|
end
|
905
1156
|
end
|
906
1157
|
|
907
|
-
# Retrieving the connection pool happens a lot so we cache it in @
|
1158
|
+
# Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool_manager.
|
908
1159
|
# This makes retrieving the connection pool O(1) once the process is warm.
|
909
1160
|
# When a connection is established or removed, we invalidate the cache.
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
# #fetch is significantly slower than #[]. So in the nil case, no caching will
|
914
|
-
# take place, but that's ok since the nil case is not the common one that we wish
|
915
|
-
# to optimise for.
|
916
|
-
def retrieve_connection_pool(spec_name)
|
917
|
-
owner_to_pool.fetch(spec_name) do
|
918
|
-
if ancestor_pool = pool_from_any_process_for(spec_name)
|
919
|
-
# A connection was established in an ancestor process that must have
|
920
|
-
# subsequently forked. We can't reuse the connection, but we can copy
|
921
|
-
# the specification and establish a new connection with it.
|
922
|
-
establish_connection(ancestor_pool.spec).tap do |pool|
|
923
|
-
pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
|
924
|
-
end
|
925
|
-
else
|
926
|
-
owner_to_pool[spec_name] = nil
|
927
|
-
end
|
928
|
-
end
|
1161
|
+
def retrieve_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
|
1162
|
+
pool_config = get_pool_manager(owner)&.get_pool_config(role, shard)
|
1163
|
+
pool_config&.pool
|
929
1164
|
end
|
930
1165
|
|
931
1166
|
private
|
1167
|
+
attr_reader :owner_to_pool_manager
|
932
1168
|
|
933
|
-
|
934
|
-
|
935
|
-
|
1169
|
+
# Returns the pool manager for an owner.
|
1170
|
+
#
|
1171
|
+
# Using `"primary"` to look up the pool manager for `ActiveRecord::Base` is
|
1172
|
+
# deprecated in favor of looking it up by `"ActiveRecord::Base"`.
|
1173
|
+
#
|
1174
|
+
# During the deprecation period, if `"primary"` is passed, the pool manager
|
1175
|
+
# for `ActiveRecord::Base` will still be returned.
|
1176
|
+
def get_pool_manager(owner)
|
1177
|
+
return owner_to_pool_manager[owner] if owner_to_pool_manager.key?(owner)
|
1178
|
+
|
1179
|
+
if owner == "primary"
|
1180
|
+
ActiveSupport::Deprecation.warn("Using `\"primary\"` as a `connection_specification_name` is deprecated and will be removed in Rails 6.2.0. Please use `ActiveRecord::Base`.")
|
1181
|
+
owner_to_pool_manager[Base.name]
|
1182
|
+
end
|
1183
|
+
end
|
936
1184
|
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
1185
|
+
# Returns an instance of PoolConfig for a given adapter.
|
1186
|
+
# Accepts a hash one layer deep that contains all connection information.
|
1187
|
+
#
|
1188
|
+
# == Example
|
1189
|
+
#
|
1190
|
+
# config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
|
1191
|
+
# pool_config = Base.configurations.resolve_pool_config(:production)
|
1192
|
+
# pool_config.db_config.configuration_hash
|
1193
|
+
# # => { host: "localhost", database: "foo", adapter: "sqlite3" }
|
1194
|
+
#
|
1195
|
+
def resolve_pool_config(config, owner_name)
|
1196
|
+
db_config = Base.configurations.resolve(config)
|
1197
|
+
|
1198
|
+
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
|
1199
|
+
|
1200
|
+
# Require the adapter itself and give useful feedback about
|
1201
|
+
# 1. Missing adapter gems and
|
1202
|
+
# 2. Adapter gems' missing dependencies.
|
1203
|
+
path_to_adapter = "active_record/connection_adapters/#{db_config.adapter}_adapter"
|
1204
|
+
begin
|
1205
|
+
require path_to_adapter
|
1206
|
+
rescue LoadError => e
|
1207
|
+
# We couldn't require the adapter itself. Raise an exception that
|
1208
|
+
# points out config typos and missing gems.
|
1209
|
+
if e.path == path_to_adapter
|
1210
|
+
# We can assume that a non-builtin adapter was specified, so it's
|
1211
|
+
# either misspelled or missing from Gemfile.
|
1212
|
+
raise LoadError, "Could not load the '#{db_config.adapter}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
|
1213
|
+
|
1214
|
+
# Bubbled up from the adapter require. Prefix the exception message
|
1215
|
+
# with some guidance about how to address it and reraise.
|
1216
|
+
else
|
1217
|
+
raise LoadError, "Error loading the '#{db_config.adapter}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
|
1218
|
+
end
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
unless ActiveRecord::Base.respond_to?(db_config.adapter_method)
|
1222
|
+
raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
|
1223
|
+
end
|
1224
|
+
|
1225
|
+
ConnectionAdapters::PoolConfig.new(owner_name, db_config)
|
1226
|
+
end
|
941
1227
|
end
|
942
1228
|
end
|
943
1229
|
end
|