activerecord 7.1.5.2 → 8.1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  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 +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. 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:
@@ -12,16 +13,15 @@ module ActiveRecord
12
13
  dirties_query_cache base, :exec_query, :execute, :create, :insert, :update, :delete, :truncate,
13
14
  :truncate_tables, :rollback_to_savepoint, :rollback_db_transaction, :restart_db_transaction,
14
15
  :exec_insert_all
15
-
16
- base.set_callback :checkout, :after, :configure_query_cache!
17
- base.set_callback :checkin, :after, :disable_query_cache!
18
16
  end
19
17
 
20
18
  def dirties_query_cache(base, *method_names)
21
19
  method_names.each do |method_name|
22
20
  base.class_eval <<-end_code, __FILE__, __LINE__ + 1
23
21
  def #{method_name}(...)
24
- ActiveRecord::Base.clear_query_caches_for_current_thread
22
+ if pool.dirties_query_cache
23
+ ActiveRecord::Base.clear_query_caches_for_current_thread
24
+ end
25
25
  super
26
26
  end
27
27
  end_code
@@ -29,60 +29,225 @@ module ActiveRecord
29
29
  end
30
30
  end
31
31
 
32
- module ConnectionPoolConfiguration
33
- def initialize(*)
32
+ # This is the actual query cache store.
33
+ #
34
+ # It has an internal hash whose keys are either SQL strings, or arrays of
35
+ # two elements [SQL string, binds], if there are binds. The hash values
36
+ # are their corresponding ActiveRecord::Result objects.
37
+ #
38
+ # Keeping the hash size under max size is achieved with LRU eviction.
39
+ #
40
+ # The store gets passed a version object, which is shared among the query
41
+ # cache stores of a given connection pool (see ConnectionPoolConfiguration
42
+ # down below). The version value may be externally changed as a way to
43
+ # signal cache invalidation, that is why all methods have a guard for it.
44
+ class Store # :nodoc:
45
+ attr_accessor :enabled, :dirties
46
+ alias_method :enabled?, :enabled
47
+ alias_method :dirties?, :dirties
48
+
49
+ def initialize(version, max_size)
50
+ @version = version
51
+ @current_version = version.value
52
+ @map = {}
53
+ @max_size = max_size
54
+ @enabled = false
55
+ @dirties = true
56
+ end
57
+
58
+ def size
59
+ check_version
60
+ @map.size
61
+ end
62
+
63
+ def empty?
64
+ check_version
65
+ @map.empty?
66
+ end
67
+
68
+ def [](key)
69
+ check_version
70
+ return unless @enabled
71
+
72
+ if entry = @map.delete(key)
73
+ @map[key] = entry
74
+ end
75
+ end
76
+
77
+ def compute_if_absent(key)
78
+ check_version
79
+
80
+ return yield unless @enabled
81
+
82
+ if entry = @map.delete(key)
83
+ return @map[key] = entry
84
+ end
85
+
86
+ if @max_size && @map.size >= @max_size
87
+ @map.shift # evict the oldest entry
88
+ end
89
+
90
+ @map[key] ||= yield
91
+ end
92
+
93
+ def clear
94
+ @map.clear
95
+ self
96
+ end
97
+
98
+ private
99
+ def check_version
100
+ if @current_version != @version.value
101
+ @map.clear
102
+ @current_version = @version.value
103
+ end
104
+ end
105
+ end
106
+
107
+ # Each connection pool has one of these registries. They map execution
108
+ # contexts to query cache stores.
109
+ #
110
+ # The keys of the internal map are threads or fibers (whatever
111
+ # ActiveSupport::IsolatedExecutionState.context returns), and their
112
+ # associated values are their respective query cache stores.
113
+ class QueryCacheRegistry # :nodoc:
114
+ def initialize
115
+ @mutex = Mutex.new
116
+ @map = ConnectionPool::WeakThreadKeyMap.new
117
+ end
118
+
119
+ def compute_if_absent(context)
120
+ @map[context] || @mutex.synchronize do
121
+ @map[context] ||= yield
122
+ end
123
+ end
124
+
125
+ def clear
126
+ @map.synchronize do
127
+ @map.clear
128
+ end
129
+ end
130
+ end
131
+
132
+ module ConnectionPoolConfiguration # :nodoc:
133
+ def initialize(...)
34
134
  super
35
- @query_cache_enabled = Concurrent::Map.new { false }
135
+ @query_cache_version = Concurrent::AtomicFixnum.new
136
+ @thread_query_caches = QueryCacheRegistry.new
137
+ @query_cache_max_size = \
138
+ case query_cache = db_config&.query_cache
139
+ when 0, false
140
+ nil
141
+ when Integer
142
+ query_cache
143
+ when nil
144
+ DEFAULT_SIZE
145
+ end
146
+ end
147
+
148
+ def checkout_and_verify(connection)
149
+ super
150
+ connection.query_cache ||= query_cache
151
+ connection
152
+ end
153
+
154
+ # Disable the query cache within the block.
155
+ def disable_query_cache(dirties: true)
156
+ cache = query_cache
157
+ old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, false, cache.dirties, dirties
158
+ begin
159
+ yield
160
+ ensure
161
+ cache.enabled, cache.dirties = old_enabled, old_dirties
162
+ end
163
+ end
164
+
165
+ def enable_query_cache
166
+ cache = query_cache
167
+ old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, true, cache.dirties, true
168
+ begin
169
+ yield
170
+ ensure
171
+ cache.enabled, cache.dirties = old_enabled, old_dirties
172
+ end
36
173
  end
37
174
 
38
175
  def enable_query_cache!
39
- @query_cache_enabled[connection_cache_key(current_thread)] = true
40
- connection.enable_query_cache! if active_connection?
176
+ query_cache.enabled = true
177
+ query_cache.dirties = true
41
178
  end
42
179
 
43
180
  def disable_query_cache!
44
- @query_cache_enabled.delete connection_cache_key(current_thread)
45
- connection.disable_query_cache! if active_connection?
181
+ query_cache.enabled = false
182
+ query_cache.dirties = true
46
183
  end
47
184
 
48
185
  def query_cache_enabled
49
- @query_cache_enabled[connection_cache_key(current_thread)]
186
+ query_cache.enabled
187
+ end
188
+
189
+ def dirties_query_cache
190
+ query_cache.dirties
191
+ end
192
+
193
+ def clear_query_cache
194
+ if @pinned_connection
195
+ # With transactional fixtures, and especially systems test
196
+ # another thread may use the same connection, but with a different
197
+ # query cache. So we must clear them all.
198
+ @query_cache_version.increment
199
+ end
200
+ query_cache.clear
50
201
  end
51
- end
52
202
 
53
- attr_reader :query_cache, :query_cache_enabled
203
+ def query_cache
204
+ @thread_query_caches.compute_if_absent(ActiveSupport::IsolatedExecutionState.context) do
205
+ Store.new(@query_cache_version, @query_cache_max_size)
206
+ end
207
+ end
208
+ end
54
209
 
55
210
  def initialize(*)
56
211
  super
57
- @query_cache = {}
58
- @query_cache_enabled = false
59
- @query_cache_max_size = nil
212
+ @query_cache = nil
213
+ end
214
+
215
+ attr_writer :query_cache
216
+
217
+ def query_cache
218
+ if @pinned && @owner != ActiveSupport::IsolatedExecutionState.context
219
+ # With transactional tests, if the connection is pinned, any thread
220
+ # other than the one that pinned the connection need to go through the
221
+ # query cache pool, so each thread get a different cache.
222
+ pool.query_cache
223
+ else
224
+ @query_cache
225
+ end
226
+ end
227
+
228
+ def query_cache_enabled
229
+ query_cache&.enabled?
60
230
  end
61
231
 
62
232
  # 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
233
+ def cache(&block)
234
+ pool.enable_query_cache(&block)
69
235
  end
70
236
 
71
237
  def enable_query_cache!
72
- @query_cache_enabled = true
238
+ pool.enable_query_cache!
73
239
  end
74
240
 
75
- def disable_query_cache!
76
- @query_cache_enabled = false
77
- clear_query_cache
241
+ # Disable the query cache within the block.
242
+ #
243
+ # Set <tt>dirties: false</tt> to prevent query caches on all connections from being cleared by write operations.
244
+ # (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.)
245
+ def uncached(dirties: true, &block)
246
+ pool.disable_query_cache(dirties: dirties, &block)
78
247
  end
79
248
 
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
249
+ def disable_query_cache!
250
+ pool.disable_query_cache!
86
251
  end
87
252
 
88
253
  # Clears the query cache.
@@ -92,24 +257,22 @@ module ActiveRecord
92
257
  # the same SQL query and repeatedly return the same result each time, silently
93
258
  # undermining the randomness you were expecting.
94
259
  def clear_query_cache
95
- @lock.synchronize do
96
- @query_cache.clear
97
- end
260
+ pool.clear_query_cache
98
261
  end
99
262
 
100
- def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :nodoc:
263
+ def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) # :nodoc:
101
264
  arel = arel_from_relation(arel)
102
265
 
103
266
  # If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
104
267
  # 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)
268
+ if query_cache_enabled && !(arel.respond_to?(:locked) && arel.locked)
269
+ sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable, allow_retry)
107
270
 
108
271
  if async
109
- result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
272
+ result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
110
273
  FutureResult.wrap(result)
111
274
  else
112
- cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async) }
275
+ cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry) }
113
276
  end
114
277
  else
115
278
  super
@@ -117,55 +280,56 @@ module ActiveRecord
117
280
  end
118
281
 
119
282
  private
283
+ def unset_query_cache!
284
+ @query_cache = nil
285
+ end
286
+
120
287
  def lookup_sql_cache(sql, name, binds)
121
288
  key = binds.empty? ? sql : [sql, binds]
122
- hit = false
123
- result = nil
124
289
 
290
+ result = nil
125
291
  @lock.synchronize do
126
- if (result = @query_cache.delete(key))
127
- hit = true
128
- @query_cache[key] = result
129
- end
292
+ result = query_cache[key]
130
293
  end
131
294
 
132
- if hit
295
+ if result
133
296
  ActiveSupport::Notifications.instrument(
134
297
  "sql.active_record",
135
- cache_notification_info(sql, name, binds)
298
+ cache_notification_info_result(sql, name, binds, result)
136
299
  )
137
-
138
- result
139
300
  end
301
+
302
+ result
140
303
  end
141
304
 
142
305
  def cache_sql(sql, name, binds)
143
306
  key = binds.empty? ? sql : [sql, binds]
144
307
  result = nil
145
- hit = false
308
+ hit = true
146
309
 
147
310
  @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
311
+ result = query_cache.compute_if_absent(key) do
312
+ hit = false
313
+ yield
156
314
  end
157
315
  end
158
316
 
159
317
  if hit
160
318
  ActiveSupport::Notifications.instrument(
161
319
  "sql.active_record",
162
- cache_notification_info(sql, name, binds)
320
+ cache_notification_info_result(sql, name, binds, result)
163
321
  )
164
322
  end
165
323
 
166
324
  result.dup
167
325
  end
168
326
 
327
+ def cache_notification_info_result(sql, name, binds, result)
328
+ payload = cache_notification_info(sql, name, binds)
329
+ payload[:row_count] = result.length
330
+ payload
331
+ end
332
+
169
333
  # Database adapters can override this method to
170
334
  # provide custom cache information.
171
335
  def cache_notification_info(sql, name, binds)
@@ -175,26 +339,10 @@ module ActiveRecord
175
339
  type_casted_binds: -> { type_casted_binds(binds) },
176
340
  name: name,
177
341
  connection: self,
342
+ transaction: current_transaction.user_transaction.presence,
178
343
  cached: true
179
344
  }
180
345
  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
346
  end
199
347
  end
200
348
  end
@@ -1,12 +1,72 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/big_decimal/conversions"
4
- require "active_support/multibyte/chars"
5
4
 
6
5
  module ActiveRecord
7
6
  module ConnectionAdapters # :nodoc:
8
7
  # = Active Record Connection Adapters \Quoting
9
8
  module Quoting
9
+ extend ActiveSupport::Concern
10
+
11
+ module ClassMethods # :nodoc:
12
+ # Regexp for column names (with or without a table name prefix).
13
+ # Matches the following:
14
+ #
15
+ # "#{table_name}.#{column_name}"
16
+ # "#{column_name}"
17
+ def column_name_matcher
18
+ /
19
+ \A
20
+ (
21
+ (?:
22
+ # table_name.column_name | function(one or no argument)
23
+ ((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
24
+ )
25
+ (?:(?:\s+AS)?\s+\w+)?
26
+ )
27
+ (?:\s*,\s*\g<1>)*
28
+ \z
29
+ /ix
30
+ end
31
+
32
+ # Regexp for column names with order (with or without a table name prefix,
33
+ # with or without various order modifiers). Matches the following:
34
+ #
35
+ # "#{table_name}.#{column_name}"
36
+ # "#{table_name}.#{column_name} #{direction}"
37
+ # "#{table_name}.#{column_name} #{direction} NULLS FIRST"
38
+ # "#{table_name}.#{column_name} NULLS LAST"
39
+ # "#{column_name}"
40
+ # "#{column_name} #{direction}"
41
+ # "#{column_name} #{direction} NULLS FIRST"
42
+ # "#{column_name} NULLS LAST"
43
+ def column_name_with_order_matcher
44
+ /
45
+ \A
46
+ (
47
+ (?:
48
+ # table_name.column_name | function(one or no argument)
49
+ ((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
50
+ )
51
+ (?:\s+ASC|\s+DESC)?
52
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
53
+ )
54
+ (?:\s*,\s*\g<1>)*
55
+ \z
56
+ /ix
57
+ end
58
+
59
+ # Quotes the column name. Must be implemented by subclasses
60
+ def quote_column_name(column_name)
61
+ raise NotImplementedError
62
+ end
63
+
64
+ # Quotes the table name. Defaults to column name quoting.
65
+ def quote_table_name(table_name)
66
+ quote_column_name(table_name)
67
+ end
68
+ end
69
+
10
70
  # Quotes the column value to help prevent
11
71
  # {SQL injection attacks}[https://en.wikipedia.org/wiki/SQL_injection].
12
72
  def quote(value)
@@ -23,10 +83,8 @@ module ActiveRecord
23
83
  when Type::Time::Value then "'#{quoted_time(value)}'"
24
84
  when Date, Time then "'#{quoted_date(value)}'"
25
85
  when Class then "'#{value}'"
26
- when ActiveSupport::Duration
27
- warn_quote_duration_deprecated
28
- value.to_s
29
- else raise TypeError, "can't quote #{value.class.name}"
86
+ else
87
+ raise TypeError, "can't quote #{value.class.name}"
30
88
  end
31
89
  end
32
90
 
@@ -35,7 +93,7 @@ module ActiveRecord
35
93
  # to a String.
36
94
  def type_cast(value)
37
95
  case value
38
- when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data
96
+ when Symbol, Type::Binary::Data, ActiveSupport::Multibyte::Chars
39
97
  value.to_s
40
98
  when true then unquoted_true
41
99
  when false then unquoted_false
@@ -44,25 +102,11 @@ module ActiveRecord
44
102
  when nil, Numeric, String then value
45
103
  when Type::Time::Value then quoted_time(value)
46
104
  when Date, Time then quoted_date(value)
47
- else raise TypeError, "can't cast #{value.class.name}"
105
+ else
106
+ raise TypeError, "can't cast #{value.class.name}"
48
107
  end
49
108
  end
50
109
 
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
110
  # Cast a value to be used as a bound parameter of unknown type. For example,
67
111
  # MySQL might perform dangerous castings when comparing a string to a number,
68
112
  # so this method will cast numbers to string.
@@ -70,33 +114,20 @@ module ActiveRecord
70
114
  value
71
115
  end
72
116
 
73
- # If you are having to call this function, you are likely doing something
74
- # wrong. The column does not have sufficient type information if the user
75
- # provided a custom type on the class level either explicitly (via
76
- # Attributes::ClassMethods#attribute) or implicitly (via
77
- # AttributeMethods::Serialization::ClassMethods#serialize, +time_zone_aware_attributes+).
78
- # In almost all cases, the sql type should only be used to change quoting behavior, when the primitive to
79
- # represent the type doesn't sufficiently reflect the differences
80
- # (varchar vs binary) for example. The type used to get this primitive
81
- # should have been provided before reaching the connection adapter.
82
- def lookup_cast_type_from_column(column) # :nodoc:
83
- lookup_cast_type(column.sql_type)
84
- end
85
-
86
117
  # Quotes a string, escaping any ' (single quote) and \ (backslash)
87
118
  # characters.
88
119
  def quote_string(s)
89
120
  s.gsub("\\", '\&\&').gsub("'", "''") # ' (for ruby-mode)
90
121
  end
91
122
 
92
- # Quotes the column name. Defaults to no quoting.
123
+ # Quotes the column name.
93
124
  def quote_column_name(column_name)
94
- column_name.to_s
125
+ self.class.quote_column_name(column_name)
95
126
  end
96
127
 
97
- # Quotes the table name. Defaults to column name quoting.
128
+ # Quotes the table name.
98
129
  def quote_table_name(table_name)
99
- quote_column_name(table_name)
130
+ self.class.quote_table_name(table_name)
100
131
  end
101
132
 
102
133
  # Override to return the quoted table name for assignment. Defaults to
@@ -115,7 +146,9 @@ module ActiveRecord
115
146
  if value.is_a?(Proc)
116
147
  value.call
117
148
  else
118
- value = lookup_cast_type(column.sql_type).serialize(value)
149
+ # TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
150
+ cast_type = column.fetch_cast_type(self)
151
+ value = cast_type.serialize(value)
119
152
  quote(value)
120
153
  end
121
154
  end
@@ -165,10 +198,10 @@ module ActiveRecord
165
198
  end
166
199
 
167
200
  def sanitize_as_sql_comment(value) # :nodoc:
168
- # Sanitize a string to appear within a SQL comment
201
+ # Sanitize a string to appear within an SQL comment
169
202
  # For compatibility, this also surrounding "/*+", "/*", and "*/"
170
203
  # charcacters, possibly with single surrounding space.
171
- # Then follows that by replacing any internal "*/" or "/ *" with
204
+ # Then follows that by replacing any internal "*/" or "/*" with
172
205
  # "* /" or "/ *"
173
206
  comment = value.to_s.dup
174
207
  comment.gsub!(%r{\A\s*/\*\+?\s?|\s?\*/\s*\Z}, "")
@@ -177,62 +210,14 @@ module ActiveRecord
177
210
  comment
178
211
  end
179
212
 
180
- def column_name_matcher # :nodoc:
181
- COLUMN_NAME
213
+ def lookup_cast_type(sql_type) # :nodoc:
214
+ # TODO: Make this method private after we release 8.1.
215
+ type_map.lookup(sql_type)
182
216
  end
183
217
 
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
218
  private
234
219
  def type_casted_binds(binds)
235
- binds.map do |value|
220
+ binds&.map do |value|
236
221
  if ActiveModel::Attribute === value
237
222
  type_cast(value.value_for_database)
238
223
  else
@@ -240,26 +225,6 @@ module ActiveRecord
240
225
  end
241
226
  end
242
227
  end
243
-
244
- def lookup_cast_type(sql_type)
245
- type_map.lookup(sql_type)
246
- 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
228
  end
264
229
  end
265
230
  end