activerecord 7.1.5.1 → 8.0.2

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +369 -2484
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +2 -1
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +21 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +7 -6
  11. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  13. data/lib/active_record/associations/builder/has_many.rb +3 -4
  14. data/lib/active_record/associations/builder/has_one.rb +3 -4
  15. data/lib/active_record/associations/collection_association.rb +17 -9
  16. data/lib/active_record/associations/collection_proxy.rb +14 -1
  17. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  18. data/lib/active_record/associations/errors.rb +265 -0
  19. data/lib/active_record/associations/has_many_association.rb +1 -1
  20. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  21. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  22. data/lib/active_record/associations/nested_error.rb +47 -0
  23. data/lib/active_record/associations/preloader/association.rb +4 -3
  24. data/lib/active_record/associations/preloader/branch.rb +7 -1
  25. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  26. data/lib/active_record/associations/singular_association.rb +14 -3
  27. data/lib/active_record/associations/through_association.rb +1 -1
  28. data/lib/active_record/associations.rb +92 -295
  29. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  30. data/lib/active_record/attribute_assignment.rb +0 -2
  31. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  32. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  33. data/lib/active_record/attribute_methods/read.rb +1 -13
  34. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -18
  36. data/lib/active_record/attribute_methods.rb +71 -75
  37. data/lib/active_record/attributes.rb +63 -49
  38. data/lib/active_record/autosave_association.rb +92 -57
  39. data/lib/active_record/base.rb +2 -3
  40. data/lib/active_record/callbacks.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +48 -122
  42. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -1
  44. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +286 -77
  45. data/lib/active_record/connection_adapters/abstract/database_statements.rb +119 -55
  46. data/lib/active_record/connection_adapters/abstract/query_cache.rb +197 -76
  47. data/lib/active_record/connection_adapters/abstract/quoting.rb +66 -92
  48. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  49. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
  50. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -12
  51. data/lib/active_record/connection_adapters/abstract/transaction.rb +140 -67
  52. data/lib/active_record/connection_adapters/abstract_adapter.rb +85 -90
  53. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +71 -52
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  56. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  57. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +56 -45
  58. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +92 -101
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +13 -31
  60. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  61. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -41
  62. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  63. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  66. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  67. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  68. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  69. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +36 -20
  70. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
  71. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +75 -28
  72. data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -113
  73. data/lib/active_record/connection_adapters/schema_cache.rb +124 -131
  74. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  75. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +81 -97
  76. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
  77. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  78. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  79. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +29 -0
  80. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +35 -3
  81. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +183 -87
  82. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  83. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +39 -69
  84. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  85. data/lib/active_record/connection_adapters.rb +65 -0
  86. data/lib/active_record/connection_handling.rb +74 -37
  87. data/lib/active_record/core.rb +132 -51
  88. data/lib/active_record/counter_cache.rb +19 -10
  89. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  90. data/lib/active_record/database_configurations/database_config.rb +23 -4
  91. data/lib/active_record/database_configurations/hash_config.rb +46 -34
  92. data/lib/active_record/database_configurations/url_config.rb +20 -1
  93. data/lib/active_record/database_configurations.rb +1 -1
  94. data/lib/active_record/delegated_type.rb +41 -17
  95. data/lib/active_record/dynamic_matchers.rb +2 -2
  96. data/lib/active_record/encryption/config.rb +3 -1
  97. data/lib/active_record/encryption/encryptable_record.rb +7 -7
  98. data/lib/active_record/encryption/encrypted_attribute_type.rb +33 -4
  99. data/lib/active_record/encryption/encryptor.rb +28 -6
  100. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  101. data/lib/active_record/encryption/key_provider.rb +1 -1
  102. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  103. data/lib/active_record/encryption/message_serializer.rb +4 -0
  104. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  105. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  106. data/lib/active_record/encryption/scheme.rb +8 -1
  107. data/lib/active_record/enum.rb +20 -16
  108. data/lib/active_record/errors.rb +54 -20
  109. data/lib/active_record/explain.rb +13 -24
  110. data/lib/active_record/fixtures.rb +37 -33
  111. data/lib/active_record/future_result.rb +21 -13
  112. data/lib/active_record/gem_version.rb +4 -4
  113. data/lib/active_record/inheritance.rb +4 -2
  114. data/lib/active_record/insert_all.rb +19 -16
  115. data/lib/active_record/integration.rb +4 -1
  116. data/lib/active_record/internal_metadata.rb +48 -34
  117. data/lib/active_record/locking/optimistic.rb +8 -7
  118. data/lib/active_record/log_subscriber.rb +5 -32
  119. data/lib/active_record/message_pack.rb +1 -1
  120. data/lib/active_record/migration/command_recorder.rb +33 -14
  121. data/lib/active_record/migration/compatibility.rb +8 -3
  122. data/lib/active_record/migration/default_strategy.rb +4 -5
  123. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  124. data/lib/active_record/migration.rb +104 -98
  125. data/lib/active_record/model_schema.rb +32 -70
  126. data/lib/active_record/nested_attributes.rb +15 -9
  127. data/lib/active_record/normalization.rb +3 -7
  128. data/lib/active_record/persistence.rb +127 -451
  129. data/lib/active_record/query_cache.rb +19 -8
  130. data/lib/active_record/query_logs.rb +104 -37
  131. data/lib/active_record/query_logs_formatter.rb +17 -28
  132. data/lib/active_record/querying.rb +24 -12
  133. data/lib/active_record/railtie.rb +26 -68
  134. data/lib/active_record/railties/controller_runtime.rb +13 -4
  135. data/lib/active_record/railties/databases.rake +43 -61
  136. data/lib/active_record/reflection.rb +112 -53
  137. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  138. data/lib/active_record/relation/batches.rb +138 -72
  139. data/lib/active_record/relation/calculations.rb +122 -82
  140. data/lib/active_record/relation/delegation.rb +30 -22
  141. data/lib/active_record/relation/finder_methods.rb +32 -18
  142. data/lib/active_record/relation/merger.rb +12 -14
  143. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  144. data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
  145. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  146. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  147. data/lib/active_record/relation/predicate_builder.rb +16 -3
  148. data/lib/active_record/relation/query_attribute.rb +1 -1
  149. data/lib/active_record/relation/query_methods.rb +317 -101
  150. data/lib/active_record/relation/spawn_methods.rb +3 -19
  151. data/lib/active_record/relation/where_clause.rb +7 -19
  152. data/lib/active_record/relation.rb +561 -119
  153. data/lib/active_record/result.rb +95 -46
  154. data/lib/active_record/runtime_registry.rb +39 -0
  155. data/lib/active_record/sanitization.rb +31 -25
  156. data/lib/active_record/schema.rb +8 -6
  157. data/lib/active_record/schema_dumper.rb +53 -20
  158. data/lib/active_record/schema_migration.rb +31 -14
  159. data/lib/active_record/scoping/named.rb +6 -2
  160. data/lib/active_record/signed_id.rb +24 -4
  161. data/lib/active_record/statement_cache.rb +19 -19
  162. data/lib/active_record/store.rb +7 -3
  163. data/lib/active_record/table_metadata.rb +2 -13
  164. data/lib/active_record/tasks/database_tasks.rb +87 -58
  165. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -3
  166. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  167. data/lib/active_record/tasks/sqlite_database_tasks.rb +4 -3
  168. data/lib/active_record/test_fixtures.rb +98 -89
  169. data/lib/active_record/testing/query_assertions.rb +121 -0
  170. data/lib/active_record/timestamp.rb +2 -2
  171. data/lib/active_record/token_for.rb +22 -12
  172. data/lib/active_record/touch_later.rb +1 -1
  173. data/lib/active_record/transaction.rb +132 -0
  174. data/lib/active_record/transactions.rb +72 -17
  175. data/lib/active_record/translation.rb +0 -2
  176. data/lib/active_record/type/serialized.rb +1 -3
  177. data/lib/active_record/type_caster/connection.rb +4 -4
  178. data/lib/active_record/validations/associated.rb +9 -3
  179. data/lib/active_record/validations/uniqueness.rb +23 -18
  180. data/lib/active_record/validations.rb +4 -1
  181. data/lib/active_record.rb +138 -57
  182. data/lib/arel/alias_predication.rb +1 -1
  183. data/lib/arel/collectors/bind.rb +4 -2
  184. data/lib/arel/collectors/composite.rb +7 -0
  185. data/lib/arel/collectors/sql_string.rb +2 -2
  186. data/lib/arel/collectors/substitute_binds.rb +3 -3
  187. data/lib/arel/nodes/binary.rb +1 -7
  188. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  189. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  190. data/lib/arel/nodes/node.rb +5 -4
  191. data/lib/arel/nodes/sql_literal.rb +8 -1
  192. data/lib/arel/nodes.rb +2 -2
  193. data/lib/arel/predications.rb +1 -1
  194. data/lib/arel/select_manager.rb +1 -1
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/tree_manager.rb +3 -2
  197. data/lib/arel/update_manager.rb +2 -1
  198. data/lib/arel/visitors/dot.rb +1 -0
  199. data/lib/arel/visitors/mysql.rb +9 -4
  200. data/lib/arel/visitors/postgresql.rb +1 -12
  201. data/lib/arel/visitors/sqlite.rb +25 -0
  202. data/lib/arel/visitors/to_sql.rb +29 -16
  203. data/lib/arel.rb +7 -3
  204. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  205. metadata +18 -16
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -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,196 @@ 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(...)
34
118
  super
35
- @query_cache_enabled = Concurrent::Map.new { false }
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)
133
+ super
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
175
+ end
176
+
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
50
191
  end
51
192
  end
52
193
 
53
- attr_reader :query_cache, :query_cache_enabled
194
+ attr_accessor :query_cache
54
195
 
55
196
  def initialize(*)
56
197
  super
57
- @query_cache = {}
58
- @query_cache_enabled = false
59
- @query_cache_max_size = nil
198
+ @query_cache = nil
199
+ end
200
+
201
+ def query_cache_enabled
202
+ @query_cache&.enabled?
60
203
  end
61
204
 
62
205
  # 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
206
+ def cache(&block)
207
+ pool.enable_query_cache(&block)
69
208
  end
70
209
 
71
210
  def enable_query_cache!
72
- @query_cache_enabled = true
211
+ pool.enable_query_cache!
73
212
  end
74
213
 
75
- def disable_query_cache!
76
- @query_cache_enabled = false
77
- clear_query_cache
214
+ # Disable the query cache within the block.
215
+ #
216
+ # Set <tt>dirties: false</tt> to prevent query caches on all connections from being cleared by write operations.
217
+ # (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.)
218
+ def uncached(dirties: true, &block)
219
+ pool.disable_query_cache(dirties: dirties, &block)
78
220
  end
79
221
 
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
222
+ def disable_query_cache!
223
+ pool.disable_query_cache!
86
224
  end
87
225
 
88
226
  # Clears the query cache.
@@ -92,24 +230,22 @@ module ActiveRecord
92
230
  # the same SQL query and repeatedly return the same result each time, silently
93
231
  # undermining the randomness you were expecting.
94
232
  def clear_query_cache
95
- @lock.synchronize do
96
- @query_cache.clear
97
- end
233
+ pool.clear_query_cache
98
234
  end
99
235
 
100
- def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :nodoc:
236
+ def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) # :nodoc:
101
237
  arel = arel_from_relation(arel)
102
238
 
103
239
  # If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
104
240
  # 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)
241
+ if @query_cache&.enabled? && !(arel.respond_to?(:locked) && arel.locked)
242
+ sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
107
243
 
108
244
  if async
109
- result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
245
+ result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
110
246
  FutureResult.wrap(result)
111
247
  else
112
- cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async) }
248
+ cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry) }
113
249
  end
114
250
  else
115
251
  super
@@ -117,55 +253,56 @@ module ActiveRecord
117
253
  end
118
254
 
119
255
  private
256
+ def unset_query_cache!
257
+ @query_cache = nil
258
+ end
259
+
120
260
  def lookup_sql_cache(sql, name, binds)
121
261
  key = binds.empty? ? sql : [sql, binds]
122
- hit = false
123
- result = nil
124
262
 
263
+ result = nil
125
264
  @lock.synchronize do
126
- if (result = @query_cache.delete(key))
127
- hit = true
128
- @query_cache[key] = result
129
- end
265
+ result = @query_cache[key]
130
266
  end
131
267
 
132
- if hit
268
+ if result
133
269
  ActiveSupport::Notifications.instrument(
134
270
  "sql.active_record",
135
- cache_notification_info(sql, name, binds)
271
+ cache_notification_info_result(sql, name, binds, result)
136
272
  )
137
-
138
- result
139
273
  end
274
+
275
+ result
140
276
  end
141
277
 
142
278
  def cache_sql(sql, name, binds)
143
279
  key = binds.empty? ? sql : [sql, binds]
144
280
  result = nil
145
- hit = false
281
+ hit = true
146
282
 
147
283
  @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
284
+ result = @query_cache.compute_if_absent(key) do
285
+ hit = false
286
+ yield
156
287
  end
157
288
  end
158
289
 
159
290
  if hit
160
291
  ActiveSupport::Notifications.instrument(
161
292
  "sql.active_record",
162
- cache_notification_info(sql, name, binds)
293
+ cache_notification_info_result(sql, name, binds, result)
163
294
  )
164
295
  end
165
296
 
166
297
  result.dup
167
298
  end
168
299
 
300
+ def cache_notification_info_result(sql, name, binds, result)
301
+ payload = cache_notification_info(sql, name, binds)
302
+ payload[:row_count] = result.length
303
+ payload
304
+ end
305
+
169
306
  # Database adapters can override this method to
170
307
  # provide custom cache information.
171
308
  def cache_notification_info(sql, name, binds)
@@ -175,26 +312,10 @@ module ActiveRecord
175
312
  type_casted_binds: -> { type_casted_binds(binds) },
176
313
  name: name,
177
314
  connection: self,
315
+ transaction: current_transaction.user_transaction.presence,
178
316
  cached: true
179
317
  }
180
318
  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
319
  end
199
320
  end
200
321
  end
@@ -7,6 +7,67 @@ module ActiveRecord
7
7
  module ConnectionAdapters # :nodoc:
8
8
  # = Active Record Connection Adapters \Quoting
9
9
  module Quoting
10
+ extend ActiveSupport::Concern
11
+
12
+ module ClassMethods # :nodoc:
13
+ # Regexp for column names (with or without a table name prefix).
14
+ # Matches the following:
15
+ #
16
+ # "#{table_name}.#{column_name}"
17
+ # "#{column_name}"
18
+ def column_name_matcher
19
+ /
20
+ \A
21
+ (
22
+ (?:
23
+ # table_name.column_name | function(one or no argument)
24
+ ((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
25
+ )
26
+ (?:(?:\s+AS)?\s+\w+)?
27
+ )
28
+ (?:\s*,\s*\g<1>)*
29
+ \z
30
+ /ix
31
+ end
32
+
33
+ # Regexp for column names with order (with or without a table name prefix,
34
+ # with or without various order modifiers). Matches the following:
35
+ #
36
+ # "#{table_name}.#{column_name}"
37
+ # "#{table_name}.#{column_name} #{direction}"
38
+ # "#{table_name}.#{column_name} #{direction} NULLS FIRST"
39
+ # "#{table_name}.#{column_name} NULLS LAST"
40
+ # "#{column_name}"
41
+ # "#{column_name} #{direction}"
42
+ # "#{column_name} #{direction} NULLS FIRST"
43
+ # "#{column_name} NULLS LAST"
44
+ def column_name_with_order_matcher
45
+ /
46
+ \A
47
+ (
48
+ (?:
49
+ # table_name.column_name | function(one or no argument)
50
+ ((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
51
+ )
52
+ (?:\s+ASC|\s+DESC)?
53
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
54
+ )
55
+ (?:\s*,\s*\g<1>)*
56
+ \z
57
+ /ix
58
+ end
59
+
60
+ # Quotes the column name. Must be implemented by subclasses
61
+ def quote_column_name(column_name)
62
+ raise NotImplementedError
63
+ end
64
+
65
+ # Quotes the table name. Defaults to column name quoting.
66
+ def quote_table_name(table_name)
67
+ quote_column_name(table_name)
68
+ end
69
+ end
70
+
10
71
  # Quotes the column value to help prevent
11
72
  # {SQL injection attacks}[https://en.wikipedia.org/wiki/SQL_injection].
12
73
  def quote(value)
@@ -23,9 +84,6 @@ module ActiveRecord
23
84
  when Type::Time::Value then "'#{quoted_time(value)}'"
24
85
  when Date, Time then "'#{quoted_date(value)}'"
25
86
  when Class then "'#{value}'"
26
- when ActiveSupport::Duration
27
- warn_quote_duration_deprecated
28
- value.to_s
29
87
  else raise TypeError, "can't quote #{value.class.name}"
30
88
  end
31
89
  end
@@ -48,21 +106,6 @@ module ActiveRecord
48
106
  end
49
107
  end
50
108
 
51
- # Quote a value to be used as a bound parameter of unknown type. For example,
52
- # MySQL might perform dangerous castings when comparing a string to a number,
53
- # so this method will cast numbers to string.
54
- #
55
- # Deprecated: Consider `Arel.sql("... ? ...", value)` or
56
- # +sanitize_sql+ instead.
57
- def quote_bound_value(value)
58
- ActiveRecord.deprecator.warn(<<~MSG.squish)
59
- #quote_bound_value is deprecated and will be removed in Rails 7.2.
60
- Consider Arel.sql(".. ? ..", value) or #sanitize_sql instead.
61
- MSG
62
-
63
- quote(cast_bound_value(value))
64
- end
65
-
66
109
  # Cast a value to be used as a bound parameter of unknown type. For example,
67
110
  # MySQL might perform dangerous castings when comparing a string to a number,
68
111
  # so this method will cast numbers to string.
@@ -89,14 +132,14 @@ module ActiveRecord
89
132
  s.gsub("\\", '\&\&').gsub("'", "''") # ' (for ruby-mode)
90
133
  end
91
134
 
92
- # Quotes the column name. Defaults to no quoting.
135
+ # Quotes the column name.
93
136
  def quote_column_name(column_name)
94
- column_name.to_s
137
+ self.class.quote_column_name(column_name)
95
138
  end
96
139
 
97
- # Quotes the table name. Defaults to column name quoting.
140
+ # Quotes the table name.
98
141
  def quote_table_name(table_name)
99
- quote_column_name(table_name)
142
+ self.class.quote_table_name(table_name)
100
143
  end
101
144
 
102
145
  # Override to return the quoted table name for assignment. Defaults to
@@ -177,62 +220,9 @@ module ActiveRecord
177
220
  comment
178
221
  end
179
222
 
180
- def column_name_matcher # :nodoc:
181
- COLUMN_NAME
182
- end
183
-
184
- def column_name_with_order_matcher # :nodoc:
185
- COLUMN_NAME_WITH_ORDER
186
- end
187
-
188
- # Regexp for column names (with or without a table name prefix).
189
- # Matches the following:
190
- #
191
- # "#{table_name}.#{column_name}"
192
- # "#{column_name}"
193
- COLUMN_NAME = /
194
- \A
195
- (
196
- (?:
197
- # table_name.column_name | function(one or no argument)
198
- ((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
199
- )
200
- (?:(?:\s+AS)?\s+\w+)?
201
- )
202
- (?:\s*,\s*\g<1>)*
203
- \z
204
- /ix
205
-
206
- # Regexp for column names with order (with or without a table name prefix,
207
- # with or without various order modifiers). Matches the following:
208
- #
209
- # "#{table_name}.#{column_name}"
210
- # "#{table_name}.#{column_name} #{direction}"
211
- # "#{table_name}.#{column_name} #{direction} NULLS FIRST"
212
- # "#{table_name}.#{column_name} NULLS LAST"
213
- # "#{column_name}"
214
- # "#{column_name} #{direction}"
215
- # "#{column_name} #{direction} NULLS FIRST"
216
- # "#{column_name} NULLS LAST"
217
- COLUMN_NAME_WITH_ORDER = /
218
- \A
219
- (
220
- (?:
221
- # table_name.column_name | function(one or no argument)
222
- ((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
223
- )
224
- (?:\s+ASC|\s+DESC)?
225
- (?:\s+NULLS\s+(?:FIRST|LAST))?
226
- )
227
- (?:\s*,\s*\g<1>)*
228
- \z
229
- /ix
230
-
231
- private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
232
-
233
223
  private
234
224
  def type_casted_binds(binds)
235
- binds.map do |value|
225
+ binds&.map do |value|
236
226
  if ActiveModel::Attribute === value
237
227
  type_cast(value.value_for_database)
238
228
  else
@@ -244,22 +234,6 @@ module ActiveRecord
244
234
  def lookup_cast_type(sql_type)
245
235
  type_map.lookup(sql_type)
246
236
  end
247
-
248
- def warn_quote_duration_deprecated
249
- ActiveRecord.deprecator.warn(<<~MSG)
250
- Using ActiveSupport::Duration as an interpolated bind parameter in a SQL
251
- string template is deprecated. To avoid this warning, you should explicitly
252
- convert the duration to a more specific database type. For example, if you
253
- want to use a duration as an integer number of seconds:
254
- ```
255
- Record.where("duration = ?", 1.hour.to_i)
256
- ```
257
- If you want to use a duration as an ISO 8601 string:
258
- ```
259
- Record.where("duration = ?", 1.hour.iso8601)
260
- ```
261
- MSG
262
- end
263
237
  end
264
238
  end
265
239
  end
@@ -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.1]
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