activerecord 7.2.2.1 → 8.0.5

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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +523 -677
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +1 -0
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +34 -10
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +7 -6
  9. data/lib/active_record/associations/collection_association.rb +10 -8
  10. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  11. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  12. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  13. data/lib/active_record/associations/join_dependency.rb +2 -2
  14. data/lib/active_record/associations/preloader/association.rb +2 -2
  15. data/lib/active_record/associations/preloader/batch.rb +7 -1
  16. data/lib/active_record/associations/singular_association.rb +8 -3
  17. data/lib/active_record/associations.rb +34 -4
  18. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  19. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  20. data/lib/active_record/attribute_methods/query.rb +34 -0
  21. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  22. data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
  23. data/lib/active_record/attribute_methods.rb +24 -19
  24. data/lib/active_record/attributes.rb +37 -26
  25. data/lib/active_record/autosave_association.rb +91 -39
  26. data/lib/active_record/base.rb +2 -2
  27. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
  28. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  29. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  30. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +55 -47
  31. data/lib/active_record/connection_adapters/abstract/database_statements.rb +91 -44
  32. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -8
  33. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  34. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  35. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
  36. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +40 -10
  37. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  38. data/lib/active_record/connection_adapters/abstract_adapter.rb +92 -70
  39. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +35 -46
  40. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
  41. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
  43. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
  44. data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -9
  45. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  46. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -2
  47. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
  48. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  49. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  50. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  51. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  52. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -24
  53. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
  54. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +60 -17
  55. data/lib/active_record/connection_adapters/postgresql_adapter.rb +61 -99
  56. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  57. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  58. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
  59. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
  60. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  61. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  62. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +10 -1
  63. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +63 -17
  64. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  65. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  66. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
  67. data/lib/active_record/connection_adapters.rb +0 -56
  68. data/lib/active_record/connection_handling.rb +35 -9
  69. data/lib/active_record/core.rb +57 -22
  70. data/lib/active_record/counter_cache.rb +1 -1
  71. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  72. data/lib/active_record/database_configurations/database_config.rb +4 -0
  73. data/lib/active_record/database_configurations/hash_config.rb +16 -2
  74. data/lib/active_record/delegated_type.rb +18 -18
  75. data/lib/active_record/encryption/config.rb +3 -1
  76. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  77. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  78. data/lib/active_record/encryption/encryptor.rb +37 -28
  79. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  80. data/lib/active_record/encryption/scheme.rb +8 -1
  81. data/lib/active_record/enum.rb +22 -34
  82. data/lib/active_record/errors.rb +16 -8
  83. data/lib/active_record/fixture_set/table_row.rb +19 -2
  84. data/lib/active_record/fixtures.rb +0 -2
  85. data/lib/active_record/future_result.rb +15 -9
  86. data/lib/active_record/gem_version.rb +4 -4
  87. data/lib/active_record/insert_all.rb +3 -3
  88. data/lib/active_record/locking/optimistic.rb +1 -1
  89. data/lib/active_record/log_subscriber.rb +5 -11
  90. data/lib/active_record/migration/command_recorder.rb +32 -12
  91. data/lib/active_record/migration/compatibility.rb +5 -2
  92. data/lib/active_record/migration.rb +40 -43
  93. data/lib/active_record/model_schema.rb +3 -4
  94. data/lib/active_record/nested_attributes.rb +4 -6
  95. data/lib/active_record/persistence.rb +128 -130
  96. data/lib/active_record/query_logs.rb +106 -50
  97. data/lib/active_record/query_logs_formatter.rb +17 -28
  98. data/lib/active_record/querying.rb +12 -12
  99. data/lib/active_record/railtie.rb +4 -28
  100. data/lib/active_record/railties/databases.rake +11 -34
  101. data/lib/active_record/reflection.rb +18 -21
  102. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  103. data/lib/active_record/relation/batches.rb +132 -72
  104. data/lib/active_record/relation/calculations.rb +71 -65
  105. data/lib/active_record/relation/delegation.rb +25 -14
  106. data/lib/active_record/relation/finder_methods.rb +32 -31
  107. data/lib/active_record/relation/merger.rb +8 -8
  108. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  109. data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
  110. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  111. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  112. data/lib/active_record/relation/predicate_builder.rb +13 -0
  113. data/lib/active_record/relation/query_attribute.rb +1 -1
  114. data/lib/active_record/relation/query_methods.rb +121 -70
  115. data/lib/active_record/relation/spawn_methods.rb +7 -7
  116. data/lib/active_record/relation/where_clause.rb +9 -3
  117. data/lib/active_record/relation.rb +95 -67
  118. data/lib/active_record/result.rb +66 -4
  119. data/lib/active_record/sanitization.rb +7 -6
  120. data/lib/active_record/schema_dumper.rb +34 -11
  121. data/lib/active_record/schema_migration.rb +2 -1
  122. data/lib/active_record/scoping/named.rb +5 -2
  123. data/lib/active_record/secure_token.rb +3 -3
  124. data/lib/active_record/signed_id.rb +7 -6
  125. data/lib/active_record/statement_cache.rb +14 -14
  126. data/lib/active_record/store.rb +7 -3
  127. data/lib/active_record/table_metadata.rb +1 -3
  128. data/lib/active_record/tasks/database_tasks.rb +69 -60
  129. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  130. data/lib/active_record/tasks/postgresql_database_tasks.rb +9 -1
  131. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  132. data/lib/active_record/test_databases.rb +1 -1
  133. data/lib/active_record/test_fixtures.rb +12 -0
  134. data/lib/active_record/token_for.rb +1 -1
  135. data/lib/active_record/transactions.rb +8 -7
  136. data/lib/active_record/type/serialized.rb +5 -0
  137. data/lib/active_record/validations/uniqueness.rb +8 -8
  138. data/lib/active_record.rb +22 -49
  139. data/lib/arel/collectors/bind.rb +2 -2
  140. data/lib/arel/collectors/sql_string.rb +1 -1
  141. data/lib/arel/collectors/substitute_binds.rb +2 -2
  142. data/lib/arel/crud.rb +2 -0
  143. data/lib/arel/delete_manager.rb +5 -0
  144. data/lib/arel/nodes/binary.rb +1 -1
  145. data/lib/arel/nodes/delete_statement.rb +4 -2
  146. data/lib/arel/nodes/node.rb +1 -1
  147. data/lib/arel/nodes/sql_literal.rb +1 -1
  148. data/lib/arel/nodes/update_statement.rb +4 -2
  149. data/lib/arel/predications.rb +1 -3
  150. data/lib/arel/select_manager.rb +6 -2
  151. data/lib/arel/table.rb +3 -7
  152. data/lib/arel/update_manager.rb +5 -0
  153. data/lib/arel/visitors/dot.rb +2 -0
  154. data/lib/arel/visitors/to_sql.rb +3 -1
  155. metadata +11 -15
  156. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
4
3
  require "concurrent/map"
5
4
  require "monitor"
6
5
 
@@ -37,7 +36,8 @@ module ActiveRecord
37
36
  end
38
37
 
39
38
  def schema_cache; end
40
- def connection_class; end
39
+ def query_cache; end
40
+ def connection_descriptor; end
41
41
  def checkin(_); end
42
42
  def remove(_); end
43
43
  def async_executor; end
@@ -118,24 +118,30 @@ module ActiveRecord
118
118
  # * private methods that require being called in a +synchronize+ blocks
119
119
  # are now explicitly documented
120
120
  class ConnectionPool
121
- class WeakThreadKeyMap # :nodoc:
122
- # FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
123
- # but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
124
- def initialize
125
- @map = {}
126
- end
127
-
128
- def clear
129
- @map.clear
130
- end
121
+ # Prior to 3.3.5, WeakKeyMap had a use after free bug
122
+ # https://bugs.ruby-lang.org/issues/20688
123
+ if ObjectSpace.const_defined?(:WeakKeyMap) && Gem::Version.new(RUBY_VERSION) >= "3.3.5"
124
+ WeakThreadKeyMap = ObjectSpace::WeakKeyMap
125
+ else
126
+ class WeakThreadKeyMap # :nodoc:
127
+ # FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
128
+ # but it currently causes GC crashes: https://github.com/byroot/rails/pull/3
129
+ def initialize
130
+ @map = {}
131
+ end
132
+
133
+ def clear
134
+ @map.clear
135
+ end
131
136
 
132
- def [](key)
133
- @map[key]
134
- end
137
+ def [](key)
138
+ @map[key]
139
+ end
135
140
 
136
- def []=(key, value)
137
- @map.select! { |c, _| c.alive? }
138
- @map[key] = value
141
+ def []=(key, value)
142
+ @map.select! { |c, _| c&.alive? }
143
+ @map[key] = value
144
+ end
139
145
  end
140
146
  end
141
147
 
@@ -309,22 +315,15 @@ module ActiveRecord
309
315
  # held in a cache keyed by a thread.
310
316
  def lease_connection
311
317
  lease = connection_lease
312
- lease.sticky = true
313
318
  lease.connection ||= checkout
319
+ lease.sticky = true
320
+ lease.connection
314
321
  end
315
322
 
316
323
  def permanent_lease? # :nodoc:
317
324
  connection_lease.sticky.nil?
318
325
  end
319
326
 
320
- def connection
321
- ActiveRecord.deprecator.warn(<<~MSG)
322
- ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
323
- and will be removed in Rails 8.0. Use #lease_connection instead.
324
- MSG
325
- lease_connection
326
- end
327
-
328
327
  def pin_connection!(lock_thread) # :nodoc:
329
328
  @pinned_connection ||= (connection_lease&.connection || checkout)
330
329
  @pinned_connections_depth += 1
@@ -336,6 +335,7 @@ module ActiveRecord
336
335
  end
337
336
 
338
337
  @pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
338
+ @pinned_connection.pinned = true
339
339
  @pinned_connection.verify! # eagerly validate the connection
340
340
  @pinned_connection.begin_transaction joinable: false, _lazy: false
341
341
  end
@@ -358,6 +358,7 @@ module ActiveRecord
358
358
  end
359
359
 
360
360
  if @pinned_connection.nil?
361
+ connection.pinned = false
361
362
  connection.steal!
362
363
  connection.lock_thread = nil
363
364
  checkin(connection)
@@ -367,8 +368,8 @@ module ActiveRecord
367
368
  clean
368
369
  end
369
370
 
370
- def connection_class # :nodoc:
371
- pool_config.connection_class
371
+ def connection_descriptor # :nodoc:
372
+ pool_config.connection_descriptor
372
373
  end
373
374
 
374
375
  # Returns true if there is an open connection being used for the current thread.
@@ -462,7 +463,7 @@ module ActiveRecord
462
463
  end
463
464
  conn.disconnect!
464
465
  end
465
- @connections = []
466
+ @connections = @pinned_connection ? [@pinned_connection] : []
466
467
  @leases.clear
467
468
  @available.clear
468
469
  end
@@ -498,7 +499,7 @@ module ActiveRecord
498
499
  @connections.nil?
499
500
  end
500
501
 
501
- # Clears the cache which maps classes and re-connects connections that
502
+ # Clears reloadable connections from the pool and re-connects connections that
502
503
  # require reloading.
503
504
  #
504
505
  # Raises:
@@ -521,7 +522,7 @@ module ActiveRecord
521
522
  end
522
523
  end
523
524
 
524
- # Clears the cache which maps classes and re-connects connections that
525
+ # Clears reloadable connections from the pool and re-connects connections that
525
526
  # require reloading.
526
527
  #
527
528
  # The pool first tries to gain ownership of all connections. If unable to
@@ -548,20 +549,25 @@ module ActiveRecord
548
549
  # Raises:
549
550
  # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
550
551
  def checkout(checkout_timeout = @checkout_timeout)
551
- if @pinned_connection
552
- @pinned_connection.lock.synchronize do
553
- synchronize do
552
+ return checkout_and_verify(acquire_connection(checkout_timeout)) unless @pinned_connection
553
+
554
+ @pinned_connection.lock.synchronize do
555
+ synchronize do
556
+ # The pinned connection may have been cleaned up before we synchronized, so check if it is still present
557
+ if @pinned_connection
554
558
  @pinned_connection.verify!
559
+
555
560
  # Any leased connection must be in @connections otherwise
556
561
  # some methods like #connected? won't behave correctly
557
562
  unless @connections.include?(@pinned_connection)
558
563
  @connections << @pinned_connection
559
564
  end
565
+
566
+ @pinned_connection
567
+ else
568
+ checkout_and_verify(acquire_connection(checkout_timeout))
560
569
  end
561
570
  end
562
- @pinned_connection
563
- else
564
- checkout_and_verify(acquire_connection(checkout_timeout))
565
571
  end
566
572
  end
567
573
 
@@ -696,6 +702,14 @@ module ActiveRecord
696
702
  Thread.pass
697
703
  end
698
704
 
705
+ def new_connection # :nodoc:
706
+ connection = db_config.new_connection
707
+ connection.pool = self
708
+ connection
709
+ rescue ConnectionNotEstablished => ex
710
+ raise ex.set_pool(self)
711
+ end
712
+
699
713
  private
700
714
  def connection_lease
701
715
  @leases[ActiveSupport::IsolatedExecutionState.context]
@@ -875,18 +889,12 @@ module ActiveRecord
875
889
  #--
876
890
  # if owner_thread param is omitted, this must be called in synchronize block
877
891
  def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
878
- @leases[owner_thread].clear(conn)
892
+ if owner_thread
893
+ @leases[owner_thread].clear(conn)
894
+ end
879
895
  end
880
896
  alias_method :release, :remove_connection_from_thread_cache
881
897
 
882
- def new_connection
883
- connection = db_config.new_connection
884
- connection.pool = self
885
- connection
886
- rescue ConnectionNotEstablished => ex
887
- raise ex.set_pool(self)
888
- end
889
-
890
898
  # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
891
899
  # to the DB is done outside main synchronized section.
892
900
  #--
@@ -102,16 +102,16 @@ module ActiveRecord
102
102
  select_all(arel, name, binds, async: async).then(&:rows)
103
103
  end
104
104
 
105
- def query_value(sql, name = nil) # :nodoc:
106
- single_value_from_rows(query(sql, name))
105
+ def query_value(...) # :nodoc:
106
+ single_value_from_rows(query(...))
107
107
  end
108
108
 
109
- def query_values(sql, name = nil) # :nodoc:
110
- query(sql, name).map(&:first)
109
+ def query_values(...) # :nodoc:
110
+ query(...).map(&:first)
111
111
  end
112
112
 
113
- def query(sql, name = nil) # :nodoc:
114
- internal_exec_query(sql, name).rows
113
+ def query(...) # :nodoc:
114
+ internal_exec_query(...).rows
115
115
  end
116
116
 
117
117
  # Determines whether the SQL statement is a write query.
@@ -163,14 +163,14 @@ module ActiveRecord
163
163
  # +binds+ as the bind substitutes. +name+ is logged along with
164
164
  # the executed +sql+ statement.
165
165
  def exec_delete(sql, name = nil, binds = [])
166
- internal_exec_query(sql, name, binds)
166
+ affected_rows(internal_execute(sql, name, binds))
167
167
  end
168
168
 
169
169
  # Executes update +sql+ statement in the context of this connection using
170
170
  # +binds+ as the bind substitutes. +name+ is logged along with
171
171
  # the executed +sql+ statement.
172
172
  def exec_update(sql, name = nil, binds = [])
173
- internal_exec_query(sql, name, binds)
173
+ affected_rows(internal_execute(sql, name, binds))
174
174
  end
175
175
 
176
176
  def exec_insert_all(sql, name) # :nodoc:
@@ -224,11 +224,9 @@ module ActiveRecord
224
224
 
225
225
  return if table_names.empty?
226
226
 
227
- with_multi_statements do
228
- disable_referential_integrity do
229
- statements = build_truncate_statements(table_names)
230
- execute_batch(statements, "Truncate Tables")
231
- end
227
+ disable_referential_integrity do
228
+ statements = build_truncate_statements(table_names)
229
+ execute_batch(statements, "Truncate Tables")
232
230
  end
233
231
  end
234
232
 
@@ -358,7 +356,7 @@ module ActiveRecord
358
356
  end
359
357
  yield current_transaction.user_transaction
360
358
  else
361
- transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable, &block)
359
+ within_new_transaction(isolation: isolation, joinable: joinable, &block)
362
360
  end
363
361
  rescue ActiveRecord::Rollback
364
362
  # rollbacks are silently swallowed
@@ -411,6 +409,14 @@ module ActiveRecord
411
409
  # Begins the transaction (and turns off auto-committing).
412
410
  def begin_db_transaction() end
413
411
 
412
+ def begin_deferred_transaction(isolation_level = nil) # :nodoc:
413
+ if isolation_level
414
+ begin_isolated_db_transaction(isolation_level)
415
+ else
416
+ begin_db_transaction
417
+ end
418
+ end
419
+
414
420
  def transaction_isolation_levels
415
421
  {
416
422
  read_uncommitted: "READ UNCOMMITTED",
@@ -427,6 +433,15 @@ module ActiveRecord
427
433
  raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation"
428
434
  end
429
435
 
436
+ # Hook point called after an isolated DB transaction is committed
437
+ # or rolled back.
438
+ # Most adapters don't need to implement anything because the isolation
439
+ # level is set on a per transaction basis.
440
+ # But some databases like SQLite set it on a per connection level
441
+ # and need to explicitly reset it after commit or rollback.
442
+ def reset_isolation_level
443
+ end
444
+
430
445
  # Commits the transaction (and turns on auto-committing).
431
446
  def commit_db_transaction() end
432
447
 
@@ -473,11 +488,9 @@ module ActiveRecord
473
488
  table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
474
489
  statements = table_deletes + fixture_inserts
475
490
 
476
- with_multi_statements do
477
- transaction(requires_new: true) do
478
- disable_referential_integrity do
479
- execute_batch(statements, "Fixtures Load")
480
- end
491
+ transaction(requires_new: true) do
492
+ disable_referential_integrity do
493
+ execute_batch(statements, "Fixtures Load")
481
494
  end
482
495
  end
483
496
  end
@@ -524,28 +537,64 @@ module ActiveRecord
524
537
  HIGH_PRECISION_CURRENT_TIMESTAMP
525
538
  end
526
539
 
527
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
528
- raise NotImplementedError
540
+ # Same as raw_execute but returns an ActiveRecord::Result object.
541
+ def raw_exec_query(...) # :nodoc:
542
+ cast_result(raw_execute(...))
543
+ end
544
+
545
+ # Execute a query and returns an ActiveRecord::Result
546
+ def internal_exec_query(...) # :nodoc:
547
+ cast_result(internal_execute(...))
529
548
  end
530
549
 
531
550
  private
532
- def internal_execute(sql, name = "SCHEMA", allow_retry: false, materialize_transactions: true)
533
- sql = transform_query(sql)
534
- check_if_write_query(sql)
551
+ # Lowest level way to execute a query. Doesn't check for illegal writes, doesn't annotate queries, yields a native result object.
552
+ def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, batch: false)
553
+ type_casted_binds = type_casted_binds(binds)
554
+ log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
555
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
556
+ perform_query(conn, sql, binds, type_casted_binds, prepare: prepare, notification_payload: notification_payload, batch: batch)
557
+ end
558
+ end
559
+ end
535
560
 
536
- mark_transaction_written_if_write(sql)
561
+ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch:)
562
+ raise NotImplementedError
563
+ end
537
564
 
538
- raw_execute(sql, name, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
565
+ # Receive a native adapter result object and returns an ActiveRecord::Result object.
566
+ def cast_result(raw_result)
567
+ raise NotImplementedError
539
568
  end
540
569
 
541
- def execute_batch(statements, name = nil)
542
- statements.each do |statement|
543
- internal_execute(statement, name)
570
+ def affected_rows(raw_result)
571
+ raise NotImplementedError
572
+ end
573
+
574
+ def preprocess_query(sql)
575
+ check_if_write_query(sql)
576
+ mark_transaction_written_if_write(sql)
577
+
578
+ # We call tranformers after the write checks so we don't add extra parsing work.
579
+ # This means we assume no transformer whille change a read for a write
580
+ # but it would be insane to do such a thing.
581
+ ActiveRecord.query_transformers.each do |transformer|
582
+ sql = transformer.call(sql, self)
544
583
  end
584
+
585
+ sql
545
586
  end
546
587
 
547
- def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
548
- raise NotImplementedError
588
+ # Same as #internal_exec_query, but yields a native adapter result
589
+ def internal_execute(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, &block)
590
+ sql = preprocess_query(sql)
591
+ raw_execute(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions, &block)
592
+ end
593
+
594
+ def execute_batch(statements, name = nil, **kwargs)
595
+ statements.each do |statement|
596
+ raw_execute(statement, name, **kwargs)
597
+ end
549
598
  end
550
599
 
551
600
  DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
@@ -614,10 +663,6 @@ module ActiveRecord
614
663
  end
615
664
  end
616
665
 
617
- def with_multi_statements
618
- yield
619
- end
620
-
621
666
  def combine_multi_statements(total_sql)
622
667
  total_sql.join(";\n")
623
668
  end
@@ -629,6 +674,8 @@ module ActiveRecord
629
674
  raise AsynchronousQueryInsideTransactionError, "Asynchronous queries are not allowed inside transactions"
630
675
  end
631
676
 
677
+ # We make sure to run query transformers on the original thread
678
+ sql = preprocess_query(sql)
632
679
  future_result = async.new(
633
680
  pool,
634
681
  sql,
@@ -636,19 +683,19 @@ module ActiveRecord
636
683
  binds,
637
684
  prepare: prepare,
638
685
  )
639
- if supports_concurrent_connections? && current_transaction.closed?
686
+ if supports_concurrent_connections? && !current_transaction.joinable?
640
687
  future_result.schedule!(ActiveRecord::Base.asynchronous_queries_session)
641
688
  else
642
689
  future_result.execute!(self)
643
690
  end
644
- return future_result
645
- end
646
-
647
- result = internal_exec_query(sql, name, binds, prepare: prepare, allow_retry: allow_retry)
648
- if async
649
- FutureResult.wrap(result)
691
+ future_result
650
692
  else
651
- result
693
+ result = internal_exec_query(sql, name, binds, prepare: prepare, allow_retry: allow_retry)
694
+ if async
695
+ FutureResult.wrap(result)
696
+ else
697
+ result
698
+ end
652
699
  end
653
700
  end
654
701
 
@@ -691,7 +738,7 @@ module ActiveRecord
691
738
  end
692
739
 
693
740
  def extract_table_ref_from_insert_sql(sql)
694
- if sql =~ /into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im
741
+ if sql =~ /into\s("[-A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im
695
742
  $1.delete('"').strip
696
743
  end
697
744
  end
@@ -191,15 +191,26 @@ module ActiveRecord
191
191
  end
192
192
  end
193
193
 
194
- attr_accessor :query_cache
195
-
196
194
  def initialize(*)
197
195
  super
198
196
  @query_cache = nil
199
197
  end
200
198
 
199
+ attr_writer :query_cache
200
+
201
+ def query_cache
202
+ if @pinned && @owner != ActiveSupport::IsolatedExecutionState.context
203
+ # With transactional tests, if the connection is pinned, any thread
204
+ # other than the one that pinned the connection need to go through the
205
+ # query cache pool, so each thread get a different cache.
206
+ pool.query_cache
207
+ else
208
+ @query_cache
209
+ end
210
+ end
211
+
201
212
  def query_cache_enabled
202
- @query_cache&.enabled?
213
+ query_cache&.enabled?
203
214
  end
204
215
 
205
216
  # Enable the query cache within the block.
@@ -238,7 +249,7 @@ module ActiveRecord
238
249
 
239
250
  # If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
240
251
  # Such queries should not be cached.
241
- if @query_cache&.enabled? && !(arel.respond_to?(:locked) && arel.locked)
252
+ if query_cache_enabled && !(arel.respond_to?(:locked) && arel.locked)
242
253
  sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
243
254
 
244
255
  if async
@@ -262,13 +273,13 @@ module ActiveRecord
262
273
 
263
274
  result = nil
264
275
  @lock.synchronize do
265
- result = @query_cache[key]
276
+ result = query_cache[key]
266
277
  end
267
278
 
268
279
  if result
269
280
  ActiveSupport::Notifications.instrument(
270
281
  "sql.active_record",
271
- cache_notification_info(sql, name, binds)
282
+ cache_notification_info_result(sql, name, binds, result)
272
283
  )
273
284
  end
274
285
 
@@ -281,7 +292,7 @@ module ActiveRecord
281
292
  hit = true
282
293
 
283
294
  @lock.synchronize do
284
- result = @query_cache.compute_if_absent(key) do
295
+ result = query_cache.compute_if_absent(key) do
285
296
  hit = false
286
297
  yield
287
298
  end
@@ -290,13 +301,19 @@ module ActiveRecord
290
301
  if hit
291
302
  ActiveSupport::Notifications.instrument(
292
303
  "sql.active_record",
293
- cache_notification_info(sql, name, binds)
304
+ cache_notification_info_result(sql, name, binds, result)
294
305
  )
295
306
  end
296
307
 
297
308
  result.dup
298
309
  end
299
310
 
311
+ def cache_notification_info_result(sql, name, binds, result)
312
+ payload = cache_notification_info(sql, name, binds)
313
+ payload[:row_count] = result.length
314
+ payload
315
+ end
316
+
300
317
  # Database adapters can override this method to
301
318
  # provide custom cache information.
302
319
  def cache_notification_info(sql, name, binds)
@@ -222,7 +222,7 @@ module ActiveRecord
222
222
 
223
223
  private
224
224
  def type_casted_binds(binds)
225
- binds.map do |value|
225
+ binds&.map do |value|
226
226
  if ActiveModel::Attribute === value
227
227
  type_cast(value.value_for_database)
228
228
  else
@@ -28,6 +28,7 @@ module ActiveRecord
28
28
  sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ")
29
29
  sql << o.check_constraint_adds.map { |con| visit_AddCheckConstraint con }.join(" ")
30
30
  sql << o.check_constraint_drops.map { |con| visit_DropCheckConstraint con }.join(" ")
31
+ sql << o.constraint_drops.map { |con| visit_DropConstraint con }.join(" ")
31
32
  end
32
33
 
33
34
  def visit_ColumnDefinition(o)
@@ -96,9 +97,11 @@ module ActiveRecord
96
97
  "ADD #{accept(o)}"
97
98
  end
98
99
 
99
- def visit_DropForeignKey(name)
100
+ def visit_DropConstraint(name)
100
101
  "DROP CONSTRAINT #{quote_column_name(name)}"
101
102
  end
103
+ alias :visit_DropForeignKey :visit_DropConstraint
104
+ alias :visit_DropCheckConstraint :visit_DropConstraint
102
105
 
103
106
  def visit_CreateIndexDefinition(o)
104
107
  index = o.index
@@ -127,10 +130,6 @@ module ActiveRecord
127
130
  "ADD #{accept(o)}"
128
131
  end
129
132
 
130
- def visit_DropCheckConstraint(name)
131
- "DROP CONSTRAINT #{quote_column_name(name)}"
132
- end
133
-
134
133
  def quoted_columns(o)
135
134
  String === o.columns ? o.columns : quoted_columns_for_index(o.columns, o.column_options)
136
135
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
-
4
3
  module ActiveRecord
5
4
  module ConnectionAdapters # :nodoc:
6
5
  # Abstract representation of an index definition on a table. Instances of
@@ -160,6 +159,8 @@ module ActiveRecord
160
159
  end
161
160
 
162
161
  def defined_for?(to_table: nil, validate: nil, **options)
162
+ options = options.slice(*self.options.keys)
163
+
163
164
  (to_table.nil? || to_table.to_s == self.to_table) &&
164
165
  (validate.nil? || validate == self.options.fetch(:validate, validate)) &&
165
166
  options.all? { |k, v| Array(self.options[k]).map(&:to_s) == Array(v).map(&:to_s) }
@@ -186,6 +187,8 @@ module ActiveRecord
186
187
  end
187
188
 
188
189
  def defined_for?(name:, expression: nil, validate: nil, **options)
190
+ options = options.slice(*self.options.keys)
191
+
189
192
  self.name == name.to_s &&
190
193
  (validate.nil? || validate == self.options.fetch(:validate, validate)) &&
191
194
  options.all? { |k, v| self.options[k].to_s == v.to_s }
@@ -348,7 +351,7 @@ module ActiveRecord
348
351
  # Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
349
352
  # is actually of this type:
350
353
  #
351
- # class SomeMigration < ActiveRecord::Migration[7.2]
354
+ # class SomeMigration < ActiveRecord::Migration[8.0]
352
355
  # def up
353
356
  # create_table :foo do |t|
354
357
  # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
@@ -431,7 +434,7 @@ module ActiveRecord
431
434
  #
432
435
  # == Examples
433
436
  #
434
- # # Assuming +td+ is an instance of TableDefinition
437
+ # # Assuming `td` is an instance of TableDefinition
435
438
  # td.column(:granted, :boolean, index: true)
436
439
  #
437
440
  # == Short-hand examples
@@ -622,6 +625,7 @@ module ActiveRecord
622
625
  attr_reader :adds
623
626
  attr_reader :foreign_key_adds, :foreign_key_drops
624
627
  attr_reader :check_constraint_adds, :check_constraint_drops
628
+ attr_reader :constraint_drops
625
629
 
626
630
  def initialize(td)
627
631
  @td = td
@@ -630,6 +634,7 @@ module ActiveRecord
630
634
  @foreign_key_drops = []
631
635
  @check_constraint_adds = []
632
636
  @check_constraint_drops = []
637
+ @constraint_drops = []
633
638
  end
634
639
 
635
640
  def name; @td.name; end
@@ -650,6 +655,10 @@ module ActiveRecord
650
655
  @check_constraint_drops << constraint_name
651
656
  end
652
657
 
658
+ def drop_constraint(constraint_name)
659
+ @constraint_drops << constraint_name
660
+ end
661
+
653
662
  def add_column(name, type, **options)
654
663
  name = name.to_s
655
664
  type = type.to_sym