activerecord 5.2.4.4 → 6.0.3.4
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 +777 -552
- data/MIT-LICENSE +3 -1
- data/README.rdoc +5 -3
- data/examples/performance.rb +1 -1
- data/lib/active_record.rb +10 -2
- data/lib/active_record/advisory_lock_base.rb +18 -0
- data/lib/active_record/aggregations.rb +4 -3
- data/lib/active_record/association_relation.rb +10 -8
- data/lib/active_record/associations.rb +21 -16
- data/lib/active_record/associations/alias_tracker.rb +0 -1
- data/lib/active_record/associations/association.rb +56 -19
- data/lib/active_record/associations/association_scope.rb +4 -6
- data/lib/active_record/associations/belongs_to_association.rb +36 -42
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +19 -52
- data/lib/active_record/associations/builder/collection_association.rb +3 -13
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -40
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_association.rb +12 -23
- data/lib/active_record/associations/collection_proxy.rb +13 -17
- data/lib/active_record/associations/foreign_association.rb +7 -0
- data/lib/active_record/associations/has_many_association.rb +2 -11
- data/lib/active_record/associations/has_many_through_association.rb +14 -14
- data/lib/active_record/associations/has_one_association.rb +28 -30
- data/lib/active_record/associations/has_one_through_association.rb +5 -5
- data/lib/active_record/associations/join_dependency.rb +37 -28
- data/lib/active_record/associations/join_dependency/join_association.rb +9 -10
- data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
- data/lib/active_record/associations/preloader.rb +39 -32
- data/lib/active_record/associations/preloader/association.rb +38 -36
- data/lib/active_record/associations/preloader/through_association.rb +48 -39
- data/lib/active_record/associations/singular_association.rb +2 -16
- data/lib/active_record/attribute_assignment.rb +7 -11
- data/lib/active_record/attribute_decorators.rb +0 -2
- data/lib/active_record/attribute_methods.rb +28 -100
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -2
- data/lib/active_record/attribute_methods/dirty.rb +111 -40
- data/lib/active_record/attribute_methods/primary_key.rb +15 -24
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +15 -54
- data/lib/active_record/attribute_methods/serialization.rb +1 -2
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -3
- data/lib/active_record/attribute_methods/write.rb +17 -25
- data/lib/active_record/attributes.rb +13 -1
- data/lib/active_record/autosave_association.rb +3 -5
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +6 -21
- data/lib/active_record/coders/yaml_column.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +103 -18
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +102 -124
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -9
- data/lib/active_record/connection_adapters/abstract/quoting.rb +68 -17
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +20 -14
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +100 -72
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +175 -79
- data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +191 -43
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +142 -215
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +54 -45
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +70 -14
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +0 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +4 -6
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +132 -16
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -10
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +26 -1
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +63 -75
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
- data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +168 -75
- data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +119 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -12
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +135 -146
- data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
- data/lib/active_record/connection_handling.rb +139 -26
- data/lib/active_record/core.rb +103 -61
- data/lib/active_record/counter_cache.rb +8 -30
- data/lib/active_record/database_configurations.rb +233 -0
- data/lib/active_record/database_configurations/database_config.rb +37 -0
- data/lib/active_record/database_configurations/hash_config.rb +50 -0
- data/lib/active_record/database_configurations/url_config.rb +78 -0
- data/lib/active_record/dynamic_matchers.rb +3 -4
- data/lib/active_record/enum.rb +37 -7
- data/lib/active_record/errors.rb +15 -7
- data/lib/active_record/explain.rb +1 -2
- data/lib/active_record/fixture_set/model_metadata.rb +33 -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 +144 -474
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +13 -6
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +68 -16
- data/lib/active_record/internal_metadata.rb +11 -3
- data/lib/active_record/locking/optimistic.rb +5 -7
- data/lib/active_record/locking/pessimistic.rb +3 -3
- data/lib/active_record/log_subscriber.rb +8 -27
- data/lib/active_record/middleware/database_selector.rb +74 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +87 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/migration.rb +104 -85
- data/lib/active_record/migration/command_recorder.rb +54 -22
- data/lib/active_record/migration/compatibility.rb +79 -52
- data/lib/active_record/migration/join_table.rb +0 -1
- data/lib/active_record/model_schema.rb +33 -11
- data/lib/active_record/nested_attributes.rb +2 -4
- data/lib/active_record/no_touching.rb +9 -2
- data/lib/active_record/null_relation.rb +0 -1
- data/lib/active_record/persistence.rb +232 -29
- data/lib/active_record/query_cache.rb +11 -4
- data/lib/active_record/querying.rb +33 -21
- data/lib/active_record/railtie.rb +80 -43
- data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
- data/lib/active_record/railties/controller_runtime.rb +30 -35
- data/lib/active_record/railties/databases.rake +199 -46
- data/lib/active_record/reflection.rb +40 -38
- data/lib/active_record/relation.rb +322 -80
- data/lib/active_record/relation/batches.rb +13 -11
- data/lib/active_record/relation/calculations.rb +54 -48
- data/lib/active_record/relation/delegation.rb +33 -49
- data/lib/active_record/relation/finder_methods.rb +23 -28
- data/lib/active_record/relation/from_clause.rb +4 -0
- data/lib/active_record/relation/merger.rb +11 -21
- data/lib/active_record/relation/predicate_builder.rb +5 -11
- data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
- data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
- data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
- data/lib/active_record/relation/query_attribute.rb +13 -8
- data/lib/active_record/relation/query_methods.rb +221 -70
- data/lib/active_record/relation/spawn_methods.rb +1 -2
- data/lib/active_record/relation/where_clause.rb +14 -11
- data/lib/active_record/relation/where_clause_factory.rb +1 -2
- data/lib/active_record/result.rb +30 -12
- data/lib/active_record/sanitization.rb +32 -40
- data/lib/active_record/schema.rb +2 -11
- data/lib/active_record/schema_dumper.rb +22 -7
- data/lib/active_record/schema_migration.rb +6 -2
- data/lib/active_record/scoping.rb +8 -9
- data/lib/active_record/scoping/default.rb +4 -6
- data/lib/active_record/scoping/named.rb +21 -17
- data/lib/active_record/statement_cache.rb +30 -3
- data/lib/active_record/store.rb +87 -8
- data/lib/active_record/suppressor.rb +2 -2
- data/lib/active_record/table_metadata.rb +23 -15
- data/lib/active_record/tasks/database_tasks.rb +194 -25
- data/lib/active_record/tasks/mysql_database_tasks.rb +5 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -8
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -9
- data/lib/active_record/test_databases.rb +23 -0
- data/lib/active_record/test_fixtures.rb +225 -0
- data/lib/active_record/timestamp.rb +39 -26
- data/lib/active_record/touch_later.rb +5 -4
- data/lib/active_record/transactions.rb +64 -73
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/type.rb +3 -5
- data/lib/active_record/type/adapter_specific_registry.rb +3 -13
- data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
- data/lib/active_record/type/serialized.rb +0 -1
- data/lib/active_record/type/type_map.rb +0 -1
- data/lib/active_record/type/unsigned_integer.rb +0 -1
- data/lib/active_record/type_caster/connection.rb +15 -14
- data/lib/active_record/type_caster/map.rb +1 -4
- data/lib/active_record/validations.rb +3 -3
- data/lib/active_record/validations/associated.rb +1 -2
- data/lib/active_record/validations/uniqueness.rb +15 -27
- data/lib/arel.rb +62 -0
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes.rb +22 -0
- data/lib/arel/attributes/attribute.rb +37 -0
- data/lib/arel/collectors/bind.rb +24 -0
- data/lib/arel/collectors/composite.rb +31 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +20 -0
- data/lib/arel/collectors/substitute_binds.rb +28 -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 +68 -0
- data/lib/arel/nodes/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +52 -0
- data/lib/arel/nodes/bind_param.rb +36 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +50 -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 +18 -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 +8 -0
- data/lib/arel/nodes/in.rb +8 -0
- data/lib/arel/nodes/infix_operation.rb +80 -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 +50 -0
- data/lib/arel/nodes/node_expression.rb +13 -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 +16 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +27 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +45 -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 +256 -0
- data/lib/arel/select_manager.rb +271 -0
- data/lib/arel/table.rb +110 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors.rb +20 -0
- data/lib/arel/visitors/depth_first.rb +203 -0
- data/lib/arel/visitors/dot.rb +296 -0
- data/lib/arel/visitors/ibm_db.rb +34 -0
- data/lib/arel/visitors/informix.rb +62 -0
- data/lib/arel/visitors/mssql.rb +156 -0
- data/lib/arel/visitors/mysql.rb +83 -0
- data/lib/arel/visitors/oracle.rb +158 -0
- data/lib/arel/visitors/oracle12.rb +65 -0
- data/lib/arel/visitors/postgresql.rb +109 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +888 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/visitors/where_sql.rb +22 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- data/lib/rails/generators/active_record/migration.rb +14 -2
- data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +115 -29
- data/lib/active_record/collection_cache_key.rb +0 -53
@@ -14,9 +14,9 @@ module ActiveRecord
|
|
14
14
|
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
|
15
15
|
#
|
16
16
|
# Account.transaction do
|
17
|
-
# # select * from accounts where name = 'shugo' limit 1 for update
|
18
|
-
# shugo = Account.
|
19
|
-
# 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")
|
20
20
|
# shugo.balance -= 100
|
21
21
|
# shugo.save!
|
22
22
|
# yuko.balance += 100
|
@@ -4,6 +4,8 @@ module ActiveRecord
|
|
4
4
|
class LogSubscriber < ActiveSupport::LogSubscriber
|
5
5
|
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
|
6
6
|
|
7
|
+
class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
|
8
|
+
|
7
9
|
def self.runtime=(value)
|
8
10
|
ActiveRecord::RuntimeRegistry.sql_runtime = value
|
9
11
|
end
|
@@ -38,7 +40,7 @@ module ActiveRecord
|
|
38
40
|
end
|
39
41
|
|
40
42
|
name = colorize_payload_name(name, payload[:name])
|
41
|
-
sql = color(sql, sql_color(sql), true)
|
43
|
+
sql = color(sql, sql_color(sql), true) if colorize_logging
|
42
44
|
|
43
45
|
debug " #{name} #{sql}#{binds}"
|
44
46
|
end
|
@@ -100,36 +102,15 @@ module ActiveRecord
|
|
100
102
|
end
|
101
103
|
|
102
104
|
def log_query_source
|
103
|
-
|
104
|
-
|
105
|
-
if source_line
|
106
|
-
if defined?(::Rails.root)
|
107
|
-
app_root = "#{::Rails.root.to_s}/".freeze
|
108
|
-
source_line = source_line.sub(app_root, "")
|
109
|
-
end
|
110
|
-
|
111
|
-
logger.debug(" ↳ #{ source_line }:#{ line_number }")
|
112
|
-
end
|
113
|
-
end
|
105
|
+
source = extract_query_source_location(caller)
|
114
106
|
|
115
|
-
|
116
|
-
|
117
|
-
frame.absolute_path && !ignored_callstack(frame.absolute_path)
|
107
|
+
if source
|
108
|
+
logger.debug(" ↳ #{source}")
|
118
109
|
end
|
119
|
-
|
120
|
-
offending_line = line || callstack.first
|
121
|
-
|
122
|
-
[
|
123
|
-
offending_line.path,
|
124
|
-
offending_line.lineno
|
125
|
-
]
|
126
110
|
end
|
127
111
|
|
128
|
-
|
129
|
-
|
130
|
-
def ignored_callstack(path)
|
131
|
-
path.start_with?(RAILS_GEM_ROOT) ||
|
132
|
-
path.start_with?(RbConfig::CONFIG["rubylibdir"])
|
112
|
+
def extract_query_source_location(locations)
|
113
|
+
backtrace_cleaner.clean(locations.lazy).first
|
133
114
|
end
|
134
115
|
end
|
135
116
|
end
|
@@ -0,0 +1,74 @@
|
|
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
|
+
if reading_request?(request)
|
63
|
+
resolver.read(&blk)
|
64
|
+
else
|
65
|
+
resolver.write(&blk)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def reading_request?(request)
|
70
|
+
request.get? || request.head?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record/middleware/database_selector/resolver/session"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Middleware
|
7
|
+
class DatabaseSelector
|
8
|
+
# The Resolver class is used by the DatabaseSelector middleware to
|
9
|
+
# determine which database the request should use.
|
10
|
+
#
|
11
|
+
# To change the behavior of the Resolver class in your application,
|
12
|
+
# create a custom resolver class that inherits from
|
13
|
+
# DatabaseSelector::Resolver and implements the methods that need to
|
14
|
+
# be changed.
|
15
|
+
#
|
16
|
+
# By default the Resolver class will send read traffic to the replica
|
17
|
+
# if it's been 2 seconds since the last write.
|
18
|
+
class Resolver # :nodoc:
|
19
|
+
SEND_TO_REPLICA_DELAY = 2.seconds
|
20
|
+
|
21
|
+
def self.call(context, options = {})
|
22
|
+
new(context, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(context, options = {})
|
26
|
+
@context = context
|
27
|
+
@options = options
|
28
|
+
@delay = @options && @options[:delay] ? @options[:delay] : SEND_TO_REPLICA_DELAY
|
29
|
+
@instrumenter = ActiveSupport::Notifications.instrumenter
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :context, :delay, :instrumenter
|
33
|
+
|
34
|
+
def read(&blk)
|
35
|
+
if read_from_primary?
|
36
|
+
read_from_primary(&blk)
|
37
|
+
else
|
38
|
+
read_from_replica(&blk)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def write(&blk)
|
43
|
+
write_to_primary(&blk)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def read_from_primary(&blk)
|
48
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: true) do
|
49
|
+
instrumenter.instrument("database_selector.active_record.read_from_primary") do
|
50
|
+
yield
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def read_from_replica(&blk)
|
56
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.reading_role, prevent_writes: true) do
|
57
|
+
instrumenter.instrument("database_selector.active_record.read_from_replica") do
|
58
|
+
yield
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def write_to_primary(&blk)
|
64
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: false) do
|
65
|
+
instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
|
66
|
+
yield
|
67
|
+
ensure
|
68
|
+
context.update_last_write_timestamp
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def read_from_primary?
|
74
|
+
!time_since_last_write_ok?
|
75
|
+
end
|
76
|
+
|
77
|
+
def send_to_replica_delay
|
78
|
+
delay
|
79
|
+
end
|
80
|
+
|
81
|
+
def time_since_last_write_ok?
|
82
|
+
Time.now - context.last_write_timestamp >= send_to_replica_delay
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,45 @@
|
|
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
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,11 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "benchmark"
|
3
4
|
require "set"
|
4
5
|
require "zlib"
|
5
6
|
require "active_support/core_ext/module/attribute_accessors"
|
7
|
+
require "active_support/actionable_error"
|
6
8
|
|
7
9
|
module ActiveRecord
|
8
|
-
class MigrationError < ActiveRecordError#:nodoc:
|
10
|
+
class MigrationError < ActiveRecordError #:nodoc:
|
9
11
|
def initialize(message = nil)
|
10
12
|
message = "\n\n#{message}\n\n" if message
|
11
13
|
super
|
@@ -22,7 +24,7 @@ module ActiveRecord
|
|
22
24
|
# t.string :zipcode
|
23
25
|
# end
|
24
26
|
#
|
25
|
-
# execute
|
27
|
+
# execute <<~SQL
|
26
28
|
# ALTER TABLE distributors
|
27
29
|
# ADD CONSTRAINT zipchk
|
28
30
|
# CHECK (char_length(zipcode) = 5) NO INHERIT;
|
@@ -40,7 +42,7 @@ module ActiveRecord
|
|
40
42
|
# t.string :zipcode
|
41
43
|
# end
|
42
44
|
#
|
43
|
-
# execute
|
45
|
+
# execute <<~SQL
|
44
46
|
# ALTER TABLE distributors
|
45
47
|
# ADD CONSTRAINT zipchk
|
46
48
|
# CHECK (char_length(zipcode) = 5) NO INHERIT;
|
@@ -48,7 +50,7 @@ module ActiveRecord
|
|
48
50
|
# end
|
49
51
|
#
|
50
52
|
# def down
|
51
|
-
# execute
|
53
|
+
# execute <<~SQL
|
52
54
|
# ALTER TABLE distributors
|
53
55
|
# DROP CONSTRAINT zipchk
|
54
56
|
# SQL
|
@@ -67,7 +69,7 @@ module ActiveRecord
|
|
67
69
|
#
|
68
70
|
# reversible do |dir|
|
69
71
|
# dir.up do
|
70
|
-
# execute
|
72
|
+
# execute <<~SQL
|
71
73
|
# ALTER TABLE distributors
|
72
74
|
# ADD CONSTRAINT zipchk
|
73
75
|
# CHECK (char_length(zipcode) = 5) NO INHERIT;
|
@@ -75,7 +77,7 @@ module ActiveRecord
|
|
75
77
|
# end
|
76
78
|
#
|
77
79
|
# dir.down do
|
78
|
-
# execute
|
80
|
+
# execute <<~SQL
|
79
81
|
# ALTER TABLE distributors
|
80
82
|
# DROP CONSTRAINT zipchk
|
81
83
|
# SQL
|
@@ -86,7 +88,7 @@ module ActiveRecord
|
|
86
88
|
class IrreversibleMigration < MigrationError
|
87
89
|
end
|
88
90
|
|
89
|
-
class DuplicateMigrationVersionError < MigrationError#:nodoc:
|
91
|
+
class DuplicateMigrationVersionError < MigrationError #:nodoc:
|
90
92
|
def initialize(version = nil)
|
91
93
|
if version
|
92
94
|
super("Multiple migrations have the version number #{version}.")
|
@@ -96,7 +98,7 @@ module ActiveRecord
|
|
96
98
|
end
|
97
99
|
end
|
98
100
|
|
99
|
-
class DuplicateMigrationNameError < MigrationError#:nodoc:
|
101
|
+
class DuplicateMigrationNameError < MigrationError #:nodoc:
|
100
102
|
def initialize(name = nil)
|
101
103
|
if name
|
102
104
|
super("Multiple migrations have the name #{name}.")
|
@@ -116,7 +118,7 @@ module ActiveRecord
|
|
116
118
|
end
|
117
119
|
end
|
118
120
|
|
119
|
-
class IllegalMigrationNameError < MigrationError#:nodoc:
|
121
|
+
class IllegalMigrationNameError < MigrationError #:nodoc:
|
120
122
|
def initialize(name = nil)
|
121
123
|
if name
|
122
124
|
super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed).")
|
@@ -126,12 +128,18 @@ module ActiveRecord
|
|
126
128
|
end
|
127
129
|
end
|
128
130
|
|
129
|
-
class PendingMigrationError < MigrationError#:nodoc:
|
131
|
+
class PendingMigrationError < MigrationError #:nodoc:
|
132
|
+
include ActiveSupport::ActionableError
|
133
|
+
|
134
|
+
action "Run pending migrations" do
|
135
|
+
ActiveRecord::Tasks::DatabaseTasks.migrate
|
136
|
+
end
|
137
|
+
|
130
138
|
def initialize(message = nil)
|
131
139
|
if !message && defined?(Rails.env)
|
132
|
-
super("Migrations are pending. To resolve this issue, run:\n\n
|
140
|
+
super("Migrations are pending. To resolve this issue, run:\n\n rails db:migrate RAILS_ENV=#{::Rails.env}")
|
133
141
|
elsif !message
|
134
|
-
super("Migrations are pending. To resolve this issue, run:\n\n
|
142
|
+
super("Migrations are pending. To resolve this issue, run:\n\n rails db:migrate")
|
135
143
|
else
|
136
144
|
super
|
137
145
|
end
|
@@ -139,8 +147,8 @@ module ActiveRecord
|
|
139
147
|
end
|
140
148
|
|
141
149
|
class ConcurrentMigrationError < MigrationError #:nodoc:
|
142
|
-
DEFAULT_MESSAGE = "Cannot run migrations because another migration process is currently running."
|
143
|
-
RELEASE_LOCK_FAILED_MESSAGE = "Failed to release advisory lock"
|
150
|
+
DEFAULT_MESSAGE = "Cannot run migrations because another migration process is currently running."
|
151
|
+
RELEASE_LOCK_FAILED_MESSAGE = "Failed to release advisory lock"
|
144
152
|
|
145
153
|
def initialize(message = DEFAULT_MESSAGE)
|
146
154
|
super
|
@@ -149,7 +157,7 @@ module ActiveRecord
|
|
149
157
|
|
150
158
|
class NoEnvironmentInSchemaError < MigrationError #:nodoc:
|
151
159
|
def initialize
|
152
|
-
msg = "Environment data not found in the schema. To resolve this issue, run: \n\n
|
160
|
+
msg = "Environment data not found in the schema. To resolve this issue, run: \n\n rails db:environment:set"
|
153
161
|
if defined?(Rails.env)
|
154
162
|
super("#{msg} RAILS_ENV=#{::Rails.env}")
|
155
163
|
else
|
@@ -160,7 +168,7 @@ module ActiveRecord
|
|
160
168
|
|
161
169
|
class ProtectedEnvironmentError < ActiveRecordError #:nodoc:
|
162
170
|
def initialize(env = "production")
|
163
|
-
msg = "You are attempting to run a destructive action against your '#{env}' database.\n"
|
171
|
+
msg = +"You are attempting to run a destructive action against your '#{env}' database.\n"
|
164
172
|
msg << "If you are sure you want to continue, run the same command with the environment variable:\n"
|
165
173
|
msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
|
166
174
|
super(msg)
|
@@ -169,10 +177,10 @@ module ActiveRecord
|
|
169
177
|
|
170
178
|
class EnvironmentMismatchError < ActiveRecordError
|
171
179
|
def initialize(current: nil, stored: nil)
|
172
|
-
msg =
|
180
|
+
msg = +"You are attempting to modify a database that was last run in `#{ stored }` environment.\n"
|
173
181
|
msg << "You are running in `#{ current }` environment. "
|
174
182
|
msg << "If you are sure you want to continue, first set the environment using:\n\n"
|
175
|
-
msg << "
|
183
|
+
msg << " rails db:environment:set"
|
176
184
|
if defined?(Rails.env)
|
177
185
|
super("#{msg} RAILS_ENV=#{::Rails.env}\n\n")
|
178
186
|
else
|
@@ -307,7 +315,7 @@ module ActiveRecord
|
|
307
315
|
# named +column_name+ from the table called +table_name+.
|
308
316
|
# * <tt>remove_columns(table_name, *column_names)</tt>: Removes the given
|
309
317
|
# columns from the table definition.
|
310
|
-
# * <tt>remove_foreign_key(from_table,
|
318
|
+
# * <tt>remove_foreign_key(from_table, to_table = nil, **options)</tt>: Removes the
|
311
319
|
# given foreign key from the table called +table_name+.
|
312
320
|
# * <tt>remove_index(table_name, column: column_names)</tt>: Removes the index
|
313
321
|
# specified by +column_names+.
|
@@ -351,7 +359,7 @@ module ActiveRecord
|
|
351
359
|
# <tt>rails db:migrate</tt>. This will update the database by running all of the
|
352
360
|
# pending migrations, creating the <tt>schema_migrations</tt> table
|
353
361
|
# (see "About the schema_migrations table" section below) if missing. It will also
|
354
|
-
# invoke the db:schema:dump
|
362
|
+
# invoke the db:schema:dump command, which will update your db/schema.rb file
|
355
363
|
# to match the structure of your database.
|
356
364
|
#
|
357
365
|
# To roll the database back to a previous migration version, use
|
@@ -486,9 +494,9 @@ module ActiveRecord
|
|
486
494
|
# This migration will create the horses table for you on the way up, and
|
487
495
|
# automatically figure out how to drop the table on the way down.
|
488
496
|
#
|
489
|
-
# Some commands
|
490
|
-
#
|
491
|
-
#
|
497
|
+
# Some commands cannot be reversed. If you care to define how to move up
|
498
|
+
# and down in these cases, you should define the +up+ and +down+ methods
|
499
|
+
# as before.
|
492
500
|
#
|
493
501
|
# If a command cannot be reversed, an
|
494
502
|
# <tt>ActiveRecord::IrreversibleMigration</tt> exception will be raised when
|
@@ -519,10 +527,10 @@ module ActiveRecord
|
|
519
527
|
autoload :Compatibility, "active_record/migration/compatibility"
|
520
528
|
|
521
529
|
# This must be defined before the inherited hook, below
|
522
|
-
class Current < Migration
|
530
|
+
class Current < Migration #:nodoc:
|
523
531
|
end
|
524
532
|
|
525
|
-
def self.inherited(subclass)
|
533
|
+
def self.inherited(subclass) #:nodoc:
|
526
534
|
super
|
527
535
|
if subclass.superclass == Migration
|
528
536
|
raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \
|
@@ -540,7 +548,7 @@ module ActiveRecord
|
|
540
548
|
ActiveRecord::VERSION::STRING.to_f
|
541
549
|
end
|
542
550
|
|
543
|
-
MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/
|
551
|
+
MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ #:nodoc:
|
544
552
|
|
545
553
|
# This class is used to verify that all migrations have been run before
|
546
554
|
# loading a web page if <tt>config.active_record.migration_error</tt> is set to :page_load
|
@@ -560,17 +568,16 @@ module ActiveRecord
|
|
560
568
|
end
|
561
569
|
|
562
570
|
private
|
563
|
-
|
564
571
|
def connection
|
565
572
|
ActiveRecord::Base.connection
|
566
573
|
end
|
567
574
|
end
|
568
575
|
|
569
576
|
class << self
|
570
|
-
attr_accessor :delegate
|
571
|
-
attr_accessor :disable_ddl_transaction
|
577
|
+
attr_accessor :delegate #:nodoc:
|
578
|
+
attr_accessor :disable_ddl_transaction #:nodoc:
|
572
579
|
|
573
|
-
def nearest_delegate
|
580
|
+
def nearest_delegate #:nodoc:
|
574
581
|
delegate || superclass.nearest_delegate
|
575
582
|
end
|
576
583
|
|
@@ -580,29 +587,38 @@ module ActiveRecord
|
|
580
587
|
end
|
581
588
|
|
582
589
|
def load_schema_if_pending!
|
583
|
-
|
590
|
+
current_config = Base.connection_config
|
591
|
+
all_configs = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env)
|
592
|
+
|
593
|
+
needs_update = !all_configs.all? do |db_config|
|
594
|
+
Tasks::DatabaseTasks.schema_up_to_date?(db_config.config, ActiveRecord::Base.schema_format, nil, Rails.env, db_config.spec_name)
|
595
|
+
end
|
596
|
+
|
597
|
+
if needs_update
|
584
598
|
# Roundtrip to Rake to allow plugins to hook into database initialization.
|
585
599
|
root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root
|
586
600
|
FileUtils.cd(root) do
|
587
|
-
current_config = Base.connection_config
|
588
601
|
Base.clear_all_connections!
|
589
602
|
system("bin/rails db:test:prepare")
|
590
|
-
# Establish a new connection, the old database may be gone (db:test:prepare uses purge)
|
591
|
-
Base.establish_connection(current_config)
|
592
603
|
end
|
593
|
-
check_pending!
|
594
604
|
end
|
605
|
+
|
606
|
+
# Establish a new connection, the old database may be gone (db:test:prepare uses purge)
|
607
|
+
Base.establish_connection(current_config)
|
608
|
+
|
609
|
+
check_pending!
|
595
610
|
end
|
596
611
|
|
597
|
-
def maintain_test_schema!
|
612
|
+
def maintain_test_schema! #:nodoc:
|
598
613
|
if ActiveRecord::Base.maintain_test_schema
|
599
614
|
suppress_messages { load_schema_if_pending! }
|
600
615
|
end
|
601
616
|
end
|
602
617
|
|
603
|
-
def method_missing(name, *args, &block)
|
618
|
+
def method_missing(name, *args, &block) #:nodoc:
|
604
619
|
nearest_delegate.send(name, *args, &block)
|
605
620
|
end
|
621
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
606
622
|
|
607
623
|
def migrate(direction)
|
608
624
|
new.migrate direction
|
@@ -617,7 +633,7 @@ module ActiveRecord
|
|
617
633
|
end
|
618
634
|
end
|
619
635
|
|
620
|
-
def disable_ddl_transaction
|
636
|
+
def disable_ddl_transaction #:nodoc:
|
621
637
|
self.class.disable_ddl_transaction
|
622
638
|
end
|
623
639
|
|
@@ -677,15 +693,13 @@ module ActiveRecord
|
|
677
693
|
if connection.respond_to? :revert
|
678
694
|
connection.revert { yield }
|
679
695
|
else
|
680
|
-
recorder =
|
696
|
+
recorder = command_recorder
|
681
697
|
@connection = recorder
|
682
698
|
suppress_messages do
|
683
699
|
connection.revert { yield }
|
684
700
|
end
|
685
701
|
@connection = recorder.delegate
|
686
|
-
recorder.
|
687
|
-
send(cmd, *args, &block)
|
688
|
-
end
|
702
|
+
recorder.replay(self)
|
689
703
|
end
|
690
704
|
end
|
691
705
|
end
|
@@ -694,7 +708,7 @@ module ActiveRecord
|
|
694
708
|
connection.respond_to?(:reverting) && connection.reverting
|
695
709
|
end
|
696
710
|
|
697
|
-
ReversibleBlockHelper = Struct.new(:reverting) do
|
711
|
+
ReversibleBlockHelper = Struct.new(:reverting) do #:nodoc:
|
698
712
|
def up
|
699
713
|
yield unless reverting
|
700
714
|
end
|
@@ -830,10 +844,14 @@ module ActiveRecord
|
|
830
844
|
write "== %s %s" % [text, "=" * length]
|
831
845
|
end
|
832
846
|
|
847
|
+
# Takes a message argument and outputs it as is.
|
848
|
+
# A second boolean argument can be passed to specify whether to indent or not.
|
833
849
|
def say(message, subitem = false)
|
834
850
|
write "#{subitem ? " ->" : "--"} #{message}"
|
835
851
|
end
|
836
852
|
|
853
|
+
# Outputs text along with how long it took to run its block.
|
854
|
+
# If the block returns an integer it assumes it is the number of rows affected.
|
837
855
|
def say_with_time(message)
|
838
856
|
say(message)
|
839
857
|
result = nil
|
@@ -843,6 +861,7 @@ module ActiveRecord
|
|
843
861
|
result
|
844
862
|
end
|
845
863
|
|
864
|
+
# Takes a block as an argument and suppresses any output generated by the block.
|
846
865
|
def suppress_messages
|
847
866
|
save, self.verbose = verbose, false
|
848
867
|
yield
|
@@ -871,21 +890,23 @@ module ActiveRecord
|
|
871
890
|
connection.send(method, *arguments, &block)
|
872
891
|
end
|
873
892
|
end
|
893
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
874
894
|
|
875
895
|
def copy(destination, sources, options = {})
|
876
896
|
copied = []
|
897
|
+
schema_migration = options[:schema_migration] || ActiveRecord::SchemaMigration
|
877
898
|
|
878
899
|
FileUtils.mkdir_p(destination) unless File.exist?(destination)
|
879
900
|
|
880
|
-
destination_migrations = ActiveRecord::MigrationContext.new(destination).migrations
|
901
|
+
destination_migrations = ActiveRecord::MigrationContext.new(destination, schema_migration).migrations
|
881
902
|
last = destination_migrations.last
|
882
903
|
sources.each do |scope, path|
|
883
|
-
source_migrations = ActiveRecord::MigrationContext.new(path).migrations
|
904
|
+
source_migrations = ActiveRecord::MigrationContext.new(path, schema_migration).migrations
|
884
905
|
|
885
906
|
source_migrations.each do |migration|
|
886
907
|
source = File.binread(migration.filename)
|
887
908
|
inserted_comment = "# This migration comes from #{scope} (originally #{migration.version})\n"
|
888
|
-
magic_comments = ""
|
909
|
+
magic_comments = +""
|
889
910
|
loop do
|
890
911
|
# If we have a magic comment in the original migration,
|
891
912
|
# insert our comment after the first newline(end of the magic comment line)
|
@@ -956,6 +977,10 @@ module ActiveRecord
|
|
956
977
|
yield
|
957
978
|
end
|
958
979
|
end
|
980
|
+
|
981
|
+
def command_recorder
|
982
|
+
CommandRecorder.new(connection)
|
983
|
+
end
|
959
984
|
end
|
960
985
|
|
961
986
|
# MigrationProxy is used to defer loading of the actual migration classes
|
@@ -977,7 +1002,6 @@ module ActiveRecord
|
|
977
1002
|
delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration
|
978
1003
|
|
979
1004
|
private
|
980
|
-
|
981
1005
|
def migration
|
982
1006
|
@migration ||= load_migration
|
983
1007
|
end
|
@@ -998,11 +1022,12 @@ module ActiveRecord
|
|
998
1022
|
end
|
999
1023
|
end
|
1000
1024
|
|
1001
|
-
class MigrationContext
|
1002
|
-
attr_reader :migrations_paths
|
1025
|
+
class MigrationContext #:nodoc:
|
1026
|
+
attr_reader :migrations_paths, :schema_migration
|
1003
1027
|
|
1004
|
-
def initialize(migrations_paths)
|
1028
|
+
def initialize(migrations_paths, schema_migration)
|
1005
1029
|
@migrations_paths = migrations_paths
|
1030
|
+
@schema_migration = schema_migration
|
1006
1031
|
end
|
1007
1032
|
|
1008
1033
|
def migrate(target_version = nil, &block)
|
@@ -1033,7 +1058,7 @@ module ActiveRecord
|
|
1033
1058
|
migrations
|
1034
1059
|
end
|
1035
1060
|
|
1036
|
-
Migrator.new(:up, selected_migrations, target_version).migrate
|
1061
|
+
Migrator.new(:up, selected_migrations, schema_migration, target_version).migrate
|
1037
1062
|
end
|
1038
1063
|
|
1039
1064
|
def down(target_version = nil)
|
@@ -1043,20 +1068,20 @@ module ActiveRecord
|
|
1043
1068
|
migrations
|
1044
1069
|
end
|
1045
1070
|
|
1046
|
-
Migrator.new(:down, selected_migrations, target_version).migrate
|
1071
|
+
Migrator.new(:down, selected_migrations, schema_migration, target_version).migrate
|
1047
1072
|
end
|
1048
1073
|
|
1049
1074
|
def run(direction, target_version)
|
1050
|
-
Migrator.new(direction, migrations, target_version).run
|
1075
|
+
Migrator.new(direction, migrations, schema_migration, target_version).run
|
1051
1076
|
end
|
1052
1077
|
|
1053
1078
|
def open
|
1054
|
-
Migrator.new(:up, migrations,
|
1079
|
+
Migrator.new(:up, migrations, schema_migration)
|
1055
1080
|
end
|
1056
1081
|
|
1057
1082
|
def get_all_versions
|
1058
|
-
if
|
1059
|
-
|
1083
|
+
if schema_migration.table_exists?
|
1084
|
+
schema_migration.all_versions.map(&:to_i)
|
1060
1085
|
else
|
1061
1086
|
[]
|
1062
1087
|
end
|
@@ -1079,10 +1104,6 @@ module ActiveRecord
|
|
1079
1104
|
migrations.last || NullMigration.new
|
1080
1105
|
end
|
1081
1106
|
|
1082
|
-
def parse_migration_filename(filename) # :nodoc:
|
1083
|
-
File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
|
1084
|
-
end
|
1085
|
-
|
1086
1107
|
def migrations
|
1087
1108
|
migrations = migration_files.map do |file|
|
1088
1109
|
version, name, scope = parse_migration_filename(file)
|
@@ -1097,12 +1118,12 @@ module ActiveRecord
|
|
1097
1118
|
end
|
1098
1119
|
|
1099
1120
|
def migrations_status
|
1100
|
-
db_list =
|
1121
|
+
db_list = schema_migration.normalized_versions
|
1101
1122
|
|
1102
1123
|
file_list = migration_files.map do |file|
|
1103
1124
|
version, name, scope = parse_migration_filename(file)
|
1104
1125
|
raise IllegalMigrationNameError.new(file) unless version
|
1105
|
-
version =
|
1126
|
+
version = schema_migration.normalize_migration_number(version)
|
1106
1127
|
status = db_list.delete(version) ? "up" : "down"
|
1107
1128
|
[status, version, (name + scope).humanize]
|
1108
1129
|
end.compact
|
@@ -1114,11 +1135,6 @@ module ActiveRecord
|
|
1114
1135
|
(db_list + file_list).sort_by { |_, version, _| version }
|
1115
1136
|
end
|
1116
1137
|
|
1117
|
-
def migration_files
|
1118
|
-
paths = Array(migrations_paths)
|
1119
|
-
Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
|
1120
|
-
end
|
1121
|
-
|
1122
1138
|
def current_environment
|
1123
1139
|
ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
|
1124
1140
|
end
|
@@ -1137,8 +1153,17 @@ module ActiveRecord
|
|
1137
1153
|
end
|
1138
1154
|
|
1139
1155
|
private
|
1156
|
+
def migration_files
|
1157
|
+
paths = Array(migrations_paths)
|
1158
|
+
Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
def parse_migration_filename(filename)
|
1162
|
+
File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
|
1163
|
+
end
|
1164
|
+
|
1140
1165
|
def move(direction, steps)
|
1141
|
-
migrator = Migrator.new(direction, migrations)
|
1166
|
+
migrator = Migrator.new(direction, migrations, schema_migration)
|
1142
1167
|
|
1143
1168
|
if current_version != 0 && !migrator.current_migration
|
1144
1169
|
raise UnknownMigrationVersionError.new(current_version)
|
@@ -1161,30 +1186,24 @@ module ActiveRecord
|
|
1161
1186
|
class << self
|
1162
1187
|
attr_accessor :migrations_paths
|
1163
1188
|
|
1164
|
-
def migrations_path=(path)
|
1165
|
-
ActiveSupport::Deprecation.warn \
|
1166
|
-
"`ActiveRecord::Migrator.migrations_path=` is now deprecated and will be removed in Rails 6.0. " \
|
1167
|
-
"You can set the `migrations_paths` on the `connection` instead through the `database.yml`."
|
1168
|
-
self.migrations_paths = [path]
|
1169
|
-
end
|
1170
|
-
|
1171
1189
|
# For cases where a table doesn't exist like loading from schema cache
|
1172
1190
|
def current_version
|
1173
|
-
MigrationContext.new(migrations_paths).current_version
|
1191
|
+
MigrationContext.new(migrations_paths, SchemaMigration).current_version
|
1174
1192
|
end
|
1175
1193
|
end
|
1176
1194
|
|
1177
1195
|
self.migrations_paths = ["db/migrate"]
|
1178
1196
|
|
1179
|
-
def initialize(direction, migrations, target_version = nil)
|
1197
|
+
def initialize(direction, migrations, schema_migration, target_version = nil)
|
1180
1198
|
@direction = direction
|
1181
1199
|
@target_version = target_version
|
1182
1200
|
@migrated_versions = nil
|
1183
1201
|
@migrations = migrations
|
1202
|
+
@schema_migration = schema_migration
|
1184
1203
|
|
1185
1204
|
validate(@migrations)
|
1186
1205
|
|
1187
|
-
|
1206
|
+
@schema_migration.create_table
|
1188
1207
|
ActiveRecord::InternalMetadata.create_table
|
1189
1208
|
end
|
1190
1209
|
|
@@ -1238,11 +1257,10 @@ module ActiveRecord
|
|
1238
1257
|
end
|
1239
1258
|
|
1240
1259
|
def load_migrated
|
1241
|
-
@migrated_versions = Set.new(
|
1260
|
+
@migrated_versions = Set.new(@schema_migration.all_versions.map(&:to_i))
|
1242
1261
|
end
|
1243
1262
|
|
1244
1263
|
private
|
1245
|
-
|
1246
1264
|
# Used for running a specific migration.
|
1247
1265
|
def run_without_lock
|
1248
1266
|
migration = migrations.detect { |m| m.version == @target_version }
|
@@ -1293,7 +1311,7 @@ module ActiveRecord
|
|
1293
1311
|
record_version_state_after_migrating(migration.version)
|
1294
1312
|
end
|
1295
1313
|
rescue => e
|
1296
|
-
msg = "An error has occurred, "
|
1314
|
+
msg = +"An error has occurred, "
|
1297
1315
|
msg << "this and " if use_transaction?(migration)
|
1298
1316
|
msg << "all later migrations canceled:\n\n#{e}"
|
1299
1317
|
raise StandardError, msg, e.backtrace
|
@@ -1322,10 +1340,10 @@ module ActiveRecord
|
|
1322
1340
|
def record_version_state_after_migrating(version)
|
1323
1341
|
if down?
|
1324
1342
|
migrated.delete(version)
|
1325
|
-
|
1343
|
+
@schema_migration.delete_by(version: version.to_s)
|
1326
1344
|
else
|
1327
1345
|
migrated << version
|
1328
|
-
|
1346
|
+
@schema_migration.create!(version: version.to_s)
|
1329
1347
|
end
|
1330
1348
|
end
|
1331
1349
|
|
@@ -1351,12 +1369,13 @@ module ActiveRecord
|
|
1351
1369
|
end
|
1352
1370
|
|
1353
1371
|
def use_advisory_lock?
|
1354
|
-
Base.connection.
|
1372
|
+
Base.connection.advisory_locks_enabled?
|
1355
1373
|
end
|
1356
1374
|
|
1357
1375
|
def with_advisory_lock
|
1358
1376
|
lock_id = generate_migrator_advisory_lock_id
|
1359
|
-
|
1377
|
+
AdvisoryLockBase.establish_connection(ActiveRecord::Base.connection_config) unless AdvisoryLockBase.connected?
|
1378
|
+
connection = AdvisoryLockBase.connection
|
1360
1379
|
got_lock = connection.get_advisory_lock(lock_id)
|
1361
1380
|
raise ConcurrentMigrationError unless got_lock
|
1362
1381
|
load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock
|