activerecord 7.0.8 → 7.1.0.rc1

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 (228) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1388 -1520
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +15 -16
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +15 -9
  15. data/lib/active_record/associations/collection_proxy.rb +15 -10
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader.rb +12 -9
  23. data/lib/active_record/associations/singular_association.rb +1 -1
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +295 -199
  26. data/lib/active_record/attribute_assignment.rb +0 -2
  27. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +40 -26
  29. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  30. data/lib/active_record/attribute_methods/query.rb +28 -16
  31. data/lib/active_record/attribute_methods/read.rb +18 -5
  32. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +105 -21
  35. data/lib/active_record/attributes.rb +3 -3
  36. data/lib/active_record/autosave_association.rb +55 -9
  37. data/lib/active_record/base.rb +7 -2
  38. data/lib/active_record/callbacks.rb +10 -24
  39. data/lib/active_record/coders/column_serializer.rb +61 -0
  40. data/lib/active_record/coders/json.rb +1 -1
  41. data/lib/active_record/coders/yaml_column.rb +70 -42
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  45. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +128 -32
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  49. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  50. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  51. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  52. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  53. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -122
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +502 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -108
  57. data/lib/active_record/connection_adapters/column.rb +9 -0
  58. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  59. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  61. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  62. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  63. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +18 -13
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  67. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  68. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  69. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +71 -40
  71. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  72. data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -6
  73. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  74. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  75. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  76. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +347 -54
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -173
  79. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  80. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  81. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +45 -39
  82. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  83. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  84. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  85. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +209 -79
  86. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  87. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  88. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  89. data/lib/active_record/connection_adapters.rb +3 -1
  90. data/lib/active_record/connection_handling.rb +71 -94
  91. data/lib/active_record/core.rb +134 -146
  92. data/lib/active_record/counter_cache.rb +46 -25
  93. data/lib/active_record/database_configurations/database_config.rb +9 -3
  94. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  95. data/lib/active_record/database_configurations/url_config.rb +17 -11
  96. data/lib/active_record/database_configurations.rb +86 -33
  97. data/lib/active_record/delegated_type.rb +8 -3
  98. data/lib/active_record/deprecator.rb +7 -0
  99. data/lib/active_record/destroy_association_async_job.rb +2 -0
  100. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  101. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  102. data/lib/active_record/encryption/config.rb +25 -1
  103. data/lib/active_record/encryption/configurable.rb +12 -19
  104. data/lib/active_record/encryption/context.rb +10 -3
  105. data/lib/active_record/encryption/contexts.rb +5 -1
  106. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  107. data/lib/active_record/encryption/encryptable_record.rb +36 -18
  108. data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
  109. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
  110. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  111. data/lib/active_record/encryption/key_generator.rb +12 -1
  112. data/lib/active_record/encryption/message_serializer.rb +2 -0
  113. data/lib/active_record/encryption/properties.rb +3 -3
  114. data/lib/active_record/encryption/scheme.rb +19 -22
  115. data/lib/active_record/encryption.rb +1 -0
  116. data/lib/active_record/enum.rb +113 -26
  117. data/lib/active_record/errors.rb +108 -15
  118. data/lib/active_record/explain.rb +23 -3
  119. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  120. data/lib/active_record/fixture_set/render_context.rb +2 -0
  121. data/lib/active_record/fixture_set/table_row.rb +29 -8
  122. data/lib/active_record/fixtures.rb +119 -71
  123. data/lib/active_record/future_result.rb +30 -5
  124. data/lib/active_record/gem_version.rb +4 -4
  125. data/lib/active_record/inheritance.rb +30 -16
  126. data/lib/active_record/insert_all.rb +55 -8
  127. data/lib/active_record/integration.rb +8 -8
  128. data/lib/active_record/internal_metadata.rb +118 -30
  129. data/lib/active_record/locking/pessimistic.rb +5 -2
  130. data/lib/active_record/log_subscriber.rb +29 -12
  131. data/lib/active_record/marshalling.rb +56 -0
  132. data/lib/active_record/message_pack.rb +124 -0
  133. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  134. data/lib/active_record/middleware/database_selector.rb +5 -7
  135. data/lib/active_record/middleware/shard_selector.rb +3 -1
  136. data/lib/active_record/migration/command_recorder.rb +100 -4
  137. data/lib/active_record/migration/compatibility.rb +131 -5
  138. data/lib/active_record/migration/default_strategy.rb +23 -0
  139. data/lib/active_record/migration/execution_strategy.rb +19 -0
  140. data/lib/active_record/migration.rb +213 -109
  141. data/lib/active_record/model_schema.rb +47 -27
  142. data/lib/active_record/nested_attributes.rb +28 -3
  143. data/lib/active_record/normalization.rb +159 -0
  144. data/lib/active_record/persistence.rb +183 -33
  145. data/lib/active_record/promise.rb +84 -0
  146. data/lib/active_record/query_cache.rb +3 -21
  147. data/lib/active_record/query_logs.rb +77 -52
  148. data/lib/active_record/query_logs_formatter.rb +41 -0
  149. data/lib/active_record/querying.rb +15 -2
  150. data/lib/active_record/railtie.rb +108 -46
  151. data/lib/active_record/railties/controller_runtime.rb +10 -5
  152. data/lib/active_record/railties/databases.rake +139 -145
  153. data/lib/active_record/railties/job_runtime.rb +23 -0
  154. data/lib/active_record/readonly_attributes.rb +32 -5
  155. data/lib/active_record/reflection.rb +162 -44
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  157. data/lib/active_record/relation/batches.rb +190 -61
  158. data/lib/active_record/relation/calculations.rb +152 -63
  159. data/lib/active_record/relation/delegation.rb +22 -8
  160. data/lib/active_record/relation/finder_methods.rb +77 -16
  161. data/lib/active_record/relation/merger.rb +2 -0
  162. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  163. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  164. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  165. data/lib/active_record/relation/predicate_builder.rb +26 -14
  166. data/lib/active_record/relation/query_attribute.rb +2 -1
  167. data/lib/active_record/relation/query_methods.rb +351 -62
  168. data/lib/active_record/relation/spawn_methods.rb +18 -1
  169. data/lib/active_record/relation.rb +76 -35
  170. data/lib/active_record/result.rb +19 -5
  171. data/lib/active_record/runtime_registry.rb +10 -1
  172. data/lib/active_record/sanitization.rb +51 -11
  173. data/lib/active_record/schema.rb +2 -3
  174. data/lib/active_record/schema_dumper.rb +46 -7
  175. data/lib/active_record/schema_migration.rb +68 -33
  176. data/lib/active_record/scoping/default.rb +15 -5
  177. data/lib/active_record/scoping/named.rb +2 -2
  178. data/lib/active_record/scoping.rb +2 -1
  179. data/lib/active_record/secure_password.rb +60 -0
  180. data/lib/active_record/secure_token.rb +21 -3
  181. data/lib/active_record/signed_id.rb +7 -5
  182. data/lib/active_record/store.rb +8 -8
  183. data/lib/active_record/suppressor.rb +3 -1
  184. data/lib/active_record/table_metadata.rb +10 -1
  185. data/lib/active_record/tasks/database_tasks.rb +127 -105
  186. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  187. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  188. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -7
  189. data/lib/active_record/test_fixtures.rb +113 -96
  190. data/lib/active_record/timestamp.rb +26 -14
  191. data/lib/active_record/token_for.rb +113 -0
  192. data/lib/active_record/touch_later.rb +11 -6
  193. data/lib/active_record/transactions.rb +36 -10
  194. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  195. data/lib/active_record/type/internal/timezone.rb +7 -2
  196. data/lib/active_record/type/time.rb +4 -0
  197. data/lib/active_record/validations/absence.rb +1 -1
  198. data/lib/active_record/validations/numericality.rb +5 -4
  199. data/lib/active_record/validations/presence.rb +5 -28
  200. data/lib/active_record/validations/uniqueness.rb +47 -2
  201. data/lib/active_record/validations.rb +8 -4
  202. data/lib/active_record/version.rb +1 -1
  203. data/lib/active_record.rb +121 -16
  204. data/lib/arel/errors.rb +10 -0
  205. data/lib/arel/factory_methods.rb +4 -0
  206. data/lib/arel/nodes/binary.rb +6 -1
  207. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  208. data/lib/arel/nodes/cte.rb +36 -0
  209. data/lib/arel/nodes/fragments.rb +35 -0
  210. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  211. data/lib/arel/nodes/leading_join.rb +8 -0
  212. data/lib/arel/nodes/node.rb +111 -2
  213. data/lib/arel/nodes/sql_literal.rb +6 -0
  214. data/lib/arel/nodes/table_alias.rb +4 -0
  215. data/lib/arel/nodes.rb +4 -0
  216. data/lib/arel/predications.rb +2 -0
  217. data/lib/arel/table.rb +9 -5
  218. data/lib/arel/visitors/mysql.rb +8 -1
  219. data/lib/arel/visitors/to_sql.rb +81 -17
  220. data/lib/arel/visitors/visitor.rb +2 -2
  221. data/lib/arel.rb +16 -2
  222. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  223. data/lib/rails/generators/active_record/migration.rb +3 -1
  224. data/lib/rails/generators/active_record/model/USAGE +113 -0
  225. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  226. metadata +48 -13
  227. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  228. data/lib/active_record/null_relation.rb +0 -63
@@ -2,6 +2,7 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
+ # = Active Record Connection Adapters Transaction State
5
6
  class TransactionState
6
7
  def initialize(state = nil)
7
8
  @state = state
@@ -73,6 +74,32 @@ module ActiveRecord
73
74
  end
74
75
  end
75
76
 
77
+ class TransactionInstrumenter
78
+ def initialize(payload = {})
79
+ @handle = nil
80
+ @started = false
81
+ @payload = nil
82
+ @base_payload = payload
83
+ end
84
+
85
+ def start
86
+ return if @started
87
+ @started = true
88
+
89
+ @payload = @base_payload.dup
90
+ @handle = ActiveSupport::Notifications.instrumenter.build_handle("transaction.active_record", @payload)
91
+ @handle.start
92
+ end
93
+
94
+ def finish(outcome)
95
+ return unless @started
96
+ @started = false
97
+
98
+ @payload[:outcome] = outcome
99
+ @handle.finish
100
+ end
101
+ end
102
+
76
103
  class NullTransaction # :nodoc:
77
104
  def initialize; end
78
105
  def state; end
@@ -80,12 +107,19 @@ module ActiveRecord
80
107
  def open?; false; end
81
108
  def joinable?; false; end
82
109
  def add_record(record, _ = true); end
110
+ def restartable?; false; end
111
+ def dirty?; false; end
112
+ def dirty!; end
113
+ def invalidated?; false; end
114
+ def invalidate!; end
83
115
  end
84
116
 
85
117
  class Transaction # :nodoc:
86
118
  attr_reader :connection, :state, :savepoint_name, :isolation_level
87
119
  attr_accessor :written
88
120
 
121
+ delegate :invalidate!, :invalidated?, to: :@state
122
+
89
123
  def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
90
124
  @connection = connection
91
125
  @state = TransactionState.new
@@ -95,6 +129,16 @@ module ActiveRecord
95
129
  @joinable = joinable
96
130
  @run_commit_callbacks = run_commit_callbacks
97
131
  @lazy_enrollment_records = nil
132
+ @dirty = false
133
+ @instrumenter = TransactionInstrumenter.new(connection: connection)
134
+ end
135
+
136
+ def dirty!
137
+ @dirty = true
138
+ end
139
+
140
+ def dirty?
141
+ @dirty
98
142
  end
99
143
 
100
144
  def add_record(record, ensure_finalize = true)
@@ -115,22 +159,40 @@ module ActiveRecord
115
159
  @records
116
160
  end
117
161
 
162
+ # Can this transaction's current state be recreated by
163
+ # rollback+begin ?
164
+ def restartable?
165
+ joinable? && !dirty?
166
+ end
167
+
168
+ def incomplete!
169
+ @instrumenter.finish(:incomplete)
170
+ end
171
+
118
172
  def materialize!
119
173
  @materialized = true
174
+ @instrumenter.start
120
175
  end
121
176
 
122
177
  def materialized?
123
178
  @materialized
124
179
  end
125
180
 
181
+ def restore!
182
+ if materialized?
183
+ @materialized = false
184
+ materialize!
185
+ end
186
+ end
187
+
126
188
  def rollback_records
127
189
  return unless records
128
- ite = records.uniq(&:__id__)
129
- already_run_callbacks = {}
130
- while record = ite.shift
131
- trigger_callbacks = record.trigger_transactional_callbacks?
132
- should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks
133
- already_run_callbacks[record] ||= trigger_callbacks
190
+
191
+ ite = unique_records
192
+
193
+ instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
194
+
195
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
134
196
  record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks)
135
197
  end
136
198
  ensure
@@ -140,20 +202,38 @@ module ActiveRecord
140
202
  end
141
203
 
142
204
  def before_commit_records
143
- records.uniq.each(&:before_committed!) if records && @run_commit_callbacks
205
+ return unless records
206
+
207
+ if @run_commit_callbacks
208
+ if ActiveRecord.before_committed_on_all_records
209
+ ite = unique_records
210
+
211
+ instances_to_run_callbacks_on = records.each_with_object({}) do |record, candidates|
212
+ candidates[record] = record
213
+ end
214
+
215
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
216
+ record.before_committed! if should_run_callbacks
217
+ end
218
+ else
219
+ records.uniq.each(&:before_committed!)
220
+ end
221
+ end
144
222
  end
145
223
 
146
224
  def commit_records
147
225
  return unless records
148
- ite = records.uniq(&:__id__)
149
- already_run_callbacks = {}
150
- while record = ite.shift
151
- if @run_commit_callbacks
152
- trigger_callbacks = record.trigger_transactional_callbacks?
153
- should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks
154
- already_run_callbacks[record] ||= trigger_callbacks
226
+
227
+ ite = unique_records
228
+
229
+ if @run_commit_callbacks
230
+ instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
231
+
232
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
155
233
  record.committed!(should_run_callbacks: should_run_callbacks)
156
- else
234
+ end
235
+ else
236
+ while record = ite.shift
157
237
  # if not running callbacks, only adds the record to the parent transaction
158
238
  connection.add_transaction_record(record)
159
239
  end
@@ -166,8 +246,76 @@ module ActiveRecord
166
246
  def joinable?; @joinable; end
167
247
  def closed?; false; end
168
248
  def open?; !closed?; end
249
+
250
+ private
251
+ def unique_records
252
+ records.uniq(&:__id__)
253
+ end
254
+
255
+ def run_action_on_records(records, instances_to_run_callbacks_on)
256
+ while record = records.shift
257
+ should_run_callbacks = record.__id__ == instances_to_run_callbacks_on[record].__id__
258
+
259
+ yield record, should_run_callbacks
260
+ end
261
+ end
262
+
263
+ def prepare_instances_to_run_callbacks_on(records)
264
+ records.each_with_object({}) do |record, candidates|
265
+ next unless record.trigger_transactional_callbacks?
266
+
267
+ earlier_saved_candidate = candidates[record]
268
+
269
+ next if earlier_saved_candidate && record.class.run_commit_callbacks_on_first_saved_instances_in_transaction
270
+
271
+ # If the candidate instance destroyed itself in the database, then
272
+ # instances which were added to the transaction afterwards, and which
273
+ # think they updated themselves, are wrong. They should not replace
274
+ # our candidate as an instance to run callbacks on
275
+ next if earlier_saved_candidate&.destroyed? && !record.destroyed?
276
+
277
+ # If the candidate instance was created inside of this transaction,
278
+ # then instances which were subsequently loaded from the database
279
+ # and updated need that state transferred to them so that
280
+ # the after_create_commit callbacks are run
281
+ record._new_record_before_last_commit = true if earlier_saved_candidate&._new_record_before_last_commit
282
+
283
+ # The last instance to save itself is likeliest to have internal
284
+ # state that matches what's committed to the database
285
+ candidates[record] = record
286
+ end
287
+ end
169
288
  end
170
289
 
290
+ # = Active Record Restart Parent \Transaction
291
+ class RestartParentTransaction < Transaction
292
+ def initialize(connection, parent_transaction, **options)
293
+ super(connection, **options)
294
+
295
+ @parent = parent_transaction
296
+
297
+ if isolation_level
298
+ raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
299
+ end
300
+
301
+ @parent.state.add_child(@state)
302
+ end
303
+
304
+ delegate :materialize!, :materialized?, :restart, to: :@parent
305
+
306
+ def rollback
307
+ @state.rollback!
308
+ @parent.restart
309
+ end
310
+
311
+ def commit
312
+ @state.commit!
313
+ end
314
+
315
+ def full_rollback?; false; end
316
+ end
317
+
318
+ # = Active Record Savepoint \Transaction
171
319
  class SavepointTransaction < Transaction
172
320
  def initialize(connection, savepoint_name, parent_transaction, **options)
173
321
  super(connection, **options)
@@ -186,19 +334,33 @@ module ActiveRecord
186
334
  super
187
335
  end
188
336
 
337
+ def restart
338
+ return unless materialized?
339
+
340
+ @instrumenter.finish(:restart)
341
+ @instrumenter.start
342
+
343
+ connection.rollback_to_savepoint(savepoint_name)
344
+ end
345
+
189
346
  def rollback
190
- connection.rollback_to_savepoint(savepoint_name) if materialized?
347
+ unless @state.invalidated?
348
+ connection.rollback_to_savepoint(savepoint_name) if materialized?
349
+ end
191
350
  @state.rollback!
351
+ @instrumenter.finish(:rollback)
192
352
  end
193
353
 
194
354
  def commit
195
355
  connection.release_savepoint(savepoint_name) if materialized?
196
356
  @state.commit!
357
+ @instrumenter.finish(:commit)
197
358
  end
198
359
 
199
360
  def full_rollback?; false; end
200
361
  end
201
362
 
363
+ # = Active Record Real \Transaction
202
364
  class RealTransaction < Transaction
203
365
  def materialize!
204
366
  if isolation_level
@@ -210,14 +372,30 @@ module ActiveRecord
210
372
  super
211
373
  end
212
374
 
375
+ def restart
376
+ return unless materialized?
377
+
378
+ @instrumenter.finish(:restart)
379
+
380
+ if connection.supports_restart_db_transaction?
381
+ @instrumenter.start
382
+ connection.restart_db_transaction
383
+ else
384
+ connection.rollback_db_transaction
385
+ materialize!
386
+ end
387
+ end
388
+
213
389
  def rollback
214
390
  connection.rollback_db_transaction if materialized?
215
391
  @state.full_rollback!
392
+ @instrumenter.finish(:rollback)
216
393
  end
217
394
 
218
395
  def commit
219
396
  connection.commit_db_transaction if materialized?
220
397
  @state.full_commit!
398
+ @instrumenter.finish(:commit)
221
399
  end
222
400
  end
223
401
 
@@ -241,21 +419,31 @@ module ActiveRecord
241
419
  joinable: joinable,
242
420
  run_commit_callbacks: run_commit_callbacks
243
421
  )
422
+ elsif current_transaction.restartable?
423
+ RestartParentTransaction.new(
424
+ @connection,
425
+ current_transaction,
426
+ isolation: isolation,
427
+ joinable: joinable,
428
+ run_commit_callbacks: run_commit_callbacks
429
+ )
244
430
  else
245
431
  SavepointTransaction.new(
246
432
  @connection,
247
433
  "active_record_#{@stack.size}",
248
- @stack.last,
434
+ current_transaction,
249
435
  isolation: isolation,
250
436
  joinable: joinable,
251
437
  run_commit_callbacks: run_commit_callbacks
252
438
  )
253
439
  end
254
440
 
255
- if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy
256
- @has_unmaterialized_transactions = true
257
- else
258
- transaction.materialize!
441
+ unless transaction.materialized?
442
+ if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy
443
+ @has_unmaterialized_transactions = true
444
+ else
445
+ transaction.materialize!
446
+ end
259
447
  end
260
448
  @stack.push(transaction)
261
449
  transaction
@@ -275,18 +463,35 @@ module ActiveRecord
275
463
  @lazy_transactions_enabled
276
464
  end
277
465
 
466
+ def dirty_current_transaction
467
+ current_transaction.dirty!
468
+ end
469
+
470
+ def restore_transactions
471
+ return false unless restorable?
472
+
473
+ @stack.each(&:restore!)
474
+
475
+ true
476
+ end
477
+
478
+ def restorable?
479
+ @stack.none?(&:dirty?)
480
+ end
481
+
278
482
  def materialize_transactions
279
483
  return if @materializing_transactions
280
- return unless @has_unmaterialized_transactions
281
484
 
282
- @connection.lock.synchronize do
283
- begin
284
- @materializing_transactions = true
285
- @stack.each { |t| t.materialize! unless t.materialized? }
286
- ensure
287
- @materializing_transactions = false
485
+ if @has_unmaterialized_transactions
486
+ @connection.lock.synchronize do
487
+ begin
488
+ @materializing_transactions = true
489
+ @stack.each { |t| t.materialize! unless t.materialized? }
490
+ ensure
491
+ @materializing_transactions = false
492
+ end
493
+ @has_unmaterialized_transactions = false
288
494
  end
289
- @has_unmaterialized_transactions = false
290
495
  end
291
496
  end
292
497
 
@@ -300,6 +505,8 @@ module ActiveRecord
300
505
  @stack.pop
301
506
  end
302
507
 
508
+ dirty_current_transaction if transaction.dirty?
509
+
303
510
  transaction.commit
304
511
  transaction.commit_records
305
512
  end
@@ -307,8 +514,12 @@ module ActiveRecord
307
514
 
308
515
  def rollback_transaction(transaction = nil)
309
516
  @connection.lock.synchronize do
310
- transaction ||= @stack.pop
311
- transaction.rollback unless transaction.state.invalidated?
517
+ transaction ||= @stack.last
518
+ begin
519
+ transaction.rollback
520
+ ensure
521
+ @stack.pop if @stack.last == transaction
522
+ end
312
523
  transaction.rollback_records
313
524
  end
314
525
  end
@@ -316,39 +527,53 @@ module ActiveRecord
316
527
  def within_new_transaction(isolation: nil, joinable: true)
317
528
  @connection.lock.synchronize do
318
529
  transaction = begin_transaction(isolation: isolation, joinable: joinable)
319
- ret = yield
320
- completed = true
321
- ret
322
- rescue Exception => error
323
- if transaction
324
- transaction.state.invalidate! if error.is_a? ActiveRecord::TransactionRollbackError
530
+ begin
531
+ ret = yield
532
+ completed = true
533
+ ret
534
+ rescue Exception => error
325
535
  rollback_transaction
326
536
  after_failure_actions(transaction, error)
327
- end
328
537
 
329
- raise
330
- ensure
331
- if transaction
332
- if error
333
- # @connection still holds an open or invalid transaction, so we must not
334
- # put it back in the pool for reuse.
335
- @connection.throw_away! unless transaction.state.rolledback?
336
- elsif Thread.current.status == "aborting" || (!completed && transaction.written)
337
- # The transaction is still open but the block returned earlier.
338
- #
339
- # The block could return early because of a timeout or because the thread is aborting,
340
- # so we are rolling back to make sure the timeout didn't caused the transaction to be
341
- # committed incompletely.
342
- rollback_transaction
343
- else
344
- begin
345
- commit_transaction
346
- rescue Exception
347
- rollback_transaction(transaction) unless transaction.state.completed?
348
- raise
538
+ raise
539
+ ensure
540
+ unless error
541
+ # In 7.1 we enforce timeout >= 0.4.0 which no longer use throw, so we can
542
+ # go back to the original behavior of committing on non-local return.
543
+ # If users are using throw, we assume it's not an error case.
544
+ completed = true if ActiveRecord.commit_transaction_on_non_local_return
545
+
546
+ if Thread.current.status == "aborting"
547
+ rollback_transaction
548
+ elsif !completed && transaction.written
549
+ ActiveRecord.deprecator.warn(<<~EOW)
550
+ A transaction is being rolled back because the transaction block was
551
+ exited using `return`, `break` or `throw`.
552
+ In Rails 7.2 this transaction will be committed instead.
553
+ To opt-in to the new behavior now and suppress this warning
554
+ you can set:
555
+
556
+ Rails.application.config.active_record.commit_transaction_on_non_local_return = true
557
+ EOW
558
+ rollback_transaction
559
+ else
560
+ begin
561
+ commit_transaction
562
+ rescue ActiveRecord::ConnectionFailed
563
+ transaction.invalidate! unless transaction.state.completed?
564
+ raise
565
+ rescue Exception
566
+ rollback_transaction(transaction) unless transaction.state.completed?
567
+ raise
568
+ end
349
569
  end
350
570
  end
351
571
  end
572
+ ensure
573
+ unless transaction&.state&.completed?
574
+ @connection.throw_away!
575
+ transaction&.incomplete!
576
+ end
352
577
  end
353
578
  end
354
579