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,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module Locking
|
3
5
|
# Locking::Pessimistic provides support for row-level locking using
|
@@ -12,9 +14,9 @@ module ActiveRecord
|
|
12
14
|
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
|
13
15
|
#
|
14
16
|
# Account.transaction do
|
15
|
-
# # select * from accounts where name = 'shugo' limit 1 for update
|
16
|
-
# shugo = Account.
|
17
|
-
# yuko = Account.
|
17
|
+
# # select * from accounts where name = 'shugo' limit 1 for update nowait
|
18
|
+
# shugo = Account.lock("FOR UPDATE NOWAIT").find_by(name: "shugo")
|
19
|
+
# yuko = Account.lock("FOR UPDATE NOWAIT").find_by(name: "yuko")
|
18
20
|
# shugo.balance -= 100
|
19
21
|
# shugo.save!
|
20
22
|
# yuko.balance += 100
|
@@ -51,15 +53,29 @@ module ActiveRecord
|
|
51
53
|
# end
|
52
54
|
#
|
53
55
|
# Database-specific information on row locking:
|
54
|
-
#
|
55
|
-
#
|
56
|
+
#
|
57
|
+
# [MySQL]
|
58
|
+
# https://dev.mysql.com/doc/refman/en/innodb-locking-reads.html
|
59
|
+
#
|
60
|
+
# [PostgreSQL]
|
61
|
+
# https://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
|
56
62
|
module Pessimistic
|
57
63
|
# Obtain a row lock on this record. Reloads the record to obtain the requested
|
58
64
|
# lock. Pass an SQL locking clause to append the end of the SELECT statement
|
59
65
|
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
|
60
66
|
# the locked record.
|
61
67
|
def lock!(lock = true)
|
62
|
-
|
68
|
+
if persisted?
|
69
|
+
if has_changes_to_save?
|
70
|
+
raise(<<-MSG.squish)
|
71
|
+
Locking a record with unpersisted changes is not supported. Use
|
72
|
+
`save` to persist the changes, or `reload` to discard them
|
73
|
+
explicitly.
|
74
|
+
MSG
|
75
|
+
end
|
76
|
+
|
77
|
+
reload(lock: lock)
|
78
|
+
end
|
63
79
|
self
|
64
80
|
end
|
65
81
|
|
@@ -1,7 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
class LogSubscriber < ActiveSupport::LogSubscriber
|
3
5
|
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
|
4
6
|
|
7
|
+
class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
|
8
|
+
|
5
9
|
def self.runtime=(value)
|
6
10
|
ActiveRecord::RuntimeRegistry.sql_runtime = value
|
7
11
|
end
|
@@ -15,9 +19,13 @@ module ActiveRecord
|
|
15
19
|
rt
|
16
20
|
end
|
17
21
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
22
|
+
def strict_loading_violation(event)
|
23
|
+
debug do
|
24
|
+
owner = event.payload[:owner]
|
25
|
+
association = event.payload[:association]
|
26
|
+
|
27
|
+
color("Strict loading violation: #{association} lazily loaded on #{owner}.", RED)
|
28
|
+
end
|
21
29
|
end
|
22
30
|
|
23
31
|
def sql(event)
|
@@ -29,48 +37,57 @@ module ActiveRecord
|
|
29
37
|
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
30
38
|
|
31
39
|
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
|
40
|
+
name = "CACHE #{name}" if payload[:cached]
|
32
41
|
sql = payload[:sql]
|
33
42
|
binds = nil
|
34
43
|
|
35
|
-
|
44
|
+
if payload[:binds]&.any?
|
36
45
|
casted_params = type_casted_binds(payload[:type_casted_binds])
|
37
|
-
|
38
|
-
|
39
|
-
|
46
|
+
|
47
|
+
binds = []
|
48
|
+
payload[:binds].each_with_index do |attr, i|
|
49
|
+
binds << render_bind(attr, casted_params[i])
|
50
|
+
end
|
51
|
+
binds = binds.inspect
|
52
|
+
binds.prepend(" ")
|
40
53
|
end
|
41
54
|
|
42
55
|
name = colorize_payload_name(name, payload[:name])
|
43
|
-
sql = color(sql, sql_color(sql), true)
|
56
|
+
sql = color(sql, sql_color(sql), true) if colorize_logging
|
44
57
|
|
45
58
|
debug " #{name} #{sql}#{binds}"
|
46
59
|
end
|
47
60
|
|
48
61
|
private
|
62
|
+
def type_casted_binds(casted_binds)
|
63
|
+
casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
|
64
|
+
end
|
49
65
|
|
50
|
-
|
51
|
-
|
52
|
-
|
66
|
+
def render_bind(attr, value)
|
67
|
+
case attr
|
68
|
+
when ActiveModel::Attribute
|
69
|
+
if attr.type.binary? && attr.value
|
70
|
+
value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
|
71
|
+
end
|
72
|
+
when Array
|
73
|
+
attr = attr.first
|
74
|
+
else
|
75
|
+
attr = nil
|
76
|
+
end
|
53
77
|
|
54
|
-
|
55
|
-
if attr.is_a?(Array)
|
56
|
-
attr = attr.first
|
57
|
-
elsif attr.type.binary? && attr.value
|
58
|
-
value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
|
78
|
+
[attr&.name, value]
|
59
79
|
end
|
60
80
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
else
|
68
|
-
color(name, CYAN, true)
|
81
|
+
def colorize_payload_name(name, payload_name)
|
82
|
+
if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists
|
83
|
+
color(name, MAGENTA, true)
|
84
|
+
else
|
85
|
+
color(name, CYAN, true)
|
86
|
+
end
|
69
87
|
end
|
70
|
-
end
|
71
88
|
|
72
|
-
|
73
|
-
|
89
|
+
def sql_color(sql)
|
90
|
+
case sql
|
74
91
|
when /\A\s*rollback/mi
|
75
92
|
RED
|
76
93
|
when /select .*for update/mi, /\A\s*lock/mi
|
@@ -87,12 +104,32 @@ module ActiveRecord
|
|
87
104
|
CYAN
|
88
105
|
else
|
89
106
|
MAGENTA
|
107
|
+
end
|
90
108
|
end
|
91
|
-
end
|
92
109
|
|
93
|
-
|
94
|
-
|
95
|
-
|
110
|
+
def logger
|
111
|
+
ActiveRecord::Base.logger
|
112
|
+
end
|
113
|
+
|
114
|
+
def debug(progname = nil, &block)
|
115
|
+
return unless super
|
116
|
+
|
117
|
+
if ActiveRecord::Base.verbose_query_logs
|
118
|
+
log_query_source
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def log_query_source
|
123
|
+
source = extract_query_source_location(caller)
|
124
|
+
|
125
|
+
if source
|
126
|
+
logger.debug(" ↳ #{source}")
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def extract_query_source_location(locations)
|
131
|
+
backtrace_cleaner.clean(locations.lazy).first
|
132
|
+
end
|
96
133
|
end
|
97
134
|
end
|
98
135
|
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record/middleware/database_selector/resolver"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Middleware
|
7
|
+
# The DatabaseSelector Middleware provides a framework for automatically
|
8
|
+
# swapping from the primary to the replica database connection. Rails
|
9
|
+
# provides a basic framework to determine when to swap and allows for
|
10
|
+
# applications to write custom strategy classes to override the default
|
11
|
+
# behavior.
|
12
|
+
#
|
13
|
+
# The resolver class defines when the application should switch (i.e. read
|
14
|
+
# from the primary if a write occurred less than 2 seconds ago) and a
|
15
|
+
# resolver context class that sets a value that helps the resolver class
|
16
|
+
# decide when to switch.
|
17
|
+
#
|
18
|
+
# Rails default middleware uses the request's session to set a timestamp
|
19
|
+
# that informs the application when to read from a primary or read from a
|
20
|
+
# replica.
|
21
|
+
#
|
22
|
+
# To use the DatabaseSelector in your application with default settings add
|
23
|
+
# the following options to your environment config:
|
24
|
+
#
|
25
|
+
# config.active_record.database_selector = { delay: 2.seconds }
|
26
|
+
# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
|
27
|
+
# config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
|
28
|
+
#
|
29
|
+
# New applications will include these lines commented out in the production.rb.
|
30
|
+
#
|
31
|
+
# The default behavior can be changed by setting the config options to a
|
32
|
+
# custom class:
|
33
|
+
#
|
34
|
+
# config.active_record.database_selector = { delay: 2.seconds }
|
35
|
+
# config.active_record.database_resolver = MyResolver
|
36
|
+
# config.active_record.database_resolver_context = MyResolver::MySession
|
37
|
+
class DatabaseSelector
|
38
|
+
def initialize(app, resolver_klass = nil, context_klass = nil, options = {})
|
39
|
+
@app = app
|
40
|
+
@resolver_klass = resolver_klass || Resolver
|
41
|
+
@context_klass = context_klass || Resolver::Session
|
42
|
+
@options = options
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :resolver_klass, :context_klass, :options
|
46
|
+
|
47
|
+
# Middleware that determines which database connection to use in a multiple
|
48
|
+
# database application.
|
49
|
+
def call(env)
|
50
|
+
request = ActionDispatch::Request.new(env)
|
51
|
+
|
52
|
+
select_database(request) do
|
53
|
+
@app.call(env)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def select_database(request, &blk)
|
59
|
+
context = context_klass.call(request)
|
60
|
+
resolver = resolver_klass.call(context, options)
|
61
|
+
|
62
|
+
response = if reading_request?(request)
|
63
|
+
resolver.read(&blk)
|
64
|
+
else
|
65
|
+
resolver.write(&blk)
|
66
|
+
end
|
67
|
+
|
68
|
+
resolver.update_context(response)
|
69
|
+
response
|
70
|
+
end
|
71
|
+
|
72
|
+
def reading_request?(request)
|
73
|
+
request.get? || request.head?
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record/middleware/database_selector/resolver/session"
|
4
|
+
require "active_support/core_ext/numeric/time"
|
5
|
+
|
6
|
+
module ActiveRecord
|
7
|
+
module Middleware
|
8
|
+
class DatabaseSelector
|
9
|
+
# The Resolver class is used by the DatabaseSelector middleware to
|
10
|
+
# determine which database the request should use.
|
11
|
+
#
|
12
|
+
# To change the behavior of the Resolver class in your application,
|
13
|
+
# create a custom resolver class that inherits from
|
14
|
+
# DatabaseSelector::Resolver and implements the methods that need to
|
15
|
+
# be changed.
|
16
|
+
#
|
17
|
+
# By default the Resolver class will send read traffic to the replica
|
18
|
+
# if it's been 2 seconds since the last write.
|
19
|
+
class Resolver # :nodoc:
|
20
|
+
SEND_TO_REPLICA_DELAY = 2.seconds
|
21
|
+
|
22
|
+
def self.call(context, options = {})
|
23
|
+
new(context, options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(context, options = {})
|
27
|
+
@context = context
|
28
|
+
@options = options
|
29
|
+
@delay = @options && @options[:delay] ? @options[:delay] : SEND_TO_REPLICA_DELAY
|
30
|
+
@instrumenter = ActiveSupport::Notifications.instrumenter
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :context, :delay, :instrumenter
|
34
|
+
|
35
|
+
def read(&blk)
|
36
|
+
if read_from_primary?
|
37
|
+
read_from_primary(&blk)
|
38
|
+
else
|
39
|
+
read_from_replica(&blk)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def write(&blk)
|
44
|
+
write_to_primary(&blk)
|
45
|
+
end
|
46
|
+
|
47
|
+
def update_context(response)
|
48
|
+
context.save(response)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def read_from_primary(&blk)
|
53
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: true) do
|
54
|
+
instrumenter.instrument("database_selector.active_record.read_from_primary") do
|
55
|
+
yield
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def read_from_replica(&blk)
|
61
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.reading_role, prevent_writes: true) do
|
62
|
+
instrumenter.instrument("database_selector.active_record.read_from_replica") do
|
63
|
+
yield
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def write_to_primary(&blk)
|
69
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: false) do
|
70
|
+
instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
|
71
|
+
yield
|
72
|
+
ensure
|
73
|
+
context.update_last_write_timestamp
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def read_from_primary?
|
79
|
+
!time_since_last_write_ok?
|
80
|
+
end
|
81
|
+
|
82
|
+
def send_to_replica_delay
|
83
|
+
delay
|
84
|
+
end
|
85
|
+
|
86
|
+
def time_since_last_write_ok?
|
87
|
+
Time.now - context.last_write_timestamp >= send_to_replica_delay
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Middleware
|
5
|
+
class DatabaseSelector
|
6
|
+
class Resolver
|
7
|
+
# The session class is used by the DatabaseSelector::Resolver to save
|
8
|
+
# timestamps of the last write in the session.
|
9
|
+
#
|
10
|
+
# The last_write is used to determine whether it's safe to read
|
11
|
+
# from the replica or the request needs to be sent to the primary.
|
12
|
+
class Session # :nodoc:
|
13
|
+
def self.call(request)
|
14
|
+
new(request.session)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Converts time to a timestamp that represents milliseconds since
|
18
|
+
# epoch.
|
19
|
+
def self.convert_time_to_timestamp(time)
|
20
|
+
time.to_i * 1000 + time.usec / 1000
|
21
|
+
end
|
22
|
+
|
23
|
+
# Converts milliseconds since epoch timestamp into a time object.
|
24
|
+
def self.convert_timestamp_to_time(timestamp)
|
25
|
+
timestamp ? Time.at(timestamp / 1000, (timestamp % 1000) * 1000) : Time.at(0)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(session)
|
29
|
+
@session = session
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :session
|
33
|
+
|
34
|
+
def last_write_timestamp
|
35
|
+
self.class.convert_timestamp_to_time(session[:last_write])
|
36
|
+
end
|
37
|
+
|
38
|
+
def update_last_write_timestamp
|
39
|
+
session[:last_write] = self.class.convert_time_to_timestamp(Time.now)
|
40
|
+
end
|
41
|
+
|
42
|
+
def save(response)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -1,9 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "benchmark"
|
1
4
|
require "set"
|
2
5
|
require "zlib"
|
6
|
+
require "active_support/core_ext/array/access"
|
7
|
+
require "active_support/core_ext/enumerable"
|
3
8
|
require "active_support/core_ext/module/attribute_accessors"
|
9
|
+
require "active_support/actionable_error"
|
4
10
|
|
5
11
|
module ActiveRecord
|
6
|
-
class MigrationError < ActiveRecordError#:nodoc:
|
12
|
+
class MigrationError < ActiveRecordError #:nodoc:
|
7
13
|
def initialize(message = nil)
|
8
14
|
message = "\n\n#{message}\n\n" if message
|
9
15
|
super
|
@@ -14,13 +20,13 @@ module ActiveRecord
|
|
14
20
|
# For example the following migration is not reversible.
|
15
21
|
# Rolling back this migration will raise an ActiveRecord::IrreversibleMigration error.
|
16
22
|
#
|
17
|
-
# class IrreversibleMigrationExample < ActiveRecord::Migration[
|
23
|
+
# class IrreversibleMigrationExample < ActiveRecord::Migration[6.0]
|
18
24
|
# def change
|
19
25
|
# create_table :distributors do |t|
|
20
26
|
# t.string :zipcode
|
21
27
|
# end
|
22
28
|
#
|
23
|
-
# execute
|
29
|
+
# execute <<~SQL
|
24
30
|
# ALTER TABLE distributors
|
25
31
|
# ADD CONSTRAINT zipchk
|
26
32
|
# CHECK (char_length(zipcode) = 5) NO INHERIT;
|
@@ -32,13 +38,13 @@ module ActiveRecord
|
|
32
38
|
#
|
33
39
|
# 1. Define <tt>#up</tt> and <tt>#down</tt> methods instead of <tt>#change</tt>:
|
34
40
|
#
|
35
|
-
# class ReversibleMigrationExample < ActiveRecord::Migration[
|
41
|
+
# class ReversibleMigrationExample < ActiveRecord::Migration[6.0]
|
36
42
|
# def up
|
37
43
|
# create_table :distributors do |t|
|
38
44
|
# t.string :zipcode
|
39
45
|
# end
|
40
46
|
#
|
41
|
-
# execute
|
47
|
+
# execute <<~SQL
|
42
48
|
# ALTER TABLE distributors
|
43
49
|
# ADD CONSTRAINT zipchk
|
44
50
|
# CHECK (char_length(zipcode) = 5) NO INHERIT;
|
@@ -46,7 +52,7 @@ module ActiveRecord
|
|
46
52
|
# end
|
47
53
|
#
|
48
54
|
# def down
|
49
|
-
# execute
|
55
|
+
# execute <<~SQL
|
50
56
|
# ALTER TABLE distributors
|
51
57
|
# DROP CONSTRAINT zipchk
|
52
58
|
# SQL
|
@@ -57,7 +63,7 @@ module ActiveRecord
|
|
57
63
|
#
|
58
64
|
# 2. Use the #reversible method in <tt>#change</tt> method:
|
59
65
|
#
|
60
|
-
# class ReversibleMigrationExample < ActiveRecord::Migration[
|
66
|
+
# class ReversibleMigrationExample < ActiveRecord::Migration[6.0]
|
61
67
|
# def change
|
62
68
|
# create_table :distributors do |t|
|
63
69
|
# t.string :zipcode
|
@@ -65,7 +71,7 @@ module ActiveRecord
|
|
65
71
|
#
|
66
72
|
# reversible do |dir|
|
67
73
|
# dir.up do
|
68
|
-
# execute
|
74
|
+
# execute <<~SQL
|
69
75
|
# ALTER TABLE distributors
|
70
76
|
# ADD CONSTRAINT zipchk
|
71
77
|
# CHECK (char_length(zipcode) = 5) NO INHERIT;
|
@@ -73,7 +79,7 @@ module ActiveRecord
|
|
73
79
|
# end
|
74
80
|
#
|
75
81
|
# dir.down do
|
76
|
-
# execute
|
82
|
+
# execute <<~SQL
|
77
83
|
# ALTER TABLE distributors
|
78
84
|
# DROP CONSTRAINT zipchk
|
79
85
|
# SQL
|
@@ -84,7 +90,7 @@ module ActiveRecord
|
|
84
90
|
class IrreversibleMigration < MigrationError
|
85
91
|
end
|
86
92
|
|
87
|
-
class DuplicateMigrationVersionError < MigrationError#:nodoc:
|
93
|
+
class DuplicateMigrationVersionError < MigrationError #:nodoc:
|
88
94
|
def initialize(version = nil)
|
89
95
|
if version
|
90
96
|
super("Multiple migrations have the version number #{version}.")
|
@@ -94,7 +100,7 @@ module ActiveRecord
|
|
94
100
|
end
|
95
101
|
end
|
96
102
|
|
97
|
-
class DuplicateMigrationNameError < MigrationError#:nodoc:
|
103
|
+
class DuplicateMigrationNameError < MigrationError #:nodoc:
|
98
104
|
def initialize(name = nil)
|
99
105
|
if name
|
100
106
|
super("Multiple migrations have the name #{name}.")
|
@@ -114,7 +120,7 @@ module ActiveRecord
|
|
114
120
|
end
|
115
121
|
end
|
116
122
|
|
117
|
-
class IllegalMigrationNameError < MigrationError#:nodoc:
|
123
|
+
class IllegalMigrationNameError < MigrationError #:nodoc:
|
118
124
|
def initialize(name = nil)
|
119
125
|
if name
|
120
126
|
super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed).")
|
@@ -124,20 +130,44 @@ module ActiveRecord
|
|
124
130
|
end
|
125
131
|
end
|
126
132
|
|
127
|
-
class PendingMigrationError < MigrationError#:nodoc:
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
133
|
+
class PendingMigrationError < MigrationError #:nodoc:
|
134
|
+
include ActiveSupport::ActionableError
|
135
|
+
|
136
|
+
action "Run pending migrations" do
|
137
|
+
ActiveRecord::Tasks::DatabaseTasks.migrate
|
138
|
+
|
139
|
+
if ActiveRecord::Base.dump_schema_after_migration
|
140
|
+
ActiveRecord::Tasks::DatabaseTasks.dump_schema(
|
141
|
+
ActiveRecord::Base.connection_db_config
|
142
|
+
)
|
135
143
|
end
|
136
144
|
end
|
145
|
+
|
146
|
+
def initialize(message = nil)
|
147
|
+
super(message || detailed_migration_message)
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
def detailed_migration_message
|
152
|
+
message = "Migrations are pending. To resolve this issue, run:\n\n bin/rails db:migrate"
|
153
|
+
message += " RAILS_ENV=#{::Rails.env}" if defined?(Rails.env)
|
154
|
+
message += "\n\n"
|
155
|
+
|
156
|
+
pending_migrations = ActiveRecord::Base.connection.migration_context.open.pending_migrations
|
157
|
+
|
158
|
+
message += "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}\n\n"
|
159
|
+
|
160
|
+
pending_migrations.each do |pending_migration|
|
161
|
+
message += "#{pending_migration.basename}\n"
|
162
|
+
end
|
163
|
+
|
164
|
+
message
|
165
|
+
end
|
137
166
|
end
|
138
167
|
|
139
168
|
class ConcurrentMigrationError < MigrationError #:nodoc:
|
140
|
-
DEFAULT_MESSAGE = "Cannot run migrations because another migration process is currently running."
|
169
|
+
DEFAULT_MESSAGE = "Cannot run migrations because another migration process is currently running."
|
170
|
+
RELEASE_LOCK_FAILED_MESSAGE = "Failed to release advisory lock"
|
141
171
|
|
142
172
|
def initialize(message = DEFAULT_MESSAGE)
|
143
173
|
super
|
@@ -157,7 +187,7 @@ module ActiveRecord
|
|
157
187
|
|
158
188
|
class ProtectedEnvironmentError < ActiveRecordError #:nodoc:
|
159
189
|
def initialize(env = "production")
|
160
|
-
msg = "You are attempting to run a destructive action against your '#{env}' database.\n"
|
190
|
+
msg = +"You are attempting to run a destructive action against your '#{env}' database.\n"
|
161
191
|
msg << "If you are sure you want to continue, run the same command with the environment variable:\n"
|
162
192
|
msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
|
163
193
|
super(msg)
|
@@ -166,7 +196,7 @@ module ActiveRecord
|
|
166
196
|
|
167
197
|
class EnvironmentMismatchError < ActiveRecordError
|
168
198
|
def initialize(current: nil, stored: nil)
|
169
|
-
msg =
|
199
|
+
msg = +"You are attempting to modify a database that was last run in `#{ stored }` environment.\n"
|
170
200
|
msg << "You are running in `#{ current }` environment. "
|
171
201
|
msg << "If you are sure you want to continue, first set the environment using:\n\n"
|
172
202
|
msg << " bin/rails db:environment:set"
|
@@ -178,6 +208,14 @@ module ActiveRecord
|
|
178
208
|
end
|
179
209
|
end
|
180
210
|
|
211
|
+
class EnvironmentStorageError < ActiveRecordError # :nodoc:
|
212
|
+
def initialize
|
213
|
+
msg = +"You are attempting to store the environment in a database where metadata is disabled.\n"
|
214
|
+
msg << "Check your database configuration to see if this is intended."
|
215
|
+
super(msg)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
181
219
|
# = Active Record Migrations
|
182
220
|
#
|
183
221
|
# Migrations can manage the evolution of a schema used by several physical
|
@@ -190,7 +228,7 @@ module ActiveRecord
|
|
190
228
|
#
|
191
229
|
# Example of a simple migration:
|
192
230
|
#
|
193
|
-
# class AddSsl < ActiveRecord::Migration[
|
231
|
+
# class AddSsl < ActiveRecord::Migration[6.0]
|
194
232
|
# def up
|
195
233
|
# add_column :accounts, :ssl_enabled, :boolean, default: true
|
196
234
|
# end
|
@@ -210,7 +248,7 @@ module ActiveRecord
|
|
210
248
|
#
|
211
249
|
# Example of a more complex migration that also needs to initialize data:
|
212
250
|
#
|
213
|
-
# class AddSystemSettings < ActiveRecord::Migration[
|
251
|
+
# class AddSystemSettings < ActiveRecord::Migration[6.0]
|
214
252
|
# def up
|
215
253
|
# create_table :system_settings do |t|
|
216
254
|
# t.string :name
|
@@ -277,8 +315,10 @@ module ActiveRecord
|
|
277
315
|
#
|
278
316
|
# * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
|
279
317
|
# the column to a different type using the same parameters as add_column.
|
280
|
-
# * <tt>change_column_default(table_name, column_name,
|
281
|
-
# default value for +column_name+ defined by +
|
318
|
+
# * <tt>change_column_default(table_name, column_name, default_or_changes)</tt>:
|
319
|
+
# Sets a default value for +column_name+ defined by +default_or_changes+ on
|
320
|
+
# +table_name+. Passing a hash containing <tt>:from</tt> and <tt>:to</tt>
|
321
|
+
# as +default_or_changes+ will make this change reversible in the migration.
|
282
322
|
# * <tt>change_column_null(table_name, column_name, null, default = nil)</tt>:
|
283
323
|
# Sets or removes a +NOT NULL+ constraint on +column_name+. The +null+ flag
|
284
324
|
# indicates whether the value can be +NULL+. See
|
@@ -302,7 +342,7 @@ module ActiveRecord
|
|
302
342
|
# named +column_name+ from the table called +table_name+.
|
303
343
|
# * <tt>remove_columns(table_name, *column_names)</tt>: Removes the given
|
304
344
|
# columns from the table definition.
|
305
|
-
# * <tt>remove_foreign_key(from_table,
|
345
|
+
# * <tt>remove_foreign_key(from_table, to_table = nil, **options)</tt>: Removes the
|
306
346
|
# given foreign key from the table called +table_name+.
|
307
347
|
# * <tt>remove_index(table_name, column: column_names)</tt>: Removes the index
|
308
348
|
# specified by +column_names+.
|
@@ -324,7 +364,7 @@ module ActiveRecord
|
|
324
364
|
# The Rails package has several tools to help create and apply migrations.
|
325
365
|
#
|
326
366
|
# To generate a new migration, you can use
|
327
|
-
# rails generate migration MyNewMigration
|
367
|
+
# bin/rails generate migration MyNewMigration
|
328
368
|
#
|
329
369
|
# where MyNewMigration is the name of your migration. The generator will
|
330
370
|
# create an empty migration file <tt>timestamp_my_new_migration.rb</tt>
|
@@ -333,41 +373,36 @@ module ActiveRecord
|
|
333
373
|
#
|
334
374
|
# There is a special syntactic shortcut to generate migrations that add fields to a table.
|
335
375
|
#
|
336
|
-
# rails generate migration add_fieldname_to_tablename fieldname:string
|
376
|
+
# bin/rails generate migration add_fieldname_to_tablename fieldname:string
|
337
377
|
#
|
338
378
|
# This will generate the file <tt>timestamp_add_fieldname_to_tablename.rb</tt>, which will look like this:
|
339
|
-
# class AddFieldnameToTablename < ActiveRecord::Migration[
|
379
|
+
# class AddFieldnameToTablename < ActiveRecord::Migration[6.0]
|
340
380
|
# def change
|
341
381
|
# add_column :tablenames, :fieldname, :string
|
342
382
|
# end
|
343
383
|
# end
|
344
384
|
#
|
345
385
|
# To run migrations against the currently configured database, use
|
346
|
-
# <tt>rails db:migrate</tt>. This will update the database by running all of the
|
386
|
+
# <tt>bin/rails db:migrate</tt>. This will update the database by running all of the
|
347
387
|
# pending migrations, creating the <tt>schema_migrations</tt> table
|
348
388
|
# (see "About the schema_migrations table" section below) if missing. It will also
|
349
|
-
# invoke the db:schema:dump
|
389
|
+
# invoke the db:schema:dump command, which will update your db/schema.rb file
|
350
390
|
# to match the structure of your database.
|
351
391
|
#
|
352
392
|
# To roll the database back to a previous migration version, use
|
353
|
-
# <tt>rails db:
|
393
|
+
# <tt>bin/rails db:rollback VERSION=X</tt> where <tt>X</tt> is the version to which
|
354
394
|
# you wish to downgrade. Alternatively, you can also use the STEP option if you
|
355
|
-
# wish to rollback last few migrations. <tt>rails db:
|
395
|
+
# wish to rollback last few migrations. <tt>bin/rails db:rollback STEP=2</tt> will rollback
|
356
396
|
# the latest two migrations.
|
357
397
|
#
|
358
398
|
# If any of the migrations throw an <tt>ActiveRecord::IrreversibleMigration</tt> exception,
|
359
399
|
# that step will fail and you'll have some manual work to do.
|
360
400
|
#
|
361
|
-
# == Database support
|
362
|
-
#
|
363
|
-
# Migrations are currently supported in MySQL, PostgreSQL, SQLite,
|
364
|
-
# SQL Server, and Oracle (all supported databases except DB2).
|
365
|
-
#
|
366
401
|
# == More examples
|
367
402
|
#
|
368
403
|
# Not all migrations change the schema. Some just fix the data:
|
369
404
|
#
|
370
|
-
# class RemoveEmptyTags < ActiveRecord::Migration[
|
405
|
+
# class RemoveEmptyTags < ActiveRecord::Migration[6.0]
|
371
406
|
# def up
|
372
407
|
# Tag.all.each { |tag| tag.destroy if tag.pages.empty? }
|
373
408
|
# end
|
@@ -380,7 +415,7 @@ module ActiveRecord
|
|
380
415
|
#
|
381
416
|
# Others remove columns when they migrate up instead of down:
|
382
417
|
#
|
383
|
-
# class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration[
|
418
|
+
# class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration[6.0]
|
384
419
|
# def up
|
385
420
|
# remove_column :items, :incomplete_items_count
|
386
421
|
# remove_column :items, :completed_items_count
|
@@ -394,7 +429,7 @@ module ActiveRecord
|
|
394
429
|
#
|
395
430
|
# And sometimes you need to do something in SQL not abstracted directly by migrations:
|
396
431
|
#
|
397
|
-
# class MakeJoinUnique < ActiveRecord::Migration[
|
432
|
+
# class MakeJoinUnique < ActiveRecord::Migration[6.0]
|
398
433
|
# def up
|
399
434
|
# execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
|
400
435
|
# end
|
@@ -411,7 +446,7 @@ module ActiveRecord
|
|
411
446
|
# <tt>Base#reset_column_information</tt> in order to ensure that the model has the
|
412
447
|
# latest column data from after the new column was added. Example:
|
413
448
|
#
|
414
|
-
# class AddPeopleSalary < ActiveRecord::Migration[
|
449
|
+
# class AddPeopleSalary < ActiveRecord::Migration[6.0]
|
415
450
|
# def up
|
416
451
|
# add_column :people, :salary, :integer
|
417
452
|
# Person.reset_column_information
|
@@ -469,7 +504,7 @@ module ActiveRecord
|
|
469
504
|
# To define a reversible migration, define the +change+ method in your
|
470
505
|
# migration like this:
|
471
506
|
#
|
472
|
-
# class TenderloveMigration < ActiveRecord::Migration[
|
507
|
+
# class TenderloveMigration < ActiveRecord::Migration[6.0]
|
473
508
|
# def change
|
474
509
|
# create_table(:horses) do |t|
|
475
510
|
# t.column :content, :text
|
@@ -481,9 +516,9 @@ module ActiveRecord
|
|
481
516
|
# This migration will create the horses table for you on the way up, and
|
482
517
|
# automatically figure out how to drop the table on the way down.
|
483
518
|
#
|
484
|
-
# Some commands
|
485
|
-
#
|
486
|
-
#
|
519
|
+
# Some commands cannot be reversed. If you care to define how to move up
|
520
|
+
# and down in these cases, you should define the +up+ and +down+ methods
|
521
|
+
# as before.
|
487
522
|
#
|
488
523
|
# If a command cannot be reversed, an
|
489
524
|
# <tt>ActiveRecord::IrreversibleMigration</tt> exception will be raised when
|
@@ -499,7 +534,7 @@ module ActiveRecord
|
|
499
534
|
# can't execute inside a transaction though, and for these situations
|
500
535
|
# you can turn the automatic transactions off.
|
501
536
|
#
|
502
|
-
# class ChangeEnum < ActiveRecord::Migration[
|
537
|
+
# class ChangeEnum < ActiveRecord::Migration[6.0]
|
503
538
|
# disable_ddl_transaction!
|
504
539
|
#
|
505
540
|
# def up
|
@@ -510,17 +545,21 @@ module ActiveRecord
|
|
510
545
|
# Remember that you can still open your own transactions, even if you
|
511
546
|
# are in a Migration with <tt>self.disable_ddl_transaction!</tt>.
|
512
547
|
class Migration
|
513
|
-
autoload :CommandRecorder,
|
514
|
-
autoload :Compatibility,
|
548
|
+
autoload :CommandRecorder, "active_record/migration/command_recorder"
|
549
|
+
autoload :Compatibility, "active_record/migration/compatibility"
|
550
|
+
autoload :JoinTable, "active_record/migration/join_table"
|
515
551
|
|
516
552
|
# This must be defined before the inherited hook, below
|
517
|
-
class Current < Migration
|
553
|
+
class Current < Migration #:nodoc:
|
518
554
|
end
|
519
555
|
|
520
|
-
def self.inherited(subclass)
|
556
|
+
def self.inherited(subclass) #:nodoc:
|
521
557
|
super
|
522
558
|
if subclass.superclass == Migration
|
523
|
-
|
559
|
+
raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \
|
560
|
+
"Please specify the Rails release the migration was written for:\n" \
|
561
|
+
"\n" \
|
562
|
+
" class #{subclass} < ActiveRecord::Migration[4.2]"
|
524
563
|
end
|
525
564
|
end
|
526
565
|
|
@@ -532,70 +571,93 @@ module ActiveRecord
|
|
532
571
|
ActiveRecord::VERSION::STRING.to_f
|
533
572
|
end
|
534
573
|
|
535
|
-
MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/
|
574
|
+
MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ #:nodoc:
|
536
575
|
|
537
576
|
# This class is used to verify that all migrations have been run before
|
538
577
|
# loading a web page if <tt>config.active_record.migration_error</tt> is set to :page_load
|
539
578
|
class CheckPending
|
540
|
-
def initialize(app)
|
579
|
+
def initialize(app, file_watcher: ActiveSupport::FileUpdateChecker)
|
541
580
|
@app = app
|
542
|
-
@
|
581
|
+
@needs_check = true
|
582
|
+
@mutex = Mutex.new
|
583
|
+
@file_watcher = file_watcher
|
543
584
|
end
|
544
585
|
|
545
586
|
def call(env)
|
546
|
-
|
547
|
-
|
548
|
-
|
587
|
+
@mutex.synchronize do
|
588
|
+
@watcher ||= build_watcher do
|
589
|
+
@needs_check = true
|
549
590
|
ActiveRecord::Migration.check_pending!(connection)
|
550
|
-
@
|
591
|
+
@needs_check = false
|
592
|
+
end
|
593
|
+
|
594
|
+
if @needs_check
|
595
|
+
@watcher.execute
|
596
|
+
else
|
597
|
+
@watcher.execute_if_updated
|
551
598
|
end
|
552
599
|
end
|
600
|
+
|
553
601
|
@app.call(env)
|
554
602
|
end
|
555
603
|
|
556
604
|
private
|
605
|
+
def build_watcher(&block)
|
606
|
+
paths = Array(connection.migration_context.migrations_paths)
|
607
|
+
@file_watcher.new([], paths.index_with(["rb"]), &block)
|
608
|
+
end
|
557
609
|
|
558
|
-
|
559
|
-
|
560
|
-
|
610
|
+
def connection
|
611
|
+
ActiveRecord::Base.connection
|
612
|
+
end
|
561
613
|
end
|
562
614
|
|
563
615
|
class << self
|
564
|
-
attr_accessor :delegate
|
565
|
-
attr_accessor :disable_ddl_transaction
|
616
|
+
attr_accessor :delegate #:nodoc:
|
617
|
+
attr_accessor :disable_ddl_transaction #:nodoc:
|
566
618
|
|
567
|
-
def nearest_delegate
|
619
|
+
def nearest_delegate #:nodoc:
|
568
620
|
delegate || superclass.nearest_delegate
|
569
621
|
end
|
570
622
|
|
571
623
|
# Raises <tt>ActiveRecord::PendingMigrationError</tt> error if any migrations are pending.
|
572
624
|
def check_pending!(connection = Base.connection)
|
573
|
-
raise ActiveRecord::PendingMigrationError if
|
625
|
+
raise ActiveRecord::PendingMigrationError if connection.migration_context.needs_migration?
|
574
626
|
end
|
575
627
|
|
576
628
|
def load_schema_if_pending!
|
577
|
-
|
629
|
+
current_db_config = Base.connection_db_config
|
630
|
+
all_configs = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env)
|
631
|
+
|
632
|
+
needs_update = !all_configs.all? do |db_config|
|
633
|
+
Tasks::DatabaseTasks.schema_up_to_date?(db_config, ActiveRecord::Base.schema_format)
|
634
|
+
end
|
635
|
+
|
636
|
+
if needs_update
|
578
637
|
# Roundtrip to Rake to allow plugins to hook into database initialization.
|
579
|
-
|
580
|
-
|
638
|
+
root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root
|
639
|
+
FileUtils.cd(root) do
|
581
640
|
Base.clear_all_connections!
|
582
641
|
system("bin/rails db:test:prepare")
|
583
|
-
# Establish a new connection, the old database may be gone (db:test:prepare uses purge)
|
584
|
-
Base.establish_connection(current_config)
|
585
642
|
end
|
586
|
-
check_pending!
|
587
643
|
end
|
644
|
+
|
645
|
+
# Establish a new connection, the old database may be gone (db:test:prepare uses purge)
|
646
|
+
Base.establish_connection(current_db_config)
|
647
|
+
|
648
|
+
check_pending!
|
588
649
|
end
|
589
650
|
|
590
|
-
def maintain_test_schema!
|
651
|
+
def maintain_test_schema! #:nodoc:
|
591
652
|
if ActiveRecord::Base.maintain_test_schema
|
592
653
|
suppress_messages { load_schema_if_pending! }
|
593
654
|
end
|
594
655
|
end
|
595
656
|
|
596
|
-
def method_missing(name, *args, &block)
|
657
|
+
def method_missing(name, *args, &block) #:nodoc:
|
597
658
|
nearest_delegate.send(name, *args, &block)
|
598
659
|
end
|
660
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
599
661
|
|
600
662
|
def migrate(direction)
|
601
663
|
new.migrate direction
|
@@ -610,7 +672,7 @@ module ActiveRecord
|
|
610
672
|
end
|
611
673
|
end
|
612
674
|
|
613
|
-
def disable_ddl_transaction
|
675
|
+
def disable_ddl_transaction #:nodoc:
|
614
676
|
self.class.disable_ddl_transaction
|
615
677
|
end
|
616
678
|
|
@@ -634,7 +696,7 @@ module ActiveRecord
|
|
634
696
|
# and create the table 'apples' on the way up, and the reverse
|
635
697
|
# on the way down.
|
636
698
|
#
|
637
|
-
# class FixTLMigration < ActiveRecord::Migration[
|
699
|
+
# class FixTLMigration < ActiveRecord::Migration[6.0]
|
638
700
|
# def change
|
639
701
|
# revert do
|
640
702
|
# create_table(:horses) do |t|
|
@@ -651,9 +713,9 @@ module ActiveRecord
|
|
651
713
|
# Or equivalently, if +TenderloveMigration+ is defined as in the
|
652
714
|
# documentation for Migration:
|
653
715
|
#
|
654
|
-
# require_relative
|
716
|
+
# require_relative "20121212123456_tenderlove_migration"
|
655
717
|
#
|
656
|
-
# class FixupTLMigration < ActiveRecord::Migration[
|
718
|
+
# class FixupTLMigration < ActiveRecord::Migration[6.0]
|
657
719
|
# def change
|
658
720
|
# revert TenderloveMigration
|
659
721
|
#
|
@@ -670,15 +732,13 @@ module ActiveRecord
|
|
670
732
|
if connection.respond_to? :revert
|
671
733
|
connection.revert { yield }
|
672
734
|
else
|
673
|
-
recorder =
|
735
|
+
recorder = command_recorder
|
674
736
|
@connection = recorder
|
675
737
|
suppress_messages do
|
676
738
|
connection.revert { yield }
|
677
739
|
end
|
678
740
|
@connection = recorder.delegate
|
679
|
-
recorder.
|
680
|
-
send(cmd, *args, &block)
|
681
|
-
end
|
741
|
+
recorder.replay(self)
|
682
742
|
end
|
683
743
|
end
|
684
744
|
end
|
@@ -687,7 +747,7 @@ module ActiveRecord
|
|
687
747
|
connection.respond_to?(:reverting) && connection.reverting
|
688
748
|
end
|
689
749
|
|
690
|
-
|
750
|
+
ReversibleBlockHelper = Struct.new(:reverting) do #:nodoc:
|
691
751
|
def up
|
692
752
|
yield unless reverting
|
693
753
|
end
|
@@ -706,7 +766,7 @@ module ActiveRecord
|
|
706
766
|
# when the three columns 'first_name', 'last_name' and 'full_name' exist,
|
707
767
|
# even when migrating down:
|
708
768
|
#
|
709
|
-
# class SplitNameMigration < ActiveRecord::Migration[
|
769
|
+
# class SplitNameMigration < ActiveRecord::Migration[6.0]
|
710
770
|
# def change
|
711
771
|
# add_column :users, :first_name, :string
|
712
772
|
# add_column :users, :last_name, :string
|
@@ -725,7 +785,25 @@ module ActiveRecord
|
|
725
785
|
# end
|
726
786
|
def reversible
|
727
787
|
helper = ReversibleBlockHelper.new(reverting?)
|
728
|
-
execute_block{ yield helper }
|
788
|
+
execute_block { yield helper }
|
789
|
+
end
|
790
|
+
|
791
|
+
# Used to specify an operation that is only run when migrating up
|
792
|
+
# (for example, populating a new column with its initial values).
|
793
|
+
#
|
794
|
+
# In the following example, the new column +published+ will be given
|
795
|
+
# the value +true+ for all existing records.
|
796
|
+
#
|
797
|
+
# class AddPublishedToPosts < ActiveRecord::Migration[6.0]
|
798
|
+
# def change
|
799
|
+
# add_column :posts, :published, :boolean, default: false
|
800
|
+
# up_only do
|
801
|
+
# execute "update posts set published = 'true'"
|
802
|
+
# end
|
803
|
+
# end
|
804
|
+
# end
|
805
|
+
def up_only
|
806
|
+
execute_block { yield } unless reverting?
|
729
807
|
end
|
730
808
|
|
731
809
|
# Runs the given migration classes.
|
@@ -767,7 +845,7 @@ module ActiveRecord
|
|
767
845
|
when :down then announce "reverting"
|
768
846
|
end
|
769
847
|
|
770
|
-
time
|
848
|
+
time = nil
|
771
849
|
ActiveRecord::Base.connection_pool.with_connection do |conn|
|
772
850
|
time = Benchmark.measure do
|
773
851
|
exec_migration(conn, direction)
|
@@ -789,13 +867,13 @@ module ActiveRecord
|
|
789
867
|
change
|
790
868
|
end
|
791
869
|
else
|
792
|
-
|
870
|
+
public_send(direction)
|
793
871
|
end
|
794
872
|
ensure
|
795
873
|
@connection = nil
|
796
874
|
end
|
797
875
|
|
798
|
-
def write(text="")
|
876
|
+
def write(text = "")
|
799
877
|
puts(text) if verbose
|
800
878
|
end
|
801
879
|
|
@@ -805,10 +883,14 @@ module ActiveRecord
|
|
805
883
|
write "== %s %s" % [text, "=" * length]
|
806
884
|
end
|
807
885
|
|
808
|
-
|
886
|
+
# Takes a message argument and outputs it as is.
|
887
|
+
# A second boolean argument can be passed to specify whether to indent or not.
|
888
|
+
def say(message, subitem = false)
|
809
889
|
write "#{subitem ? " ->" : "--"} #{message}"
|
810
890
|
end
|
811
891
|
|
892
|
+
# Outputs text along with how long it took to run its block.
|
893
|
+
# If the block returns an integer it assumes it is the number of rows affected.
|
812
894
|
def say_with_time(message)
|
813
895
|
say(message)
|
814
896
|
result = nil
|
@@ -818,6 +900,7 @@ module ActiveRecord
|
|
818
900
|
result
|
819
901
|
end
|
820
902
|
|
903
|
+
# Takes a block as an argument and suppresses any output generated by the block.
|
821
904
|
def suppress_messages
|
822
905
|
save, self.verbose = verbose, false
|
823
906
|
yield
|
@@ -830,7 +913,7 @@ module ActiveRecord
|
|
830
913
|
end
|
831
914
|
|
832
915
|
def method_missing(method, *arguments, &block)
|
833
|
-
arg_list = arguments.map(&:inspect) *
|
916
|
+
arg_list = arguments.map(&:inspect) * ", "
|
834
917
|
|
835
918
|
say_with_time "#{method}(#{arg_list})" do
|
836
919
|
unless connection.respond_to? :revert
|
@@ -846,29 +929,33 @@ module ActiveRecord
|
|
846
929
|
connection.send(method, *arguments, &block)
|
847
930
|
end
|
848
931
|
end
|
932
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
849
933
|
|
850
934
|
def copy(destination, sources, options = {})
|
851
935
|
copied = []
|
936
|
+
schema_migration = options[:schema_migration] || ActiveRecord::SchemaMigration
|
852
937
|
|
853
938
|
FileUtils.mkdir_p(destination) unless File.exist?(destination)
|
854
939
|
|
855
|
-
destination_migrations = ActiveRecord::
|
940
|
+
destination_migrations = ActiveRecord::MigrationContext.new(destination, schema_migration).migrations
|
856
941
|
last = destination_migrations.last
|
857
942
|
sources.each do |scope, path|
|
858
|
-
source_migrations = ActiveRecord::
|
943
|
+
source_migrations = ActiveRecord::MigrationContext.new(path, schema_migration).migrations
|
859
944
|
|
860
945
|
source_migrations.each do |migration|
|
861
946
|
source = File.binread(migration.filename)
|
862
947
|
inserted_comment = "# This migration comes from #{scope} (originally #{migration.version})\n"
|
863
|
-
|
948
|
+
magic_comments = +""
|
949
|
+
loop do
|
864
950
|
# If we have a magic comment in the original migration,
|
865
951
|
# insert our comment after the first newline(end of the magic comment line)
|
866
952
|
# so the magic keep working.
|
867
953
|
# Note that magic comments must be at the first line(except sh-bang).
|
868
|
-
source
|
869
|
-
|
870
|
-
|
954
|
+
source.sub!(/\A(?:#.*\b(?:en)?coding:\s*\S+|#\s*frozen_string_literal:\s*(?:true|false)).*\n/) do |magic_comment|
|
955
|
+
magic_comments << magic_comment; ""
|
956
|
+
end || break
|
871
957
|
end
|
958
|
+
source = "#{magic_comments}#{inserted_comment}#{source}"
|
872
959
|
|
873
960
|
if duplicate = destination_migrations.detect { |m| m.name == migration.name }
|
874
961
|
if options[:on_skip] && duplicate.scope != scope.to_s
|
@@ -922,19 +1009,22 @@ module ActiveRecord
|
|
922
1009
|
end
|
923
1010
|
|
924
1011
|
private
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
1012
|
+
def execute_block
|
1013
|
+
if connection.respond_to? :execute_block
|
1014
|
+
super # use normal delegation to record the block
|
1015
|
+
else
|
1016
|
+
yield
|
1017
|
+
end
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
def command_recorder
|
1021
|
+
CommandRecorder.new(connection)
|
930
1022
|
end
|
931
|
-
end
|
932
1023
|
end
|
933
1024
|
|
934
1025
|
# MigrationProxy is used to defer loading of the actual migration classes
|
935
1026
|
# until they are needed
|
936
|
-
|
937
|
-
|
1027
|
+
MigrationProxy = Struct.new(:name, :version, :filename, :scope) do
|
938
1028
|
def initialize(name, version, filename, scope)
|
939
1029
|
super
|
940
1030
|
@migration = nil
|
@@ -944,14 +1034,9 @@ module ActiveRecord
|
|
944
1034
|
File.basename(filename)
|
945
1035
|
end
|
946
1036
|
|
947
|
-
def mtime
|
948
|
-
File.mtime filename
|
949
|
-
end
|
950
|
-
|
951
1037
|
delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration
|
952
1038
|
|
953
1039
|
private
|
954
|
-
|
955
1040
|
def migration
|
956
1041
|
@migration ||= load_migration
|
957
1042
|
end
|
@@ -960,172 +1045,188 @@ module ActiveRecord
|
|
960
1045
|
require(File.expand_path(filename))
|
961
1046
|
name.constantize.new(name, version)
|
962
1047
|
end
|
963
|
-
|
964
1048
|
end
|
965
1049
|
|
966
|
-
class
|
967
|
-
|
968
|
-
super(nil, 0, nil, nil)
|
969
|
-
end
|
1050
|
+
class MigrationContext #:nodoc:
|
1051
|
+
attr_reader :migrations_paths, :schema_migration
|
970
1052
|
|
971
|
-
def
|
972
|
-
|
1053
|
+
def initialize(migrations_paths, schema_migration)
|
1054
|
+
@migrations_paths = migrations_paths
|
1055
|
+
@schema_migration = schema_migration
|
973
1056
|
end
|
974
|
-
end
|
975
|
-
|
976
|
-
class Migrator#:nodoc:
|
977
|
-
class << self
|
978
|
-
attr_writer :migrations_paths
|
979
|
-
alias :migrations_path= :migrations_paths=
|
980
|
-
|
981
|
-
def migrate(migrations_paths, target_version = nil, &block)
|
982
|
-
case
|
983
|
-
when target_version.nil?
|
984
|
-
up(migrations_paths, target_version, &block)
|
985
|
-
when current_version == 0 && target_version == 0
|
986
|
-
[]
|
987
|
-
when current_version > target_version
|
988
|
-
down(migrations_paths, target_version, &block)
|
989
|
-
else
|
990
|
-
up(migrations_paths, target_version, &block)
|
991
|
-
end
|
992
|
-
end
|
993
1057
|
|
994
|
-
|
995
|
-
|
1058
|
+
def migrate(target_version = nil, &block)
|
1059
|
+
case
|
1060
|
+
when target_version.nil?
|
1061
|
+
up(target_version, &block)
|
1062
|
+
when current_version == 0 && target_version == 0
|
1063
|
+
[]
|
1064
|
+
when current_version > target_version
|
1065
|
+
down(target_version, &block)
|
1066
|
+
else
|
1067
|
+
up(target_version, &block)
|
996
1068
|
end
|
1069
|
+
end
|
997
1070
|
|
998
|
-
|
999
|
-
|
1000
|
-
|
1071
|
+
def rollback(steps = 1)
|
1072
|
+
move(:down, steps)
|
1073
|
+
end
|
1001
1074
|
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1075
|
+
def forward(steps = 1)
|
1076
|
+
move(:up, steps)
|
1077
|
+
end
|
1005
1078
|
|
1006
|
-
|
1079
|
+
def up(target_version = nil)
|
1080
|
+
selected_migrations = if block_given?
|
1081
|
+
migrations.select { |m| yield m }
|
1082
|
+
else
|
1083
|
+
migrations
|
1007
1084
|
end
|
1008
1085
|
|
1009
|
-
|
1010
|
-
|
1011
|
-
migrations.select! { |m| yield m } if block_given?
|
1012
|
-
|
1013
|
-
new(:down, migrations, target_version).migrate
|
1014
|
-
end
|
1086
|
+
Migrator.new(:up, selected_migrations, schema_migration, target_version).migrate
|
1087
|
+
end
|
1015
1088
|
|
1016
|
-
|
1017
|
-
|
1089
|
+
def down(target_version = nil)
|
1090
|
+
selected_migrations = if block_given?
|
1091
|
+
migrations.select { |m| yield m }
|
1092
|
+
else
|
1093
|
+
migrations
|
1018
1094
|
end
|
1019
1095
|
|
1020
|
-
|
1021
|
-
|
1022
|
-
end
|
1096
|
+
Migrator.new(:down, selected_migrations, schema_migration, target_version).migrate
|
1097
|
+
end
|
1023
1098
|
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1099
|
+
def run(direction, target_version)
|
1100
|
+
Migrator.new(direction, migrations, schema_migration, target_version).run
|
1101
|
+
end
|
1027
1102
|
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
SchemaMigration.all.map { |x| x.version.to_i }.sort
|
1032
|
-
else
|
1033
|
-
[]
|
1034
|
-
end
|
1035
|
-
end
|
1036
|
-
end
|
1103
|
+
def open
|
1104
|
+
Migrator.new(:up, migrations, schema_migration)
|
1105
|
+
end
|
1037
1106
|
|
1038
|
-
|
1039
|
-
|
1107
|
+
def get_all_versions
|
1108
|
+
if schema_migration.table_exists?
|
1109
|
+
schema_migration.all_versions.map(&:to_i)
|
1110
|
+
else
|
1111
|
+
[]
|
1040
1112
|
end
|
1113
|
+
end
|
1041
1114
|
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1115
|
+
def current_version
|
1116
|
+
get_all_versions.max || 0
|
1117
|
+
rescue ActiveRecord::NoDatabaseError
|
1118
|
+
end
|
1045
1119
|
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1120
|
+
def needs_migration?
|
1121
|
+
(migrations.collect(&:version) - get_all_versions).size > 0
|
1122
|
+
end
|
1049
1123
|
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1124
|
+
def any_migrations?
|
1125
|
+
migrations.any?
|
1126
|
+
end
|
1053
1127
|
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1128
|
+
def migrations
|
1129
|
+
migrations = migration_files.map do |file|
|
1130
|
+
version, name, scope = parse_migration_filename(file)
|
1131
|
+
raise IllegalMigrationNameError.new(file) unless version
|
1132
|
+
version = version.to_i
|
1133
|
+
name = name.camelize
|
1059
1134
|
|
1060
|
-
|
1061
|
-
File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
|
1135
|
+
MigrationProxy.new(name, version, file, scope)
|
1062
1136
|
end
|
1063
1137
|
|
1064
|
-
|
1065
|
-
|
1138
|
+
migrations.sort_by(&:version)
|
1139
|
+
end
|
1066
1140
|
|
1067
|
-
|
1068
|
-
|
1069
|
-
raise IllegalMigrationNameError.new(file) unless version
|
1070
|
-
version = version.to_i
|
1071
|
-
name = name.camelize
|
1141
|
+
def migrations_status
|
1142
|
+
db_list = schema_migration.normalized_versions
|
1072
1143
|
|
1073
|
-
|
1074
|
-
|
1144
|
+
file_list = migration_files.map do |file|
|
1145
|
+
version, name, scope = parse_migration_filename(file)
|
1146
|
+
raise IllegalMigrationNameError.new(file) unless version
|
1147
|
+
version = schema_migration.normalize_migration_number(version)
|
1148
|
+
status = db_list.delete(version) ? "up" : "down"
|
1149
|
+
[status, version, (name + scope).humanize]
|
1150
|
+
end.compact
|
1075
1151
|
|
1076
|
-
|
1152
|
+
db_list.map! do |version|
|
1153
|
+
["up", version, "********** NO FILE **********"]
|
1077
1154
|
end
|
1078
1155
|
|
1079
|
-
|
1080
|
-
|
1156
|
+
(db_list + file_list).sort_by { |_, version, _| version }
|
1157
|
+
end
|
1081
1158
|
|
1082
|
-
|
1159
|
+
def current_environment
|
1160
|
+
ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
|
1161
|
+
end
|
1083
1162
|
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
version = ActiveRecord::SchemaMigration.normalize_migration_number(version)
|
1088
|
-
status = db_list.delete(version) ? "up" : "down"
|
1089
|
-
[status, version, (name + scope).humanize]
|
1090
|
-
end.compact
|
1163
|
+
def protected_environment?
|
1164
|
+
ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment
|
1165
|
+
end
|
1091
1166
|
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1167
|
+
def last_stored_environment
|
1168
|
+
return nil unless ActiveRecord::InternalMetadata.enabled?
|
1169
|
+
return nil if current_version == 0
|
1170
|
+
raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists?
|
1095
1171
|
|
1096
|
-
|
1097
|
-
|
1172
|
+
environment = ActiveRecord::InternalMetadata[:environment]
|
1173
|
+
raise NoEnvironmentInSchemaError unless environment
|
1174
|
+
environment
|
1175
|
+
end
|
1098
1176
|
|
1099
|
-
|
1177
|
+
private
|
1178
|
+
def migration_files
|
1179
|
+
paths = Array(migrations_paths)
|
1100
1180
|
Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
|
1101
1181
|
end
|
1102
1182
|
|
1103
|
-
|
1183
|
+
def parse_migration_filename(filename)
|
1184
|
+
File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
|
1185
|
+
end
|
1104
1186
|
|
1105
|
-
def move(direction,
|
1106
|
-
migrator = new(direction, migrations
|
1107
|
-
start_index = migrator.migrations.index(migrator.current_migration)
|
1187
|
+
def move(direction, steps)
|
1188
|
+
migrator = Migrator.new(direction, migrations, schema_migration)
|
1108
1189
|
|
1109
|
-
if
|
1110
|
-
|
1111
|
-
version = finish ? finish.version : 0
|
1112
|
-
send(direction, migrations_paths, version)
|
1190
|
+
if current_version != 0 && !migrator.current_migration
|
1191
|
+
raise UnknownMigrationVersionError.new(current_version)
|
1113
1192
|
end
|
1193
|
+
|
1194
|
+
start_index =
|
1195
|
+
if current_version == 0
|
1196
|
+
0
|
1197
|
+
else
|
1198
|
+
migrator.migrations.index(migrator.current_migration)
|
1199
|
+
end
|
1200
|
+
|
1201
|
+
finish = migrator.migrations[start_index + steps]
|
1202
|
+
version = finish ? finish.version : 0
|
1203
|
+
public_send(direction, version)
|
1204
|
+
end
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
class Migrator # :nodoc:
|
1208
|
+
class << self
|
1209
|
+
attr_accessor :migrations_paths
|
1210
|
+
|
1211
|
+
# For cases where a table doesn't exist like loading from schema cache
|
1212
|
+
def current_version
|
1213
|
+
MigrationContext.new(migrations_paths, SchemaMigration).current_version
|
1114
1214
|
end
|
1115
1215
|
end
|
1116
1216
|
|
1117
|
-
|
1118
|
-
raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
|
1217
|
+
self.migrations_paths = ["db/migrate"]
|
1119
1218
|
|
1219
|
+
def initialize(direction, migrations, schema_migration, target_version = nil)
|
1120
1220
|
@direction = direction
|
1121
1221
|
@target_version = target_version
|
1122
1222
|
@migrated_versions = nil
|
1123
1223
|
@migrations = migrations
|
1224
|
+
@schema_migration = schema_migration
|
1124
1225
|
|
1125
1226
|
validate(@migrations)
|
1126
1227
|
|
1127
|
-
|
1128
|
-
|
1228
|
+
@schema_migration.create_table
|
1229
|
+
ActiveRecord::InternalMetadata.create_table
|
1129
1230
|
end
|
1130
1231
|
|
1131
1232
|
def current_version
|
@@ -1178,153 +1279,149 @@ module ActiveRecord
|
|
1178
1279
|
end
|
1179
1280
|
|
1180
1281
|
def load_migrated
|
1181
|
-
@migrated_versions = Set.new(
|
1282
|
+
@migrated_versions = Set.new(@schema_migration.all_versions.map(&:to_i))
|
1182
1283
|
end
|
1183
1284
|
|
1184
1285
|
private
|
1286
|
+
# Used for running a specific migration.
|
1287
|
+
def run_without_lock
|
1288
|
+
migration = migrations.detect { |m| m.version == @target_version }
|
1289
|
+
raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
|
1290
|
+
result = execute_migration_in_transaction(migration)
|
1291
|
+
|
1292
|
+
record_environment
|
1293
|
+
result
|
1294
|
+
end
|
1185
1295
|
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
record_environment
|
1193
|
-
result
|
1194
|
-
end
|
1296
|
+
# Used for running multiple migrations up to or down to a certain value.
|
1297
|
+
def migrate_without_lock
|
1298
|
+
if invalid_target?
|
1299
|
+
raise UnknownMigrationVersionError.new(@target_version)
|
1300
|
+
end
|
1195
1301
|
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
raise UnknownMigrationVersionError.new(@target_version)
|
1302
|
+
result = runnable.each(&method(:execute_migration_in_transaction))
|
1303
|
+
record_environment
|
1304
|
+
result
|
1200
1305
|
end
|
1201
1306
|
|
1202
|
-
|
1203
|
-
|
1307
|
+
# Stores the current environment in the database.
|
1308
|
+
def record_environment
|
1309
|
+
return if down?
|
1310
|
+
ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment
|
1204
1311
|
end
|
1205
1312
|
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
# Stores the current environment in the database.
|
1211
|
-
def record_environment
|
1212
|
-
return if down?
|
1213
|
-
ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
|
1214
|
-
end
|
1215
|
-
|
1216
|
-
def ran?(migration)
|
1217
|
-
migrated.include?(migration.version.to_i)
|
1218
|
-
end
|
1313
|
+
def ran?(migration)
|
1314
|
+
migrated.include?(migration.version.to_i)
|
1315
|
+
end
|
1219
1316
|
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1317
|
+
# Return true if a valid version is not provided.
|
1318
|
+
def invalid_target?
|
1319
|
+
@target_version && @target_version != 0 && !target
|
1320
|
+
end
|
1224
1321
|
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1322
|
+
def execute_migration_in_transaction(migration)
|
1323
|
+
return if down? && !migrated.include?(migration.version.to_i)
|
1324
|
+
return if up? && migrated.include?(migration.version.to_i)
|
1228
1325
|
|
1229
|
-
|
1326
|
+
Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
|
1230
1327
|
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1328
|
+
ddl_transaction(migration) do
|
1329
|
+
migration.migrate(@direction)
|
1330
|
+
record_version_state_after_migrating(migration.version)
|
1331
|
+
end
|
1332
|
+
rescue => e
|
1333
|
+
msg = +"An error has occurred, "
|
1334
|
+
msg << "this and " if use_transaction?(migration)
|
1335
|
+
msg << "all later migrations canceled:\n\n#{e}"
|
1336
|
+
raise StandardError, msg, e.backtrace
|
1234
1337
|
end
|
1235
|
-
rescue => e
|
1236
|
-
msg = "An error has occurred, "
|
1237
|
-
msg << "this and " if use_transaction?(migration)
|
1238
|
-
msg << "all later migrations canceled:\n\n#{e}"
|
1239
|
-
raise StandardError, msg, e.backtrace
|
1240
|
-
end
|
1241
|
-
|
1242
|
-
def target
|
1243
|
-
migrations.detect { |m| m.version == @target_version }
|
1244
|
-
end
|
1245
1338
|
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1339
|
+
def target
|
1340
|
+
migrations.detect { |m| m.version == @target_version }
|
1341
|
+
end
|
1249
1342
|
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1343
|
+
def finish
|
1344
|
+
migrations.index(target) || migrations.size - 1
|
1345
|
+
end
|
1253
1346
|
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1347
|
+
def start
|
1348
|
+
up? ? 0 : (migrations.index(current) || 0)
|
1349
|
+
end
|
1257
1350
|
|
1258
|
-
|
1259
|
-
|
1260
|
-
|
1351
|
+
def validate(migrations)
|
1352
|
+
name, = migrations.group_by(&:name).find { |_, v| v.length > 1 }
|
1353
|
+
raise DuplicateMigrationNameError.new(name) if name
|
1261
1354
|
|
1262
|
-
|
1263
|
-
|
1264
|
-
migrated.delete(version)
|
1265
|
-
ActiveRecord::SchemaMigration.where(:version => version.to_s).delete_all
|
1266
|
-
else
|
1267
|
-
migrated << version
|
1268
|
-
ActiveRecord::SchemaMigration.create!(version: version.to_s)
|
1355
|
+
version, = migrations.group_by(&:version).find { |_, v| v.length > 1 }
|
1356
|
+
raise DuplicateMigrationVersionError.new(version) if version
|
1269
1357
|
end
|
1270
|
-
end
|
1271
1358
|
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1359
|
+
def record_version_state_after_migrating(version)
|
1360
|
+
if down?
|
1361
|
+
migrated.delete(version)
|
1362
|
+
@schema_migration.delete_by(version: version.to_s)
|
1363
|
+
else
|
1364
|
+
migrated << version
|
1365
|
+
@schema_migration.create!(version: version.to_s)
|
1366
|
+
end
|
1367
|
+
end
|
1280
1368
|
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1369
|
+
def up?
|
1370
|
+
@direction == :up
|
1371
|
+
end
|
1284
1372
|
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
1373
|
+
def down?
|
1374
|
+
@direction == :down
|
1375
|
+
end
|
1288
1376
|
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1377
|
+
# Wrap the migration in a transaction only if supported by the adapter.
|
1378
|
+
def ddl_transaction(migration)
|
1379
|
+
if use_transaction?(migration)
|
1380
|
+
Base.transaction { yield }
|
1381
|
+
else
|
1382
|
+
yield
|
1383
|
+
end
|
1384
|
+
end
|
1292
1385
|
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1386
|
+
def use_transaction?(migration)
|
1387
|
+
!migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions?
|
1388
|
+
end
|
1296
1389
|
|
1297
|
-
|
1298
|
-
|
1299
|
-
if use_transaction?(migration)
|
1300
|
-
Base.transaction { yield }
|
1301
|
-
else
|
1302
|
-
yield
|
1390
|
+
def use_advisory_lock?
|
1391
|
+
Base.connection.advisory_locks_enabled?
|
1303
1392
|
end
|
1304
|
-
end
|
1305
1393
|
|
1306
|
-
|
1307
|
-
|
1308
|
-
|
1394
|
+
def with_advisory_lock
|
1395
|
+
lock_id = generate_migrator_advisory_lock_id
|
1396
|
+
|
1397
|
+
with_advisory_lock_connection do |connection|
|
1398
|
+
got_lock = connection.get_advisory_lock(lock_id)
|
1399
|
+
raise ConcurrentMigrationError unless got_lock
|
1400
|
+
load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock
|
1401
|
+
yield
|
1402
|
+
ensure
|
1403
|
+
if got_lock && !connection.release_advisory_lock(lock_id)
|
1404
|
+
raise ConcurrentMigrationError.new(
|
1405
|
+
ConcurrentMigrationError::RELEASE_LOCK_FAILED_MESSAGE
|
1406
|
+
)
|
1407
|
+
end
|
1408
|
+
end
|
1409
|
+
end
|
1309
1410
|
|
1310
|
-
|
1311
|
-
|
1312
|
-
|
1411
|
+
def with_advisory_lock_connection
|
1412
|
+
pool = ActiveRecord::ConnectionAdapters::ConnectionHandler.new.establish_connection(
|
1413
|
+
ActiveRecord::Base.connection_db_config
|
1414
|
+
)
|
1313
1415
|
|
1314
|
-
|
1315
|
-
|
1316
|
-
|
1317
|
-
|
1318
|
-
load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock
|
1319
|
-
yield
|
1320
|
-
ensure
|
1321
|
-
Base.connection.release_advisory_lock(lock_id) if got_lock
|
1322
|
-
end
|
1416
|
+
pool.with_connection { |connection| yield(connection) }
|
1417
|
+
ensure
|
1418
|
+
pool&.disconnect!
|
1419
|
+
end
|
1323
1420
|
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1327
|
-
|
1328
|
-
|
1421
|
+
MIGRATOR_SALT = 2053462845
|
1422
|
+
def generate_migrator_advisory_lock_id
|
1423
|
+
db_name_hash = Zlib.crc32(Base.connection.current_database)
|
1424
|
+
MIGRATOR_SALT * db_name_hash
|
1425
|
+
end
|
1329
1426
|
end
|
1330
1427
|
end
|