activerecord 7.0.8.6 → 7.2.2.1

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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +631 -1939
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +64 -50
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +11 -25
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +29 -8
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +4 -4
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +234 -117
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +63 -14
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +27 -6
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +106 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +2 -0
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +56 -14
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. data/lib/active_record/null_relation.rb +0 -63
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/digest"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
7
+ # = Active Record Connection Adapters Transaction State
5
8
  class TransactionState
6
9
  def initialize(state = nil)
7
10
  @state = state
@@ -73,28 +76,110 @@ module ActiveRecord
73
76
  end
74
77
  end
75
78
 
79
+ class TransactionInstrumenter
80
+ def initialize(payload = {})
81
+ @handle = nil
82
+ @started = false
83
+ @payload = nil
84
+ @base_payload = payload
85
+ end
86
+
87
+ class InstrumentationNotStartedError < ActiveRecordError; end
88
+ class InstrumentationAlreadyStartedError < ActiveRecordError; end
89
+
90
+ def start
91
+ raise InstrumentationAlreadyStartedError.new("Called start on an already started transaction") if @started
92
+ @started = true
93
+
94
+ ActiveSupport::Notifications.instrument("start_transaction.active_record", @base_payload)
95
+
96
+ @payload = @base_payload.dup # We dup because the payload for a given event is mutated later to add the outcome.
97
+ @handle = ActiveSupport::Notifications.instrumenter.build_handle("transaction.active_record", @payload)
98
+ @handle.start
99
+ end
100
+
101
+ def finish(outcome)
102
+ raise InstrumentationNotStartedError.new("Called finish on a transaction that hasn't started") unless @started
103
+ @started = false
104
+
105
+ @payload[:outcome] = outcome
106
+ @handle.finish
107
+ end
108
+ end
109
+
76
110
  class NullTransaction # :nodoc:
77
- def initialize; end
78
111
  def state; end
79
112
  def closed?; true; end
80
113
  def open?; false; end
81
114
  def joinable?; false; end
82
115
  def add_record(record, _ = true); end
116
+ def restartable?; false; end
117
+ def dirty?; false; end
118
+ def dirty!; end
119
+ def invalidated?; false; end
120
+ def invalidate!; end
121
+ def materialized?; false; end
122
+ def before_commit; yield; end
123
+ def after_commit; yield; end
124
+ def after_rollback; end
125
+ def user_transaction; ActiveRecord::Transaction::NULL_TRANSACTION; end
83
126
  end
84
127
 
85
128
  class Transaction # :nodoc:
86
- attr_reader :connection, :state, :savepoint_name, :isolation_level
129
+ class Callback # :nodoc:
130
+ def initialize(event, callback)
131
+ @event = event
132
+ @callback = callback
133
+ end
134
+
135
+ def before_commit
136
+ @callback.call if @event == :before_commit
137
+ end
138
+
139
+ def after_commit
140
+ @callback.call if @event == :after_commit
141
+ end
142
+
143
+ def after_rollback
144
+ @callback.call if @event == :after_rollback
145
+ end
146
+ end
147
+
148
+ attr_reader :connection, :state, :savepoint_name, :isolation_level, :user_transaction
87
149
  attr_accessor :written
88
150
 
151
+ delegate :invalidate!, :invalidated?, to: :@state
152
+
89
153
  def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
154
+ super()
90
155
  @connection = connection
91
156
  @state = TransactionState.new
157
+ @callbacks = nil
92
158
  @records = nil
93
159
  @isolation_level = isolation
94
160
  @materialized = false
95
161
  @joinable = joinable
96
162
  @run_commit_callbacks = run_commit_callbacks
97
163
  @lazy_enrollment_records = nil
164
+ @dirty = false
165
+ @user_transaction = joinable ? ActiveRecord::Transaction.new(self) : ActiveRecord::Transaction::NULL_TRANSACTION
166
+ @instrumenter = TransactionInstrumenter.new(connection: connection, transaction: @user_transaction)
167
+ end
168
+
169
+ def dirty!
170
+ @dirty = true
171
+ end
172
+
173
+ def dirty?
174
+ @dirty
175
+ end
176
+
177
+ def open?
178
+ true
179
+ end
180
+
181
+ def closed?
182
+ false
98
183
  end
99
184
 
100
185
  def add_record(record, ensure_finalize = true)
@@ -107,6 +192,30 @@ module ActiveRecord
107
192
  end
108
193
  end
109
194
 
195
+ def before_commit(&block)
196
+ if @state.finalized?
197
+ raise ActiveRecordError, "Cannot register callbacks on a finalized transaction"
198
+ end
199
+
200
+ (@callbacks ||= []) << Callback.new(:before_commit, block)
201
+ end
202
+
203
+ def after_commit(&block)
204
+ if @state.finalized?
205
+ raise ActiveRecordError, "Cannot register callbacks on a finalized transaction"
206
+ end
207
+
208
+ (@callbacks ||= []) << Callback.new(:after_commit, block)
209
+ end
210
+
211
+ def after_rollback(&block)
212
+ if @state.finalized?
213
+ raise ActiveRecordError, "Cannot register callbacks on a finalized transaction"
214
+ end
215
+
216
+ (@callbacks ||= []) << Callback.new(:after_rollback, block)
217
+ end
218
+
110
219
  def records
111
220
  if @lazy_enrollment_records
112
221
  @records.concat @lazy_enrollment_records.values
@@ -115,59 +224,183 @@ module ActiveRecord
115
224
  @records
116
225
  end
117
226
 
227
+ # Can this transaction's current state be recreated by
228
+ # rollback+begin ?
229
+ def restartable?
230
+ joinable? && !dirty?
231
+ end
232
+
233
+ def incomplete!
234
+ @instrumenter.finish(:incomplete) if materialized?
235
+ end
236
+
118
237
  def materialize!
119
238
  @materialized = true
239
+ @instrumenter.start
120
240
  end
121
241
 
122
242
  def materialized?
123
243
  @materialized
124
244
  end
125
245
 
126
- def rollback_records
127
- 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
134
- record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks)
246
+ def restore!
247
+ if materialized?
248
+ incomplete!
249
+ @materialized = false
250
+ materialize!
135
251
  end
136
- ensure
137
- ite&.each do |i|
138
- i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false)
252
+ end
253
+
254
+ def rollback_records
255
+ if records
256
+ begin
257
+ ite = unique_records
258
+
259
+ instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
260
+
261
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
262
+ record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks)
263
+ end
264
+ ensure
265
+ ite&.each do |i|
266
+ i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false)
267
+ end
268
+ end
139
269
  end
270
+
271
+ @callbacks&.each(&:after_rollback)
140
272
  end
141
273
 
142
274
  def before_commit_records
143
- records.uniq.each(&:before_committed!) if records && @run_commit_callbacks
275
+ if @run_commit_callbacks
276
+ if records
277
+ if ActiveRecord.before_committed_on_all_records
278
+ ite = unique_records
279
+
280
+ instances_to_run_callbacks_on = records.each_with_object({}) do |record, candidates|
281
+ candidates[record] = record
282
+ end
283
+
284
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
285
+ record.before_committed! if should_run_callbacks
286
+ end
287
+ else
288
+ records.uniq.each(&:before_committed!)
289
+ end
290
+ end
291
+
292
+ @callbacks&.each(&:before_commit)
293
+ end
294
+ # Note: When @run_commit_callbacks is false #commit_records takes care of appending
295
+ # remaining callbacks to the parent transaction
144
296
  end
145
297
 
146
298
  def commit_records
147
- 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
155
- record.committed!(should_run_callbacks: should_run_callbacks)
156
- else
157
- # if not running callbacks, only adds the record to the parent transaction
158
- connection.add_transaction_record(record)
299
+ if records
300
+ begin
301
+ ite = unique_records
302
+
303
+ if @run_commit_callbacks
304
+ instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
305
+
306
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
307
+ record.committed!(should_run_callbacks: should_run_callbacks)
308
+ end
309
+ else
310
+ while record = ite.shift
311
+ # if not running callbacks, only adds the record to the parent transaction
312
+ connection.add_transaction_record(record)
313
+ end
314
+ end
315
+ ensure
316
+ ite&.each { |i| i.committed!(should_run_callbacks: false) }
159
317
  end
160
318
  end
161
- ensure
162
- ite&.each { |i| i.committed!(should_run_callbacks: false) }
319
+
320
+ if @run_commit_callbacks
321
+ @callbacks&.each(&:after_commit)
322
+ elsif @callbacks
323
+ connection.current_transaction.append_callbacks(@callbacks)
324
+ end
163
325
  end
164
326
 
165
327
  def full_rollback?; true; end
166
328
  def joinable?; @joinable; end
167
- def closed?; false; end
168
- def open?; !closed?; end
329
+
330
+ protected
331
+ def append_callbacks(callbacks) # :nodoc:
332
+ (@callbacks ||= []).concat(callbacks)
333
+ end
334
+
335
+ private
336
+ def unique_records
337
+ records.uniq(&:__id__)
338
+ end
339
+
340
+ def run_action_on_records(records, instances_to_run_callbacks_on)
341
+ while record = records.shift
342
+ should_run_callbacks = record.__id__ == instances_to_run_callbacks_on[record].__id__
343
+
344
+ yield record, should_run_callbacks
345
+ end
346
+ end
347
+
348
+ def prepare_instances_to_run_callbacks_on(records)
349
+ records.each_with_object({}) do |record, candidates|
350
+ next unless record.trigger_transactional_callbacks?
351
+
352
+ earlier_saved_candidate = candidates[record]
353
+
354
+ next if earlier_saved_candidate && record.class.run_commit_callbacks_on_first_saved_instances_in_transaction
355
+
356
+ # If the candidate instance destroyed itself in the database, then
357
+ # instances which were added to the transaction afterwards, and which
358
+ # think they updated themselves, are wrong. They should not replace
359
+ # our candidate as an instance to run callbacks on
360
+ next if earlier_saved_candidate&.destroyed? && !record.destroyed?
361
+
362
+ # If the candidate instance was created inside of this transaction,
363
+ # then instances which were subsequently loaded from the database
364
+ # and updated need that state transferred to them so that
365
+ # the after_create_commit callbacks are run
366
+ record._new_record_before_last_commit = true if earlier_saved_candidate&._new_record_before_last_commit
367
+
368
+ # The last instance to save itself is likeliest to have internal
369
+ # state that matches what's committed to the database
370
+ candidates[record] = record
371
+ end
372
+ end
169
373
  end
170
374
 
375
+ # = Active Record Restart Parent \Transaction
376
+ class RestartParentTransaction < Transaction
377
+ def initialize(connection, parent_transaction, **options)
378
+ super(connection, **options)
379
+
380
+ @parent = parent_transaction
381
+
382
+ if isolation_level
383
+ raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
384
+ end
385
+
386
+ @parent.state.add_child(@state)
387
+ end
388
+
389
+ delegate :materialize!, :materialized?, :restart, to: :@parent
390
+
391
+ def rollback
392
+ @state.rollback!
393
+ @parent.restart
394
+ end
395
+
396
+ def commit
397
+ @state.commit!
398
+ end
399
+
400
+ def full_rollback?; false; end
401
+ end
402
+
403
+ # = Active Record Savepoint \Transaction
171
404
  class SavepointTransaction < Transaction
172
405
  def initialize(connection, savepoint_name, parent_transaction, **options)
173
406
  super(connection, **options)
@@ -186,19 +419,33 @@ module ActiveRecord
186
419
  super
187
420
  end
188
421
 
422
+ def restart
423
+ return unless materialized?
424
+
425
+ @instrumenter.finish(:restart)
426
+ @instrumenter.start
427
+
428
+ connection.rollback_to_savepoint(savepoint_name)
429
+ end
430
+
189
431
  def rollback
190
- connection.rollback_to_savepoint(savepoint_name) if materialized?
432
+ unless @state.invalidated?
433
+ connection.rollback_to_savepoint(savepoint_name) if materialized? && connection.active?
434
+ end
191
435
  @state.rollback!
436
+ @instrumenter.finish(:rollback) if materialized?
192
437
  end
193
438
 
194
439
  def commit
195
440
  connection.release_savepoint(savepoint_name) if materialized?
196
441
  @state.commit!
442
+ @instrumenter.finish(:commit) if materialized?
197
443
  end
198
444
 
199
445
  def full_rollback?; false; end
200
446
  end
201
447
 
448
+ # = Active Record Real \Transaction
202
449
  class RealTransaction < Transaction
203
450
  def materialize!
204
451
  if isolation_level
@@ -210,14 +457,30 @@ module ActiveRecord
210
457
  super
211
458
  end
212
459
 
460
+ def restart
461
+ return unless materialized?
462
+
463
+ @instrumenter.finish(:restart)
464
+
465
+ if connection.supports_restart_db_transaction?
466
+ @instrumenter.start
467
+ connection.restart_db_transaction
468
+ else
469
+ connection.rollback_db_transaction
470
+ materialize!
471
+ end
472
+ end
473
+
213
474
  def rollback
214
475
  connection.rollback_db_transaction if materialized?
215
476
  @state.full_rollback!
477
+ @instrumenter.finish(:rollback) if materialized?
216
478
  end
217
479
 
218
480
  def commit
219
481
  connection.commit_db_transaction if materialized?
220
482
  @state.full_commit!
483
+ @instrumenter.finish(:commit) if materialized?
221
484
  end
222
485
  end
223
486
 
@@ -241,21 +504,31 @@ module ActiveRecord
241
504
  joinable: joinable,
242
505
  run_commit_callbacks: run_commit_callbacks
243
506
  )
507
+ elsif current_transaction.restartable?
508
+ RestartParentTransaction.new(
509
+ @connection,
510
+ current_transaction,
511
+ isolation: isolation,
512
+ joinable: joinable,
513
+ run_commit_callbacks: run_commit_callbacks
514
+ )
244
515
  else
245
516
  SavepointTransaction.new(
246
517
  @connection,
247
518
  "active_record_#{@stack.size}",
248
- @stack.last,
519
+ current_transaction,
249
520
  isolation: isolation,
250
521
  joinable: joinable,
251
522
  run_commit_callbacks: run_commit_callbacks
252
523
  )
253
524
  end
254
525
 
255
- if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy
256
- @has_unmaterialized_transactions = true
257
- else
258
- transaction.materialize!
526
+ unless transaction.materialized?
527
+ if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy
528
+ @has_unmaterialized_transactions = true
529
+ else
530
+ transaction.materialize!
531
+ end
259
532
  end
260
533
  @stack.push(transaction)
261
534
  transaction
@@ -275,18 +548,35 @@ module ActiveRecord
275
548
  @lazy_transactions_enabled
276
549
  end
277
550
 
551
+ def dirty_current_transaction
552
+ current_transaction.dirty!
553
+ end
554
+
555
+ def restore_transactions
556
+ return false unless restorable?
557
+
558
+ @stack.each(&:restore!)
559
+
560
+ true
561
+ end
562
+
563
+ def restorable?
564
+ @stack.none?(&:dirty?)
565
+ end
566
+
278
567
  def materialize_transactions
279
568
  return if @materializing_transactions
280
- return unless @has_unmaterialized_transactions
281
569
 
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
570
+ if @has_unmaterialized_transactions
571
+ @connection.lock.synchronize do
572
+ begin
573
+ @materializing_transactions = true
574
+ @stack.each { |t| t.materialize! unless t.materialized? }
575
+ ensure
576
+ @materializing_transactions = false
577
+ end
578
+ @has_unmaterialized_transactions = false
288
579
  end
289
- @has_unmaterialized_transactions = false
290
580
  end
291
581
  end
292
582
 
@@ -300,6 +590,8 @@ module ActiveRecord
300
590
  @stack.pop
301
591
  end
302
592
 
593
+ dirty_current_transaction if transaction.dirty?
594
+
303
595
  transaction.commit
304
596
  transaction.commit_records
305
597
  end
@@ -307,8 +599,12 @@ module ActiveRecord
307
599
 
308
600
  def rollback_transaction(transaction = nil)
309
601
  @connection.lock.synchronize do
310
- transaction ||= @stack.pop
311
- transaction.rollback unless transaction.state.invalidated?
602
+ transaction ||= @stack.last
603
+ begin
604
+ transaction.rollback
605
+ ensure
606
+ @stack.pop if @stack.last == transaction
607
+ end
312
608
  transaction.rollback_records
313
609
  end
314
610
  end
@@ -316,39 +612,35 @@ module ActiveRecord
316
612
  def within_new_transaction(isolation: nil, joinable: true)
317
613
  @connection.lock.synchronize do
318
614
  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
615
+ begin
616
+ yield transaction.user_transaction
617
+ rescue Exception => error
325
618
  rollback_transaction
326
619
  after_failure_actions(transaction, error)
327
- end
328
620
 
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
621
+ raise
622
+ ensure
623
+ unless error
624
+ if Thread.current.status == "aborting"
625
+ rollback_transaction
626
+ else
627
+ begin
628
+ commit_transaction
629
+ rescue ActiveRecord::ConnectionFailed
630
+ transaction.invalidate! unless transaction.state.completed?
631
+ raise
632
+ rescue Exception
633
+ rollback_transaction(transaction) unless transaction.state.completed?
634
+ raise
635
+ end
349
636
  end
350
637
  end
351
638
  end
639
+ ensure
640
+ unless transaction&.state&.completed?
641
+ @connection.throw_away!
642
+ transaction&.incomplete!
643
+ end
352
644
  end
353
645
  end
354
646
 
@@ -361,7 +653,7 @@ module ActiveRecord
361
653
  end
362
654
 
363
655
  private
364
- NULL_TRANSACTION = NullTransaction.new
656
+ NULL_TRANSACTION = NullTransaction.new.freeze
365
657
 
366
658
  # Deallocate invalidated prepared statements outside of the transaction
367
659
  def after_failure_actions(transaction, error)