activerecord 7.1.6 → 7.2.3

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 (193) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +839 -2248
  3. data/README.rdoc +16 -16
  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 +31 -23
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  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 +16 -8
  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 +1 -1
  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 +5 -25
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  33. data/lib/active_record/attribute_methods.rb +51 -60
  34. data/lib/active_record/attributes.rb +93 -68
  35. data/lib/active_record/autosave_association.rb +25 -32
  36. data/lib/active_record/base.rb +4 -5
  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 +294 -72
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -75
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -2
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +18 -6
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +46 -44
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +53 -15
  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 +19 -18
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -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 +30 -8
  60. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +36 -26
  63. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  64. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
  67. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +133 -78
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  74. data/lib/active_record/connection_adapters.rb +121 -0
  75. data/lib/active_record/connection_handling.rb +68 -49
  76. data/lib/active_record/core.rb +112 -44
  77. data/lib/active_record/counter_cache.rb +19 -10
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  79. data/lib/active_record/database_configurations/database_config.rb +19 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  81. data/lib/active_record/database_configurations/url_config.rb +20 -1
  82. data/lib/active_record/database_configurations.rb +1 -1
  83. data/lib/active_record/delegated_type.rb +42 -18
  84. data/lib/active_record/dynamic_matchers.rb +2 -2
  85. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  86. data/lib/active_record/encryption/encrypted_attribute_type.rb +25 -5
  87. data/lib/active_record/encryption/encryptor.rb +35 -19
  88. data/lib/active_record/encryption/key_provider.rb +1 -1
  89. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  90. data/lib/active_record/encryption/message_serializer.rb +4 -0
  91. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  93. data/lib/active_record/enum.rb +31 -13
  94. data/lib/active_record/errors.rb +49 -23
  95. data/lib/active_record/explain.rb +13 -24
  96. data/lib/active_record/fixture_set/table_row.rb +19 -2
  97. data/lib/active_record/fixtures.rb +37 -31
  98. data/lib/active_record/future_result.rb +8 -4
  99. data/lib/active_record/gem_version.rb +2 -2
  100. data/lib/active_record/inheritance.rb +4 -2
  101. data/lib/active_record/insert_all.rb +18 -15
  102. data/lib/active_record/integration.rb +4 -1
  103. data/lib/active_record/internal_metadata.rb +48 -34
  104. data/lib/active_record/locking/optimistic.rb +7 -6
  105. data/lib/active_record/log_subscriber.rb +0 -21
  106. data/lib/active_record/message_pack.rb +1 -1
  107. data/lib/active_record/migration/command_recorder.rb +2 -3
  108. data/lib/active_record/migration/compatibility.rb +5 -3
  109. data/lib/active_record/migration/default_strategy.rb +4 -5
  110. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  111. data/lib/active_record/migration.rb +87 -77
  112. data/lib/active_record/model_schema.rb +31 -68
  113. data/lib/active_record/nested_attributes.rb +11 -3
  114. data/lib/active_record/normalization.rb +3 -7
  115. data/lib/active_record/persistence.rb +30 -352
  116. data/lib/active_record/query_cache.rb +19 -8
  117. data/lib/active_record/query_logs.rb +19 -0
  118. data/lib/active_record/querying.rb +25 -13
  119. data/lib/active_record/railtie.rb +39 -57
  120. data/lib/active_record/railties/controller_runtime.rb +13 -4
  121. data/lib/active_record/railties/databases.rake +42 -44
  122. data/lib/active_record/reflection.rb +98 -36
  123. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  124. data/lib/active_record/relation/batches.rb +14 -8
  125. data/lib/active_record/relation/calculations.rb +127 -89
  126. data/lib/active_record/relation/delegation.rb +8 -11
  127. data/lib/active_record/relation/finder_methods.rb +26 -12
  128. data/lib/active_record/relation/merger.rb +4 -6
  129. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  130. data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
  131. data/lib/active_record/relation/predicate_builder.rb +3 -3
  132. data/lib/active_record/relation/query_attribute.rb +1 -1
  133. data/lib/active_record/relation/query_methods.rb +238 -65
  134. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  135. data/lib/active_record/relation/spawn_methods.rb +2 -18
  136. data/lib/active_record/relation/where_clause.rb +15 -21
  137. data/lib/active_record/relation.rb +508 -74
  138. data/lib/active_record/result.rb +31 -44
  139. data/lib/active_record/runtime_registry.rb +39 -0
  140. data/lib/active_record/sanitization.rb +24 -19
  141. data/lib/active_record/schema.rb +8 -6
  142. data/lib/active_record/schema_dumper.rb +48 -20
  143. data/lib/active_record/schema_migration.rb +30 -14
  144. data/lib/active_record/scoping/named.rb +1 -0
  145. data/lib/active_record/secure_token.rb +3 -3
  146. data/lib/active_record/signed_id.rb +27 -7
  147. data/lib/active_record/statement_cache.rb +7 -7
  148. data/lib/active_record/table_metadata.rb +1 -10
  149. data/lib/active_record/tasks/database_tasks.rb +69 -41
  150. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/postgresql_database_tasks.rb +8 -1
  152. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  153. data/lib/active_record/test_fixtures.rb +86 -89
  154. data/lib/active_record/testing/query_assertions.rb +121 -0
  155. data/lib/active_record/timestamp.rb +2 -2
  156. data/lib/active_record/token_for.rb +22 -12
  157. data/lib/active_record/touch_later.rb +1 -1
  158. data/lib/active_record/transaction.rb +132 -0
  159. data/lib/active_record/transactions.rb +73 -15
  160. data/lib/active_record/translation.rb +0 -2
  161. data/lib/active_record/type/serialized.rb +1 -3
  162. data/lib/active_record/type_caster/connection.rb +4 -4
  163. data/lib/active_record/validations/associated.rb +9 -3
  164. data/lib/active_record/validations/uniqueness.rb +15 -10
  165. data/lib/active_record/validations.rb +4 -1
  166. data/lib/active_record.rb +148 -39
  167. data/lib/arel/alias_predication.rb +1 -1
  168. data/lib/arel/collectors/bind.rb +3 -1
  169. data/lib/arel/collectors/composite.rb +7 -0
  170. data/lib/arel/collectors/sql_string.rb +1 -1
  171. data/lib/arel/collectors/substitute_binds.rb +1 -1
  172. data/lib/arel/crud.rb +2 -0
  173. data/lib/arel/delete_manager.rb +5 -0
  174. data/lib/arel/nodes/binary.rb +0 -6
  175. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  176. data/lib/arel/nodes/delete_statement.rb +4 -2
  177. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  178. data/lib/arel/nodes/node.rb +4 -3
  179. data/lib/arel/nodes/sql_literal.rb +7 -0
  180. data/lib/arel/nodes/update_statement.rb +4 -2
  181. data/lib/arel/nodes.rb +2 -2
  182. data/lib/arel/predications.rb +1 -1
  183. data/lib/arel/select_manager.rb +7 -3
  184. data/lib/arel/tree_manager.rb +3 -2
  185. data/lib/arel/update_manager.rb +7 -1
  186. data/lib/arel/visitors/dot.rb +3 -0
  187. data/lib/arel/visitors/mysql.rb +9 -4
  188. data/lib/arel/visitors/postgresql.rb +1 -12
  189. data/lib/arel/visitors/sqlite.rb +25 -0
  190. data/lib/arel/visitors/to_sql.rb +31 -16
  191. data/lib/arel.rb +7 -3
  192. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  193. metadata +16 -10
@@ -14,7 +14,7 @@ module ActiveRecord
14
14
  sql
15
15
  end
16
16
 
17
- def to_sql_and_binds(arel_or_sql_string, binds = [], preparable = nil) # :nodoc:
17
+ def to_sql_and_binds(arel_or_sql_string, binds = [], preparable = nil, allow_retry = false) # :nodoc:
18
18
  # Arel::TreeManager -> Arel::Node
19
19
  if arel_or_sql_string.respond_to?(:ast)
20
20
  arel_or_sql_string = arel_or_sql_string.ast
@@ -27,6 +27,7 @@ module ActiveRecord
27
27
  end
28
28
 
29
29
  collector = collector()
30
+ collector.retryable = true
30
31
 
31
32
  if prepared_statements
32
33
  collector.preparable = true
@@ -41,10 +42,11 @@ module ActiveRecord
41
42
  else
42
43
  sql = visitor.compile(arel_or_sql_string, collector)
43
44
  end
44
- [sql.freeze, binds, preparable]
45
+ allow_retry = collector.retryable
46
+ [sql.freeze, binds, preparable, allow_retry]
45
47
  else
46
48
  arel_or_sql_string = arel_or_sql_string.dup.freeze unless arel_or_sql_string.frozen?
47
- [arel_or_sql_string, binds, preparable]
49
+ [arel_or_sql_string, binds, preparable, allow_retry]
48
50
  end
49
51
  end
50
52
  private :to_sql_and_binds
@@ -64,11 +66,15 @@ module ActiveRecord
64
66
  end
65
67
 
66
68
  # Returns an ActiveRecord::Result instance.
67
- def select_all(arel, name = nil, binds = [], preparable: nil, async: false)
69
+ def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false)
68
70
  arel = arel_from_relation(arel)
69
- sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
71
+ sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable, allow_retry)
70
72
 
71
- select(sql, name, binds, prepare: prepared_statements && preparable, async: async && FutureResult::SelectAll)
73
+ select(sql, name, binds,
74
+ prepare: prepared_statements && preparable,
75
+ async: async && FutureResult::SelectAll,
76
+ allow_retry: allow_retry
77
+ )
72
78
  rescue ::RangeError
73
79
  ActiveRecord::Result.empty(async: async)
74
80
  end
@@ -214,7 +220,7 @@ module ActiveRecord
214
220
  end
215
221
 
216
222
  def truncate_tables(*table_names) # :nodoc:
217
- table_names -= [schema_migration.table_name, internal_metadata.table_name]
223
+ table_names -= [pool.schema_migration.table_name, pool.internal_metadata.table_name]
218
224
 
219
225
  return if table_names.empty?
220
226
 
@@ -229,6 +235,17 @@ module ActiveRecord
229
235
  # Runs the given block in a database transaction, and returns the result
230
236
  # of the block.
231
237
  #
238
+ # == Transaction callbacks
239
+ #
240
+ # #transaction yields an ActiveRecord::Transaction object on which it is
241
+ # possible to register callback:
242
+ #
243
+ # ActiveRecord::Base.transaction do |transaction|
244
+ # transaction.before_commit { puts "before commit!" }
245
+ # transaction.after_commit { puts "after commit!" }
246
+ # transaction.after_rollback { puts "after rollback!" }
247
+ # end
248
+ #
232
249
  # == Nested transactions support
233
250
  #
234
251
  # #transaction calls can be nested. By default, this makes all database
@@ -296,9 +313,9 @@ module ActiveRecord
296
313
  # #transaction will raise exceptions when it tries to release the
297
314
  # already-automatically-released savepoints:
298
315
  #
299
- # Model.connection.transaction do # BEGIN
300
- # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
301
- # Model.connection.create_table(...)
316
+ # Model.lease_connection.transaction do # BEGIN
317
+ # Model.lease_connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
318
+ # Model.lease_connection.create_table(...)
302
319
  # # active_record_1 now automatically released
303
320
  # end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
304
321
  # end
@@ -339,7 +356,7 @@ module ActiveRecord
339
356
  if isolation
340
357
  raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
341
358
  end
342
- yield
359
+ yield current_transaction.user_transaction
343
360
  else
344
361
  transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable, &block)
345
362
  end
@@ -457,8 +474,8 @@ module ActiveRecord
457
474
  statements = table_deletes + fixture_inserts
458
475
 
459
476
  with_multi_statements do
460
- disable_referential_integrity do
461
- transaction(requires_new: true) do
477
+ transaction(requires_new: true) do
478
+ disable_referential_integrity do
462
479
  execute_batch(statements, "Fixtures Load")
463
480
  end
464
481
  end
@@ -495,7 +512,7 @@ module ActiveRecord
495
512
  end
496
513
 
497
514
  # This is a safe default, even if not high precision on all databases
498
- HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
515
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP", retryable: true).freeze # :nodoc:
499
516
  private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
500
517
 
501
518
  # Returns an Arel SQL literal for the CURRENT_TIMESTAMP for usage with
@@ -507,7 +524,7 @@ module ActiveRecord
507
524
  HIGH_PRECISION_CURRENT_TIMESTAMP
508
525
  end
509
526
 
510
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
527
+ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
511
528
  raise NotImplementedError
512
529
  end
513
530
 
@@ -606,7 +623,7 @@ module ActiveRecord
606
623
  end
607
624
 
608
625
  # Returns an ActiveRecord::Result instance.
609
- def select(sql, name = nil, binds = [], prepare: false, async: false)
626
+ def select(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false)
610
627
  if async && async_enabled?
611
628
  if current_transaction.joinable?
612
629
  raise AsynchronousQueryInsideTransactionError, "Asynchronous queries are not allowed inside transactions"
@@ -627,7 +644,7 @@ module ActiveRecord
627
644
  return future_result
628
645
  end
629
646
 
630
- result = internal_exec_query(sql, name, binds, prepare: prepare)
647
+ result = internal_exec_query(sql, name, binds, prepare: prepare, allow_retry: allow_retry)
631
648
  if async
632
649
  FutureResult.wrap(result)
633
650
  else
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "concurrent/map"
4
+ require "concurrent/atomic/atomic_fixnum"
4
5
 
5
6
  module ActiveRecord
6
7
  module ConnectionAdapters # :nodoc:
@@ -13,15 +14,16 @@ module ActiveRecord
13
14
  :truncate_tables, :rollback_to_savepoint, :rollback_db_transaction, :restart_db_transaction,
14
15
  :exec_insert_all
15
16
 
16
- base.set_callback :checkout, :after, :configure_query_cache!
17
- base.set_callback :checkin, :after, :disable_query_cache!
17
+ base.set_callback :checkin, :after, :unset_query_cache!
18
18
  end
19
19
 
20
20
  def dirties_query_cache(base, *method_names)
21
21
  method_names.each do |method_name|
22
22
  base.class_eval <<-end_code, __FILE__, __LINE__ + 1
23
23
  def #{method_name}(...)
24
- ActiveRecord::Base.clear_query_caches_for_current_thread
24
+ if pool.dirties_query_cache
25
+ ActiveRecord::Base.clear_query_caches_for_current_thread
26
+ end
25
27
  super
26
28
  end
27
29
  end_code
@@ -29,60 +31,207 @@ module ActiveRecord
29
31
  end
30
32
  end
31
33
 
32
- module ConnectionPoolConfiguration
33
- def initialize(*)
34
+ class Store # :nodoc:
35
+ attr_accessor :enabled, :dirties
36
+ alias_method :enabled?, :enabled
37
+ alias_method :dirties?, :dirties
38
+
39
+ def initialize(version, max_size)
40
+ @version = version
41
+ @current_version = version.value
42
+ @map = {}
43
+ @max_size = max_size
44
+ @enabled = false
45
+ @dirties = true
46
+ end
47
+
48
+ def size
49
+ check_version
50
+ @map.size
51
+ end
52
+
53
+ def empty?
54
+ check_version
55
+ @map.empty?
56
+ end
57
+
58
+ def [](key)
59
+ check_version
60
+ return unless @enabled
61
+
62
+ if entry = @map.delete(key)
63
+ @map[key] = entry
64
+ end
65
+ end
66
+
67
+ def compute_if_absent(key)
68
+ check_version
69
+
70
+ return yield unless @enabled
71
+
72
+ if entry = @map.delete(key)
73
+ return @map[key] = entry
74
+ end
75
+
76
+ if @max_size && @map.size >= @max_size
77
+ @map.shift # evict the oldest entry
78
+ end
79
+
80
+ @map[key] ||= yield
81
+ end
82
+
83
+ def clear
84
+ @map.clear
85
+ self
86
+ end
87
+
88
+ private
89
+ def check_version
90
+ if @current_version != @version.value
91
+ @map.clear
92
+ @current_version = @version.value
93
+ end
94
+ end
95
+ end
96
+
97
+ class QueryCacheRegistry # :nodoc:
98
+ def initialize
99
+ @mutex = Mutex.new
100
+ @map = ConnectionPool::WeakThreadKeyMap.new
101
+ end
102
+
103
+ def compute_if_absent(context)
104
+ @map[context] || @mutex.synchronize do
105
+ @map[context] ||= yield
106
+ end
107
+ end
108
+
109
+ def clear
110
+ @map.synchronize do
111
+ @map.clear
112
+ end
113
+ end
114
+ end
115
+
116
+ module ConnectionPoolConfiguration # :nodoc:
117
+ def initialize(...)
118
+ super
119
+ @query_cache_version = Concurrent::AtomicFixnum.new
120
+ @thread_query_caches = QueryCacheRegistry.new
121
+ @query_cache_max_size = \
122
+ case query_cache = db_config&.query_cache
123
+ when 0, false
124
+ nil
125
+ when Integer
126
+ query_cache
127
+ when nil
128
+ DEFAULT_SIZE
129
+ end
130
+ end
131
+
132
+ def checkout_and_verify(connection)
34
133
  super
35
- @query_cache_enabled = Concurrent::Map.new { false }
134
+ connection.query_cache ||= query_cache
135
+ connection
136
+ end
137
+
138
+ # Disable the query cache within the block.
139
+ def disable_query_cache(dirties: true)
140
+ cache = query_cache
141
+ old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, false, cache.dirties, dirties
142
+ begin
143
+ yield
144
+ ensure
145
+ cache.enabled, cache.dirties = old_enabled, old_dirties
146
+ end
147
+ end
148
+
149
+ def enable_query_cache
150
+ cache = query_cache
151
+ old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, true, cache.dirties, true
152
+ begin
153
+ yield
154
+ ensure
155
+ cache.enabled, cache.dirties = old_enabled, old_dirties
156
+ end
36
157
  end
37
158
 
38
159
  def enable_query_cache!
39
- @query_cache_enabled[connection_cache_key(current_thread)] = true
40
- connection.enable_query_cache! if active_connection?
160
+ query_cache.enabled = true
161
+ query_cache.dirties = true
41
162
  end
42
163
 
43
164
  def disable_query_cache!
44
- @query_cache_enabled.delete connection_cache_key(current_thread)
45
- connection.disable_query_cache! if active_connection?
165
+ query_cache.enabled = false
166
+ query_cache.dirties = true
46
167
  end
47
168
 
48
169
  def query_cache_enabled
49
- @query_cache_enabled[connection_cache_key(current_thread)]
170
+ query_cache.enabled
171
+ end
172
+
173
+ def dirties_query_cache
174
+ query_cache.dirties
50
175
  end
51
- end
52
176
 
53
- attr_reader :query_cache, :query_cache_enabled
177
+ def clear_query_cache
178
+ if @pinned_connection
179
+ # With transactional fixtures, and especially systems test
180
+ # another thread may use the same connection, but with a different
181
+ # query cache. So we must clear them all.
182
+ @query_cache_version.increment
183
+ end
184
+ query_cache.clear
185
+ end
186
+
187
+ def query_cache
188
+ @thread_query_caches.compute_if_absent(ActiveSupport::IsolatedExecutionState.context) do
189
+ Store.new(@query_cache_version, @query_cache_max_size)
190
+ end
191
+ end
192
+ end
54
193
 
55
194
  def initialize(*)
56
195
  super
57
- @query_cache = {}
58
- @query_cache_enabled = false
59
- @query_cache_max_size = nil
196
+ @query_cache = nil
197
+ end
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
+
212
+ def query_cache_enabled
213
+ query_cache&.enabled?
60
214
  end
61
215
 
62
216
  # Enable the query cache within the block.
63
- def cache
64
- old, @query_cache_enabled = @query_cache_enabled, true
65
- yield
66
- ensure
67
- @query_cache_enabled = old
68
- clear_query_cache unless @query_cache_enabled
217
+ def cache(&block)
218
+ pool.enable_query_cache(&block)
69
219
  end
70
220
 
71
221
  def enable_query_cache!
72
- @query_cache_enabled = true
222
+ pool.enable_query_cache!
73
223
  end
74
224
 
75
- def disable_query_cache!
76
- @query_cache_enabled = false
77
- clear_query_cache
225
+ # Disable the query cache within the block.
226
+ #
227
+ # Set <tt>dirties: false</tt> to prevent query caches on all connections from being cleared by write operations.
228
+ # (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.)
229
+ def uncached(dirties: true, &block)
230
+ pool.disable_query_cache(dirties: dirties, &block)
78
231
  end
79
232
 
80
- # Disable the query cache within the block.
81
- def uncached
82
- old, @query_cache_enabled = @query_cache_enabled, false
83
- yield
84
- ensure
85
- @query_cache_enabled = old
233
+ def disable_query_cache!
234
+ pool.disable_query_cache!
86
235
  end
87
236
 
88
237
  # Clears the query cache.
@@ -92,24 +241,22 @@ module ActiveRecord
92
241
  # the same SQL query and repeatedly return the same result each time, silently
93
242
  # undermining the randomness you were expecting.
94
243
  def clear_query_cache
95
- @lock.synchronize do
96
- @query_cache.clear
97
- end
244
+ pool.clear_query_cache
98
245
  end
99
246
 
100
- def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :nodoc:
247
+ def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) # :nodoc:
101
248
  arel = arel_from_relation(arel)
102
249
 
103
250
  # If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
104
251
  # Such queries should not be cached.
105
- if @query_cache_enabled && !(arel.respond_to?(:locked) && arel.locked)
106
- sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
252
+ if query_cache_enabled && !(arel.respond_to?(:locked) && arel.locked)
253
+ sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
107
254
 
108
255
  if async
109
- result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
256
+ result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
110
257
  FutureResult.wrap(result)
111
258
  else
112
- cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async) }
259
+ cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry) }
113
260
  end
114
261
  else
115
262
  super
@@ -117,42 +264,37 @@ module ActiveRecord
117
264
  end
118
265
 
119
266
  private
267
+ def unset_query_cache!
268
+ @query_cache = nil
269
+ end
270
+
120
271
  def lookup_sql_cache(sql, name, binds)
121
272
  key = binds.empty? ? sql : [sql, binds]
122
- hit = false
123
- result = nil
124
273
 
274
+ result = nil
125
275
  @lock.synchronize do
126
- if (result = @query_cache.delete(key))
127
- hit = true
128
- @query_cache[key] = result
129
- end
276
+ result = query_cache[key]
130
277
  end
131
278
 
132
- if hit
279
+ if result
133
280
  ActiveSupport::Notifications.instrument(
134
281
  "sql.active_record",
135
282
  cache_notification_info(sql, name, binds)
136
283
  )
137
-
138
- result
139
284
  end
285
+
286
+ result
140
287
  end
141
288
 
142
289
  def cache_sql(sql, name, binds)
143
290
  key = binds.empty? ? sql : [sql, binds]
144
291
  result = nil
145
- hit = false
292
+ hit = true
146
293
 
147
294
  @lock.synchronize do
148
- if (result = @query_cache.delete(key))
149
- hit = true
150
- @query_cache[key] = result
151
- else
152
- result = @query_cache[key] = yield
153
- if @query_cache_max_size && @query_cache.size > @query_cache_max_size
154
- @query_cache.shift
155
- end
295
+ result = query_cache.compute_if_absent(key) do
296
+ hit = false
297
+ yield
156
298
  end
157
299
  end
158
300
 
@@ -175,26 +317,10 @@ module ActiveRecord
175
317
  type_casted_binds: -> { type_casted_binds(binds) },
176
318
  name: name,
177
319
  connection: self,
320
+ transaction: current_transaction.user_transaction.presence,
178
321
  cached: true
179
322
  }
180
323
  end
181
-
182
- def configure_query_cache!
183
- case query_cache = pool.db_config.query_cache
184
- when 0, false
185
- return
186
- when Integer
187
- @query_cache_max_size = query_cache
188
- when nil
189
- @query_cache_max_size = DEFAULT_SIZE
190
- else
191
- @query_cache_max_size = nil # no limit
192
- end
193
-
194
- if pool.query_cache_enabled
195
- enable_query_cache!
196
- end
197
- end
198
324
  end
199
325
  end
200
326
  end