activerecord 8.0.2.1 → 8.1.0.beta1

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 (159) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +459 -421
  3. data/README.rdoc +2 -2
  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 +9 -1
  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_association.rb +3 -3
  13. data/lib/active_record/associations/collection_proxy.rb +22 -4
  14. data/lib/active_record/associations/deprecation.rb +88 -0
  15. data/lib/active_record/associations/errors.rb +3 -0
  16. data/lib/active_record/associations/join_dependency.rb +2 -0
  17. data/lib/active_record/associations/preloader/branch.rb +1 -0
  18. data/lib/active_record/associations.rb +159 -21
  19. data/lib/active_record/attribute_methods/query.rb +34 -0
  20. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  21. data/lib/active_record/attributes.rb +38 -24
  22. data/lib/active_record/base.rb +0 -1
  23. data/lib/active_record/coders/json.rb +14 -5
  24. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +2 -4
  25. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
  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 +384 -49
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +26 -30
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -1
  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 +89 -23
  35. data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
  36. data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -13
  37. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
  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 -0
  45. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -16
  46. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  47. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  48. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  49. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
  50. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
  51. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
  52. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
  53. data/lib/active_record/connection_adapters/postgresql_adapter.rb +12 -7
  54. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  55. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -27
  56. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  57. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
  58. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +56 -32
  59. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  60. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  61. data/lib/active_record/connection_adapters.rb +1 -0
  62. data/lib/active_record/connection_handling.rb +1 -1
  63. data/lib/active_record/core.rb +12 -9
  64. data/lib/active_record/counter_cache.rb +33 -8
  65. data/lib/active_record/database_configurations/database_config.rb +5 -1
  66. data/lib/active_record/database_configurations/hash_config.rb +56 -9
  67. data/lib/active_record/database_configurations/url_config.rb +13 -3
  68. data/lib/active_record/database_configurations.rb +7 -3
  69. data/lib/active_record/delegated_type.rb +2 -2
  70. data/lib/active_record/dynamic_matchers.rb +54 -69
  71. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  72. data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
  73. data/lib/active_record/encryption/encryptor.rb +27 -25
  74. data/lib/active_record/encryption/scheme.rb +1 -1
  75. data/lib/active_record/enum.rb +37 -20
  76. data/lib/active_record/errors.rb +20 -4
  77. data/lib/active_record/explain_registry.rb +0 -1
  78. data/lib/active_record/filter_attribute_handler.rb +73 -0
  79. data/lib/active_record/fixture_set/table_row.rb +19 -2
  80. data/lib/active_record/fixtures.rb +2 -2
  81. data/lib/active_record/gem_version.rb +3 -3
  82. data/lib/active_record/inheritance.rb +1 -1
  83. data/lib/active_record/insert_all.rb +12 -7
  84. data/lib/active_record/locking/optimistic.rb +7 -0
  85. data/lib/active_record/locking/pessimistic.rb +5 -0
  86. data/lib/active_record/log_subscriber.rb +1 -5
  87. data/lib/active_record/middleware/shard_selector.rb +34 -17
  88. data/lib/active_record/migration/command_recorder.rb +14 -1
  89. data/lib/active_record/migration/compatibility.rb +34 -24
  90. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  91. data/lib/active_record/migration.rb +31 -21
  92. data/lib/active_record/model_schema.rb +10 -7
  93. data/lib/active_record/nested_attributes.rb +2 -0
  94. data/lib/active_record/persistence.rb +34 -3
  95. data/lib/active_record/query_cache.rb +22 -15
  96. data/lib/active_record/query_logs.rb +7 -7
  97. data/lib/active_record/querying.rb +4 -4
  98. data/lib/active_record/railtie.rb +34 -5
  99. data/lib/active_record/railties/databases.rake +23 -19
  100. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  101. data/lib/active_record/railties/job_runtime.rb +10 -11
  102. data/lib/active_record/reflection.rb +42 -3
  103. data/lib/active_record/relation/batches.rb +26 -12
  104. data/lib/active_record/relation/calculations.rb +35 -25
  105. data/lib/active_record/relation/delegation.rb +0 -1
  106. data/lib/active_record/relation/finder_methods.rb +37 -21
  107. data/lib/active_record/relation/merger.rb +2 -2
  108. data/lib/active_record/relation/predicate_builder.rb +2 -2
  109. data/lib/active_record/relation/query_attribute.rb +3 -1
  110. data/lib/active_record/relation/query_methods.rb +43 -33
  111. data/lib/active_record/relation/spawn_methods.rb +6 -6
  112. data/lib/active_record/relation/where_clause.rb +7 -10
  113. data/lib/active_record/relation.rb +37 -15
  114. data/lib/active_record/result.rb +44 -21
  115. data/lib/active_record/sanitization.rb +2 -0
  116. data/lib/active_record/schema_dumper.rb +12 -10
  117. data/lib/active_record/scoping.rb +0 -1
  118. data/lib/active_record/secure_token.rb +3 -3
  119. data/lib/active_record/signed_id.rb +46 -18
  120. data/lib/active_record/statement_cache.rb +13 -9
  121. data/lib/active_record/store.rb +44 -19
  122. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  123. data/lib/active_record/tasks/database_tasks.rb +24 -35
  124. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  125. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  126. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  127. data/lib/active_record/test_databases.rb +11 -3
  128. data/lib/active_record/test_fixtures.rb +27 -2
  129. data/lib/active_record/testing/query_assertions.rb +8 -2
  130. data/lib/active_record/timestamp.rb +4 -2
  131. data/lib/active_record/transaction.rb +2 -5
  132. data/lib/active_record/transactions.rb +34 -10
  133. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  134. data/lib/active_record/type/internal/timezone.rb +7 -0
  135. data/lib/active_record/type/json.rb +15 -2
  136. data/lib/active_record/type/serialized.rb +11 -4
  137. data/lib/active_record/type/type_map.rb +1 -1
  138. data/lib/active_record/type_caster/connection.rb +2 -1
  139. data/lib/active_record/validations/associated.rb +1 -1
  140. data/lib/active_record.rb +68 -5
  141. data/lib/arel/alias_predication.rb +2 -0
  142. data/lib/arel/crud.rb +8 -11
  143. data/lib/arel/delete_manager.rb +5 -0
  144. data/lib/arel/nodes/count.rb +2 -2
  145. data/lib/arel/nodes/delete_statement.rb +4 -2
  146. data/lib/arel/nodes/function.rb +4 -10
  147. data/lib/arel/nodes/named_function.rb +2 -2
  148. data/lib/arel/nodes/node.rb +1 -1
  149. data/lib/arel/nodes/update_statement.rb +4 -2
  150. data/lib/arel/nodes.rb +0 -2
  151. data/lib/arel/select_manager.rb +13 -4
  152. data/lib/arel/update_manager.rb +5 -0
  153. data/lib/arel/visitors/dot.rb +2 -3
  154. data/lib/arel/visitors/postgresql.rb +55 -0
  155. data/lib/arel/visitors/sqlite.rb +55 -8
  156. data/lib/arel/visitors/to_sql.rb +5 -21
  157. data/lib/arel.rb +3 -1
  158. metadata +13 -9
  159. data/lib/active_record/normalization.rb +0 -163
@@ -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
@@ -373,7 +371,8 @@ module ActiveRecord
373
371
  database_adapter_for(db_config, *arguments).structure_load(filename, flags)
374
372
  end
375
373
 
376
- 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
377
376
  file ||= schema_dump_path(db_config, format)
378
377
  return unless file
379
378
 
@@ -394,7 +393,7 @@ module ActiveRecord
394
393
  Migration.verbose = verbose_was
395
394
  end
396
395
 
397
- def schema_up_to_date?(configuration, format = ActiveRecord.schema_format, file = nil)
396
+ def schema_up_to_date?(configuration, _ = nil, file = nil)
398
397
  db_config = resolve_configuration(configuration)
399
398
 
400
399
  file ||= schema_dump_path(db_config)
@@ -410,25 +409,32 @@ module ActiveRecord
410
409
  end
411
410
  end
412
411
 
413
- def reconstruct_from_schema(db_config, format = ActiveRecord.schema_format, file = nil) # :nodoc:
414
- 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)
415
414
 
416
415
  check_schema_file(file) if file
417
416
 
418
417
  with_temporary_pool(db_config, clobber: true) do
419
- if schema_up_to_date?(db_config, format, file)
418
+ if schema_up_to_date?(db_config, nil, file)
420
419
  truncate_tables(db_config) unless ENV["SKIP_TEST_DATABASE_TRUNCATE"]
421
420
  else
422
421
  purge(db_config)
423
- load_schema(db_config, format, file)
422
+ load_schema(db_config, db_config.schema_format, file)
424
423
  end
425
424
  rescue ActiveRecord::NoDatabaseError
426
425
  create(db_config)
427
- load_schema(db_config, format, file)
426
+ load_schema(db_config, db_config.schema_format, file)
428
427
  end
429
428
  end
430
429
 
431
- def dump_schema(db_config, format = ActiveRecord.schema_format) # :nodoc:
430
+ def dump_all
431
+ with_temporary_pool_for_each do |pool|
432
+ db_config = pool.db_config
433
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
434
+ end
435
+ end
436
+
437
+ def dump_schema(db_config, format = db_config.schema_format) # :nodoc:
432
438
  return unless db_config.schema_dump
433
439
 
434
440
  require "active_record/schema_dumper"
@@ -436,7 +442,7 @@ module ActiveRecord
436
442
  return unless filename
437
443
 
438
444
  FileUtils.mkdir_p(db_dir)
439
- case format
445
+ case format.to_sym
440
446
  when :ruby
441
447
  File.open(filename, "w:utf-8") do |file|
442
448
  ActiveRecord::SchemaDumper.dump(migration_connection_pool, file)
@@ -445,14 +451,14 @@ module ActiveRecord
445
451
  structure_dump(db_config, filename)
446
452
  if migration_connection_pool.schema_migration.table_exists?
447
453
  File.open(filename, "a") do |f|
448
- f.puts migration_connection.dump_schema_information
454
+ f.puts migration_connection.dump_schema_versions
449
455
  f.print "\n"
450
456
  end
451
457
  end
452
458
  end
453
459
  end
454
460
 
455
- def schema_dump_path(db_config, format = ActiveRecord.schema_format)
461
+ def schema_dump_path(db_config, format = db_config.schema_format)
456
462
  return ENV["SCHEMA"] if ENV["SCHEMA"]
457
463
 
458
464
  filename = db_config.schema_dump(format)
@@ -471,10 +477,10 @@ module ActiveRecord
471
477
  db_config.default_schema_cache_path(ActiveRecord::Tasks::DatabaseTasks.db_dir)
472
478
  end
473
479
 
474
- def load_schema_current(format = ActiveRecord.schema_format, file = nil, environment = env)
480
+ def load_schema_current(format = nil, file = nil, environment = env)
475
481
  each_current_configuration(environment) do |db_config|
476
482
  with_temporary_connection(db_config) do
477
- load_schema(db_config, format, file)
483
+ load_schema(db_config, format || db_config.schema_format, file)
478
484
  end
479
485
  end
480
486
  end
@@ -632,23 +638,6 @@ module ActiveRecord
632
638
  end
633
639
  end
634
640
 
635
- def check_current_protected_environment!(db_config)
636
- with_temporary_pool(db_config) do |pool|
637
- migration_context = pool.migration_context
638
- current = migration_context.current_environment
639
- stored = migration_context.last_stored_environment
640
-
641
- if migration_context.protected_environment?
642
- raise ActiveRecord::ProtectedEnvironmentError.new(stored)
643
- end
644
-
645
- if stored && stored != current
646
- raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored)
647
- end
648
- rescue ActiveRecord::NoDatabaseError
649
- end
650
- end
651
-
652
641
  def initialize_database(db_config)
653
642
  with_temporary_pool(db_config) do
654
643
  begin
@@ -661,7 +650,7 @@ module ActiveRecord
661
650
  unless database_already_initialized
662
651
  schema_dump_path = schema_dump_path(db_config)
663
652
  if schema_dump_path && File.exist?(schema_dump_path)
664
- load_schema(db_config, ActiveRecord.schema_format, nil)
653
+ load_schema(db_config)
665
654
  end
666
655
  end
667
656
 
@@ -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,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
@@ -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
@@ -4,17 +4,25 @@ 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
22
  ActiveRecord::Base.configurations.configs_for(env_name: env_name).each do |db_config|
15
- db_config._database = "#{db_config.database}-#{i}"
23
+ db_config._database = "#{db_config.database}_#{i}"
16
24
 
17
- ActiveRecord::Tasks::DatabaseTasks.reconstruct_from_schema(db_config, ActiveRecord.schema_format, nil)
25
+ ActiveRecord::Tasks::DatabaseTasks.reconstruct_from_schema(db_config, nil)
18
26
  end
19
27
  ensure
20
28
  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
 
@@ -169,11 +185,19 @@ module ActiveRecord
169
185
  @@already_loaded_fixtures.clear
170
186
  end
171
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
+
172
192
  def setup_transactional_fixtures
173
193
  setup_shared_connection_pool
174
194
 
175
195
  # Begin transactions for connections already established
176
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
+
177
201
  @fixture_connection_pools.each do |pool|
178
202
  pool.pin_connection!(lock_threads)
179
203
  pool.lease_connection
@@ -189,7 +213,8 @@ module ActiveRecord
189
213
  if pool
190
214
  setup_shared_connection_pool
191
215
 
192
- 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)
193
218
  pool.pin_connection!(lock_threads)
194
219
  pool.lease_connection
195
220
  @fixture_connection_pools << pool
@@ -11,12 +11,18 @@ module ActiveRecord
11
11
  # # Check for any number of queries
12
12
  # assert_queries_count { Post.first }
13
13
  #
14
- # If the +:include_schema+ option is provided, any queries (including schema related) are counted.
14
+ # Any unmaterialized transactions will be materialized to ensure only
15
+ # queries attempted intside the block are counted.
16
+ #
17
+ # If the +:include_schema+ option is provided, any queries (including
18
+ # schema related) are counted. Setting this option also skips leasing a
19
+ # connection to materialize pending transactions since we want to count
20
+ # queries executed at connection open (e.g., type map).
15
21
  #
16
22
  # assert_queries_count(1, include_schema: true) { Post.columns }
17
23
  #
18
24
  def assert_queries_count(count = nil, include_schema: false, &block)
19
- ActiveRecord::Base.lease_connection.materialize_transactions
25
+ ActiveRecord::Base.lease_connection.materialize_transactions unless include_schema
20
26
 
21
27
  counter = SQLCounter.new
22
28
  ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
@@ -169,8 +169,10 @@ module ActiveRecord
169
169
  # Clear attributes and changed_attributes
170
170
  def clear_timestamp_attributes
171
171
  all_timestamp_attributes_in_model.each do |attribute_name|
172
- self[attribute_name] = nil
173
- clear_attribute_change(attribute_name)
172
+ if self[attribute_name]
173
+ self[attribute_name] = nil
174
+ clear_attribute_change(attribute_name)
175
+ end
174
176
  end
175
177
  end
176
178
  end
@@ -26,7 +26,7 @@ module ActiveRecord
26
26
  # # We are inside a real and not finalized transaction.
27
27
  # end
28
28
  #
29
- # Closed transactions are `blank?` too.
29
+ # Closed transactions are +blank?+ too.
30
30
  #
31
31
  # == Callbacks
32
32
  #
@@ -101,9 +101,6 @@ module ActiveRecord
101
101
  #
102
102
  # If the entire chain of nested transactions are all successfully committed,
103
103
  # the block is never called.
104
- #
105
- # If the transaction is already finalized, attempting to register a callback
106
- # will raise ActiveRecord::ActiveRecordError.
107
104
  def after_rollback(&block)
108
105
  @internal_transaction&.after_rollback(&block)
109
106
  end
@@ -115,7 +112,7 @@ module ActiveRecord
115
112
 
116
113
  # Returns true if the transaction doesn't exist or is finalized.
117
114
  def closed?
118
- @internal_transaction.nil? || @internal_transaction.state.finalized?
115
+ @internal_transaction.nil? || @internal_transaction.closed?
119
116
  end
120
117
 
121
118
  alias_method :blank?, :closed?
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  scope: [:kind, :name]
14
14
  end
15
15
 
16
- attr_accessor :_new_record_before_last_commit # :nodoc:
16
+ attr_accessor :_new_record_before_last_commit, :_last_transaction_return_status # :nodoc:
17
17
 
18
18
  # = Active Record \Transactions
19
19
  #
@@ -230,8 +230,28 @@ module ActiveRecord
230
230
  # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
231
231
  def transaction(**options, &block)
232
232
  with_connection do |connection|
233
- connection.transaction(**options, &block)
233
+ connection.pool.with_pool_transaction_isolation_level(ActiveRecord.default_transaction_isolation_level, connection.transaction_open?) do
234
+ connection.transaction(**options, &block)
235
+ end
236
+ end
237
+ end
238
+
239
+ # Makes all transactions the current pool use the isolation level initiated within the block.
240
+ def with_pool_transaction_isolation_level(isolation_level, &block)
241
+ if current_transaction.open?
242
+ raise ActiveRecord::TransactionIsolationError, "cannot set default isolation level while transaction is open"
234
243
  end
244
+
245
+ old_level = connection_pool.pool_transaction_isolation_level
246
+ connection_pool.pool_transaction_isolation_level = isolation_level
247
+ yield
248
+ ensure
249
+ connection_pool.pool_transaction_isolation_level = old_level
250
+ end
251
+
252
+ # Returns the default isolation level for the connection pool, set earlier by #with_pool_transaction_isolation_level.
253
+ def pool_transaction_isolation_level
254
+ connection_pool.pool_transaction_isolation_level
235
255
  end
236
256
 
237
257
  # Returns a representation of the current transaction state,
@@ -407,17 +427,20 @@ module ActiveRecord
407
427
  # instance.
408
428
  def with_transaction_returning_status
409
429
  self.class.with_connection do |connection|
410
- status = nil
411
- ensure_finalize = !connection.transaction_open?
430
+ connection.pool.with_pool_transaction_isolation_level(ActiveRecord.default_transaction_isolation_level, connection.transaction_open?) do
431
+ status = nil
432
+ ensure_finalize = !connection.transaction_open?
412
433
 
413
- connection.transaction do
414
- add_to_transaction(ensure_finalize || has_transactional_callbacks?)
415
- remember_transaction_record_state
434
+ connection.transaction do
435
+ add_to_transaction(ensure_finalize || has_transactional_callbacks?)
436
+ remember_transaction_record_state
416
437
 
417
- status = yield
418
- raise ActiveRecord::Rollback unless status
438
+ status = yield
439
+ raise ActiveRecord::Rollback unless status
440
+ end
441
+ @_last_transaction_return_status = status
442
+ status
419
443
  end
420
- status
421
444
  end
422
445
  end
423
446
 
@@ -432,6 +455,7 @@ module ActiveRecord
432
455
  def init_internals
433
456
  super
434
457
  @_start_transaction_state = nil
458
+ @_last_transaction_return_status = nil
435
459
  @_committed_already_called = nil
436
460
  @_new_record_before_last_commit = nil
437
461
  end
@@ -26,6 +26,7 @@ module ActiveRecord
26
26
  if block
27
27
  @mapping[key] = block
28
28
  else
29
+ value.freeze
29
30
  @mapping[key] = proc { value }
30
31
  end
31
32
  @cache.clear
@@ -50,7 +51,7 @@ module ActiveRecord
50
51
 
51
52
  private
52
53
  def perform_fetch(type, *args, &block)
53
- @mapping.fetch(type, block).call(type, *args)
54
+ @mapping.fetch(type, block).call(type, *args).freeze
54
55
  end
55
56
  end
56
57
  end
@@ -16,6 +16,13 @@ module ActiveRecord
16
16
  def default_timezone
17
17
  @timezone || ActiveRecord.default_timezone
18
18
  end
19
+
20
+ def ==(other)
21
+ super(other) && timezone == other.timezone
22
+ end
23
+
24
+ protected
25
+ attr_reader :timezone
19
26
  end
20
27
  end
21
28
  end