activerecord 7.1.5.1 → 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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +645 -2329
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +14 -7
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +7 -1
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/errors.rb +265 -0
  17. data/lib/active_record/associations/has_many_association.rb +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +7 -1
  19. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +59 -292
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +1 -13
  31. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  33. data/lib/active_record/attribute_methods.rb +54 -63
  34. data/lib/active_record/attributes.rb +61 -47
  35. data/lib/active_record/autosave_association.rb +12 -29
  36. data/lib/active_record/base.rb +2 -3
  37. data/lib/active_record/callbacks.rb +1 -1
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +270 -65
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +189 -74
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +15 -6
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -44
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +40 -10
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  58. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  59. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  60. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -11
  61. data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
  62. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  63. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  64. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  65. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  66. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  67. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  70. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +125 -75
  71. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  72. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  73. data/lib/active_record/connection_adapters.rb +121 -0
  74. data/lib/active_record/connection_handling.rb +56 -41
  75. data/lib/active_record/core.rb +85 -37
  76. data/lib/active_record/counter_cache.rb +18 -9
  77. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  78. data/lib/active_record/database_configurations/database_config.rb +19 -4
  79. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  80. data/lib/active_record/database_configurations/url_config.rb +20 -1
  81. data/lib/active_record/database_configurations.rb +1 -1
  82. data/lib/active_record/delegated_type.rb +24 -0
  83. data/lib/active_record/dynamic_matchers.rb +2 -2
  84. data/lib/active_record/encryption/encryptable_record.rb +3 -3
  85. data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
  86. data/lib/active_record/encryption/encryptor.rb +18 -3
  87. data/lib/active_record/encryption/key_provider.rb +1 -1
  88. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  89. data/lib/active_record/encryption/message_serializer.rb +4 -0
  90. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  91. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  92. data/lib/active_record/enum.rb +18 -1
  93. data/lib/active_record/errors.rb +46 -20
  94. data/lib/active_record/explain.rb +13 -24
  95. data/lib/active_record/fixtures.rb +37 -31
  96. data/lib/active_record/future_result.rb +8 -4
  97. data/lib/active_record/gem_version.rb +2 -2
  98. data/lib/active_record/inheritance.rb +4 -2
  99. data/lib/active_record/insert_all.rb +18 -15
  100. data/lib/active_record/integration.rb +4 -1
  101. data/lib/active_record/internal_metadata.rb +48 -34
  102. data/lib/active_record/locking/optimistic.rb +7 -6
  103. data/lib/active_record/log_subscriber.rb +0 -21
  104. data/lib/active_record/message_pack.rb +1 -1
  105. data/lib/active_record/migration/command_recorder.rb +2 -3
  106. data/lib/active_record/migration/compatibility.rb +5 -3
  107. data/lib/active_record/migration/default_strategy.rb +4 -5
  108. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  109. data/lib/active_record/migration.rb +85 -76
  110. data/lib/active_record/model_schema.rb +31 -68
  111. data/lib/active_record/nested_attributes.rb +11 -3
  112. data/lib/active_record/normalization.rb +3 -7
  113. data/lib/active_record/persistence.rb +30 -352
  114. data/lib/active_record/query_cache.rb +19 -8
  115. data/lib/active_record/query_logs.rb +15 -0
  116. data/lib/active_record/querying.rb +21 -9
  117. data/lib/active_record/railtie.rb +37 -55
  118. data/lib/active_record/railties/controller_runtime.rb +13 -4
  119. data/lib/active_record/railties/databases.rake +40 -43
  120. data/lib/active_record/reflection.rb +98 -36
  121. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  122. data/lib/active_record/relation/batches.rb +14 -8
  123. data/lib/active_record/relation/calculations.rb +96 -63
  124. data/lib/active_record/relation/delegation.rb +8 -11
  125. data/lib/active_record/relation/finder_methods.rb +16 -2
  126. data/lib/active_record/relation/merger.rb +4 -6
  127. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  128. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
  129. data/lib/active_record/relation/predicate_builder.rb +3 -3
  130. data/lib/active_record/relation/query_methods.rb +224 -58
  131. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  132. data/lib/active_record/relation/spawn_methods.rb +2 -18
  133. data/lib/active_record/relation/where_clause.rb +7 -19
  134. data/lib/active_record/relation.rb +496 -72
  135. data/lib/active_record/result.rb +31 -44
  136. data/lib/active_record/runtime_registry.rb +39 -0
  137. data/lib/active_record/sanitization.rb +24 -19
  138. data/lib/active_record/schema.rb +8 -6
  139. data/lib/active_record/schema_dumper.rb +19 -9
  140. data/lib/active_record/schema_migration.rb +30 -14
  141. data/lib/active_record/scoping/named.rb +1 -0
  142. data/lib/active_record/signed_id.rb +20 -1
  143. data/lib/active_record/statement_cache.rb +7 -7
  144. data/lib/active_record/table_metadata.rb +1 -10
  145. data/lib/active_record/tasks/database_tasks.rb +69 -41
  146. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  147. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  148. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  149. data/lib/active_record/test_fixtures.rb +86 -89
  150. data/lib/active_record/testing/query_assertions.rb +121 -0
  151. data/lib/active_record/timestamp.rb +2 -2
  152. data/lib/active_record/token_for.rb +22 -12
  153. data/lib/active_record/touch_later.rb +1 -1
  154. data/lib/active_record/transaction.rb +132 -0
  155. data/lib/active_record/transactions.rb +70 -14
  156. data/lib/active_record/translation.rb +0 -2
  157. data/lib/active_record/type/serialized.rb +1 -3
  158. data/lib/active_record/type_caster/connection.rb +4 -4
  159. data/lib/active_record/validations/associated.rb +9 -3
  160. data/lib/active_record/validations/uniqueness.rb +15 -10
  161. data/lib/active_record/validations.rb +4 -1
  162. data/lib/active_record.rb +148 -39
  163. data/lib/arel/alias_predication.rb +1 -1
  164. data/lib/arel/collectors/bind.rb +2 -0
  165. data/lib/arel/collectors/composite.rb +7 -0
  166. data/lib/arel/collectors/sql_string.rb +1 -1
  167. data/lib/arel/collectors/substitute_binds.rb +1 -1
  168. data/lib/arel/nodes/binary.rb +0 -6
  169. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  170. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  171. data/lib/arel/nodes/node.rb +4 -3
  172. data/lib/arel/nodes/sql_literal.rb +7 -0
  173. data/lib/arel/nodes.rb +2 -2
  174. data/lib/arel/predications.rb +1 -1
  175. data/lib/arel/select_manager.rb +1 -1
  176. data/lib/arel/tree_manager.rb +3 -2
  177. data/lib/arel/update_manager.rb +2 -1
  178. data/lib/arel/visitors/dot.rb +1 -0
  179. data/lib/arel/visitors/mysql.rb +9 -4
  180. data/lib/arel/visitors/postgresql.rb +1 -12
  181. data/lib/arel/visitors/sqlite.rb +25 -0
  182. data/lib/arel/visitors/to_sql.rb +29 -16
  183. data/lib/arel.rb +7 -3
  184. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  185. metadata +16 -10
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/digest"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  # = Active Record Connection Adapters Transaction State
@@ -89,7 +91,9 @@ module ActiveRecord
89
91
  raise InstrumentationAlreadyStartedError.new("Called start on an already started transaction") if @started
90
92
  @started = true
91
93
 
92
- @payload = @base_payload.dup
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.
93
97
  @handle = ActiveSupport::Notifications.instrumenter.build_handle("transaction.active_record", @payload)
94
98
  @handle.start
95
99
  end
@@ -104,7 +108,6 @@ module ActiveRecord
104
108
  end
105
109
 
106
110
  class NullTransaction # :nodoc:
107
- def initialize; end
108
111
  def state; end
109
112
  def closed?; true; end
110
113
  def open?; false; end
@@ -115,17 +118,43 @@ module ActiveRecord
115
118
  def dirty!; end
116
119
  def invalidated?; false; end
117
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
118
126
  end
119
127
 
120
128
  class Transaction # :nodoc:
121
- 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
122
149
  attr_accessor :written
123
150
 
124
151
  delegate :invalidate!, :invalidated?, to: :@state
125
152
 
126
153
  def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
154
+ super()
127
155
  @connection = connection
128
156
  @state = TransactionState.new
157
+ @callbacks = nil
129
158
  @records = nil
130
159
  @isolation_level = isolation
131
160
  @materialized = false
@@ -133,7 +162,8 @@ module ActiveRecord
133
162
  @run_commit_callbacks = run_commit_callbacks
134
163
  @lazy_enrollment_records = nil
135
164
  @dirty = false
136
- @instrumenter = TransactionInstrumenter.new(connection: connection)
165
+ @user_transaction = joinable ? ActiveRecord::Transaction.new(self) : ActiveRecord::Transaction::NULL_TRANSACTION
166
+ @instrumenter = TransactionInstrumenter.new(connection: connection, transaction: @user_transaction)
137
167
  end
138
168
 
139
169
  def dirty!
@@ -144,6 +174,14 @@ module ActiveRecord
144
174
  @dirty
145
175
  end
146
176
 
177
+ def open?
178
+ true
179
+ end
180
+
181
+ def closed?
182
+ false
183
+ end
184
+
147
185
  def add_record(record, ensure_finalize = true)
148
186
  @records ||= []
149
187
  if ensure_finalize
@@ -154,6 +192,30 @@ module ActiveRecord
154
192
  end
155
193
  end
156
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
+
157
219
  def records
158
220
  if @lazy_enrollment_records
159
221
  @records.concat @lazy_enrollment_records.values
@@ -190,66 +252,85 @@ module ActiveRecord
190
252
  end
191
253
 
192
254
  def rollback_records
193
- return unless records
194
-
195
- ite = unique_records
255
+ if records
256
+ begin
257
+ ite = unique_records
196
258
 
197
- instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
259
+ instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
198
260
 
199
- run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
200
- record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks)
201
- end
202
- ensure
203
- ite&.each do |i|
204
- i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false)
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
205
269
  end
270
+
271
+ @callbacks&.each(&:after_rollback)
206
272
  end
207
273
 
208
274
  def before_commit_records
209
- return unless records
210
-
211
275
  if @run_commit_callbacks
212
- if ActiveRecord.before_committed_on_all_records
213
- ite = unique_records
276
+ if records
277
+ if ActiveRecord.before_committed_on_all_records
278
+ ite = unique_records
214
279
 
215
- instances_to_run_callbacks_on = records.each_with_object({}) do |record, candidates|
216
- candidates[record] = record
217
- end
280
+ instances_to_run_callbacks_on = records.each_with_object({}) do |record, candidates|
281
+ candidates[record] = record
282
+ end
218
283
 
219
- run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
220
- record.before_committed! if should_run_callbacks
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!)
221
289
  end
222
- else
223
- records.uniq.each(&:before_committed!)
224
290
  end
291
+
292
+ @callbacks&.each(&:before_commit)
225
293
  end
294
+ # Note: When @run_commit_callbacks is false #commit_records takes care of appending
295
+ # remaining callbacks to the parent transaction
226
296
  end
227
297
 
228
298
  def commit_records
229
- return unless records
230
-
231
- ite = unique_records
299
+ if records
300
+ begin
301
+ ite = unique_records
232
302
 
233
- if @run_commit_callbacks
234
- instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
303
+ if @run_commit_callbacks
304
+ instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
235
305
 
236
- run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
237
- record.committed!(should_run_callbacks: should_run_callbacks)
238
- end
239
- else
240
- while record = ite.shift
241
- # if not running callbacks, only adds the record to the parent transaction
242
- connection.add_transaction_record(record)
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) }
243
317
  end
244
318
  end
245
- ensure
246
- 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
247
325
  end
248
326
 
249
327
  def full_rollback?; true; end
250
328
  def joinable?; @joinable; end
251
- def closed?; false; end
252
- def open?; !closed?; end
329
+
330
+ protected
331
+ def append_callbacks(callbacks) # :nodoc:
332
+ (@callbacks ||= []).concat(callbacks)
333
+ end
253
334
 
254
335
  private
255
336
  def unique_records
@@ -349,7 +430,7 @@ module ActiveRecord
349
430
 
350
431
  def rollback
351
432
  unless @state.invalidated?
352
- connection.rollback_to_savepoint(savepoint_name) if materialized?
433
+ connection.rollback_to_savepoint(savepoint_name) if materialized? && connection.active?
353
434
  end
354
435
  @state.rollback!
355
436
  @instrumenter.finish(:rollback) if materialized?
@@ -532,9 +613,7 @@ module ActiveRecord
532
613
  @connection.lock.synchronize do
533
614
  transaction = begin_transaction(isolation: isolation, joinable: joinable)
534
615
  begin
535
- ret = yield
536
- completed = true
537
- ret
616
+ yield transaction.user_transaction
538
617
  rescue Exception => error
539
618
  rollback_transaction
540
619
  after_failure_actions(transaction, error)
@@ -542,24 +621,8 @@ module ActiveRecord
542
621
  raise
543
622
  ensure
544
623
  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
624
  if Thread.current.status == "aborting"
551
625
  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
626
  else
564
627
  begin
565
628
  commit_transaction
@@ -590,7 +653,7 @@ module ActiveRecord
590
653
  end
591
654
 
592
655
  private
593
- NULL_TRANSACTION = NullTransaction.new
656
+ NULL_TRANSACTION = NullTransaction.new.freeze
594
657
 
595
658
  # Deallocate invalidated prepared statements outside of the transaction
596
659
  def after_failure_actions(transaction, error)
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  # and +:limit+ options, etc.
24
24
  #
25
25
  # All the concrete database adapters follow the interface laid down in this class.
26
- # {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling#connection] returns an AbstractAdapter object, which
26
+ # {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling#lease_connection] returns an AbstractAdapter object, which
27
27
  # you can use.
28
28
  #
29
29
  # Most of the methods in the adapter are useful during migrations. Most
@@ -49,8 +49,6 @@ module ActiveRecord
49
49
  return if value.eql?(@pool)
50
50
  @schema_cache = nil
51
51
  @pool = value
52
-
53
- @pool.schema_reflection.load!(self) if ActiveRecord.lazily_load_schema_cache
54
52
  end
55
53
 
56
54
  set_callback :checkin, :after, :enable_lazy_transactions!
@@ -136,7 +134,7 @@ module ActiveRecord
136
134
  @logger = ActiveRecord::Base.logger
137
135
 
138
136
  if deprecated_logger || deprecated_connection_options || deprecated_config
139
- raise ArgumentError, "when initializing an ActiveRecord adapter with a config hash, that should be the only argument"
137
+ raise ArgumentError, "when initializing an Active Record adapter with a config hash, that should be the only argument"
140
138
  end
141
139
  else
142
140
  # Soft-deprecated for now; we'll probably warn in future.
@@ -174,19 +172,20 @@ module ActiveRecord
174
172
  @verified = false
175
173
  end
176
174
 
177
- THREAD_LOCK = ActiveSupport::Concurrency::ThreadLoadInterlockAwareMonitor.new
178
- private_constant :THREAD_LOCK
175
+ def inspect # :nodoc:
176
+ name_field = " name=#{pool.db_config.name.inspect}" unless pool.db_config.name == "primary"
177
+ shard_field = " shard=#{shard.inspect}" unless shard == :default
179
178
 
180
- FIBER_LOCK = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
181
- private_constant :FIBER_LOCK
179
+ "#<#{self.class.name}:#{'%#016x' % (object_id << 1)} env_name=#{pool.db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
180
+ end
182
181
 
183
182
  def lock_thread=(lock_thread) # :nodoc:
184
183
  @lock =
185
184
  case lock_thread
186
185
  when Thread
187
- THREAD_LOCK
186
+ ActiveSupport::Concurrency::ThreadLoadInterlockAwareMonitor.new
188
187
  when Fiber
189
- FIBER_LOCK
188
+ ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
190
189
  else
191
190
  ActiveSupport::Concurrency::NullLock
192
191
  end
@@ -215,10 +214,6 @@ module ActiveRecord
215
214
  @config[:replica] || false
216
215
  end
217
216
 
218
- def use_metadata_table?
219
- @config.fetch(:use_metadata_table, true)
220
- end
221
-
222
217
  def connection_retries
223
218
  (@config[:connection_retries] || 1).to_i
224
219
  end
@@ -246,22 +241,6 @@ module ActiveRecord
246
241
  connection_class.current_preventing_writes
247
242
  end
248
243
 
249
- def migrations_paths # :nodoc:
250
- @config[:migrations_paths] || Migrator.migrations_paths
251
- end
252
-
253
- def migration_context # :nodoc:
254
- MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
255
- end
256
-
257
- def schema_migration # :nodoc:
258
- SchemaMigration.new(self)
259
- end
260
-
261
- def internal_metadata # :nodoc:
262
- InternalMetadata.new(self)
263
- end
264
-
265
244
  def prepared_statements?
266
245
  @prepared_statements && !prepared_statements_disabled_cache.include?(object_id)
267
246
  end
@@ -327,7 +306,7 @@ module ActiveRecord
327
306
  end
328
307
 
329
308
  def schema_cache
330
- @schema_cache ||= BoundSchemaReflection.new(@pool.schema_reflection, self)
309
+ @pool.schema_cache || (@schema_cache ||= BoundSchemaReflection.for_lone_connection(@pool.schema_reflection, self))
331
310
  end
332
311
 
333
312
  # this method must only be called while holding connection pool's mutex
@@ -580,7 +559,7 @@ module ActiveRecord
580
559
  end
581
560
 
582
561
  def return_value_after_insert?(column) # :nodoc:
583
- column.auto_incremented_by_db?
562
+ column.auto_populated?
584
563
  end
585
564
 
586
565
  def async_enabled? # :nodoc:
@@ -651,15 +630,6 @@ module ActiveRecord
651
630
  yield
652
631
  end
653
632
 
654
- # Override to check all foreign key constraints in a database.
655
- def all_foreign_keys_valid?
656
- check_all_foreign_keys_valid!
657
- true
658
- rescue ActiveRecord::StatementInvalid
659
- false
660
- end
661
- deprecate :all_foreign_keys_valid?, deprecator: ActiveRecord.deprecator
662
-
663
633
  # Override to check all foreign key constraints in a database.
664
634
  # The adapter should raise a +ActiveRecord::StatementInvalid+ if foreign key
665
635
  # constraints are not met.
@@ -668,6 +638,13 @@ module ActiveRecord
668
638
 
669
639
  # CONNECTION MANAGEMENT ====================================
670
640
 
641
+ # Checks whether the connection to the database was established. This doesn't
642
+ # include checking whether the database is actually capable of responding, i.e.
643
+ # whether the connection is stale.
644
+ def connected?
645
+ !@raw_connection.nil?
646
+ end
647
+
671
648
  # Checks whether the connection to the database is still active. This includes
672
649
  # checking whether the database is actually capable of responding, i.e. whether
673
650
  # the connection isn't stale.
@@ -867,7 +844,7 @@ module ActiveRecord
867
844
  end
868
845
 
869
846
  def database_version # :nodoc:
870
- schema_cache.database_version
847
+ pool.server_version(self)
871
848
  end
872
849
 
873
850
  def check_version # :nodoc:
@@ -878,7 +855,7 @@ module ActiveRecord
878
855
  # numbered migration that has been executed, or 0 if no schema
879
856
  # information is present / the database is empty.
880
857
  def schema_version
881
- migration_context.current_version
858
+ pool.migration_context.current_version
882
859
  end
883
860
 
884
861
  class << self
@@ -1148,6 +1125,8 @@ module ActiveRecord
1148
1125
  statement_name: statement_name,
1149
1126
  async: async,
1150
1127
  connection: self,
1128
+ transaction: current_transaction.user_transaction.presence,
1129
+ row_count: 0,
1151
1130
  &block
1152
1131
  )
1153
1132
  rescue ActiveRecord::StatementInvalid => ex
@@ -1211,7 +1190,7 @@ module ActiveRecord
1211
1190
  #
1212
1191
  # This is an internal hook to make possible connection adapters to build
1213
1192
  # custom result objects with connection-specific data.
1214
- def build_result(columns:, rows:, column_types: {})
1193
+ def build_result(columns:, rows:, column_types: nil)
1215
1194
  ActiveRecord::Result.new(columns, rows, column_types)
1216
1195
  end
1217
1196
 
@@ -1223,6 +1202,7 @@ module ActiveRecord
1223
1202
  # Implementations may assume this method will only be called while
1224
1203
  # holding @lock (or from #initialize).
1225
1204
  def configure_connection
1205
+ check_version
1226
1206
  end
1227
1207
 
1228
1208
  def default_prepared_statements
@@ -170,6 +170,10 @@ module ActiveRecord
170
170
  true
171
171
  end
172
172
 
173
+ def supports_insert_returning?
174
+ mariadb? && database_version >= "10.5.0"
175
+ end
176
+
173
177
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
174
178
  query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
175
179
  end
@@ -214,7 +218,7 @@ module ActiveRecord
214
218
  update("SET FOREIGN_KEY_CHECKS = 0")
215
219
  yield
216
220
  ensure
217
- update("SET FOREIGN_KEY_CHECKS = #{old}")
221
+ update("SET FOREIGN_KEY_CHECKS = #{old}") if active?
218
222
  end
219
223
  end
220
224
 
@@ -225,12 +229,12 @@ module ActiveRecord
225
229
  # Mysql2Adapter doesn't have to free a result after using it, but we use this method
226
230
  # to write stuff in an abstract way without concerning ourselves about whether it
227
231
  # needs to be explicitly freed or not.
228
- def execute_and_free(sql, name = nil, async: false) # :nodoc:
232
+ def execute_and_free(sql, name = nil, async: false, allow_retry: false) # :nodoc:
229
233
  sql = transform_query(sql)
230
234
  check_if_write_query(sql)
231
235
 
232
236
  mark_transaction_written_if_write(sql)
233
- yield raw_execute(sql, name, async: async)
237
+ yield raw_execute(sql, name, async: async, allow_retry: allow_retry)
234
238
  end
235
239
 
236
240
  def begin_db_transaction # :nodoc:
@@ -635,16 +639,18 @@ module ActiveRecord
635
639
  end
636
640
 
637
641
  def build_insert_sql(insert) # :nodoc:
638
- no_op_column = quote_column_name(insert.keys.first)
642
+ no_op_column = quote_column_name(insert.keys.first) if insert.keys.first
639
643
 
640
644
  # MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
641
645
  # then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
642
646
  if supports_insert_raw_alias_syntax?
643
- values_alias = quote_table_name("#{insert.model.table_name}_values")
647
+ values_alias = quote_table_name("#{insert.model.table_name.parameterize}_values")
644
648
  sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
645
649
 
646
650
  if insert.skip_duplicates?
647
- sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{values_alias}.#{no_op_column}"
651
+ if no_op_column
652
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{values_alias}.#{no_op_column}"
653
+ end
648
654
  elsif insert.update_duplicates?
649
655
  if insert.raw_update_sql?
650
656
  sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
@@ -658,7 +664,9 @@ module ActiveRecord
658
664
  sql = +"INSERT #{insert.into} #{insert.values_list}"
659
665
 
660
666
  if insert.skip_duplicates?
661
- sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
667
+ if no_op_column
668
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
669
+ end
662
670
  elsif insert.update_duplicates?
663
671
  sql << " ON DUPLICATE KEY UPDATE "
664
672
  if insert.raw_update_sql?
@@ -670,12 +678,24 @@ module ActiveRecord
670
678
  end
671
679
  end
672
680
 
681
+ sql << " RETURNING #{insert.returning}" if insert.returning
673
682
  sql
674
683
  end
675
684
 
676
685
  def check_version # :nodoc:
677
686
  if database_version < "5.5.8"
678
- raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
687
+ raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
688
+ end
689
+ end
690
+
691
+ #--
692
+ # QUOTING ==================================================
693
+ #++
694
+
695
+ # Quotes strings for use in SQL input.
696
+ def quote_string(string)
697
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
698
+ connection.escape(string)
679
699
  end
680
700
  end
681
701
 
@@ -754,7 +774,11 @@ module ActiveRecord
754
774
  return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
755
775
 
756
776
  @affected_rows_before_warnings = @raw_connection.affected_rows
777
+ warning_count = @raw_connection.warning_count
757
778
  result = @raw_connection.query("SHOW WARNINGS")
779
+ result = [
780
+ ["Warning", nil, "Query had warning_count=#{warning_count} but ‘SHOW WARNINGS’ did not return the warnings. Check MySQL logs or database configuration."],
781
+ ] if result.count == 0
758
782
  result.each do |level, code, message|
759
783
  warning = SQLWarning.new(message, code, level, sql, @pool)
760
784
  next if warning_ignored?(warning)
@@ -776,6 +800,7 @@ module ActiveRecord
776
800
  ER_DB_CREATE_EXISTS = 1007
777
801
  ER_FILSORT_ABORT = 1028
778
802
  ER_DUP_ENTRY = 1062
803
+ ER_SERVER_SHUTDOWN = 1053
779
804
  ER_NOT_NULL_VIOLATION = 1048
780
805
  ER_NO_REFERENCED_ROW = 1216
781
806
  ER_ROW_IS_REFERENCED = 1217
@@ -804,7 +829,7 @@ module ActiveRecord
804
829
  else
805
830
  super
806
831
  end
807
- when ER_CONNECTION_KILLED, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
832
+ when ER_CONNECTION_KILLED, ER_SERVER_SHUTDOWN, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
808
833
  ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
809
834
  when ER_DB_CREATE_EXISTS
810
835
  DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
@@ -894,6 +919,7 @@ module ActiveRecord
894
919
  end
895
920
 
896
921
  def configure_connection
922
+ super
897
923
  variables = @config.fetch(:variables, {}).stringify_keys
898
924
 
899
925
  # Increase timeout so the server doesn't disconnect us.
@@ -1001,7 +1027,11 @@ module ActiveRecord
1001
1027
  end
1002
1028
 
1003
1029
  def version_string(full_version_string)
1004
- full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
1030
+ if full_version_string && matches = full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)
1031
+ matches[1]
1032
+ else
1033
+ raise DatabaseVersionError, "Unable to parse MySQL version from #{full_version_string.inspect}"
1034
+ end
1005
1035
  end
1006
1036
  end
1007
1037
  end
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
 
12
12
  # https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_current-timestamp
13
13
  # https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html
14
- HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
14
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)", retryable: true).freeze # :nodoc:
15
15
  private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
16
16
 
17
17
  def write_query?(sql) # :nodoc:
@@ -55,6 +55,14 @@ module ActiveRecord
55
55
  super unless column.auto_increment?
56
56
  end
57
57
 
58
+ def returning_column_values(result)
59
+ if supports_insert_returning?
60
+ result.rows.first
61
+ else
62
+ super
63
+ end
64
+ end
65
+
58
66
  def combine_multi_statements(total_sql)
59
67
  total_sql.each_with_object([]) do |sql, total_sql_chunks|
60
68
  previous_packet = total_sql_chunks.last