activerecord 7.0.8 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1401 -1513
  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 +13 -10
  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 +287 -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 +10 -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 +337 -176
  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 +3 -3
  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 +60 -40
  142. data/lib/active_record/nested_attributes.rb +23 -3
  143. data/lib/active_record/normalization.rb +159 -0
  144. data/lib/active_record/persistence.rb +184 -34
  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 +15 -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 +46 -11
  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,35 @@ 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
+ class InstrumentationNotStartedError < ActiveRecordError; end
86
+ class InstrumentationAlreadyStartedError < ActiveRecordError; end
87
+
88
+ def start
89
+ raise InstrumentationAlreadyStartedError.new("Called start on an already started transaction") if @started
90
+ @started = true
91
+
92
+ @payload = @base_payload.dup
93
+ @handle = ActiveSupport::Notifications.instrumenter.build_handle("transaction.active_record", @payload)
94
+ @handle.start
95
+ end
96
+
97
+ def finish(outcome)
98
+ raise InstrumentationNotStartedError.new("Called finish on a transaction that hasn't started") unless @started
99
+ @started = false
100
+
101
+ @payload[:outcome] = outcome
102
+ @handle.finish
103
+ end
104
+ end
105
+
76
106
  class NullTransaction # :nodoc:
77
107
  def initialize; end
78
108
  def state; end
@@ -80,12 +110,19 @@ module ActiveRecord
80
110
  def open?; false; end
81
111
  def joinable?; false; end
82
112
  def add_record(record, _ = true); end
113
+ def restartable?; false; end
114
+ def dirty?; false; end
115
+ def dirty!; end
116
+ def invalidated?; false; end
117
+ def invalidate!; end
83
118
  end
84
119
 
85
120
  class Transaction # :nodoc:
86
121
  attr_reader :connection, :state, :savepoint_name, :isolation_level
87
122
  attr_accessor :written
88
123
 
124
+ delegate :invalidate!, :invalidated?, to: :@state
125
+
89
126
  def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
90
127
  @connection = connection
91
128
  @state = TransactionState.new
@@ -95,6 +132,16 @@ module ActiveRecord
95
132
  @joinable = joinable
96
133
  @run_commit_callbacks = run_commit_callbacks
97
134
  @lazy_enrollment_records = nil
135
+ @dirty = false
136
+ @instrumenter = TransactionInstrumenter.new(connection: connection)
137
+ end
138
+
139
+ def dirty!
140
+ @dirty = true
141
+ end
142
+
143
+ def dirty?
144
+ @dirty
98
145
  end
99
146
 
100
147
  def add_record(record, ensure_finalize = true)
@@ -115,22 +162,41 @@ module ActiveRecord
115
162
  @records
116
163
  end
117
164
 
165
+ # Can this transaction's current state be recreated by
166
+ # rollback+begin ?
167
+ def restartable?
168
+ joinable? && !dirty?
169
+ end
170
+
171
+ def incomplete!
172
+ @instrumenter.finish(:incomplete) if materialized?
173
+ end
174
+
118
175
  def materialize!
119
176
  @materialized = true
177
+ @instrumenter.start
120
178
  end
121
179
 
122
180
  def materialized?
123
181
  @materialized
124
182
  end
125
183
 
184
+ def restore!
185
+ if materialized?
186
+ incomplete!
187
+ @materialized = false
188
+ materialize!
189
+ end
190
+ end
191
+
126
192
  def rollback_records
127
193
  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
194
+
195
+ ite = unique_records
196
+
197
+ instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
198
+
199
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
134
200
  record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks)
135
201
  end
136
202
  ensure
@@ -140,20 +206,38 @@ module ActiveRecord
140
206
  end
141
207
 
142
208
  def before_commit_records
143
- records.uniq.each(&:before_committed!) if records && @run_commit_callbacks
209
+ return unless records
210
+
211
+ if @run_commit_callbacks
212
+ if ActiveRecord.before_committed_on_all_records
213
+ ite = unique_records
214
+
215
+ instances_to_run_callbacks_on = records.each_with_object({}) do |record, candidates|
216
+ candidates[record] = record
217
+ end
218
+
219
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
220
+ record.before_committed! if should_run_callbacks
221
+ end
222
+ else
223
+ records.uniq.each(&:before_committed!)
224
+ end
225
+ end
144
226
  end
145
227
 
146
228
  def commit_records
147
229
  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
230
+
231
+ ite = unique_records
232
+
233
+ if @run_commit_callbacks
234
+ instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
235
+
236
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
155
237
  record.committed!(should_run_callbacks: should_run_callbacks)
156
- else
238
+ end
239
+ else
240
+ while record = ite.shift
157
241
  # if not running callbacks, only adds the record to the parent transaction
158
242
  connection.add_transaction_record(record)
159
243
  end
@@ -166,8 +250,76 @@ module ActiveRecord
166
250
  def joinable?; @joinable; end
167
251
  def closed?; false; end
168
252
  def open?; !closed?; end
253
+
254
+ private
255
+ def unique_records
256
+ records.uniq(&:__id__)
257
+ end
258
+
259
+ def run_action_on_records(records, instances_to_run_callbacks_on)
260
+ while record = records.shift
261
+ should_run_callbacks = record.__id__ == instances_to_run_callbacks_on[record].__id__
262
+
263
+ yield record, should_run_callbacks
264
+ end
265
+ end
266
+
267
+ def prepare_instances_to_run_callbacks_on(records)
268
+ records.each_with_object({}) do |record, candidates|
269
+ next unless record.trigger_transactional_callbacks?
270
+
271
+ earlier_saved_candidate = candidates[record]
272
+
273
+ next if earlier_saved_candidate && record.class.run_commit_callbacks_on_first_saved_instances_in_transaction
274
+
275
+ # If the candidate instance destroyed itself in the database, then
276
+ # instances which were added to the transaction afterwards, and which
277
+ # think they updated themselves, are wrong. They should not replace
278
+ # our candidate as an instance to run callbacks on
279
+ next if earlier_saved_candidate&.destroyed? && !record.destroyed?
280
+
281
+ # If the candidate instance was created inside of this transaction,
282
+ # then instances which were subsequently loaded from the database
283
+ # and updated need that state transferred to them so that
284
+ # the after_create_commit callbacks are run
285
+ record._new_record_before_last_commit = true if earlier_saved_candidate&._new_record_before_last_commit
286
+
287
+ # The last instance to save itself is likeliest to have internal
288
+ # state that matches what's committed to the database
289
+ candidates[record] = record
290
+ end
291
+ end
292
+ end
293
+
294
+ # = Active Record Restart Parent \Transaction
295
+ class RestartParentTransaction < Transaction
296
+ def initialize(connection, parent_transaction, **options)
297
+ super(connection, **options)
298
+
299
+ @parent = parent_transaction
300
+
301
+ if isolation_level
302
+ raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
303
+ end
304
+
305
+ @parent.state.add_child(@state)
306
+ end
307
+
308
+ delegate :materialize!, :materialized?, :restart, to: :@parent
309
+
310
+ def rollback
311
+ @state.rollback!
312
+ @parent.restart
313
+ end
314
+
315
+ def commit
316
+ @state.commit!
317
+ end
318
+
319
+ def full_rollback?; false; end
169
320
  end
170
321
 
322
+ # = Active Record Savepoint \Transaction
171
323
  class SavepointTransaction < Transaction
172
324
  def initialize(connection, savepoint_name, parent_transaction, **options)
173
325
  super(connection, **options)
@@ -186,19 +338,33 @@ module ActiveRecord
186
338
  super
187
339
  end
188
340
 
341
+ def restart
342
+ return unless materialized?
343
+
344
+ @instrumenter.finish(:restart)
345
+ @instrumenter.start
346
+
347
+ connection.rollback_to_savepoint(savepoint_name)
348
+ end
349
+
189
350
  def rollback
190
- connection.rollback_to_savepoint(savepoint_name) if materialized?
351
+ unless @state.invalidated?
352
+ connection.rollback_to_savepoint(savepoint_name) if materialized?
353
+ end
191
354
  @state.rollback!
355
+ @instrumenter.finish(:rollback) if materialized?
192
356
  end
193
357
 
194
358
  def commit
195
359
  connection.release_savepoint(savepoint_name) if materialized?
196
360
  @state.commit!
361
+ @instrumenter.finish(:commit) if materialized?
197
362
  end
198
363
 
199
364
  def full_rollback?; false; end
200
365
  end
201
366
 
367
+ # = Active Record Real \Transaction
202
368
  class RealTransaction < Transaction
203
369
  def materialize!
204
370
  if isolation_level
@@ -210,14 +376,30 @@ module ActiveRecord
210
376
  super
211
377
  end
212
378
 
379
+ def restart
380
+ return unless materialized?
381
+
382
+ @instrumenter.finish(:restart)
383
+
384
+ if connection.supports_restart_db_transaction?
385
+ @instrumenter.start
386
+ connection.restart_db_transaction
387
+ else
388
+ connection.rollback_db_transaction
389
+ materialize!
390
+ end
391
+ end
392
+
213
393
  def rollback
214
394
  connection.rollback_db_transaction if materialized?
215
395
  @state.full_rollback!
396
+ @instrumenter.finish(:rollback) if materialized?
216
397
  end
217
398
 
218
399
  def commit
219
400
  connection.commit_db_transaction if materialized?
220
401
  @state.full_commit!
402
+ @instrumenter.finish(:commit) if materialized?
221
403
  end
222
404
  end
223
405
 
@@ -241,21 +423,31 @@ module ActiveRecord
241
423
  joinable: joinable,
242
424
  run_commit_callbacks: run_commit_callbacks
243
425
  )
426
+ elsif current_transaction.restartable?
427
+ RestartParentTransaction.new(
428
+ @connection,
429
+ current_transaction,
430
+ isolation: isolation,
431
+ joinable: joinable,
432
+ run_commit_callbacks: run_commit_callbacks
433
+ )
244
434
  else
245
435
  SavepointTransaction.new(
246
436
  @connection,
247
437
  "active_record_#{@stack.size}",
248
- @stack.last,
438
+ current_transaction,
249
439
  isolation: isolation,
250
440
  joinable: joinable,
251
441
  run_commit_callbacks: run_commit_callbacks
252
442
  )
253
443
  end
254
444
 
255
- if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy
256
- @has_unmaterialized_transactions = true
257
- else
258
- transaction.materialize!
445
+ unless transaction.materialized?
446
+ if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy
447
+ @has_unmaterialized_transactions = true
448
+ else
449
+ transaction.materialize!
450
+ end
259
451
  end
260
452
  @stack.push(transaction)
261
453
  transaction
@@ -275,18 +467,35 @@ module ActiveRecord
275
467
  @lazy_transactions_enabled
276
468
  end
277
469
 
470
+ def dirty_current_transaction
471
+ current_transaction.dirty!
472
+ end
473
+
474
+ def restore_transactions
475
+ return false unless restorable?
476
+
477
+ @stack.each(&:restore!)
478
+
479
+ true
480
+ end
481
+
482
+ def restorable?
483
+ @stack.none?(&:dirty?)
484
+ end
485
+
278
486
  def materialize_transactions
279
487
  return if @materializing_transactions
280
- return unless @has_unmaterialized_transactions
281
488
 
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
489
+ if @has_unmaterialized_transactions
490
+ @connection.lock.synchronize do
491
+ begin
492
+ @materializing_transactions = true
493
+ @stack.each { |t| t.materialize! unless t.materialized? }
494
+ ensure
495
+ @materializing_transactions = false
496
+ end
497
+ @has_unmaterialized_transactions = false
288
498
  end
289
- @has_unmaterialized_transactions = false
290
499
  end
291
500
  end
292
501
 
@@ -300,6 +509,8 @@ module ActiveRecord
300
509
  @stack.pop
301
510
  end
302
511
 
512
+ dirty_current_transaction if transaction.dirty?
513
+
303
514
  transaction.commit
304
515
  transaction.commit_records
305
516
  end
@@ -307,8 +518,12 @@ module ActiveRecord
307
518
 
308
519
  def rollback_transaction(transaction = nil)
309
520
  @connection.lock.synchronize do
310
- transaction ||= @stack.pop
311
- transaction.rollback unless transaction.state.invalidated?
521
+ transaction ||= @stack.last
522
+ begin
523
+ transaction.rollback
524
+ ensure
525
+ @stack.pop if @stack.last == transaction
526
+ end
312
527
  transaction.rollback_records
313
528
  end
314
529
  end
@@ -316,39 +531,53 @@ module ActiveRecord
316
531
  def within_new_transaction(isolation: nil, joinable: true)
317
532
  @connection.lock.synchronize do
318
533
  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
534
+ begin
535
+ ret = yield
536
+ completed = true
537
+ ret
538
+ rescue Exception => error
325
539
  rollback_transaction
326
540
  after_failure_actions(transaction, error)
327
- end
328
541
 
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
542
+ raise
543
+ ensure
544
+ unless error
545
+ # In 7.1 we enforce timeout >= 0.4.0 which no longer use throw, so we can
546
+ # go back to the original behavior of committing on non-local return.
547
+ # If users are using throw, we assume it's not an error case.
548
+ completed = true if ActiveRecord.commit_transaction_on_non_local_return
549
+
550
+ if Thread.current.status == "aborting"
551
+ rollback_transaction
552
+ elsif !completed && transaction.written
553
+ ActiveRecord.deprecator.warn(<<~EOW)
554
+ A transaction is being rolled back because the transaction block was
555
+ exited using `return`, `break` or `throw`.
556
+ In Rails 7.2 this transaction will be committed instead.
557
+ To opt-in to the new behavior now and suppress this warning
558
+ you can set:
559
+
560
+ Rails.application.config.active_record.commit_transaction_on_non_local_return = true
561
+ EOW
562
+ rollback_transaction
563
+ else
564
+ begin
565
+ commit_transaction
566
+ rescue ActiveRecord::ConnectionFailed
567
+ transaction.invalidate! unless transaction.state.completed?
568
+ raise
569
+ rescue Exception
570
+ rollback_transaction(transaction) unless transaction.state.completed?
571
+ raise
572
+ end
349
573
  end
350
574
  end
351
575
  end
576
+ ensure
577
+ unless transaction&.state&.completed?
578
+ @connection.throw_away!
579
+ transaction&.incomplete!
580
+ end
352
581
  end
353
582
  end
354
583