activerecord 7.0.8.7 → 7.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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1339 -1572
  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 +17 -9
  15. data/lib/active_record/associations/collection_proxy.rb +16 -11
  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 +193 -97
  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 +109 -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 +280 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +502 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +200 -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 +17 -12
  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 +76 -29
  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 +42 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +351 -54
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -168
  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 +42 -36
  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 +162 -77
  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 +128 -138
  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 +2 -2
  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 +89 -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 +158 -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 +107 -45
  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 +169 -45
  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 +85 -15
  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/relation_handler.rb +5 -1
  164. data/lib/active_record/relation/predicate_builder.rb +26 -14
  165. data/lib/active_record/relation/query_attribute.rb +2 -1
  166. data/lib/active_record/relation/query_methods.rb +351 -62
  167. data/lib/active_record/relation/spawn_methods.rb +18 -1
  168. data/lib/active_record/relation.rb +76 -35
  169. data/lib/active_record/result.rb +19 -5
  170. data/lib/active_record/runtime_registry.rb +10 -1
  171. data/lib/active_record/sanitization.rb +51 -11
  172. data/lib/active_record/schema.rb +2 -3
  173. data/lib/active_record/schema_dumper.rb +41 -7
  174. data/lib/active_record/schema_migration.rb +68 -33
  175. data/lib/active_record/scoping/default.rb +15 -5
  176. data/lib/active_record/scoping/named.rb +2 -2
  177. data/lib/active_record/scoping.rb +2 -1
  178. data/lib/active_record/secure_password.rb +60 -0
  179. data/lib/active_record/secure_token.rb +21 -3
  180. data/lib/active_record/signed_id.rb +7 -5
  181. data/lib/active_record/store.rb +8 -8
  182. data/lib/active_record/suppressor.rb +3 -1
  183. data/lib/active_record/table_metadata.rb +10 -1
  184. data/lib/active_record/tasks/database_tasks.rb +127 -105
  185. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  186. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  187. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -7
  188. data/lib/active_record/test_fixtures.rb +113 -96
  189. data/lib/active_record/timestamp.rb +26 -14
  190. data/lib/active_record/token_for.rb +113 -0
  191. data/lib/active_record/touch_later.rb +11 -6
  192. data/lib/active_record/transactions.rb +36 -10
  193. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  194. data/lib/active_record/type/internal/timezone.rb +7 -2
  195. data/lib/active_record/type/time.rb +4 -0
  196. data/lib/active_record/validations/absence.rb +1 -1
  197. data/lib/active_record/validations/numericality.rb +5 -4
  198. data/lib/active_record/validations/presence.rb +5 -28
  199. data/lib/active_record/validations/uniqueness.rb +47 -2
  200. data/lib/active_record/validations.rb +8 -4
  201. data/lib/active_record/version.rb +1 -1
  202. data/lib/active_record.rb +121 -16
  203. data/lib/arel/errors.rb +10 -0
  204. data/lib/arel/factory_methods.rb +4 -0
  205. data/lib/arel/nodes/binary.rb +6 -1
  206. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  207. data/lib/arel/nodes/cte.rb +36 -0
  208. data/lib/arel/nodes/fragments.rb +35 -0
  209. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  210. data/lib/arel/nodes/leading_join.rb +8 -0
  211. data/lib/arel/nodes/node.rb +111 -2
  212. data/lib/arel/nodes/sql_literal.rb +6 -0
  213. data/lib/arel/nodes/table_alias.rb +4 -0
  214. data/lib/arel/nodes.rb +4 -0
  215. data/lib/arel/predications.rb +2 -0
  216. data/lib/arel/table.rb +9 -5
  217. data/lib/arel/visitors/mysql.rb +8 -1
  218. data/lib/arel/visitors/to_sql.rb +81 -17
  219. data/lib/arel/visitors/visitor.rb +2 -2
  220. data/lib/arel.rb +16 -2
  221. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  222. data/lib/rails/generators/active_record/migration.rb +3 -1
  223. data/lib/rails/generators/active_record/model/USAGE +113 -0
  224. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  225. metadata +52 -17
  226. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  227. 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,29 @@ 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 = payload
82
+ end
83
+
84
+ def start
85
+ return if @started
86
+ @started = true
87
+
88
+ @handle = ActiveSupport::Notifications.instrumenter.build_handle("transaction.active_record", @payload)
89
+ @handle.start
90
+ end
91
+
92
+ def finish
93
+ return unless @started
94
+ @started = false
95
+
96
+ @handle.finish
97
+ end
98
+ end
99
+
76
100
  class NullTransaction # :nodoc:
77
101
  def initialize; end
78
102
  def state; end
@@ -80,12 +104,19 @@ module ActiveRecord
80
104
  def open?; false; end
81
105
  def joinable?; false; end
82
106
  def add_record(record, _ = true); end
107
+ def restartable?; false; end
108
+ def dirty?; false; end
109
+ def dirty!; end
110
+ def invalidated?; false; end
111
+ def invalidate!; end
83
112
  end
84
113
 
85
114
  class Transaction # :nodoc:
86
115
  attr_reader :connection, :state, :savepoint_name, :isolation_level
87
116
  attr_accessor :written
88
117
 
118
+ delegate :invalidate!, :invalidated?, to: :@state
119
+
89
120
  def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
90
121
  @connection = connection
91
122
  @state = TransactionState.new
@@ -95,6 +126,16 @@ module ActiveRecord
95
126
  @joinable = joinable
96
127
  @run_commit_callbacks = run_commit_callbacks
97
128
  @lazy_enrollment_records = nil
129
+ @dirty = false
130
+ @instrumenter = TransactionInstrumenter.new(connection: connection)
131
+ end
132
+
133
+ def dirty!
134
+ @dirty = true
135
+ end
136
+
137
+ def dirty?
138
+ @dirty
98
139
  end
99
140
 
100
141
  def add_record(record, ensure_finalize = true)
@@ -115,22 +156,40 @@ module ActiveRecord
115
156
  @records
116
157
  end
117
158
 
159
+ # Can this transaction's current state be recreated by
160
+ # rollback+begin ?
161
+ def restartable?
162
+ joinable? && !dirty?
163
+ end
164
+
165
+ def incomplete!
166
+ @instrumenter.finish
167
+ end
168
+
118
169
  def materialize!
119
170
  @materialized = true
171
+ @instrumenter.start
120
172
  end
121
173
 
122
174
  def materialized?
123
175
  @materialized
124
176
  end
125
177
 
178
+ def restore!
179
+ if materialized?
180
+ @materialized = false
181
+ materialize!
182
+ end
183
+ end
184
+
126
185
  def rollback_records
127
186
  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
187
+
188
+ ite = unique_records
189
+
190
+ instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
191
+
192
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
134
193
  record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks)
135
194
  end
136
195
  ensure
@@ -140,20 +199,38 @@ module ActiveRecord
140
199
  end
141
200
 
142
201
  def before_commit_records
143
- records.uniq.each(&:before_committed!) if records && @run_commit_callbacks
202
+ return unless records
203
+
204
+ if @run_commit_callbacks
205
+ if ActiveRecord.before_committed_on_all_records
206
+ ite = unique_records
207
+
208
+ instances_to_run_callbacks_on = records.each_with_object({}) do |record, candidates|
209
+ candidates[record] = record
210
+ end
211
+
212
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
213
+ record.before_committed! if should_run_callbacks
214
+ end
215
+ else
216
+ records.uniq.each(&:before_committed!)
217
+ end
218
+ end
144
219
  end
145
220
 
146
221
  def commit_records
147
222
  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
223
+
224
+ ite = unique_records
225
+
226
+ if @run_commit_callbacks
227
+ instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
228
+
229
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
155
230
  record.committed!(should_run_callbacks: should_run_callbacks)
156
- else
231
+ end
232
+ else
233
+ while record = ite.shift
157
234
  # if not running callbacks, only adds the record to the parent transaction
158
235
  connection.add_transaction_record(record)
159
236
  end
@@ -166,8 +243,76 @@ module ActiveRecord
166
243
  def joinable?; @joinable; end
167
244
  def closed?; false; end
168
245
  def open?; !closed?; end
246
+
247
+ private
248
+ def unique_records
249
+ records.uniq(&:__id__)
250
+ end
251
+
252
+ def run_action_on_records(records, instances_to_run_callbacks_on)
253
+ while record = records.shift
254
+ should_run_callbacks = record.__id__ == instances_to_run_callbacks_on[record].__id__
255
+
256
+ yield record, should_run_callbacks
257
+ end
258
+ end
259
+
260
+ def prepare_instances_to_run_callbacks_on(records)
261
+ records.each_with_object({}) do |record, candidates|
262
+ next unless record.trigger_transactional_callbacks?
263
+
264
+ earlier_saved_candidate = candidates[record]
265
+
266
+ next if earlier_saved_candidate && record.class.run_commit_callbacks_on_first_saved_instances_in_transaction
267
+
268
+ # If the candidate instance destroyed itself in the database, then
269
+ # instances which were added to the transaction afterwards, and which
270
+ # think they updated themselves, are wrong. They should not replace
271
+ # our candidate as an instance to run callbacks on
272
+ next if earlier_saved_candidate&.destroyed? && !record.destroyed?
273
+
274
+ # If the candidate instance was created inside of this transaction,
275
+ # then instances which were subsequently loaded from the database
276
+ # and updated need that state transferred to them so that
277
+ # the after_create_commit callbacks are run
278
+ record._new_record_before_last_commit = true if earlier_saved_candidate&._new_record_before_last_commit
279
+
280
+ # The last instance to save itself is likeliest to have internal
281
+ # state that matches what's committed to the database
282
+ candidates[record] = record
283
+ end
284
+ end
169
285
  end
170
286
 
287
+ # = Active Record Restart Parent \Transaction
288
+ class RestartParentTransaction < Transaction
289
+ def initialize(connection, parent_transaction, **options)
290
+ super(connection, **options)
291
+
292
+ @parent = parent_transaction
293
+
294
+ if isolation_level
295
+ raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
296
+ end
297
+
298
+ @parent.state.add_child(@state)
299
+ end
300
+
301
+ delegate :materialize!, :materialized?, :restart, to: :@parent
302
+
303
+ def rollback
304
+ @state.rollback!
305
+ @parent.restart
306
+ end
307
+
308
+ def commit
309
+ @state.commit!
310
+ end
311
+
312
+ def full_rollback?; false; end
313
+ end
314
+
315
+ # = Active Record Savepoint \Transaction
171
316
  class SavepointTransaction < Transaction
172
317
  def initialize(connection, savepoint_name, parent_transaction, **options)
173
318
  super(connection, **options)
@@ -186,19 +331,33 @@ module ActiveRecord
186
331
  super
187
332
  end
188
333
 
334
+ def restart
335
+ return unless materialized?
336
+
337
+ @instrumenter.finish
338
+ @instrumenter.start
339
+
340
+ connection.rollback_to_savepoint(savepoint_name)
341
+ end
342
+
189
343
  def rollback
190
- connection.rollback_to_savepoint(savepoint_name) if materialized?
344
+ unless @state.invalidated?
345
+ connection.rollback_to_savepoint(savepoint_name) if materialized?
346
+ end
191
347
  @state.rollback!
348
+ @instrumenter.finish
192
349
  end
193
350
 
194
351
  def commit
195
352
  connection.release_savepoint(savepoint_name) if materialized?
196
353
  @state.commit!
354
+ @instrumenter.finish
197
355
  end
198
356
 
199
357
  def full_rollback?; false; end
200
358
  end
201
359
 
360
+ # = Active Record Real \Transaction
202
361
  class RealTransaction < Transaction
203
362
  def materialize!
204
363
  if isolation_level
@@ -210,14 +369,30 @@ module ActiveRecord
210
369
  super
211
370
  end
212
371
 
372
+ def restart
373
+ return unless materialized?
374
+
375
+ @instrumenter.finish
376
+
377
+ if connection.supports_restart_db_transaction?
378
+ @instrumenter.start
379
+ connection.restart_db_transaction
380
+ else
381
+ connection.rollback_db_transaction
382
+ materialize!
383
+ end
384
+ end
385
+
213
386
  def rollback
214
387
  connection.rollback_db_transaction if materialized?
215
388
  @state.full_rollback!
389
+ @instrumenter.finish
216
390
  end
217
391
 
218
392
  def commit
219
393
  connection.commit_db_transaction if materialized?
220
394
  @state.full_commit!
395
+ @instrumenter.finish
221
396
  end
222
397
  end
223
398
 
@@ -241,21 +416,31 @@ module ActiveRecord
241
416
  joinable: joinable,
242
417
  run_commit_callbacks: run_commit_callbacks
243
418
  )
419
+ elsif current_transaction.restartable?
420
+ RestartParentTransaction.new(
421
+ @connection,
422
+ current_transaction,
423
+ isolation: isolation,
424
+ joinable: joinable,
425
+ run_commit_callbacks: run_commit_callbacks
426
+ )
244
427
  else
245
428
  SavepointTransaction.new(
246
429
  @connection,
247
430
  "active_record_#{@stack.size}",
248
- @stack.last,
431
+ current_transaction,
249
432
  isolation: isolation,
250
433
  joinable: joinable,
251
434
  run_commit_callbacks: run_commit_callbacks
252
435
  )
253
436
  end
254
437
 
255
- if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy
256
- @has_unmaterialized_transactions = true
257
- else
258
- transaction.materialize!
438
+ unless transaction.materialized?
439
+ if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy
440
+ @has_unmaterialized_transactions = true
441
+ else
442
+ transaction.materialize!
443
+ end
259
444
  end
260
445
  @stack.push(transaction)
261
446
  transaction
@@ -275,18 +460,35 @@ module ActiveRecord
275
460
  @lazy_transactions_enabled
276
461
  end
277
462
 
463
+ def dirty_current_transaction
464
+ current_transaction.dirty!
465
+ end
466
+
467
+ def restore_transactions
468
+ return false unless restorable?
469
+
470
+ @stack.each(&:restore!)
471
+
472
+ true
473
+ end
474
+
475
+ def restorable?
476
+ @stack.none?(&:dirty?)
477
+ end
478
+
278
479
  def materialize_transactions
279
480
  return if @materializing_transactions
280
- return unless @has_unmaterialized_transactions
281
481
 
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
482
+ if @has_unmaterialized_transactions
483
+ @connection.lock.synchronize do
484
+ begin
485
+ @materializing_transactions = true
486
+ @stack.each { |t| t.materialize! unless t.materialized? }
487
+ ensure
488
+ @materializing_transactions = false
489
+ end
490
+ @has_unmaterialized_transactions = false
288
491
  end
289
- @has_unmaterialized_transactions = false
290
492
  end
291
493
  end
292
494
 
@@ -300,6 +502,8 @@ module ActiveRecord
300
502
  @stack.pop
301
503
  end
302
504
 
505
+ dirty_current_transaction if transaction.dirty?
506
+
303
507
  transaction.commit
304
508
  transaction.commit_records
305
509
  end
@@ -307,8 +511,12 @@ module ActiveRecord
307
511
 
308
512
  def rollback_transaction(transaction = nil)
309
513
  @connection.lock.synchronize do
310
- transaction ||= @stack.pop
311
- transaction.rollback unless transaction.state.invalidated?
514
+ transaction ||= @stack.last
515
+ begin
516
+ transaction.rollback
517
+ ensure
518
+ @stack.pop if @stack.last == transaction
519
+ end
312
520
  transaction.rollback_records
313
521
  end
314
522
  end
@@ -316,39 +524,53 @@ module ActiveRecord
316
524
  def within_new_transaction(isolation: nil, joinable: true)
317
525
  @connection.lock.synchronize do
318
526
  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
527
+ begin
528
+ ret = yield
529
+ completed = true
530
+ ret
531
+ rescue Exception => error
325
532
  rollback_transaction
326
533
  after_failure_actions(transaction, error)
327
- end
328
534
 
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
535
+ raise
536
+ ensure
537
+ unless error
538
+ # In 7.1 we enforce timeout >= 0.4.0 which no longer use throw, so we can
539
+ # go back to the original behavior of committing on non-local return.
540
+ # If users are using throw, we assume it's not an error case.
541
+ completed = true if ActiveRecord.commit_transaction_on_non_local_return
542
+
543
+ if Thread.current.status == "aborting"
544
+ rollback_transaction
545
+ elsif !completed && transaction.written
546
+ ActiveRecord.deprecator.warn(<<~EOW)
547
+ A transaction is being rolled back because the transaction block was
548
+ exited using `return`, `break` or `throw`.
549
+ In Rails 7.2 this transaction will be committed instead.
550
+ To opt-in to the new behavior now and suppress this warning
551
+ you can set:
552
+
553
+ Rails.application.config.active_record.commit_transaction_on_non_local_return = true
554
+ EOW
555
+ rollback_transaction
556
+ else
557
+ begin
558
+ commit_transaction
559
+ rescue ActiveRecord::ConnectionFailed
560
+ transaction.invalidate! unless transaction.state.completed?
561
+ raise
562
+ rescue Exception
563
+ rollback_transaction(transaction) unless transaction.state.completed?
564
+ raise
565
+ end
349
566
  end
350
567
  end
351
568
  end
569
+ ensure
570
+ unless transaction&.state&.completed?
571
+ @connection.throw_away!
572
+ transaction&.incomplete!
573
+ end
352
574
  end
353
575
  end
354
576