activerecord 7.1.5.1 → 7.2.0.beta1

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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -2445
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +9 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +14 -7
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +6 -4
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +1 -1
  17. data/lib/active_record/associations/join_dependency/join_association.rb +29 -28
  18. data/lib/active_record/associations/join_dependency.rb +5 -5
  19. data/lib/active_record/associations/nested_error.rb +47 -0
  20. data/lib/active_record/associations/preloader/association.rb +2 -1
  21. data/lib/active_record/associations/preloader/branch.rb +7 -1
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  23. data/lib/active_record/associations/singular_association.rb +6 -0
  24. data/lib/active_record/associations/through_association.rb +1 -1
  25. data/lib/active_record/associations.rb +33 -16
  26. data/lib/active_record/attribute_assignment.rb +1 -11
  27. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +4 -16
  31. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -10
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +60 -71
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +13 -32
  37. data/lib/active_record/base.rb +2 -3
  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 +248 -65
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -74
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +14 -5
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
  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 +7 -1
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  59. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  60. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -13
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
  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 +44 -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 +25 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +107 -75
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  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 +56 -41
  76. data/lib/active_record/core.rb +53 -37
  77. data/lib/active_record/counter_cache.rb +18 -9
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  79. data/lib/active_record/database_configurations/database_config.rb +15 -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 +24 -0
  84. data/lib/active_record/dynamic_matchers.rb +2 -2
  85. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  86. data/lib/active_record/encryption/encrypted_attribute_type.rb +22 -2
  87. data/lib/active_record/encryption/encryptor.rb +17 -2
  88. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  89. data/lib/active_record/encryption/message_serializer.rb +4 -0
  90. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  91. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption.rb +0 -2
  93. data/lib/active_record/enum.rb +10 -1
  94. data/lib/active_record/errors.rb +16 -11
  95. data/lib/active_record/explain.rb +13 -24
  96. data/lib/active_record/fixtures.rb +37 -31
  97. data/lib/active_record/future_result.rb +8 -4
  98. data/lib/active_record/gem_version.rb +3 -3
  99. data/lib/active_record/inheritance.rb +4 -2
  100. data/lib/active_record/insert_all.rb +18 -15
  101. data/lib/active_record/integration.rb +4 -1
  102. data/lib/active_record/internal_metadata.rb +48 -34
  103. data/lib/active_record/locking/optimistic.rb +7 -6
  104. data/lib/active_record/log_subscriber.rb +0 -21
  105. data/lib/active_record/marshalling.rb +1 -4
  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 +85 -76
  112. data/lib/active_record/model_schema.rb +28 -68
  113. data/lib/active_record/nested_attributes.rb +13 -16
  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 +18 -6
  117. data/lib/active_record/query_logs.rb +15 -0
  118. data/lib/active_record/querying.rb +21 -9
  119. data/lib/active_record/railtie.rb +50 -62
  120. data/lib/active_record/railties/controller_runtime.rb +13 -4
  121. data/lib/active_record/railties/databases.rake +41 -44
  122. data/lib/active_record/reflection.rb +90 -35
  123. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  124. data/lib/active_record/relation/batches.rb +3 -3
  125. data/lib/active_record/relation/calculations.rb +94 -61
  126. data/lib/active_record/relation/delegation.rb +8 -11
  127. data/lib/active_record/relation/finder_methods.rb +16 -2
  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.rb +3 -3
  131. data/lib/active_record/relation/query_methods.rb +196 -57
  132. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  133. data/lib/active_record/relation/spawn_methods.rb +2 -18
  134. data/lib/active_record/relation/where_clause.rb +7 -19
  135. data/lib/active_record/relation.rb +496 -72
  136. data/lib/active_record/result.rb +31 -44
  137. data/lib/active_record/runtime_registry.rb +39 -0
  138. data/lib/active_record/sanitization.rb +24 -19
  139. data/lib/active_record/schema.rb +8 -6
  140. data/lib/active_record/schema_dumper.rb +19 -9
  141. data/lib/active_record/schema_migration.rb +30 -14
  142. data/lib/active_record/signed_id.rb +11 -1
  143. data/lib/active_record/statement_cache.rb +7 -7
  144. data/lib/active_record/table_metadata.rb +1 -10
  145. data/lib/active_record/tasks/database_tasks.rb +76 -70
  146. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  147. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  148. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  149. data/lib/active_record/test_fixtures.rb +81 -91
  150. data/lib/active_record/testing/query_assertions.rb +121 -0
  151. data/lib/active_record/timestamp.rb +1 -1
  152. data/lib/active_record/token_for.rb +22 -12
  153. data/lib/active_record/touch_later.rb +1 -1
  154. data/lib/active_record/transaction.rb +68 -0
  155. data/lib/active_record/transactions.rb +43 -14
  156. data/lib/active_record/translation.rb +0 -2
  157. data/lib/active_record/type/serialized.rb +1 -3
  158. data/lib/active_record/type_caster/connection.rb +4 -4
  159. data/lib/active_record/validations/associated.rb +9 -3
  160. data/lib/active_record/validations/uniqueness.rb +14 -10
  161. data/lib/active_record/validations.rb +4 -1
  162. data/lib/active_record.rb +149 -40
  163. data/lib/arel/alias_predication.rb +1 -1
  164. data/lib/arel/collectors/bind.rb +2 -0
  165. data/lib/arel/collectors/composite.rb +7 -0
  166. data/lib/arel/collectors/sql_string.rb +1 -1
  167. data/lib/arel/collectors/substitute_binds.rb +1 -1
  168. data/lib/arel/nodes/binary.rb +0 -6
  169. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  170. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  171. data/lib/arel/nodes/node.rb +4 -3
  172. data/lib/arel/nodes/sql_literal.rb +7 -0
  173. data/lib/arel/nodes.rb +2 -2
  174. data/lib/arel/predications.rb +1 -1
  175. data/lib/arel/select_manager.rb +1 -1
  176. data/lib/arel/tree_manager.rb +3 -2
  177. data/lib/arel/update_manager.rb +2 -1
  178. data/lib/arel/visitors/dot.rb +1 -0
  179. data/lib/arel/visitors/mysql.rb +9 -4
  180. data/lib/arel/visitors/postgresql.rb +1 -12
  181. data/lib/arel/visitors/to_sql.rb +29 -16
  182. data/lib/arel.rb +7 -3
  183. metadata +20 -15
@@ -13,15 +13,16 @@ module ActiveRecord
13
13
  :truncate_tables, :rollback_to_savepoint, :rollback_db_transaction, :restart_db_transaction,
14
14
  :exec_insert_all
15
15
 
16
- base.set_callback :checkout, :after, :configure_query_cache!
17
- base.set_callback :checkin, :after, :disable_query_cache!
16
+ base.set_callback :checkin, :after, :unset_query_cache!
18
17
  end
19
18
 
20
19
  def dirties_query_cache(base, *method_names)
21
20
  method_names.each do |method_name|
22
21
  base.class_eval <<-end_code, __FILE__, __LINE__ + 1
23
22
  def #{method_name}(...)
24
- ActiveRecord::Base.clear_query_caches_for_current_thread
23
+ if pool.dirties_query_cache
24
+ ActiveRecord::Base.clear_query_caches_for_current_thread
25
+ end
25
26
  super
26
27
  end
27
28
  end_code
@@ -29,60 +30,168 @@ module ActiveRecord
29
30
  end
30
31
  end
31
32
 
32
- module ConnectionPoolConfiguration
33
- def initialize(*)
33
+ class Store # :nodoc:
34
+ attr_accessor :enabled, :dirties
35
+ alias_method :enabled?, :enabled
36
+ alias_method :dirties?, :dirties
37
+
38
+ def initialize(max_size)
39
+ @map = {}
40
+ @max_size = max_size
41
+ @enabled = false
42
+ @dirties = true
43
+ end
44
+
45
+ def size
46
+ @map.size
47
+ end
48
+
49
+ def empty?
50
+ @map.empty?
51
+ end
52
+
53
+ def [](key)
54
+ return unless @enabled
55
+
56
+ if entry = @map.delete(key)
57
+ @map[key] = entry
58
+ end
59
+ end
60
+
61
+ def compute_if_absent(key)
62
+ return yield unless @enabled
63
+
64
+ if entry = @map.delete(key)
65
+ return @map[key] = entry
66
+ end
67
+
68
+ if @max_size && @map.size >= @max_size
69
+ @map.shift # evict the oldest entry
70
+ end
71
+
72
+ @map[key] ||= yield
73
+ end
74
+
75
+ def clear
76
+ @map.clear
77
+ self
78
+ end
79
+ end
80
+
81
+ module ConnectionPoolConfiguration # :nodoc:
82
+ def initialize(...)
83
+ super
84
+ @thread_query_caches = Concurrent::Map.new(initial_capacity: @size)
85
+ @query_cache_max_size = \
86
+ case query_cache = db_config&.query_cache
87
+ when 0, false
88
+ nil
89
+ when Integer
90
+ query_cache
91
+ when nil
92
+ DEFAULT_SIZE
93
+ end
94
+ end
95
+
96
+ def checkout_and_verify(connection)
34
97
  super
35
- @query_cache_enabled = Concurrent::Map.new { false }
98
+ connection.query_cache ||= query_cache
99
+ connection
100
+ end
101
+
102
+ # Disable the query cache within the block.
103
+ def disable_query_cache(dirties: true)
104
+ cache = query_cache
105
+ old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, false, cache.dirties, dirties
106
+ begin
107
+ yield
108
+ ensure
109
+ cache.enabled, cache.dirties = old_enabled, old_dirties
110
+ end
111
+ end
112
+
113
+ def enable_query_cache
114
+ cache = query_cache
115
+ old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, true, cache.dirties, true
116
+ begin
117
+ yield
118
+ ensure
119
+ cache.enabled, cache.dirties = old_enabled, old_dirties
120
+ end
36
121
  end
37
122
 
38
123
  def enable_query_cache!
39
- @query_cache_enabled[connection_cache_key(current_thread)] = true
40
- connection.enable_query_cache! if active_connection?
124
+ query_cache.enabled, query_cache.dirties = true, true
41
125
  end
42
126
 
43
127
  def disable_query_cache!
44
- @query_cache_enabled.delete connection_cache_key(current_thread)
45
- connection.disable_query_cache! if active_connection?
128
+ query_cache.enabled, query_cache.dirties = false, true
46
129
  end
47
130
 
48
131
  def query_cache_enabled
49
- @query_cache_enabled[connection_cache_key(current_thread)]
132
+ query_cache.enabled
133
+ end
134
+
135
+ def dirties_query_cache
136
+ query_cache.dirties
50
137
  end
138
+
139
+ def clear_query_cache
140
+ if @pinned_connection
141
+ # With transactional fixtures, and especially systems test
142
+ # another thread may use the same connection, but with a different
143
+ # query cache. So we must clear them all.
144
+ @thread_query_caches.each_value(&:clear)
145
+ else
146
+ query_cache.clear
147
+ end
148
+ end
149
+
150
+ def query_cache
151
+ @thread_query_caches.compute_if_absent(ActiveSupport::IsolatedExecutionState.context) do
152
+ Store.new(@query_cache_max_size)
153
+ end
154
+ end
155
+
156
+ private
157
+ def prune_thread_cache
158
+ dead_threads = @thread_query_caches.keys.reject(&:alive?)
159
+ dead_threads.each do |dead_thread|
160
+ @thread_query_caches.delete(dead_thread)
161
+ end
162
+ end
51
163
  end
52
164
 
53
- attr_reader :query_cache, :query_cache_enabled
165
+ attr_accessor :query_cache
54
166
 
55
167
  def initialize(*)
56
168
  super
57
- @query_cache = {}
58
- @query_cache_enabled = false
59
- @query_cache_max_size = nil
169
+ @query_cache = nil
170
+ end
171
+
172
+ def query_cache_enabled
173
+ @query_cache&.enabled?
60
174
  end
61
175
 
62
176
  # 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
177
+ def cache(&block)
178
+ pool.enable_query_cache(&block)
69
179
  end
70
180
 
71
181
  def enable_query_cache!
72
- @query_cache_enabled = true
182
+ pool.enable_query_cache!
73
183
  end
74
184
 
75
- def disable_query_cache!
76
- @query_cache_enabled = false
77
- clear_query_cache
185
+ # Disable the query cache within the block.
186
+ #
187
+ # Set <tt>dirties: false</tt> to prevent query caches on all connections from being cleared by write operations.
188
+ # (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.)
189
+ def uncached(dirties: true, &block)
190
+ pool.disable_query_cache(dirties: dirties, &block)
78
191
  end
79
192
 
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
193
+ def disable_query_cache!
194
+ pool.disable_query_cache!
86
195
  end
87
196
 
88
197
  # Clears the query cache.
@@ -92,24 +201,22 @@ module ActiveRecord
92
201
  # the same SQL query and repeatedly return the same result each time, silently
93
202
  # undermining the randomness you were expecting.
94
203
  def clear_query_cache
95
- @lock.synchronize do
96
- @query_cache.clear
97
- end
204
+ pool.clear_query_cache
98
205
  end
99
206
 
100
- def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :nodoc:
207
+ def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) # :nodoc:
101
208
  arel = arel_from_relation(arel)
102
209
 
103
210
  # If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
104
211
  # 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)
212
+ if @query_cache&.enabled? && !(arel.respond_to?(:locked) && arel.locked)
213
+ sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
107
214
 
108
215
  if async
109
- result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
216
+ result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
110
217
  FutureResult.wrap(result)
111
218
  else
112
- cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async) }
219
+ cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry) }
113
220
  end
114
221
  else
115
222
  super
@@ -117,42 +224,37 @@ module ActiveRecord
117
224
  end
118
225
 
119
226
  private
227
+ def unset_query_cache!
228
+ @query_cache = nil
229
+ end
230
+
120
231
  def lookup_sql_cache(sql, name, binds)
121
232
  key = binds.empty? ? sql : [sql, binds]
122
- hit = false
123
- result = nil
124
233
 
234
+ result = nil
125
235
  @lock.synchronize do
126
- if (result = @query_cache.delete(key))
127
- hit = true
128
- @query_cache[key] = result
129
- end
236
+ result = @query_cache[key]
130
237
  end
131
238
 
132
- if hit
239
+ if result
133
240
  ActiveSupport::Notifications.instrument(
134
241
  "sql.active_record",
135
242
  cache_notification_info(sql, name, binds)
136
243
  )
137
-
138
- result
139
244
  end
245
+
246
+ result
140
247
  end
141
248
 
142
249
  def cache_sql(sql, name, binds)
143
250
  key = binds.empty? ? sql : [sql, binds]
144
251
  result = nil
145
- hit = false
252
+ hit = true
146
253
 
147
254
  @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
255
+ result = @query_cache.compute_if_absent(key) do
256
+ hit = false
257
+ yield
156
258
  end
157
259
  end
158
260
 
@@ -178,23 +280,6 @@ module ActiveRecord
178
280
  cached: true
179
281
  }
180
282
  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
283
  end
199
284
  end
200
285
  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,59 +220,6 @@ 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
225
  binds.map do |value|
@@ -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
@@ -348,7 +348,7 @@ module ActiveRecord
348
348
  # Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
349
349
  # is actually of this type:
350
350
  #
351
- # class SomeMigration < ActiveRecord::Migration[7.1]
351
+ # class SomeMigration < ActiveRecord::Migration[7.2]
352
352
  # def up
353
353
  # create_table :foo do |t|
354
354
  # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
@@ -293,6 +293,11 @@ module ActiveRecord
293
293
  def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options, &block)
294
294
  validate_create_table_options!(options)
295
295
  validate_table_length!(table_name) unless options[:_uses_legacy_table_name]
296
+
297
+ if force && options.key?(:if_not_exists)
298
+ raise ArgumentError, "Options `:force` and `:if_not_exists` cannot be used simultaneously."
299
+ end
300
+
296
301
  td = build_create_table_definition(table_name, id: id, primary_key: primary_key, force: force, **options, &block)
297
302
 
298
303
  if force
@@ -876,9 +881,12 @@ module ActiveRecord
876
881
  # ====== Creating an index with a specific algorithm
877
882
  #
878
883
  # add_index(:developers, :name, algorithm: :concurrently)
879
- # # CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
884
+ # # CREATE INDEX CONCURRENTLY developers_on_name on developers (name) -- PostgreSQL
880
885
  #
881
- # Note: only supported by PostgreSQL.
886
+ # add_index(:developers, :name, algorithm: :inplace)
887
+ # # CREATE INDEX `index_developers_on_name` ON `developers` (`name`) ALGORITHM = INPLACE -- MySQL
888
+ #
889
+ # Note: only supported by PostgreSQL and MySQL.
882
890
  #
883
891
  # Concurrently adding an index is not supported in a transaction.
884
892
  #
@@ -1318,7 +1326,7 @@ module ActiveRecord
1318
1326
  end
1319
1327
 
1320
1328
  def dump_schema_information # :nodoc:
1321
- versions = schema_migration.versions
1329
+ versions = pool.schema_migration.versions
1322
1330
  insert_versions_sql(versions) if versions.any?
1323
1331
  end
1324
1332
 
@@ -1328,8 +1336,9 @@ module ActiveRecord
1328
1336
 
1329
1337
  def assume_migrated_upto_version(version)
1330
1338
  version = version.to_i
1331
- sm_table = quote_table_name(schema_migration.table_name)
1339
+ sm_table = quote_table_name(pool.schema_migration.table_name)
1332
1340
 
1341
+ migration_context = pool.migration_context
1333
1342
  migrated = migration_context.get_all_versions
1334
1343
  versions = migration_context.migrations.map(&:version)
1335
1344
 
@@ -1839,7 +1848,7 @@ module ActiveRecord
1839
1848
  end
1840
1849
 
1841
1850
  def insert_versions_sql(versions)
1842
- sm_table = quote_table_name(schema_migration.table_name)
1851
+ sm_table = quote_table_name(pool.schema_migration.table_name)
1843
1852
 
1844
1853
  if versions.is_a?(Array)
1845
1854
  sql = +"INSERT INTO #{sm_table} (version) VALUES\n"