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.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +538 -512
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/belongs_to_association.rb +2 -0
  7. data/lib/active_record/associations/builder/association.rb +16 -5
  8. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  9. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  10. data/lib/active_record/associations/builder/has_one.rb +1 -1
  11. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  12. data/lib/active_record/associations/collection_proxy.rb +22 -4
  13. data/lib/active_record/associations/deprecation.rb +88 -0
  14. data/lib/active_record/associations/errors.rb +3 -0
  15. data/lib/active_record/associations/join_dependency.rb +2 -0
  16. data/lib/active_record/associations/preloader/branch.rb +1 -0
  17. data/lib/active_record/associations.rb +159 -21
  18. data/lib/active_record/attribute_methods/serialization.rb +16 -3
  19. data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
  20. data/lib/active_record/attributes.rb +3 -0
  21. data/lib/active_record/autosave_association.rb +1 -1
  22. data/lib/active_record/base.rb +0 -2
  23. data/lib/active_record/coders/json.rb +14 -5
  24. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
  25. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
  26. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  27. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +405 -72
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -3
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  31. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  32. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
  33. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  34. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +85 -22
  35. data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
  36. data/lib/active_record/connection_adapters/abstract_adapter.rb +86 -20
  37. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -13
  38. data/lib/active_record/connection_adapters/column.rb +17 -4
  39. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  40. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  41. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
  43. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
  44. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -2
  45. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  46. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +17 -15
  47. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  48. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  49. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  50. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
  51. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
  52. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +67 -31
  53. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +81 -48
  54. data/lib/active_record/connection_adapters/postgresql_adapter.rb +23 -7
  55. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  56. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
  57. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  58. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
  59. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +56 -32
  60. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  61. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  62. data/lib/active_record/connection_adapters.rb +1 -0
  63. data/lib/active_record/connection_handling.rb +14 -9
  64. data/lib/active_record/core.rb +5 -4
  65. data/lib/active_record/counter_cache.rb +33 -8
  66. data/lib/active_record/database_configurations/database_config.rb +5 -1
  67. data/lib/active_record/database_configurations/hash_config.rb +53 -9
  68. data/lib/active_record/database_configurations/url_config.rb +13 -3
  69. data/lib/active_record/database_configurations.rb +7 -3
  70. data/lib/active_record/delegated_type.rb +1 -1
  71. data/lib/active_record/dynamic_matchers.rb +54 -69
  72. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  73. data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
  74. data/lib/active_record/encryption/encryptor.rb +12 -0
  75. data/lib/active_record/encryption/scheme.rb +1 -1
  76. data/lib/active_record/enum.rb +24 -8
  77. data/lib/active_record/errors.rb +20 -4
  78. data/lib/active_record/explain.rb +1 -1
  79. data/lib/active_record/explain_registry.rb +51 -2
  80. data/lib/active_record/filter_attribute_handler.rb +73 -0
  81. data/lib/active_record/fixtures.rb +2 -2
  82. data/lib/active_record/gem_version.rb +2 -2
  83. data/lib/active_record/inheritance.rb +1 -1
  84. data/lib/active_record/insert_all.rb +12 -7
  85. data/lib/active_record/locking/optimistic.rb +7 -0
  86. data/lib/active_record/locking/pessimistic.rb +5 -0
  87. data/lib/active_record/log_subscriber.rb +2 -6
  88. data/lib/active_record/middleware/shard_selector.rb +34 -17
  89. data/lib/active_record/migration/command_recorder.rb +14 -1
  90. data/lib/active_record/migration/compatibility.rb +34 -24
  91. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  92. data/lib/active_record/migration.rb +26 -16
  93. data/lib/active_record/model_schema.rb +36 -10
  94. data/lib/active_record/nested_attributes.rb +2 -0
  95. data/lib/active_record/persistence.rb +34 -3
  96. data/lib/active_record/query_cache.rb +22 -15
  97. data/lib/active_record/query_logs.rb +3 -7
  98. data/lib/active_record/railtie.rb +32 -3
  99. data/lib/active_record/railties/controller_runtime.rb +11 -6
  100. data/lib/active_record/railties/databases.rake +15 -3
  101. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  102. data/lib/active_record/railties/job_runtime.rb +10 -11
  103. data/lib/active_record/reflection.rb +35 -0
  104. data/lib/active_record/relation/batches.rb +25 -11
  105. data/lib/active_record/relation/calculations.rb +20 -9
  106. data/lib/active_record/relation/delegation.rb +0 -1
  107. data/lib/active_record/relation/finder_methods.rb +27 -11
  108. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  109. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
  110. data/lib/active_record/relation/predicate_builder.rb +9 -7
  111. data/lib/active_record/relation/query_attribute.rb +3 -1
  112. data/lib/active_record/relation/query_methods.rb +40 -29
  113. data/lib/active_record/relation/where_clause.rb +1 -8
  114. data/lib/active_record/relation.rb +24 -12
  115. data/lib/active_record/result.rb +44 -21
  116. data/lib/active_record/runtime_registry.rb +41 -58
  117. data/lib/active_record/sanitization.rb +2 -0
  118. data/lib/active_record/schema_dumper.rb +12 -10
  119. data/lib/active_record/scoping.rb +0 -1
  120. data/lib/active_record/signed_id.rb +43 -15
  121. data/lib/active_record/statement_cache.rb +13 -9
  122. data/lib/active_record/store.rb +44 -19
  123. data/lib/active_record/structured_event_subscriber.rb +85 -0
  124. data/lib/active_record/table_metadata.rb +5 -20
  125. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  126. data/lib/active_record/tasks/database_tasks.rb +25 -34
  127. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  128. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
  129. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  130. data/lib/active_record/test_databases.rb +14 -4
  131. data/lib/active_record/test_fixtures.rb +27 -2
  132. data/lib/active_record/testing/query_assertions.rb +8 -2
  133. data/lib/active_record/timestamp.rb +4 -2
  134. data/lib/active_record/transaction.rb +2 -5
  135. data/lib/active_record/transactions.rb +32 -10
  136. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  137. data/lib/active_record/type/internal/timezone.rb +7 -0
  138. data/lib/active_record/type/json.rb +15 -2
  139. data/lib/active_record/type/serialized.rb +11 -4
  140. data/lib/active_record/type/type_map.rb +1 -1
  141. data/lib/active_record/type_caster/connection.rb +2 -1
  142. data/lib/active_record/validations/associated.rb +1 -1
  143. data/lib/active_record.rb +65 -3
  144. data/lib/arel/alias_predication.rb +2 -0
  145. data/lib/arel/crud.rb +6 -11
  146. data/lib/arel/nodes/count.rb +2 -2
  147. data/lib/arel/nodes/function.rb +4 -10
  148. data/lib/arel/nodes/named_function.rb +2 -2
  149. data/lib/arel/nodes/node.rb +1 -1
  150. data/lib/arel/nodes.rb +0 -2
  151. data/lib/arel/select_manager.rb +7 -2
  152. data/lib/arel/visitors/dot.rb +0 -3
  153. data/lib/arel/visitors/postgresql.rb +55 -0
  154. data/lib/arel/visitors/sqlite.rb +55 -8
  155. data/lib/arel/visitors/to_sql.rb +3 -21
  156. data/lib/arel.rb +3 -1
  157. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  158. metadata +14 -10
  159. data/lib/active_record/explain_subscriber.rb +0 -34
  160. data/lib/active_record/normalization.rb +0 -163
@@ -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
- prev_store&.dig(key) != new_store&.dig(key)
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
- [prev_store&.dig(key), new_store&.dig(key)]
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
- prev_store&.dig(key)
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
- prev_store&.dig(key) != new_store&.dig(key)
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
- [prev_store&.dig(key), new_store&.dig(key)]
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
- prev_store&.dig(key)
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
- object.public_send(attribute)[key]
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
- object.public_send(attribute)[key] = value if value != read(object, attribute, key)
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
- object.public_send :"#{attribute}=", {} unless object.send(attribute)
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, store_attribute)
255
- attribute = object.send(store_attribute)
256
- unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
257
- attribute = IndifferentCoder.as_indifferent_hash(attribute)
258
- object.public_send :"#{store_attribute}=", attribute
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
- attribute
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
- delegate :join_primary_key, :join_primary_type, :join_foreign_key, :join_foreign_type, to: :reflection
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?(table_name)
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, reflection)
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, reflection)
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, :reflection
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
- with_temporary_pool_for_each do |pool|
434
- db_config = pool.db_config
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
- FileUtils.mkdir_p(db_dir)
447
- case format.to_sym
448
- when :ruby
449
- File.open(filename, "w:utf-8") do |file|
450
- ActiveRecord::SchemaDumper.dump(migration_connection_pool, file)
451
- end
452
- when :sql
453
- structure_dump(db_config, filename)
454
- if migration_connection_pool.schema_migration.table_exists?
455
- File.open(filename, "a") do |f|
456
- f.puts migration_connection.dump_schema_information
457
- f.print "\n"
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, "dumping")
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, "loading")
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, "dumping")
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, "loading")
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, action)
121
- fail run_cmd_error(cmd, args, action) unless Kernel.system(psql_env, 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
- run_cmd("sqlite3", args, filename)
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
- private
66
- attr_reader :db_config, :root
67
-
68
- def connection
69
- ActiveRecord::Base.lease_connection
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