activerecord 7.2.2.1 → 8.1.2

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +564 -753
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +35 -11
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +10 -8
  14. data/lib/active_record/associations/collection_proxy.rb +22 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  19. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  20. data/lib/active_record/associations/join_dependency.rb +4 -2
  21. data/lib/active_record/associations/preloader/association.rb +2 -2
  22. data/lib/active_record/associations/preloader/batch.rb +7 -1
  23. data/lib/active_record/associations/preloader/branch.rb +1 -0
  24. data/lib/active_record/associations/singular_association.rb +8 -3
  25. data/lib/active_record/associations.rb +192 -24
  26. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  27. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  28. data/lib/active_record/attribute_methods/query.rb +34 -0
  29. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  30. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  31. data/lib/active_record/attribute_methods.rb +24 -19
  32. data/lib/active_record/attributes.rb +40 -26
  33. data/lib/active_record/autosave_association.rb +91 -39
  34. data/lib/active_record/base.rb +3 -4
  35. data/lib/active_record/coders/json.rb +14 -5
  36. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  37. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  38. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
  39. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -117
  40. data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
  41. data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
  42. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  43. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  45. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
  47. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  48. data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
  49. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
  50. data/lib/active_record/connection_adapters/column.rb +17 -4
  51. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  52. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
  53. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  54. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  55. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
  56. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  57. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -11
  58. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  59. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  60. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  62. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  65. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  66. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  67. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +28 -45
  68. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  69. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
  70. data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
  71. data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
  72. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  73. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  74. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  75. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  76. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -13
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
  78. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  79. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
  80. data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -19
  81. data/lib/active_record/connection_adapters.rb +1 -56
  82. data/lib/active_record/connection_handling.rb +37 -10
  83. data/lib/active_record/core.rb +61 -25
  84. data/lib/active_record/counter_cache.rb +34 -9
  85. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  86. data/lib/active_record/database_configurations/database_config.rb +9 -1
  87. data/lib/active_record/database_configurations/hash_config.rb +67 -9
  88. data/lib/active_record/database_configurations/url_config.rb +13 -3
  89. data/lib/active_record/database_configurations.rb +7 -3
  90. data/lib/active_record/delegated_type.rb +19 -19
  91. data/lib/active_record/dynamic_matchers.rb +54 -69
  92. data/lib/active_record/encryption/config.rb +3 -1
  93. data/lib/active_record/encryption/encryptable_record.rb +9 -9
  94. data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
  95. data/lib/active_record/encryption/encryptor.rb +49 -28
  96. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  97. data/lib/active_record/encryption/scheme.rb +9 -2
  98. data/lib/active_record/enum.rb +46 -42
  99. data/lib/active_record/errors.rb +36 -12
  100. data/lib/active_record/explain.rb +1 -1
  101. data/lib/active_record/explain_registry.rb +51 -2
  102. data/lib/active_record/filter_attribute_handler.rb +73 -0
  103. data/lib/active_record/fixture_set/table_row.rb +19 -2
  104. data/lib/active_record/fixtures.rb +2 -4
  105. data/lib/active_record/future_result.rb +13 -9
  106. data/lib/active_record/gem_version.rb +3 -3
  107. data/lib/active_record/inheritance.rb +1 -1
  108. data/lib/active_record/insert_all.rb +12 -7
  109. data/lib/active_record/locking/optimistic.rb +8 -1
  110. data/lib/active_record/locking/pessimistic.rb +5 -0
  111. data/lib/active_record/log_subscriber.rb +3 -13
  112. data/lib/active_record/middleware/shard_selector.rb +34 -17
  113. data/lib/active_record/migration/command_recorder.rb +44 -11
  114. data/lib/active_record/migration/compatibility.rb +37 -24
  115. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  116. data/lib/active_record/migration.rb +50 -43
  117. data/lib/active_record/model_schema.rb +38 -13
  118. data/lib/active_record/nested_attributes.rb +6 -6
  119. data/lib/active_record/persistence.rb +162 -133
  120. data/lib/active_record/query_cache.rb +22 -15
  121. data/lib/active_record/query_logs.rb +104 -52
  122. data/lib/active_record/query_logs_formatter.rb +17 -28
  123. data/lib/active_record/querying.rb +12 -12
  124. data/lib/active_record/railtie.rb +37 -32
  125. data/lib/active_record/railties/controller_runtime.rb +11 -6
  126. data/lib/active_record/railties/databases.rake +26 -37
  127. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  128. data/lib/active_record/railties/job_runtime.rb +10 -11
  129. data/lib/active_record/reflection.rb +53 -21
  130. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  131. data/lib/active_record/relation/batches.rb +147 -73
  132. data/lib/active_record/relation/calculations.rb +80 -63
  133. data/lib/active_record/relation/delegation.rb +25 -15
  134. data/lib/active_record/relation/finder_methods.rb +54 -37
  135. data/lib/active_record/relation/merger.rb +8 -8
  136. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  137. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  138. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  139. data/lib/active_record/relation/predicate_builder.rb +22 -7
  140. data/lib/active_record/relation/query_attribute.rb +4 -2
  141. data/lib/active_record/relation/query_methods.rb +156 -95
  142. data/lib/active_record/relation/spawn_methods.rb +7 -7
  143. data/lib/active_record/relation/where_clause.rb +10 -11
  144. data/lib/active_record/relation.rb +122 -80
  145. data/lib/active_record/result.rb +109 -24
  146. data/lib/active_record/runtime_registry.rb +42 -58
  147. data/lib/active_record/sanitization.rb +9 -6
  148. data/lib/active_record/schema_dumper.rb +47 -22
  149. data/lib/active_record/schema_migration.rb +2 -1
  150. data/lib/active_record/scoping/named.rb +5 -2
  151. data/lib/active_record/scoping.rb +0 -1
  152. data/lib/active_record/secure_token.rb +3 -3
  153. data/lib/active_record/signed_id.rb +47 -18
  154. data/lib/active_record/statement_cache.rb +24 -20
  155. data/lib/active_record/store.rb +51 -22
  156. data/lib/active_record/structured_event_subscriber.rb +85 -0
  157. data/lib/active_record/table_metadata.rb +6 -23
  158. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  159. data/lib/active_record/tasks/database_tasks.rb +85 -85
  160. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  161. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  162. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  163. data/lib/active_record/test_databases.rb +14 -4
  164. data/lib/active_record/test_fixtures.rb +39 -2
  165. data/lib/active_record/testing/query_assertions.rb +8 -2
  166. data/lib/active_record/timestamp.rb +4 -2
  167. data/lib/active_record/token_for.rb +1 -1
  168. data/lib/active_record/transaction.rb +2 -5
  169. data/lib/active_record/transactions.rb +39 -16
  170. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  171. data/lib/active_record/type/internal/timezone.rb +7 -0
  172. data/lib/active_record/type/json.rb +15 -2
  173. data/lib/active_record/type/serialized.rb +11 -4
  174. data/lib/active_record/type/type_map.rb +1 -1
  175. data/lib/active_record/type_caster/connection.rb +2 -1
  176. data/lib/active_record/validations/associated.rb +1 -1
  177. data/lib/active_record/validations/uniqueness.rb +8 -8
  178. data/lib/active_record.rb +85 -50
  179. data/lib/arel/alias_predication.rb +2 -0
  180. data/lib/arel/collectors/bind.rb +2 -2
  181. data/lib/arel/collectors/sql_string.rb +1 -1
  182. data/lib/arel/collectors/substitute_binds.rb +2 -2
  183. data/lib/arel/crud.rb +8 -11
  184. data/lib/arel/delete_manager.rb +5 -0
  185. data/lib/arel/nodes/binary.rb +1 -1
  186. data/lib/arel/nodes/count.rb +2 -2
  187. data/lib/arel/nodes/delete_statement.rb +4 -2
  188. data/lib/arel/nodes/function.rb +4 -10
  189. data/lib/arel/nodes/named_function.rb +2 -2
  190. data/lib/arel/nodes/node.rb +2 -2
  191. data/lib/arel/nodes/sql_literal.rb +1 -1
  192. data/lib/arel/nodes/update_statement.rb +4 -2
  193. data/lib/arel/nodes.rb +0 -2
  194. data/lib/arel/select_manager.rb +13 -4
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/update_manager.rb +5 -0
  197. data/lib/arel/visitors/dot.rb +2 -3
  198. data/lib/arel/visitors/postgresql.rb +55 -0
  199. data/lib/arel/visitors/sqlite.rb +55 -8
  200. data/lib/arel/visitors/to_sql.rb +6 -22
  201. data/lib/arel.rb +3 -1
  202. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  203. metadata +17 -17
  204. data/lib/active_record/explain_subscriber.rb +0 -34
  205. data/lib/active_record/normalization.rb +0 -163
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -45,7 +45,7 @@ module ActiveRecord
45
45
  # Example:
46
46
  # ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = {
47
47
  # mysql2: ['--no-defaults', '--skip-add-drop-table'],
48
- # postgres: '--no-tablespaces'
48
+ # postgresql: '--no-tablespaces'
49
49
  # }
50
50
  mattr_accessor :structure_dump_flags, instance_accessor: false
51
51
 
@@ -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
@@ -178,22 +176,9 @@ module ActiveRecord
178
176
  dump_db_configs = []
179
177
 
180
178
  each_current_configuration(env) do |db_config|
181
- with_temporary_pool(db_config) do
182
- begin
183
- database_initialized = migration_connection_pool.schema_migration.table_exists?
184
- rescue ActiveRecord::NoDatabaseError
185
- create(db_config)
186
- retry
187
- end
179
+ database_initialized = initialize_database(db_config)
188
180
 
189
- unless database_initialized
190
- if File.exist?(schema_dump_path(db_config))
191
- load_schema(db_config, ActiveRecord.schema_format, nil)
192
- end
193
-
194
- seed = true
195
- end
196
- end
181
+ seed = true if database_initialized && db_config.seeds?
197
182
  end
198
183
 
199
184
  each_current_environment(env) do |environment|
@@ -253,12 +238,33 @@ module ActiveRecord
253
238
  end
254
239
  end
255
240
 
256
- def migrate(version = nil)
241
+ def migrate_all
242
+ db_configs = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env)
243
+ db_configs.each { |db_config| initialize_database(db_config) }
244
+
245
+ if db_configs.size == 1 && db_configs.first.primary?
246
+ ActiveRecord::Tasks::DatabaseTasks.migrate(skip_initialize: true)
247
+ else
248
+ mapped_versions = ActiveRecord::Tasks::DatabaseTasks.db_configs_with_versions
249
+
250
+ mapped_versions.sort.each do |version, db_configs|
251
+ db_configs.each do |db_config|
252
+ ActiveRecord::Tasks::DatabaseTasks.with_temporary_connection(db_config) do
253
+ ActiveRecord::Tasks::DatabaseTasks.migrate(version, skip_initialize: true)
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
259
+
260
+ def migrate(version = nil, skip_initialize: false)
257
261
  scope = ENV["SCOPE"]
258
262
  verbose_was, Migration.verbose = Migration.verbose, verbose?
259
263
 
260
264
  check_target_version
261
265
 
266
+ initialize_database(migration_connection_pool.db_config) unless skip_initialize
267
+
262
268
  migration_connection_pool.migration_context.migrate(target_version) do |migration|
263
269
  if version.blank?
264
270
  scope.blank? || scope == migration.scope
@@ -365,7 +371,8 @@ module ActiveRecord
365
371
  database_adapter_for(db_config, *arguments).structure_load(filename, flags)
366
372
  end
367
373
 
368
- def load_schema(db_config, format = ActiveRecord.schema_format, file = nil) # :nodoc:
374
+ def load_schema(db_config, format = db_config.schema_format, file = nil) # :nodoc:
375
+ format = format.to_sym
369
376
  file ||= schema_dump_path(db_config, format)
370
377
  return unless file
371
378
 
@@ -386,7 +393,7 @@ module ActiveRecord
386
393
  Migration.verbose = verbose_was
387
394
  end
388
395
 
389
- def schema_up_to_date?(configuration, format = ActiveRecord.schema_format, file = nil)
396
+ def schema_up_to_date?(configuration, _ = nil, file = nil)
390
397
  db_config = resolve_configuration(configuration)
391
398
 
392
399
  file ||= schema_dump_path(db_config)
@@ -402,49 +409,66 @@ module ActiveRecord
402
409
  end
403
410
  end
404
411
 
405
- def reconstruct_from_schema(db_config, format = ActiveRecord.schema_format, file = nil) # :nodoc:
406
- file ||= schema_dump_path(db_config, format)
412
+ def reconstruct_from_schema(db_config, file = nil) # :nodoc:
413
+ file ||= schema_dump_path(db_config, db_config.schema_format)
407
414
 
408
415
  check_schema_file(file) if file
409
416
 
410
417
  with_temporary_pool(db_config, clobber: true) do
411
- if schema_up_to_date?(db_config, format, file)
418
+ if schema_up_to_date?(db_config, nil, file)
412
419
  truncate_tables(db_config) unless ENV["SKIP_TEST_DATABASE_TRUNCATE"]
413
420
  else
414
421
  purge(db_config)
415
- load_schema(db_config, format, file)
422
+ load_schema(db_config, db_config.schema_format, file)
416
423
  end
417
424
  rescue ActiveRecord::NoDatabaseError
418
425
  create(db_config)
419
- load_schema(db_config, format, file)
426
+ load_schema(db_config, db_config.schema_format, file)
420
427
  end
421
428
  end
422
429
 
423
- def dump_schema(db_config, format = ActiveRecord.schema_format) # :nodoc:
430
+ def dump_all
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
+
438
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
439
+ seen_schemas << schema_path
440
+ end
441
+ end
442
+
443
+ def dump_schema(db_config, format = db_config.schema_format) # :nodoc:
424
444
  return unless db_config.schema_dump
425
445
 
426
446
  require "active_record/schema_dumper"
427
447
  filename = schema_dump_path(db_config, format)
428
448
  return unless filename
429
449
 
430
- FileUtils.mkdir_p(db_dir)
431
- case format
432
- when :ruby
433
- File.open(filename, "w:utf-8") do |file|
434
- ActiveRecord::SchemaDumper.dump(migration_connection_pool, file)
435
- end
436
- when :sql
437
- structure_dump(db_config, filename)
438
- if migration_connection_pool.schema_migration.table_exists?
439
- File.open(filename, "a") do |f|
440
- f.puts migration_connection.dump_schema_information
441
- 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
442
466
  end
443
467
  end
444
468
  end
445
469
  end
446
470
 
447
- def schema_dump_path(db_config, format = ActiveRecord.schema_format)
471
+ def schema_dump_path(db_config, format = db_config.schema_format)
448
472
  return ENV["SCHEMA"] if ENV["SCHEMA"]
449
473
 
450
474
  filename = db_config.schema_dump(format)
@@ -457,32 +481,16 @@ module ActiveRecord
457
481
  end
458
482
  end
459
483
 
460
- def cache_dump_filename(db_config_or_name, schema_cache_path: nil)
461
- if db_config_or_name.is_a?(DatabaseConfigurations::DatabaseConfig)
462
- schema_cache_path ||
463
- db_config_or_name.schema_cache_path ||
464
- schema_cache_env ||
465
- db_config_or_name.default_schema_cache_path(ActiveRecord::Tasks::DatabaseTasks.db_dir)
466
- else
467
- ActiveRecord.deprecator.warn(<<~MSG.squish)
468
- Passing a database name to `cache_dump_filename` is deprecated and will be removed in Rails 8.0. Pass a
469
- `ActiveRecord::DatabaseConfigurations::DatabaseConfig` object instead.
470
- MSG
471
-
472
- filename = if ActiveRecord::Base.configurations.primary?(db_config_or_name)
473
- "schema_cache.yml"
474
- else
475
- "#{db_config_or_name}_schema_cache.yml"
476
- end
477
-
478
- schema_cache_path || schema_cache_env || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
479
- end
484
+ def cache_dump_filename(db_config, schema_cache_path: nil)
485
+ schema_cache_path ||
486
+ db_config.schema_cache_path ||
487
+ db_config.default_schema_cache_path(ActiveRecord::Tasks::DatabaseTasks.db_dir)
480
488
  end
481
489
 
482
- def load_schema_current(format = ActiveRecord.schema_format, file = nil, environment = env)
490
+ def load_schema_current(format = nil, file = nil, environment = env)
483
491
  each_current_configuration(environment) do |db_config|
484
492
  with_temporary_connection(db_config) do
485
- load_schema(db_config, format, file)
493
+ load_schema(db_config, format || db_config.schema_format, file)
486
494
  end
487
495
  end
488
496
  end
@@ -547,17 +555,6 @@ module ActiveRecord
547
555
  end
548
556
 
549
557
  private
550
- def schema_cache_env
551
- if ENV["SCHEMA_CACHE"]
552
- ActiveRecord.deprecator.warn(<<~MSG.squish)
553
- Setting `ENV["SCHEMA_CACHE"]` is deprecated and will be removed in Rails 8.0.
554
- Configure the `:schema_cache_path` in the database configuration instead.
555
- MSG
556
-
557
- nil
558
- end
559
- end
560
-
561
558
  def with_temporary_pool(db_config, clobber: false)
562
559
  original_db_config = migration_class.connection_db_config
563
560
  pool = migration_class.connection_handler.establish_connection(db_config, clobber: clobber)
@@ -651,20 +648,23 @@ module ActiveRecord
651
648
  end
652
649
  end
653
650
 
654
- def check_current_protected_environment!(db_config)
655
- with_temporary_pool(db_config) do |pool|
656
- migration_context = pool.migration_context
657
- current = migration_context.current_environment
658
- stored = migration_context.last_stored_environment
659
-
660
- if migration_context.protected_environment?
661
- raise ActiveRecord::ProtectedEnvironmentError.new(stored)
651
+ def initialize_database(db_config)
652
+ with_temporary_pool(db_config) do
653
+ begin
654
+ database_already_initialized = migration_connection_pool.schema_migration.table_exists?
655
+ rescue ActiveRecord::NoDatabaseError
656
+ create(db_config)
657
+ retry
662
658
  end
663
659
 
664
- if stored && stored != current
665
- raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored)
660
+ unless database_already_initialized
661
+ schema_dump_path = schema_dump_path(db_config)
662
+ if schema_dump_path && File.exist?(schema_dump_path)
663
+ load_schema(db_config)
664
+ end
666
665
  end
667
- rescue ActiveRecord::NoDatabaseError
666
+
667
+ !database_already_initialized
668
668
  end
669
669
  end
670
670
  end
@@ -2,18 +2,7 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Tasks # :nodoc:
5
- class MySQLDatabaseTasks # :nodoc:
6
- ER_DB_CREATE_EXISTS = 1007
7
-
8
- def self.using_database_configurations?
9
- true
10
- end
11
-
12
- def initialize(db_config)
13
- @db_config = db_config
14
- @configuration_hash = db_config.configuration_hash
15
- end
16
-
5
+ class MySQLDatabaseTasks < AbstractTasks # :nodoc:
17
6
  def create
18
7
  establish_connection(configuration_hash_without_database)
19
8
  connection.create_database(db_config.database, creation_options)
@@ -35,10 +24,6 @@ module ActiveRecord
35
24
  connection.charset
36
25
  end
37
26
 
38
- def collation
39
- connection.collation
40
- end
41
-
42
27
  def structure_dump(filename, extra_flags)
43
28
  args = prepare_command_options
44
29
  args.concat(["--result-file", "#{filename}"])
@@ -55,7 +40,7 @@ module ActiveRecord
55
40
  args.concat([db_config.database.to_s])
56
41
  args.unshift(*extra_flags) if extra_flags
57
42
 
58
- run_cmd("mysqldump", args, "dumping")
43
+ run_cmd("mysqldump", *args)
59
44
  end
60
45
 
61
46
  def structure_load(filename, extra_flags)
@@ -64,24 +49,10 @@ module ActiveRecord
64
49
  args.concat(["--database", db_config.database.to_s])
65
50
  args.unshift(*extra_flags) if extra_flags
66
51
 
67
- run_cmd("mysql", args, "loading")
52
+ run_cmd("mysql", *args)
68
53
  end
69
54
 
70
55
  private
71
- attr_reader :db_config, :configuration_hash
72
-
73
- def connection
74
- ActiveRecord::Base.lease_connection
75
- end
76
-
77
- def establish_connection(config = db_config)
78
- ActiveRecord::Base.establish_connection(config)
79
- end
80
-
81
- def configuration_hash_without_database
82
- configuration_hash.merge(database: nil)
83
- end
84
-
85
56
  def creation_options
86
57
  Hash.new.tap do |options|
87
58
  options[:charset] = configuration_hash[:encoding] if configuration_hash.include?(:encoding)
@@ -107,16 +78,6 @@ module ActiveRecord
107
78
 
108
79
  args
109
80
  end
110
-
111
- def run_cmd(cmd, args, action)
112
- fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args)
113
- end
114
-
115
- def run_cmd_error(cmd, args, action)
116
- msg = +"failed to execute: `#{cmd}`\n"
117
- 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"
118
- msg
119
- end
120
81
  end
121
82
  end
122
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,29 +55,20 @@ 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
79
62
 
80
63
  def structure_load(filename, extra_flags)
81
- args = ["--set", ON_ERROR_STOP_1, "--quiet", "--no-psqlrc", "--output", File::NULL, "--file", filename]
64
+ args = ["--set", ON_ERROR_STOP_1, "--quiet", "--no-psqlrc", "--output", File::NULL]
82
65
  args.concat(Array(extra_flags)) if extra_flags
66
+ args.concat(["--file", filename])
83
67
  args << db_config.database
84
- run_cmd("psql", args, "loading")
68
+ run_cmd("psql", *args)
85
69
  end
86
70
 
87
71
  private
88
- attr_reader :db_config, :configuration_hash
89
-
90
- def connection
91
- ActiveRecord::Base.lease_connection
92
- end
93
-
94
- def establish_connection(config = db_config)
95
- ActiveRecord::Base.establish_connection(config)
96
- end
97
-
98
72
  def encoding
99
73
  configuration_hash[:encoding] || DEFAULT_ENCODING
100
74
  end
@@ -116,15 +90,8 @@ module ActiveRecord
116
90
  end
117
91
  end
118
92
 
119
- def run_cmd(cmd, args, action)
120
- fail run_cmd_error(cmd, args, action) unless Kernel.system(psql_env, cmd, *args)
121
- end
122
-
123
- def run_cmd_error(cmd, args, action)
124
- msg = +"failed to execute:\n"
125
- msg << "#{cmd} #{args.join(' ')}\n\n"
126
- 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"
127
- msg
93
+ def run_cmd(cmd, *args, **opts)
94
+ fail run_cmd_error(cmd, args) unless Kernel.system(psql_env, cmd, *args, **opts)
128
95
  end
129
96
 
130
97
  def remove_sql_header_comments(filename)
@@ -132,6 +99,13 @@ module ActiveRecord
132
99
  tempfile = Tempfile.open("uncommented_structure.sql")
133
100
  begin
134
101
  File.foreach(filename) do |line|
102
+ next if line.start_with?("\\restrict ")
103
+
104
+ if line.start_with?("\\unrestrict ")
105
+ removing_comments = true
106
+ next
107
+ end
108
+
135
109
  unless removing_comments && (line.start_with?(SQL_COMMENT_BEGIN) || line.blank?)
136
110
  tempfile << line
137
111
  removing_comments = false
@@ -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
@@ -50,11 +42,12 @@ module ActiveRecord
50
42
  if ignore_tables.any?
51
43
  ignore_tables = connection.data_sources.select { |table| ignore_tables.any? { |pattern| pattern === table } }
52
44
  condition = ignore_tables.map { |table| connection.quote(table) }.join(", ")
53
- args << "SELECT sql FROM sqlite_master WHERE tbl_name NOT IN (#{condition}) ORDER BY tbl_name, type DESC, name"
45
+ args << "SELECT sql || ';' FROM sqlite_master WHERE tbl_name NOT IN (#{condition}) ORDER BY tbl_name, type DESC, name"
54
46
  else
55
- args << ".schema"
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
@@ -4,17 +4,27 @@ require "active_support/testing/parallelization"
4
4
 
5
5
  module ActiveRecord
6
6
  module TestDatabases # :nodoc:
7
+ ActiveSupport::Testing::Parallelization.before_fork_hook do
8
+ if ActiveSupport.parallelize_test_databases
9
+ ActiveRecord::Base.connection_handler.clear_all_connections!
10
+ end
11
+ end
12
+
7
13
  ActiveSupport::Testing::Parallelization.after_fork_hook do |i|
8
- create_and_load_schema(i, env_name: ActiveRecord::ConnectionHandling::DEFAULT_ENV.call)
14
+ if ActiveSupport.parallelize_test_databases
15
+ create_and_load_schema(i, env_name: ActiveRecord::ConnectionHandling::DEFAULT_ENV.call)
16
+ end
9
17
  end
10
18
 
11
19
  def self.create_and_load_schema(i, env_name:)
12
20
  old, ENV["VERBOSE"] = ENV["VERBOSE"], "false"
13
21
 
14
- ActiveRecord::Base.configurations.configs_for(env_name: env_name).each do |db_config|
15
- db_config._database = "#{db_config.database}-#{i}"
22
+ ActiveRecord::Base.configurations.configs_for(env_name: env_name, include_hidden: true).each do |db_config|
23
+ db_config._database = "#{db_config.database}_#{i}"
16
24
 
17
- ActiveRecord::Tasks::DatabaseTasks.reconstruct_from_schema(db_config, ActiveRecord.schema_format, nil)
25
+ if db_config.database_tasks?
26
+ ActiveRecord::Tasks::DatabaseTasks.reconstruct_from_schema(db_config, nil)
27
+ end
18
28
  end
19
29
  ensure
20
30
  ActiveRecord::Base.establish_connection
@@ -36,11 +36,26 @@ module ActiveRecord
36
36
  class_attribute :pre_loaded_fixtures, default: false
37
37
  class_attribute :lock_threads, default: true
38
38
  class_attribute :fixture_sets, default: {}
39
+ class_attribute :database_transactions_config, default: {}
39
40
 
40
41
  ActiveSupport.run_load_hooks(:active_record_fixtures, self)
41
42
  end
42
43
 
43
44
  module ClassMethods
45
+ # Do not use transactional tests for the given database. This overrides
46
+ # the default setting as defined by `use_transactional_tests`, which
47
+ # applies to all database connection pools not explicitly configured here.
48
+ def skip_transactional_tests_for_database(database_name)
49
+ use_transactional_tests_for_database(database_name, false)
50
+ end
51
+
52
+ # Enable or disable transactions per database. This overrides the default
53
+ # setting as defined by `use_transactional_tests`, which applies to all
54
+ # database connection pools not explicitly configured here.
55
+ def use_transactional_tests_for_database(database_name, enabled = true)
56
+ self.database_transactions_config = database_transactions_config.merge(database_name => enabled)
57
+ end
58
+
44
59
  # Sets the model class for a fixture when the class name cannot be inferred from the fixture name.
45
60
  #
46
61
  # Examples:
@@ -106,7 +121,8 @@ module ActiveRecord
106
121
 
107
122
  private
108
123
  def run_in_transaction?
109
- use_transactional_tests &&
124
+ has_explicit_config = database_transactions_config.any? { |_, enabled| enabled }
125
+ (use_transactional_tests || has_explicit_config) &&
110
126
  !self.class.uses_transaction?(name)
111
127
  end
112
128
 
@@ -137,12 +153,15 @@ module ActiveRecord
137
153
  invalidate_already_loaded_fixtures
138
154
  @loaded_fixtures = load_fixtures(config)
139
155
  end
156
+ setup_asynchronous_queries_session
140
157
 
141
158
  # Instantiate fixtures for every test if requested.
142
159
  instantiate_fixtures if use_instantiated_fixtures
143
160
  end
144
161
 
145
162
  def teardown_fixtures
163
+ teardown_asynchronous_queries_session
164
+
146
165
  # Rollback changes if a transaction is active.
147
166
  if run_in_transaction?
148
167
  teardown_transactional_fixtures
@@ -154,15 +173,31 @@ module ActiveRecord
154
173
  ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
155
174
  end
156
175
 
176
+ def setup_asynchronous_queries_session
177
+ @_async_queries_session = ActiveRecord::Base.asynchronous_queries_tracker.start_session
178
+ end
179
+
180
+ def teardown_asynchronous_queries_session
181
+ ActiveRecord::Base.asynchronous_queries_tracker.finalize_session(true) if @_async_queries_session
182
+ end
183
+
157
184
  def invalidate_already_loaded_fixtures
158
185
  @@already_loaded_fixtures.clear
159
186
  end
160
187
 
188
+ def transactional_tests_for_pool?(pool)
189
+ database_transactions_config.fetch(pool.db_config.name.to_sym, use_transactional_tests)
190
+ end
191
+
161
192
  def setup_transactional_fixtures
162
193
  setup_shared_connection_pool
163
194
 
164
195
  # Begin transactions for connections already established
165
196
  @fixture_connection_pools = ActiveRecord::Base.connection_handler.connection_pool_list(:writing)
197
+
198
+ # Filter to pools that want to use transactions
199
+ @fixture_connection_pools.select! { |pool| transactional_tests_for_pool?(pool) }
200
+
166
201
  @fixture_connection_pools.each do |pool|
167
202
  pool.pin_connection!(lock_threads)
168
203
  pool.lease_connection
@@ -178,7 +213,8 @@ module ActiveRecord
178
213
  if pool
179
214
  setup_shared_connection_pool
180
215
 
181
- unless @fixture_connection_pools.include?(pool)
216
+ # Don't begin a transaction if we've already done so, or are not using them for this pool
217
+ if !@fixture_connection_pools.include?(pool) && transactional_tests_for_pool?(pool)
182
218
  pool.pin_connection!(lock_threads)
183
219
  pool.lease_connection
184
220
  @fixture_connection_pools << pool
@@ -190,6 +226,7 @@ module ActiveRecord
190
226
 
191
227
  def teardown_transactional_fixtures
192
228
  ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
229
+
193
230
  unless @fixture_connection_pools.map(&:unpin_connection!).all?
194
231
  # Something caused the transaction to be committed or rolled back
195
232
  # We can no longer trust the database is in a clean state.