activerecord 7.1.3.4 → 7.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +507 -2133
  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 +18 -11
  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 +4 -2
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +3 -3
  17. data/lib/active_record/associations/has_one_association.rb +2 -2
  18. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  19. data/lib/active_record/associations/join_dependency.rb +5 -7
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +34 -11
  27. data/lib/active_record/attribute_assignment.rb +1 -11
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  30. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  31. data/lib/active_record/attribute_methods/read.rb +1 -13
  32. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  33. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  34. data/lib/active_record/attribute_methods.rb +87 -58
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +14 -30
  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 -58
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +160 -75
  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 +22 -9
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -61
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
  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 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  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 +6 -0
  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 +16 -12
  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 +109 -77
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  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 +59 -38
  77. data/lib/active_record/counter_cache.rb +23 -10
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  79. data/lib/active_record/database_configurations/database_config.rb +15 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  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 +30 -6
  84. data/lib/active_record/destroy_association_async_job.rb +1 -1
  85. data/lib/active_record/dynamic_matchers.rb +2 -2
  86. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  87. data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
  88. data/lib/active_record/encryption/encryptor.rb +17 -2
  89. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  90. data/lib/active_record/encryption/message_serializer.rb +4 -0
  91. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  93. data/lib/active_record/encryption/scheme.rb +8 -4
  94. data/lib/active_record/enum.rb +11 -2
  95. data/lib/active_record/errors.rb +16 -11
  96. data/lib/active_record/explain.rb +13 -24
  97. data/lib/active_record/fixtures.rb +37 -31
  98. data/lib/active_record/future_result.rb +17 -4
  99. data/lib/active_record/gem_version.rb +3 -3
  100. data/lib/active_record/inheritance.rb +4 -2
  101. data/lib/active_record/insert_all.rb +18 -15
  102. data/lib/active_record/integration.rb +4 -1
  103. data/lib/active_record/internal_metadata.rb +48 -34
  104. data/lib/active_record/locking/optimistic.rb +8 -7
  105. data/lib/active_record/log_subscriber.rb +0 -21
  106. data/lib/active_record/marshalling.rb +1 -1
  107. data/lib/active_record/message_pack.rb +2 -2
  108. data/lib/active_record/migration/command_recorder.rb +2 -3
  109. data/lib/active_record/migration/compatibility.rb +11 -3
  110. data/lib/active_record/migration/default_strategy.rb +4 -5
  111. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  112. data/lib/active_record/migration.rb +85 -76
  113. data/lib/active_record/model_schema.rb +34 -69
  114. data/lib/active_record/nested_attributes.rb +11 -3
  115. data/lib/active_record/normalization.rb +3 -7
  116. data/lib/active_record/persistence.rb +32 -354
  117. data/lib/active_record/query_cache.rb +18 -6
  118. data/lib/active_record/query_logs.rb +15 -0
  119. data/lib/active_record/query_logs_formatter.rb +1 -1
  120. data/lib/active_record/querying.rb +21 -9
  121. data/lib/active_record/railtie.rb +52 -64
  122. data/lib/active_record/railties/controller_runtime.rb +13 -4
  123. data/lib/active_record/railties/databases.rake +41 -44
  124. data/lib/active_record/reflection.rb +98 -37
  125. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  126. data/lib/active_record/relation/batches.rb +3 -3
  127. data/lib/active_record/relation/calculations.rb +94 -61
  128. data/lib/active_record/relation/delegation.rb +8 -11
  129. data/lib/active_record/relation/finder_methods.rb +16 -2
  130. data/lib/active_record/relation/merger.rb +4 -6
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  132. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  133. data/lib/active_record/relation/predicate_builder.rb +3 -3
  134. data/lib/active_record/relation/query_methods.rb +196 -43
  135. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  136. data/lib/active_record/relation/spawn_methods.rb +2 -18
  137. data/lib/active_record/relation/where_clause.rb +7 -19
  138. data/lib/active_record/relation.rb +500 -66
  139. data/lib/active_record/result.rb +32 -45
  140. data/lib/active_record/runtime_registry.rb +39 -0
  141. data/lib/active_record/sanitization.rb +24 -19
  142. data/lib/active_record/schema.rb +8 -6
  143. data/lib/active_record/schema_dumper.rb +19 -9
  144. data/lib/active_record/schema_migration.rb +30 -14
  145. data/lib/active_record/signed_id.rb +11 -1
  146. data/lib/active_record/statement_cache.rb +7 -7
  147. data/lib/active_record/table_metadata.rb +1 -10
  148. data/lib/active_record/tasks/database_tasks.rb +70 -42
  149. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  152. data/lib/active_record/test_fixtures.rb +82 -91
  153. data/lib/active_record/testing/query_assertions.rb +121 -0
  154. data/lib/active_record/timestamp.rb +4 -2
  155. data/lib/active_record/token_for.rb +22 -12
  156. data/lib/active_record/touch_later.rb +1 -1
  157. data/lib/active_record/transaction.rb +68 -0
  158. data/lib/active_record/transactions.rb +43 -14
  159. data/lib/active_record/translation.rb +0 -2
  160. data/lib/active_record/type/serialized.rb +1 -3
  161. data/lib/active_record/type_caster/connection.rb +4 -4
  162. data/lib/active_record/validations/associated.rb +9 -3
  163. data/lib/active_record/validations/uniqueness.rb +14 -10
  164. data/lib/active_record/validations.rb +4 -1
  165. data/lib/active_record.rb +149 -40
  166. data/lib/arel/alias_predication.rb +1 -1
  167. data/lib/arel/collectors/bind.rb +2 -0
  168. data/lib/arel/collectors/composite.rb +7 -0
  169. data/lib/arel/collectors/sql_string.rb +1 -1
  170. data/lib/arel/collectors/substitute_binds.rb +1 -1
  171. data/lib/arel/nodes/binary.rb +0 -6
  172. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  173. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  174. data/lib/arel/nodes/node.rb +4 -3
  175. data/lib/arel/nodes/sql_literal.rb +7 -0
  176. data/lib/arel/nodes.rb +2 -2
  177. data/lib/arel/predications.rb +1 -1
  178. data/lib/arel/select_manager.rb +1 -1
  179. data/lib/arel/tree_manager.rb +8 -3
  180. data/lib/arel/update_manager.rb +2 -1
  181. data/lib/arel/visitors/dot.rb +1 -0
  182. data/lib/arel/visitors/mysql.rb +9 -4
  183. data/lib/arel/visitors/postgresql.rb +1 -12
  184. data/lib/arel/visitors/to_sql.rb +31 -17
  185. data/lib/arel.rb +7 -3
  186. metadata +17 -12
@@ -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)
110
- FutureResult::Complete.new(result)
216
+ result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
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
  #
@@ -963,7 +971,11 @@ module ActiveRecord
963
971
  def index_name(table_name, options) # :nodoc:
964
972
  if Hash === options
965
973
  if options[:column]
966
- generate_index_name(table_name, options[:column])
974
+ if options[:_uses_legacy_index_name]
975
+ "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
976
+ else
977
+ generate_index_name(table_name, options[:column])
978
+ end
967
979
  elsif options[:name]
968
980
  options[:name]
969
981
  else
@@ -1314,7 +1326,7 @@ module ActiveRecord
1314
1326
  end
1315
1327
 
1316
1328
  def dump_schema_information # :nodoc:
1317
- versions = schema_migration.versions
1329
+ versions = pool.schema_migration.versions
1318
1330
  insert_versions_sql(versions) if versions.any?
1319
1331
  end
1320
1332
 
@@ -1324,8 +1336,9 @@ module ActiveRecord
1324
1336
 
1325
1337
  def assume_migrated_upto_version(version)
1326
1338
  version = version.to_i
1327
- sm_table = quote_table_name(schema_migration.table_name)
1339
+ sm_table = quote_table_name(pool.schema_migration.table_name)
1328
1340
 
1341
+ migration_context = pool.migration_context
1329
1342
  migrated = migration_context.get_all_versions
1330
1343
  versions = migration_context.migrations.map(&:version)
1331
1344
 
@@ -1636,11 +1649,11 @@ module ActiveRecord
1636
1649
  end
1637
1650
  end
1638
1651
 
1639
- def rename_table_indexes(table_name, new_name)
1652
+ def rename_table_indexes(table_name, new_name, **options)
1640
1653
  indexes(new_name).each do |index|
1641
- generated_index_name = index_name(table_name, column: index.columns)
1654
+ generated_index_name = index_name(table_name, column: index.columns, **options)
1642
1655
  if generated_index_name == index.name
1643
- rename_index new_name, generated_index_name, index_name(new_name, column: index.columns)
1656
+ rename_index new_name, generated_index_name, index_name(new_name, column: index.columns, **options)
1644
1657
  end
1645
1658
  end
1646
1659
  end
@@ -1835,7 +1848,7 @@ module ActiveRecord
1835
1848
  end
1836
1849
 
1837
1850
  def insert_versions_sql(versions)
1838
- sm_table = quote_table_name(schema_migration.table_name)
1851
+ sm_table = quote_table_name(pool.schema_migration.table_name)
1839
1852
 
1840
1853
  if versions.is_a?(Array)
1841
1854
  sql = +"INSERT INTO #{sm_table} (version) VALUES\n"