activerecord 8.0.3 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +538 -512
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +2 -0
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/join_dependency.rb +2 -0
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/serialization.rb +16 -3
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/base.rb +0 -2
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +405 -72
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -3
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +85 -22
- data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +86 -20
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -13
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +17 -15
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +67 -31
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +81 -48
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +23 -7
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +56 -32
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +14 -9
- data/lib/active_record/core.rb +5 -4
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +53 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/encryptor.rb +12 -0
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +24 -8
- data/lib/active_record/errors.rb +20 -4
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +12 -7
- data/lib/active_record/locking/optimistic.rb +7 -0
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +2 -6
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +14 -1
- data/lib/active_record/migration/compatibility.rb +34 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +26 -16
- data/lib/active_record/model_schema.rb +36 -10
- data/lib/active_record/nested_attributes.rb +2 -0
- data/lib/active_record/persistence.rb +34 -3
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +3 -7
- data/lib/active_record/railtie.rb +32 -3
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +15 -3
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +35 -0
- data/lib/active_record/relation/batches.rb +25 -11
- data/lib/active_record/relation/calculations.rb +20 -9
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +27 -11
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
- data/lib/active_record/relation/predicate_builder.rb +9 -7
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +40 -29
- data/lib/active_record/relation/where_clause.rb +1 -8
- data/lib/active_record/relation.rb +24 -12
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/runtime_registry.rb +41 -58
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema_dumper.rb +12 -10
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +43 -15
- data/lib/active_record/statement_cache.rb +13 -9
- data/lib/active_record/store.rb +44 -19
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +5 -20
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +25 -34
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +27 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +32 -10
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +11 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record.rb +65 -3
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +3 -21
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +14 -10
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
data/lib/active_record/store.rb
CHANGED
|
@@ -146,37 +146,43 @@ module ActiveRecord
|
|
|
146
146
|
define_method("#{accessor_key}_changed?") do
|
|
147
147
|
return false unless attribute_changed?(store_attribute)
|
|
148
148
|
prev_store, new_store = changes[store_attribute]
|
|
149
|
-
|
|
149
|
+
accessor = store_accessor_for(store_attribute)
|
|
150
|
+
accessor.get(prev_store, key) != accessor.get(new_store, key)
|
|
150
151
|
end
|
|
151
152
|
|
|
152
153
|
define_method("#{accessor_key}_change") do
|
|
153
154
|
return unless attribute_changed?(store_attribute)
|
|
154
155
|
prev_store, new_store = changes[store_attribute]
|
|
155
|
-
|
|
156
|
+
accessor = store_accessor_for(store_attribute)
|
|
157
|
+
[accessor.get(prev_store, key), accessor.get(new_store, key)]
|
|
156
158
|
end
|
|
157
159
|
|
|
158
160
|
define_method("#{accessor_key}_was") do
|
|
159
161
|
return unless attribute_changed?(store_attribute)
|
|
160
162
|
prev_store, _new_store = changes[store_attribute]
|
|
161
|
-
|
|
163
|
+
accessor = store_accessor_for(store_attribute)
|
|
164
|
+
accessor.get(prev_store, key)
|
|
162
165
|
end
|
|
163
166
|
|
|
164
167
|
define_method("saved_change_to_#{accessor_key}?") do
|
|
165
168
|
return false unless saved_change_to_attribute?(store_attribute)
|
|
166
169
|
prev_store, new_store = saved_changes[store_attribute]
|
|
167
|
-
|
|
170
|
+
accessor = store_accessor_for(store_attribute)
|
|
171
|
+
accessor.get(prev_store, key) != accessor.get(new_store, key)
|
|
168
172
|
end
|
|
169
173
|
|
|
170
174
|
define_method("saved_change_to_#{accessor_key}") do
|
|
171
175
|
return unless saved_change_to_attribute?(store_attribute)
|
|
172
176
|
prev_store, new_store = saved_changes[store_attribute]
|
|
173
|
-
|
|
177
|
+
accessor = store_accessor_for(store_attribute)
|
|
178
|
+
[accessor.get(prev_store, key), accessor.get(new_store, key)]
|
|
174
179
|
end
|
|
175
180
|
|
|
176
181
|
define_method("#{accessor_key}_before_last_save") do
|
|
177
182
|
return unless saved_change_to_attribute?(store_attribute)
|
|
178
183
|
prev_store, _new_store = saved_changes[store_attribute]
|
|
179
|
-
|
|
184
|
+
accessor = store_accessor_for(store_attribute)
|
|
185
|
+
accessor.get(prev_store, key)
|
|
180
186
|
end
|
|
181
187
|
end
|
|
182
188
|
end
|
|
@@ -225,39 +231,58 @@ module ActiveRecord
|
|
|
225
231
|
end
|
|
226
232
|
|
|
227
233
|
class HashAccessor # :nodoc:
|
|
234
|
+
def self.get(store_object, key)
|
|
235
|
+
if store_object
|
|
236
|
+
store_object[key]
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
228
240
|
def self.read(object, attribute, key)
|
|
229
|
-
prepare(object, attribute)
|
|
230
|
-
|
|
241
|
+
store_object = prepare(object, attribute)
|
|
242
|
+
store_object[key]
|
|
231
243
|
end
|
|
232
244
|
|
|
233
245
|
def self.write(object, attribute, key, value)
|
|
234
|
-
prepare(object, attribute)
|
|
235
|
-
|
|
246
|
+
store_object = prepare(object, attribute)
|
|
247
|
+
store_object[key] = value if value != store_object[key]
|
|
236
248
|
end
|
|
237
249
|
|
|
238
250
|
def self.prepare(object, attribute)
|
|
239
|
-
|
|
251
|
+
store_object = object.public_send(attribute)
|
|
252
|
+
|
|
253
|
+
if store_object.nil?
|
|
254
|
+
store_object = {}
|
|
255
|
+
object.public_send(:"#{attribute}=", store_object)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
store_object
|
|
240
259
|
end
|
|
241
260
|
end
|
|
242
261
|
|
|
243
262
|
class StringKeyedHashAccessor < HashAccessor # :nodoc:
|
|
263
|
+
def self.get(store_object, key)
|
|
264
|
+
super store_object, Symbol === key ? key.name : key.to_s
|
|
265
|
+
end
|
|
266
|
+
|
|
244
267
|
def self.read(object, attribute, key)
|
|
245
|
-
super object, attribute, key.to_s
|
|
268
|
+
super object, attribute, Symbol === key ? key.name : key.to_s
|
|
246
269
|
end
|
|
247
270
|
|
|
248
271
|
def self.write(object, attribute, key, value)
|
|
249
|
-
super object, attribute, key.to_s, value
|
|
272
|
+
super object, attribute, Symbol === key ? key.name : key.to_s, value
|
|
250
273
|
end
|
|
251
274
|
end
|
|
252
275
|
|
|
253
276
|
class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc:
|
|
254
|
-
def self.prepare(object,
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
277
|
+
def self.prepare(object, attribute)
|
|
278
|
+
store_object = object.public_send(attribute)
|
|
279
|
+
|
|
280
|
+
unless store_object.is_a?(ActiveSupport::HashWithIndifferentAccess)
|
|
281
|
+
store_object = IndifferentCoder.as_indifferent_hash(store_object)
|
|
282
|
+
object.public_send :"#{attribute}=", store_object
|
|
259
283
|
end
|
|
260
|
-
|
|
284
|
+
|
|
285
|
+
store_object
|
|
261
286
|
end
|
|
262
287
|
end
|
|
263
288
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/structured_event_subscriber"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
class StructuredEventSubscriber < ActiveSupport::StructuredEventSubscriber # :nodoc:
|
|
7
|
+
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
|
|
8
|
+
|
|
9
|
+
def strict_loading_violation(event)
|
|
10
|
+
owner = event.payload[:owner]
|
|
11
|
+
reflection = event.payload[:reflection]
|
|
12
|
+
|
|
13
|
+
emit_debug_event("active_record.strict_loading_violation",
|
|
14
|
+
owner: owner.name,
|
|
15
|
+
class: reflection.polymorphic? ? nil : reflection.klass.name,
|
|
16
|
+
name: reflection.name,
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
debug_only :strict_loading_violation
|
|
20
|
+
|
|
21
|
+
def sql(event)
|
|
22
|
+
payload = event.payload
|
|
23
|
+
|
|
24
|
+
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
|
25
|
+
|
|
26
|
+
binds = nil
|
|
27
|
+
|
|
28
|
+
if payload[:binds]&.any?
|
|
29
|
+
casted_params = type_casted_binds(payload[:type_casted_binds])
|
|
30
|
+
|
|
31
|
+
binds = []
|
|
32
|
+
payload[:binds].each_with_index do |attr, i|
|
|
33
|
+
attribute_name = if attr.respond_to?(:name)
|
|
34
|
+
attr.name
|
|
35
|
+
elsif attr.respond_to?(:[]) && attr[i].respond_to?(:name)
|
|
36
|
+
attr[i].name
|
|
37
|
+
else
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
filtered_params = filter(attribute_name, casted_params[i])
|
|
42
|
+
|
|
43
|
+
binds << render_bind(attr, filtered_params)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
emit_debug_event("active_record.sql",
|
|
48
|
+
async: payload[:async],
|
|
49
|
+
name: payload[:name],
|
|
50
|
+
sql: payload[:sql],
|
|
51
|
+
cached: payload[:cached],
|
|
52
|
+
lock_wait: payload[:lock_wait],
|
|
53
|
+
binds: binds,
|
|
54
|
+
duration_ms: event.duration.round(2),
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
debug_only :sql
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
def type_casted_binds(casted_binds)
|
|
61
|
+
casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def render_bind(attr, value)
|
|
65
|
+
case attr
|
|
66
|
+
when ActiveModel::Attribute
|
|
67
|
+
if attr.type.binary? && attr.value
|
|
68
|
+
value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
|
|
69
|
+
end
|
|
70
|
+
when Array
|
|
71
|
+
attr = attr.first
|
|
72
|
+
else
|
|
73
|
+
attr = nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
[attr&.name, value]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def filter(name, value)
|
|
80
|
+
ActiveRecord::Base.inspection_filter.filter_param(name, value)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
ActiveRecord::StructuredEventSubscriber.attach_to :active_record
|
|
@@ -2,12 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
class TableMetadata # :nodoc:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def initialize(klass, arel_table, reflection = nil)
|
|
5
|
+
def initialize(klass, arel_table)
|
|
8
6
|
@klass = klass
|
|
9
7
|
@arel_table = arel_table
|
|
10
|
-
@reflection = reflection
|
|
11
8
|
end
|
|
12
9
|
|
|
13
10
|
def primary_key
|
|
@@ -22,7 +19,7 @@ module ActiveRecord
|
|
|
22
19
|
klass&.columns_hash&.key?(column_name)
|
|
23
20
|
end
|
|
24
21
|
|
|
25
|
-
def associated_with
|
|
22
|
+
def associated_with(table_name)
|
|
26
23
|
klass&._reflect_on_association(table_name)
|
|
27
24
|
end
|
|
28
25
|
|
|
@@ -42,26 +39,14 @@ module ActiveRecord
|
|
|
42
39
|
if association_klass
|
|
43
40
|
arel_table = association_klass.arel_table
|
|
44
41
|
arel_table = arel_table.alias(table_name) if arel_table.name != table_name
|
|
45
|
-
TableMetadata.new(association_klass, arel_table
|
|
42
|
+
TableMetadata.new(association_klass, arel_table)
|
|
46
43
|
else
|
|
47
44
|
type_caster = TypeCaster::Connection.new(klass, table_name)
|
|
48
45
|
arel_table = Arel::Table.new(table_name, type_caster: type_caster)
|
|
49
|
-
TableMetadata.new(nil, arel_table
|
|
46
|
+
TableMetadata.new(nil, arel_table)
|
|
50
47
|
end
|
|
51
48
|
end
|
|
52
49
|
|
|
53
|
-
def polymorphic_association?
|
|
54
|
-
reflection&.polymorphic?
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def polymorphic_name_association
|
|
58
|
-
reflection&.polymorphic_name
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def through_association?
|
|
62
|
-
reflection&.through_reflection?
|
|
63
|
-
end
|
|
64
|
-
|
|
65
50
|
def reflect_on_aggregation(aggregation_name)
|
|
66
51
|
klass&.reflect_on_aggregation(aggregation_name)
|
|
67
52
|
end
|
|
@@ -78,6 +63,6 @@ module ActiveRecord
|
|
|
78
63
|
attr_reader :arel_table
|
|
79
64
|
|
|
80
65
|
private
|
|
81
|
-
attr_reader :klass
|
|
66
|
+
attr_reader :klass
|
|
82
67
|
end
|
|
83
68
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Tasks # :nodoc:
|
|
5
|
+
class AbstractTasks # :nodoc:
|
|
6
|
+
def self.using_database_configurations?
|
|
7
|
+
true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(db_config)
|
|
11
|
+
@db_config = db_config
|
|
12
|
+
@configuration_hash = db_config.configuration_hash
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def charset
|
|
16
|
+
connection.encoding
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def collation
|
|
20
|
+
connection.collation
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def check_current_protected_environment!(db_config, migration_class)
|
|
24
|
+
with_temporary_pool(db_config, migration_class) do |pool|
|
|
25
|
+
migration_context = pool.migration_context
|
|
26
|
+
current = migration_context.current_environment
|
|
27
|
+
stored = migration_context.last_stored_environment
|
|
28
|
+
|
|
29
|
+
if migration_context.protected_environment?
|
|
30
|
+
raise ActiveRecord::ProtectedEnvironmentError.new(stored)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if stored && stored != current
|
|
34
|
+
raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored)
|
|
35
|
+
end
|
|
36
|
+
rescue ActiveRecord::NoDatabaseError
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
attr_reader :db_config, :configuration_hash
|
|
42
|
+
|
|
43
|
+
def connection
|
|
44
|
+
ActiveRecord::Base.lease_connection
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def establish_connection(config = db_config)
|
|
48
|
+
ActiveRecord::Base.establish_connection(config)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def configuration_hash_without_database
|
|
52
|
+
configuration_hash.merge(database: nil)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def run_cmd(cmd, *args, **opts)
|
|
56
|
+
fail run_cmd_error(cmd, args) unless Kernel.system(cmd, *args, opts)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def run_cmd_error(cmd, args)
|
|
60
|
+
msg = +"failed to execute:\n"
|
|
61
|
+
msg << "#{cmd} #{args.join(' ')}\n\n"
|
|
62
|
+
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
|
|
63
|
+
msg
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def with_temporary_pool(db_config, migration_class, clobber: false)
|
|
67
|
+
original_db_config = migration_class.connection_db_config
|
|
68
|
+
pool = migration_class.connection_handler.establish_connection(db_config, clobber: clobber)
|
|
69
|
+
|
|
70
|
+
yield pool
|
|
71
|
+
ensure
|
|
72
|
+
migration_class.connection_handler.establish_connection(original_db_config, clobber: clobber)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -66,7 +66,7 @@ module ActiveRecord
|
|
|
66
66
|
return if ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"]
|
|
67
67
|
|
|
68
68
|
configs_for(env_name: environment).each do |db_config|
|
|
69
|
-
check_current_protected_environment!(db_config)
|
|
69
|
+
database_adapter_for(db_config).check_current_protected_environment!(db_config, migration_class)
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
|
|
@@ -147,8 +147,6 @@ module ActiveRecord
|
|
|
147
147
|
return if database_configs.count == 1
|
|
148
148
|
|
|
149
149
|
database_configs.each do |db_config|
|
|
150
|
-
next unless db_config.database_tasks?
|
|
151
|
-
|
|
152
150
|
yield db_config.name
|
|
153
151
|
end
|
|
154
152
|
end
|
|
@@ -430,9 +428,15 @@ module ActiveRecord
|
|
|
430
428
|
end
|
|
431
429
|
|
|
432
430
|
def dump_all
|
|
433
|
-
|
|
434
|
-
|
|
431
|
+
seen_schemas = []
|
|
432
|
+
|
|
433
|
+
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
|
|
434
|
+
schema_path = schema_dump_path(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
|
|
435
|
+
|
|
436
|
+
next if seen_schemas.include?(schema_path)
|
|
437
|
+
|
|
435
438
|
ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
|
|
439
|
+
seen_schemas << schema_path
|
|
436
440
|
end
|
|
437
441
|
end
|
|
438
442
|
|
|
@@ -443,18 +447,22 @@ module ActiveRecord
|
|
|
443
447
|
filename = schema_dump_path(db_config, format)
|
|
444
448
|
return unless filename
|
|
445
449
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
450
|
+
with_temporary_pool(db_config) do |pool|
|
|
451
|
+
FileUtils.mkdir_p(db_dir)
|
|
452
|
+
case format.to_sym
|
|
453
|
+
when :ruby
|
|
454
|
+
File.open(filename, "w:utf-8") do |file|
|
|
455
|
+
ActiveRecord::SchemaDumper.dump(pool, file)
|
|
456
|
+
end
|
|
457
|
+
when :sql
|
|
458
|
+
structure_dump(db_config, filename)
|
|
459
|
+
if pool.schema_migration.table_exists?
|
|
460
|
+
File.open(filename, "a") do |f|
|
|
461
|
+
pool.with_connection do |connection|
|
|
462
|
+
f.puts connection.dump_schema_versions
|
|
463
|
+
end
|
|
464
|
+
f.print "\n"
|
|
465
|
+
end
|
|
458
466
|
end
|
|
459
467
|
end
|
|
460
468
|
end
|
|
@@ -640,23 +648,6 @@ module ActiveRecord
|
|
|
640
648
|
end
|
|
641
649
|
end
|
|
642
650
|
|
|
643
|
-
def check_current_protected_environment!(db_config)
|
|
644
|
-
with_temporary_pool(db_config) do |pool|
|
|
645
|
-
migration_context = pool.migration_context
|
|
646
|
-
current = migration_context.current_environment
|
|
647
|
-
stored = migration_context.last_stored_environment
|
|
648
|
-
|
|
649
|
-
if migration_context.protected_environment?
|
|
650
|
-
raise ActiveRecord::ProtectedEnvironmentError.new(stored)
|
|
651
|
-
end
|
|
652
|
-
|
|
653
|
-
if stored && stored != current
|
|
654
|
-
raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored)
|
|
655
|
-
end
|
|
656
|
-
rescue ActiveRecord::NoDatabaseError
|
|
657
|
-
end
|
|
658
|
-
end
|
|
659
|
-
|
|
660
651
|
def initialize_database(db_config)
|
|
661
652
|
with_temporary_pool(db_config) do
|
|
662
653
|
begin
|
|
@@ -2,16 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
module Tasks # :nodoc:
|
|
5
|
-
class MySQLDatabaseTasks # :nodoc:
|
|
6
|
-
def self.using_database_configurations?
|
|
7
|
-
true
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def initialize(db_config)
|
|
11
|
-
@db_config = db_config
|
|
12
|
-
@configuration_hash = db_config.configuration_hash
|
|
13
|
-
end
|
|
14
|
-
|
|
5
|
+
class MySQLDatabaseTasks < AbstractTasks # :nodoc:
|
|
15
6
|
def create
|
|
16
7
|
establish_connection(configuration_hash_without_database)
|
|
17
8
|
connection.create_database(db_config.database, creation_options)
|
|
@@ -33,10 +24,6 @@ module ActiveRecord
|
|
|
33
24
|
connection.charset
|
|
34
25
|
end
|
|
35
26
|
|
|
36
|
-
def collation
|
|
37
|
-
connection.collation
|
|
38
|
-
end
|
|
39
|
-
|
|
40
27
|
def structure_dump(filename, extra_flags)
|
|
41
28
|
args = prepare_command_options
|
|
42
29
|
args.concat(["--result-file", "#{filename}"])
|
|
@@ -53,7 +40,7 @@ module ActiveRecord
|
|
|
53
40
|
args.concat([db_config.database.to_s])
|
|
54
41
|
args.unshift(*extra_flags) if extra_flags
|
|
55
42
|
|
|
56
|
-
run_cmd("mysqldump", args
|
|
43
|
+
run_cmd("mysqldump", *args)
|
|
57
44
|
end
|
|
58
45
|
|
|
59
46
|
def structure_load(filename, extra_flags)
|
|
@@ -62,24 +49,10 @@ module ActiveRecord
|
|
|
62
49
|
args.concat(["--database", db_config.database.to_s])
|
|
63
50
|
args.unshift(*extra_flags) if extra_flags
|
|
64
51
|
|
|
65
|
-
run_cmd("mysql", args
|
|
52
|
+
run_cmd("mysql", *args)
|
|
66
53
|
end
|
|
67
54
|
|
|
68
55
|
private
|
|
69
|
-
attr_reader :db_config, :configuration_hash
|
|
70
|
-
|
|
71
|
-
def connection
|
|
72
|
-
ActiveRecord::Base.lease_connection
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def establish_connection(config = db_config)
|
|
76
|
-
ActiveRecord::Base.establish_connection(config)
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def configuration_hash_without_database
|
|
80
|
-
configuration_hash.merge(database: nil)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
56
|
def creation_options
|
|
84
57
|
Hash.new.tap do |options|
|
|
85
58
|
options[:charset] = configuration_hash[:encoding] if configuration_hash.include?(:encoding)
|
|
@@ -105,16 +78,6 @@ module ActiveRecord
|
|
|
105
78
|
|
|
106
79
|
args
|
|
107
80
|
end
|
|
108
|
-
|
|
109
|
-
def run_cmd(cmd, args, action)
|
|
110
|
-
fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args)
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def run_cmd_error(cmd, args, action)
|
|
114
|
-
msg = +"failed to execute: `#{cmd}`\n"
|
|
115
|
-
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
|
|
116
|
-
msg
|
|
117
|
-
end
|
|
118
81
|
end
|
|
119
82
|
end
|
|
120
83
|
end
|
|
@@ -4,20 +4,11 @@ require "tempfile"
|
|
|
4
4
|
|
|
5
5
|
module ActiveRecord
|
|
6
6
|
module Tasks # :nodoc:
|
|
7
|
-
class PostgreSQLDatabaseTasks # :nodoc:
|
|
7
|
+
class PostgreSQLDatabaseTasks < AbstractTasks # :nodoc:
|
|
8
8
|
DEFAULT_ENCODING = ENV["CHARSET"] || "utf8"
|
|
9
9
|
ON_ERROR_STOP_1 = "ON_ERROR_STOP=1"
|
|
10
10
|
SQL_COMMENT_BEGIN = "--"
|
|
11
11
|
|
|
12
|
-
def self.using_database_configurations?
|
|
13
|
-
true
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def initialize(db_config)
|
|
17
|
-
@db_config = db_config
|
|
18
|
-
@configuration_hash = db_config.configuration_hash
|
|
19
|
-
end
|
|
20
|
-
|
|
21
12
|
def create(connection_already_established = false)
|
|
22
13
|
establish_connection(public_schema_config) unless connection_already_established
|
|
23
14
|
connection.create_database(db_config.database, configuration_hash.merge(encoding: encoding))
|
|
@@ -29,14 +20,6 @@ module ActiveRecord
|
|
|
29
20
|
connection.drop_database(db_config.database)
|
|
30
21
|
end
|
|
31
22
|
|
|
32
|
-
def charset
|
|
33
|
-
connection.encoding
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def collation
|
|
37
|
-
connection.collation
|
|
38
|
-
end
|
|
39
|
-
|
|
40
23
|
def purge
|
|
41
24
|
ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
|
|
42
25
|
drop
|
|
@@ -72,7 +55,7 @@ module ActiveRecord
|
|
|
72
55
|
end
|
|
73
56
|
|
|
74
57
|
args << db_config.database
|
|
75
|
-
run_cmd("pg_dump", args
|
|
58
|
+
run_cmd("pg_dump", *args)
|
|
76
59
|
remove_sql_header_comments(filename)
|
|
77
60
|
File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" }
|
|
78
61
|
end
|
|
@@ -82,20 +65,10 @@ module ActiveRecord
|
|
|
82
65
|
args.concat(Array(extra_flags)) if extra_flags
|
|
83
66
|
args.concat(["--file", filename])
|
|
84
67
|
args << db_config.database
|
|
85
|
-
run_cmd("psql", args
|
|
68
|
+
run_cmd("psql", *args)
|
|
86
69
|
end
|
|
87
70
|
|
|
88
71
|
private
|
|
89
|
-
attr_reader :db_config, :configuration_hash
|
|
90
|
-
|
|
91
|
-
def connection
|
|
92
|
-
ActiveRecord::Base.lease_connection
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def establish_connection(config = db_config)
|
|
96
|
-
ActiveRecord::Base.establish_connection(config)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
72
|
def encoding
|
|
100
73
|
configuration_hash[:encoding] || DEFAULT_ENCODING
|
|
101
74
|
end
|
|
@@ -117,15 +90,8 @@ module ActiveRecord
|
|
|
117
90
|
end
|
|
118
91
|
end
|
|
119
92
|
|
|
120
|
-
def run_cmd(cmd, args,
|
|
121
|
-
fail run_cmd_error(cmd, args
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def run_cmd_error(cmd, args, action)
|
|
125
|
-
msg = +"failed to execute:\n"
|
|
126
|
-
msg << "#{cmd} #{args.join(' ')}\n\n"
|
|
127
|
-
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
|
|
128
|
-
msg
|
|
93
|
+
def run_cmd(cmd, *args, **opts)
|
|
94
|
+
fail run_cmd_error(cmd, args) unless Kernel.system(psql_env, cmd, *args, **opts)
|
|
129
95
|
end
|
|
130
96
|
|
|
131
97
|
def remove_sql_header_comments(filename)
|
|
@@ -2,11 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
module Tasks # :nodoc:
|
|
5
|
-
class SQLiteDatabaseTasks # :nodoc:
|
|
6
|
-
def self.using_database_configurations?
|
|
7
|
-
true
|
|
8
|
-
end
|
|
9
|
-
|
|
5
|
+
class SQLiteDatabaseTasks < AbstractTasks # :nodoc:
|
|
10
6
|
def initialize(db_config, root = ActiveRecord::Tasks::DatabaseTasks.root)
|
|
11
7
|
@db_config = db_config
|
|
12
8
|
@root = root
|
|
@@ -37,10 +33,6 @@ module ActiveRecord
|
|
|
37
33
|
connection.reconnect!
|
|
38
34
|
end
|
|
39
35
|
|
|
40
|
-
def charset
|
|
41
|
-
connection.encoding
|
|
42
|
-
end
|
|
43
|
-
|
|
44
36
|
def structure_dump(filename, extra_flags)
|
|
45
37
|
args = []
|
|
46
38
|
args.concat(Array(extra_flags)) if extra_flags
|
|
@@ -54,7 +46,8 @@ module ActiveRecord
|
|
|
54
46
|
else
|
|
55
47
|
args << ".schema --nosys"
|
|
56
48
|
end
|
|
57
|
-
|
|
49
|
+
|
|
50
|
+
run_cmd("sqlite3", *args, out: filename)
|
|
58
51
|
end
|
|
59
52
|
|
|
60
53
|
def structure_load(filename, extra_flags)
|
|
@@ -62,28 +55,23 @@ module ActiveRecord
|
|
|
62
55
|
`sqlite3 #{flags} #{db_config.database} < "#{filename}"`
|
|
63
56
|
end
|
|
64
57
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
58
|
+
def check_current_protected_environment!(db_config, migration_class)
|
|
59
|
+
super
|
|
60
|
+
rescue ActiveRecord::StatementInvalid => e
|
|
61
|
+
case e.cause
|
|
62
|
+
when SQLite3::ReadOnlyException
|
|
63
|
+
else
|
|
64
|
+
raise e
|
|
70
65
|
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
attr_reader :root
|
|
71
70
|
|
|
72
71
|
def establish_connection(config = db_config)
|
|
73
72
|
ActiveRecord::Base.establish_connection(config)
|
|
74
73
|
connection.connect!
|
|
75
74
|
end
|
|
76
|
-
|
|
77
|
-
def run_cmd(cmd, args, out)
|
|
78
|
-
fail run_cmd_error(cmd, args) unless Kernel.system(cmd, *args, out: out)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def run_cmd_error(cmd, args)
|
|
82
|
-
msg = +"failed to execute:\n"
|
|
83
|
-
msg << "#{cmd} #{args.join(' ')}\n\n"
|
|
84
|
-
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
|
|
85
|
-
msg
|
|
86
|
-
end
|
|
87
75
|
end
|
|
88
76
|
end
|
|
89
77
|
end
|