activerecord 7.0.8.7 → 7.1.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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1339 -1572
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +15 -16
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +17 -9
  15. data/lib/active_record/associations/collection_proxy.rb +16 -11
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader.rb +12 -9
  23. data/lib/active_record/associations/singular_association.rb +1 -1
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +193 -97
  26. data/lib/active_record/attribute_assignment.rb +0 -2
  27. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +40 -26
  29. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  30. data/lib/active_record/attribute_methods/query.rb +28 -16
  31. data/lib/active_record/attribute_methods/read.rb +18 -5
  32. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +105 -21
  35. data/lib/active_record/attributes.rb +3 -3
  36. data/lib/active_record/autosave_association.rb +55 -9
  37. data/lib/active_record/base.rb +7 -2
  38. data/lib/active_record/callbacks.rb +10 -24
  39. data/lib/active_record/coders/column_serializer.rb +61 -0
  40. data/lib/active_record/coders/json.rb +1 -1
  41. data/lib/active_record/coders/yaml_column.rb +70 -42
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  45. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +109 -32
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  49. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  50. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  51. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  52. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  53. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -122
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +280 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +502 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +200 -108
  57. data/lib/active_record/connection_adapters/column.rb +9 -0
  58. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  59. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  61. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  62. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  63. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -12
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  67. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  68. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  69. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -29
  71. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  72. data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -6
  73. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  74. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  75. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  76. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +42 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +351 -54
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -168
  79. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  80. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  81. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +42 -36
  82. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  83. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  84. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  85. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +162 -77
  86. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  87. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  88. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  89. data/lib/active_record/connection_adapters.rb +3 -1
  90. data/lib/active_record/connection_handling.rb +71 -94
  91. data/lib/active_record/core.rb +128 -138
  92. data/lib/active_record/counter_cache.rb +46 -25
  93. data/lib/active_record/database_configurations/database_config.rb +9 -3
  94. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  95. data/lib/active_record/database_configurations/url_config.rb +17 -11
  96. data/lib/active_record/database_configurations.rb +86 -33
  97. data/lib/active_record/delegated_type.rb +8 -3
  98. data/lib/active_record/deprecator.rb +7 -0
  99. data/lib/active_record/destroy_association_async_job.rb +2 -0
  100. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  101. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  102. data/lib/active_record/encryption/config.rb +25 -1
  103. data/lib/active_record/encryption/configurable.rb +12 -19
  104. data/lib/active_record/encryption/context.rb +10 -3
  105. data/lib/active_record/encryption/contexts.rb +5 -1
  106. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  107. data/lib/active_record/encryption/encryptable_record.rb +36 -18
  108. data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
  109. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
  110. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +2 -2
  111. data/lib/active_record/encryption/key_generator.rb +12 -1
  112. data/lib/active_record/encryption/message_serializer.rb +2 -0
  113. data/lib/active_record/encryption/properties.rb +3 -3
  114. data/lib/active_record/encryption/scheme.rb +19 -22
  115. data/lib/active_record/encryption.rb +1 -0
  116. data/lib/active_record/enum.rb +113 -26
  117. data/lib/active_record/errors.rb +89 -15
  118. data/lib/active_record/explain.rb +23 -3
  119. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  120. data/lib/active_record/fixture_set/render_context.rb +2 -0
  121. data/lib/active_record/fixture_set/table_row.rb +29 -8
  122. data/lib/active_record/fixtures.rb +119 -71
  123. data/lib/active_record/future_result.rb +30 -5
  124. data/lib/active_record/gem_version.rb +4 -4
  125. data/lib/active_record/inheritance.rb +30 -16
  126. data/lib/active_record/insert_all.rb +55 -8
  127. data/lib/active_record/integration.rb +8 -8
  128. data/lib/active_record/internal_metadata.rb +118 -30
  129. data/lib/active_record/locking/pessimistic.rb +5 -2
  130. data/lib/active_record/log_subscriber.rb +29 -12
  131. data/lib/active_record/marshalling.rb +56 -0
  132. data/lib/active_record/message_pack.rb +124 -0
  133. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  134. data/lib/active_record/middleware/database_selector.rb +5 -7
  135. data/lib/active_record/middleware/shard_selector.rb +3 -1
  136. data/lib/active_record/migration/command_recorder.rb +100 -4
  137. data/lib/active_record/migration/compatibility.rb +131 -5
  138. data/lib/active_record/migration/default_strategy.rb +23 -0
  139. data/lib/active_record/migration/execution_strategy.rb +19 -0
  140. data/lib/active_record/migration.rb +213 -109
  141. data/lib/active_record/model_schema.rb +47 -27
  142. data/lib/active_record/nested_attributes.rb +28 -3
  143. data/lib/active_record/normalization.rb +158 -0
  144. data/lib/active_record/persistence.rb +183 -33
  145. data/lib/active_record/promise.rb +84 -0
  146. data/lib/active_record/query_cache.rb +3 -21
  147. data/lib/active_record/query_logs.rb +77 -52
  148. data/lib/active_record/query_logs_formatter.rb +41 -0
  149. data/lib/active_record/querying.rb +15 -2
  150. data/lib/active_record/railtie.rb +107 -45
  151. data/lib/active_record/railties/controller_runtime.rb +10 -5
  152. data/lib/active_record/railties/databases.rake +139 -145
  153. data/lib/active_record/railties/job_runtime.rb +23 -0
  154. data/lib/active_record/readonly_attributes.rb +32 -5
  155. data/lib/active_record/reflection.rb +169 -45
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  157. data/lib/active_record/relation/batches.rb +190 -61
  158. data/lib/active_record/relation/calculations.rb +152 -63
  159. data/lib/active_record/relation/delegation.rb +22 -8
  160. data/lib/active_record/relation/finder_methods.rb +85 -15
  161. data/lib/active_record/relation/merger.rb +2 -0
  162. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  164. data/lib/active_record/relation/predicate_builder.rb +26 -14
  165. data/lib/active_record/relation/query_attribute.rb +2 -1
  166. data/lib/active_record/relation/query_methods.rb +351 -62
  167. data/lib/active_record/relation/spawn_methods.rb +18 -1
  168. data/lib/active_record/relation.rb +76 -35
  169. data/lib/active_record/result.rb +19 -5
  170. data/lib/active_record/runtime_registry.rb +10 -1
  171. data/lib/active_record/sanitization.rb +51 -11
  172. data/lib/active_record/schema.rb +2 -3
  173. data/lib/active_record/schema_dumper.rb +41 -7
  174. data/lib/active_record/schema_migration.rb +68 -33
  175. data/lib/active_record/scoping/default.rb +15 -5
  176. data/lib/active_record/scoping/named.rb +2 -2
  177. data/lib/active_record/scoping.rb +2 -1
  178. data/lib/active_record/secure_password.rb +60 -0
  179. data/lib/active_record/secure_token.rb +21 -3
  180. data/lib/active_record/signed_id.rb +7 -5
  181. data/lib/active_record/store.rb +8 -8
  182. data/lib/active_record/suppressor.rb +3 -1
  183. data/lib/active_record/table_metadata.rb +10 -1
  184. data/lib/active_record/tasks/database_tasks.rb +127 -105
  185. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  186. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  187. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -7
  188. data/lib/active_record/test_fixtures.rb +113 -96
  189. data/lib/active_record/timestamp.rb +26 -14
  190. data/lib/active_record/token_for.rb +113 -0
  191. data/lib/active_record/touch_later.rb +11 -6
  192. data/lib/active_record/transactions.rb +36 -10
  193. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  194. data/lib/active_record/type/internal/timezone.rb +7 -2
  195. data/lib/active_record/type/time.rb +4 -0
  196. data/lib/active_record/validations/absence.rb +1 -1
  197. data/lib/active_record/validations/numericality.rb +5 -4
  198. data/lib/active_record/validations/presence.rb +5 -28
  199. data/lib/active_record/validations/uniqueness.rb +47 -2
  200. data/lib/active_record/validations.rb +8 -4
  201. data/lib/active_record/version.rb +1 -1
  202. data/lib/active_record.rb +121 -16
  203. data/lib/arel/errors.rb +10 -0
  204. data/lib/arel/factory_methods.rb +4 -0
  205. data/lib/arel/nodes/binary.rb +6 -1
  206. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  207. data/lib/arel/nodes/cte.rb +36 -0
  208. data/lib/arel/nodes/fragments.rb +35 -0
  209. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  210. data/lib/arel/nodes/leading_join.rb +8 -0
  211. data/lib/arel/nodes/node.rb +111 -2
  212. data/lib/arel/nodes/sql_literal.rb +6 -0
  213. data/lib/arel/nodes/table_alias.rb +4 -0
  214. data/lib/arel/nodes.rb +4 -0
  215. data/lib/arel/predications.rb +2 -0
  216. data/lib/arel/table.rb +9 -5
  217. data/lib/arel/visitors/mysql.rb +8 -1
  218. data/lib/arel/visitors/to_sql.rb +81 -17
  219. data/lib/arel/visitors/visitor.rb +2 -2
  220. data/lib/arel.rb +16 -2
  221. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  222. data/lib/rails/generators/active_record/migration.rb +3 -1
  223. data/lib/rails/generators/active_record/model/USAGE +113 -0
  224. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  225. metadata +52 -17
  226. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  227. data/lib/active_record/null_relation.rb +0 -63
@@ -6,7 +6,6 @@ require "active_record/relation/merger"
6
6
 
7
7
  module ActiveRecord
8
8
  module SpawnMethods
9
- # This is overridden by Associations::CollectionProxy
10
9
  def spawn # :nodoc:
11
10
  already_in_scope?(klass.scope_registry) ? klass.all : clone
12
11
  end
@@ -28,6 +27,9 @@ module ActiveRecord
28
27
  # # => Post.where(published: true).joins(:comments)
29
28
  #
30
29
  # This is mainly intended for sharing common conditions between multiple associations.
30
+ #
31
+ # For conditions that exist in both relations, those from <tt>other</tt> will take precedence.
32
+ # To find the intersection of two relations, use QueryMethods#and.
31
33
  def merge(other, *rest)
32
34
  if other.is_a?(Array)
33
35
  records & other
@@ -40,6 +42,21 @@ module ActiveRecord
40
42
 
41
43
  def merge!(other, *rest) # :nodoc:
42
44
  options = rest.extract_options!
45
+
46
+ if options.key?(:rewhere)
47
+ if options[:rewhere]
48
+ ActiveRecord.deprecator.warn(<<-MSG.squish)
49
+ Specifying `Relation#merge(rewhere: true)` is deprecated, as that has now been
50
+ the default since Rails 7.0. Setting the rewhere option will error in Rails 7.2
51
+ MSG
52
+ else
53
+ ActiveRecord.deprecator.warn(<<-MSG.squish)
54
+ `Relation#merge(rewhere: false)` is deprecated without replacement,
55
+ and will be removed in Rails 7.2
56
+ MSG
57
+ end
58
+ end
59
+
43
60
  if other.is_a?(Hash)
44
61
  Relation::HashMerger.new(self, other, options[:rewhere]).merge
45
62
  elsif other.is_a?(Relation)
@@ -5,13 +5,14 @@ module ActiveRecord
5
5
  class Relation
6
6
  MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
7
7
  :order, :joins, :left_outer_joins, :references,
8
- :extending, :unscope, :optimizer_hints, :annotate]
8
+ :extending, :unscope, :optimizer_hints, :annotate,
9
+ :with]
9
10
 
10
11
  SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, :strict_loading,
11
12
  :reverse_order, :distinct, :create_with, :skip_query_cache]
12
13
 
13
14
  CLAUSE_METHODS = [:where, :having, :from]
14
- INVALID_METHODS_FOR_DELETE_ALL = [:distinct]
15
+ INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :with]
15
16
 
16
17
  VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
17
18
 
@@ -33,6 +34,8 @@ module ActiveRecord
33
34
  @delegate_to_klass = false
34
35
  @future_result = nil
35
36
  @records = nil
37
+ @async = false
38
+ @none = false
36
39
  end
37
40
 
38
41
  def initialize_copy(other)
@@ -43,7 +46,7 @@ module ActiveRecord
43
46
  def bind_attribute(name, value) # :nodoc:
44
47
  if reflection = klass._reflect_on_association(name)
45
48
  name = reflection.foreign_key
46
- value = value.read_attribute(reflection.klass.primary_key) unless value.nil?
49
+ value = value.read_attribute(reflection.association_primary_key) unless value.nil?
47
50
  end
48
51
 
49
52
  attr = table[name]
@@ -159,21 +162,25 @@ module ActiveRecord
159
162
  # failed due to validation errors it won't be persisted, you get what
160
163
  # #create returns in such situation.
161
164
  #
162
- # Please note <b>this method is not atomic</b>, it runs first a SELECT, and if
163
- # there are no results an INSERT is attempted. If there are other threads
164
- # or processes there is a race condition between both calls and it could
165
- # be the case that you end up with two similar records.
165
+ # If creation failed because of a unique constraint, this method will
166
+ # assume it encountered a race condition and will try finding the record
167
+ # once more If somehow the second find still find no record because a
168
+ # concurrent DELETE happened, it will then raise an
169
+ # ActiveRecord::RecordNotFound exception.
166
170
  #
167
- # If this might be a problem for your application, please see #create_or_find_by.
171
+ # Please note <b>this method is not atomic</b>, it runs first a SELECT,
172
+ # and if there are no results an INSERT is attempted. So if the table
173
+ # doesn't have a relevant unique constraint it could be the case that
174
+ # you end up with two or more similar records.
168
175
  def find_or_create_by(attributes, &block)
169
- find_by(attributes) || create(attributes, &block)
176
+ find_by(attributes) || create_or_find_by(attributes, &block)
170
177
  end
171
178
 
172
179
  # Like #find_or_create_by, but calls
173
180
  # {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception
174
181
  # is raised if the created record is invalid.
175
182
  def find_or_create_by!(attributes, &block)
176
- find_by(attributes) || create!(attributes, &block)
183
+ find_by(attributes) || create_or_find_by!(attributes, &block)
177
184
  end
178
185
 
179
186
  # Attempts to create a record with the given attributes in a table that has a unique database constraint
@@ -181,16 +188,15 @@ module ActiveRecord
181
188
  # unique constraints, the exception such an insertion would normally raise is caught,
182
189
  # and the existing record with those attributes is found using #find_by!.
183
190
  #
184
- # This is similar to #find_or_create_by, but avoids the problem of stale reads between the SELECT
185
- # and the INSERT, as that method needs to first query the table, then attempt to insert a row
186
- # if none is found.
191
+ # This is similar to #find_or_create_by, but tries to create the record first. As such it is
192
+ # better suited for cases where the record is most likely not to exist yet.
187
193
  #
188
194
  # There are several drawbacks to #create_or_find_by, though:
189
195
  #
190
196
  # * The underlying table must have the relevant columns defined with unique database constraints.
191
197
  # * A unique constraint violation may be triggered by only one, or at least less than all,
192
198
  # of the given attributes. This means that the subsequent #find_by! may fail to find a
193
- # matching record, which will then raise an <tt>ActiveRecord::RecordNotFound</tt> exception,
199
+ # matching record, which will then raise an ActiveRecord::RecordNotFound exception,
194
200
  # rather than a record with the given attributes.
195
201
  # * While we avoid the race condition between SELECT -> INSERT from #find_or_create_by,
196
202
  # we actually have another race condition between INSERT -> SELECT, which can be triggered
@@ -199,7 +205,7 @@ module ActiveRecord
199
205
  # * It relies on exception handling to handle control flow, which may be marginally slower.
200
206
  # * The primary key may auto-increment on each create, even if it fails. This can accelerate
201
207
  # the problem of running out of integers, if the underlying table is still stuck on a primary
202
- # key of type int (note: All Rails apps since 5.1+ have defaulted to bigint, which is not liable
208
+ # key of type int (note: All \Rails apps since 5.1+ have defaulted to bigint, which is not liable
203
209
  # to this problem).
204
210
  #
205
211
  # This method will return a record if all given attributes are covered by unique constraints
@@ -209,7 +215,11 @@ module ActiveRecord
209
215
  def create_or_find_by(attributes, &block)
210
216
  transaction(requires_new: true) { create(attributes, &block) }
211
217
  rescue ActiveRecord::RecordNotUnique
212
- find_by!(attributes)
218
+ if connection.transaction_open?
219
+ where(attributes).lock.find_by!(attributes)
220
+ else
221
+ find_by!(attributes)
222
+ end
213
223
  end
214
224
 
215
225
  # Like #create_or_find_by, but calls
@@ -218,7 +228,11 @@ module ActiveRecord
218
228
  def create_or_find_by!(attributes, &block)
219
229
  transaction(requires_new: true) { create!(attributes, &block) }
220
230
  rescue ActiveRecord::RecordNotUnique
221
- find_by!(attributes)
231
+ if connection.transaction_open?
232
+ where(attributes).lock.find_by!(attributes)
233
+ else
234
+ find_by!(attributes)
235
+ end
222
236
  end
223
237
 
224
238
  # Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
@@ -236,8 +250,8 @@ module ActiveRecord
236
250
  #
237
251
  # Please see further details in the
238
252
  # {Active Record Query Interface guide}[https://guides.rubyonrails.org/active_record_querying.html#running-explain].
239
- def explain
240
- exec_explain(collecting_queries_for_explain { exec_queries })
253
+ def explain(*options)
254
+ exec_explain(collecting_queries_for_explain { exec_queries }, options)
241
255
  end
242
256
 
243
257
  # Converts relation objects to Array.
@@ -267,6 +281,8 @@ module ActiveRecord
267
281
 
268
282
  # Returns true if there are no records.
269
283
  def empty?
284
+ return true if @none
285
+
270
286
  if loaded?
271
287
  records.empty?
272
288
  else
@@ -275,26 +291,34 @@ module ActiveRecord
275
291
  end
276
292
 
277
293
  # Returns true if there are no records.
278
- def none?
279
- return super if block_given?
294
+ def none?(*args)
295
+ return true if @none
296
+
297
+ return super if args.present? || block_given?
280
298
  empty?
281
299
  end
282
300
 
283
301
  # Returns true if there are any records.
284
- def any?
285
- return super if block_given?
302
+ def any?(*args)
303
+ return false if @none
304
+
305
+ return super if args.present? || block_given?
286
306
  !empty?
287
307
  end
288
308
 
289
309
  # Returns true if there is exactly one record.
290
- def one?
291
- return super if block_given?
310
+ def one?(*args)
311
+ return false if @none
312
+
313
+ return super if args.present? || block_given?
292
314
  return records.one? if loaded?
293
315
  limited_count == 1
294
316
  end
295
317
 
296
318
  # Returns true if there is more than one record.
297
319
  def many?
320
+ return false if @none
321
+
298
322
  return super if block_given?
299
323
  return records.many? if loaded?
300
324
  limited_count > 1
@@ -307,7 +331,7 @@ module ActiveRecord
307
331
  # # => "products/query-1850ab3d302391b85b8693e941286659"
308
332
  #
309
333
  # If ActiveRecord::Base.collection_cache_versioning is turned off, as it was
310
- # in Rails 6.0 and earlier, the cache key will also include a version.
334
+ # in \Rails 6.0 and earlier, the cache key will also include a version.
311
335
  #
312
336
  # ActiveRecord::Base.collection_cache_versioning = false
313
337
  # Product.where("name like ?", "%Cosmic Encounter%").cache_key
@@ -409,7 +433,7 @@ module ActiveRecord
409
433
  # Comment.where(post_id: 1).scoping do
410
434
  # Comment.first
411
435
  # end
412
- # # => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
436
+ # # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
413
437
  #
414
438
  # If <tt>all_queries: true</tt> is passed, scoping will apply to all queries
415
439
  # for the relation including +update+ and +delete+ on instances.
@@ -446,7 +470,8 @@ module ActiveRecord
446
470
  #
447
471
  # ==== Parameters
448
472
  #
449
- # * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
473
+ # * +updates+ - A string, array, or hash representing the SET part of an SQL statement. Any strings provided will
474
+ # be type cast, unless you use +Arel.sql+. (Don't pass user-provided values to +Arel.sql+.)
450
475
  #
451
476
  # ==== Examples
452
477
  #
@@ -461,9 +486,14 @@ module ActiveRecord
461
486
  #
462
487
  # # Update all invoices and set the number column to its id value.
463
488
  # Invoice.update_all('number = id')
489
+ #
490
+ # # Update all books with 'Rails' in their title
491
+ # Book.where('title LIKE ?', '%Rails%').update_all(title: Arel.sql("title + ' - volume 1'"))
464
492
  def update_all(updates)
465
493
  raise ArgumentError, "Empty list of attributes to change" if updates.blank?
466
494
 
495
+ return 0 if @none
496
+
467
497
  if updates.is_a?(Hash)
468
498
  if klass.locking_enabled? &&
469
499
  !updates.key?(klass.locking_column) &&
@@ -599,6 +629,8 @@ module ActiveRecord
599
629
  # Post.distinct.delete_all
600
630
  # # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
601
631
  def delete_all
632
+ return 0 if @none
633
+
602
634
  invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
603
635
  value = @values[method]
604
636
  method == :distinct ? value : value&.any?
@@ -720,7 +752,7 @@ module ActiveRecord
720
752
  # Returns sql statement for the relation.
721
753
  #
722
754
  # User.where(name: 'Oscar').to_sql
723
- # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
755
+ # # SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
724
756
  def to_sql
725
757
  @to_sql ||= if eager_loading?
726
758
  apply_join_dependency do |relation, join_dependency|
@@ -774,8 +806,13 @@ module ActiveRecord
774
806
  end
775
807
  end
776
808
 
777
- def pretty_print(q)
778
- q.pp(records)
809
+ def pretty_print(pp)
810
+ subject = loaded? ? records : annotate("loading for pp")
811
+ entries = subject.take([limit_value, 11].compact.min)
812
+
813
+ entries[10] = "..." if entries.size == 11
814
+
815
+ pp.pp(entries)
779
816
  end
780
817
 
781
818
  # Returns true if relation is blank.
@@ -837,10 +874,6 @@ module ActiveRecord
837
874
  @loaded = true
838
875
  end
839
876
 
840
- def null_relation? # :nodoc:
841
- is_a?(NullRelation)
842
- end
843
-
844
877
  private
845
878
  def already_in_scope?(registry)
846
879
  @delegate_to_klass && registry.current_scope(klass, true)
@@ -925,6 +958,14 @@ module ActiveRecord
925
958
  end
926
959
 
927
960
  def exec_main_query(async: false)
961
+ if @none
962
+ if async
963
+ return Promise::Complete.new([])
964
+ else
965
+ return []
966
+ end
967
+ end
968
+
928
969
  skip_query_cache_if_necessary do
929
970
  if where_clause.contradiction?
930
971
  [].freeze
@@ -2,6 +2,8 @@
2
2
 
3
3
  module ActiveRecord
4
4
  ###
5
+ # = Active Record \Result
6
+ #
5
7
  # This class encapsulates a result returned from calling
6
8
  # {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query]
7
9
  # on any database connection adapter. For example:
@@ -36,8 +38,12 @@ module ActiveRecord
36
38
 
37
39
  attr_reader :columns, :rows, :column_types
38
40
 
39
- def self.empty # :nodoc:
40
- EMPTY
41
+ def self.empty(async: false) # :nodoc:
42
+ if async
43
+ EMPTY_ASYNC
44
+ else
45
+ EMPTY
46
+ end
41
47
  end
42
48
 
43
49
  def initialize(columns, rows, column_types = {})
@@ -47,9 +53,6 @@ module ActiveRecord
47
53
  @column_types = column_types
48
54
  end
49
55
 
50
- EMPTY = new([].freeze, [].freeze, {}.freeze)
51
- private_constant :EMPTY
52
-
53
56
  # Returns true if this result set includes the column named +name+
54
57
  def includes_column?(name)
55
58
  @columns.include? name
@@ -134,6 +137,11 @@ module ActiveRecord
134
137
  @hash_rows = nil
135
138
  end
136
139
 
140
+ def freeze # :nodoc:
141
+ hash_rows.freeze
142
+ super
143
+ end
144
+
137
145
  private
138
146
  def column_type(name, index, type_overrides)
139
147
  type_overrides.fetch(name) do
@@ -183,5 +191,11 @@ module ActiveRecord
183
191
  }
184
192
  end
185
193
  end
194
+
195
+ EMPTY = new([].freeze, [].freeze, {}.freeze).freeze
196
+ private_constant :EMPTY
197
+
198
+ EMPTY_ASYNC = FutureResult::Complete.new(EMPTY).freeze
199
+ private_constant :EMPTY_ASYNC
186
200
  end
187
201
  end
@@ -10,11 +10,20 @@ module ActiveRecord
10
10
  extend self
11
11
 
12
12
  def sql_runtime
13
- ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime]
13
+ ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] ||= 0.0
14
14
  end
15
15
 
16
16
  def sql_runtime=(runtime)
17
17
  ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] = runtime
18
18
  end
19
+
20
+ def reset
21
+ rt, self.sql_runtime = sql_runtime, 0.0
22
+ rt
23
+ end
19
24
  end
20
25
  end
26
+
27
+ ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload|
28
+ ActiveRecord::RuntimeRegistry.sql_runtime += (finish - start) * 1_000.0
29
+ end
@@ -5,8 +5,8 @@ module ActiveRecord
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  module ClassMethods
8
- # Accepts an array or string of SQL conditions and sanitizes
9
- # them into a valid SQL fragment for a WHERE clause.
8
+ # Accepts an array of SQL conditions and sanitizes them into a valid
9
+ # SQL fragment for a WHERE clause.
10
10
  #
11
11
  # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
12
12
  # # => "name='foo''bar' and group_id=4"
@@ -17,8 +17,19 @@ module ActiveRecord
17
17
  # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
18
18
  # # => "name='foo''bar' and group_id='4'"
19
19
  #
20
+ # This method will NOT sanitize a SQL string since it won't contain
21
+ # any conditions in it and will return the string as is.
22
+ #
20
23
  # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
21
24
  # # => "name='foo''bar' and group_id='4'"
25
+ #
26
+ # Note that this sanitization method is not schema-aware, hence won't do any type casting
27
+ # and will directly use the database adapter's +quote+ method.
28
+ # For MySQL specifically this means that numeric parameters will be quoted as strings
29
+ # to prevent query manipulation attacks.
30
+ #
31
+ # sanitize_sql_for_conditions(["role = ?", 0])
32
+ # # => "role = '0'"
22
33
  def sanitize_sql_for_conditions(condition)
23
34
  return nil if condition.blank?
24
35
 
@@ -29,8 +40,8 @@ module ActiveRecord
29
40
  end
30
41
  alias :sanitize_sql :sanitize_sql_for_conditions
31
42
 
32
- # Accepts an array, hash, or string of SQL conditions and sanitizes
33
- # them into a valid SQL fragment for a SET clause.
43
+ # Accepts an array or hash of SQL conditions and sanitizes them into
44
+ # a valid SQL fragment for a SET clause.
34
45
  #
35
46
  # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
36
47
  # # => "name=NULL and group_id=4"
@@ -41,8 +52,19 @@ module ActiveRecord
41
52
  # Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
42
53
  # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
43
54
  #
55
+ # This method will NOT sanitize a SQL string since it won't contain
56
+ # any conditions in it and will return the string as is.
57
+ #
44
58
  # sanitize_sql_for_assignment("name=NULL and group_id='4'")
45
59
  # # => "name=NULL and group_id='4'"
60
+ #
61
+ # Note that this sanitization method is not schema-aware, hence won't do any type casting
62
+ # and will directly use the database adapter's +quote+ method.
63
+ # For MySQL specifically this means that numeric parameters will be quoted as strings
64
+ # to prevent query manipulation attacks.
65
+ #
66
+ # sanitize_sql_for_assignment(["role = ?", 0])
67
+ # # => "role = '0'"
46
68
  def sanitize_sql_for_assignment(assignments, default_table_name = table_name)
47
69
  case assignments
48
70
  when Array; sanitize_sql_array(assignments)
@@ -107,12 +129,17 @@ module ActiveRecord
107
129
  # sanitize_sql_like("snake_cased_string", "!")
108
130
  # # => "snake!_cased!_string"
109
131
  def sanitize_sql_like(string, escape_character = "\\")
110
- pattern = Regexp.union(escape_character, "%", "_")
111
- string.gsub(pattern) { |x| [escape_character, x].join }
132
+ if string.include?(escape_character) && escape_character != "%" && escape_character != "_"
133
+ string = string.gsub(escape_character, '\0\0')
134
+ end
135
+
136
+ string.gsub(/(?=[%_])/, escape_character)
112
137
  end
113
138
 
114
139
  # Accepts an array of conditions. The array has each value
115
- # sanitized and interpolated into the SQL statement.
140
+ # sanitized and interpolated into the SQL statement. If using named bind
141
+ # variables in SQL statements where a colon is required verbatim use a
142
+ # backslash to escape.
116
143
  #
117
144
  # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
118
145
  # # => "name='foo''bar' and group_id=4"
@@ -120,8 +147,19 @@ module ActiveRecord
120
147
  # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
121
148
  # # => "name='foo''bar' and group_id=4"
122
149
  #
150
+ # sanitize_sql_array(["TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12\\:MI\\:SS')", date: "foo"])
151
+ # # => "TO_TIMESTAMP('foo', 'YYYY/MM/DD HH12:MI:SS')"
152
+ #
123
153
  # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
124
154
  # # => "name='foo''bar' and group_id='4'"
155
+ #
156
+ # Note that this sanitization method is not schema-aware, hence won't do any type casting
157
+ # and will directly use the database adapter's +quote+ method.
158
+ # For MySQL specifically this means that numeric parameters will be quoted as strings
159
+ # to prevent query manipulation attacks.
160
+ #
161
+ # sanitize_sql_array(["role = ?", 0])
162
+ # # => "role = '0'"
125
163
  def sanitize_sql_array(ary)
126
164
  statement, *values = ary
127
165
  if values.first.is_a?(Hash) && /:\w+/.match?(statement)
@@ -173,9 +211,11 @@ module ActiveRecord
173
211
  end
174
212
 
175
213
  def replace_named_bind_variables(statement, bind_vars)
176
- statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
214
+ statement.gsub(/([:\\]?):([a-zA-Z]\w*)/) do |match|
177
215
  if $1 == ":" # skip postgresql casts
178
216
  match # return the whole match
217
+ elsif $1 == "\\" # escaped literal colon
218
+ match[1..-1] # return match with escaping backlash char removed
179
219
  elsif bind_vars.include?(match = $2.to_sym)
180
220
  replace_bind_variable(bind_vars[match])
181
221
  else
@@ -188,13 +228,13 @@ module ActiveRecord
188
228
  if value.respond_to?(:map) && !value.acts_like?(:string)
189
229
  values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
190
230
  if values.empty?
191
- c.quote_bound_value(nil)
231
+ c.quote(c.cast_bound_value(nil))
192
232
  else
193
- values.map! { |v| c.quote_bound_value(v) }.join(",")
233
+ values.map! { |v| c.quote(c.cast_bound_value(v)) }.join(",")
194
234
  end
195
235
  else
196
236
  value = value.id_for_database if value.respond_to?(:id_for_database)
197
- c.quote_bound_value(value)
237
+ c.quote(c.cast_bound_value(value))
198
238
  end
199
239
  end
200
240
 
@@ -54,13 +54,12 @@ module ActiveRecord
54
54
  def define(info, &block) # :nodoc:
55
55
  instance_eval(&block)
56
56
 
57
+ connection.schema_migration.create_table
57
58
  if info[:version].present?
58
- connection.schema_migration.create_table
59
59
  connection.assume_migrated_upto_version(info[:version])
60
60
  end
61
61
 
62
- ActiveRecord::InternalMetadata.create_table
63
- ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
62
+ connection.internal_metadata.create_table_and_set_flags(connection.migration_context.current_environment)
64
63
  end
65
64
  end
66
65
 
@@ -13,8 +13,7 @@ module ActiveRecord
13
13
  ##
14
14
  # :singleton-method:
15
15
  # A list of tables which should not be dumped to the schema.
16
- # Acceptable values are strings as well as regexp if ActiveRecord.schema_format == :ruby.
17
- # Only strings are accepted if ActiveRecord.schema_format == :sql.
16
+ # Acceptable values are strings and regexps.
18
17
  cattr_accessor :ignore_tables, default: []
19
18
 
20
19
  ##
@@ -29,6 +28,18 @@ module ActiveRecord
29
28
  # should not be dumped to db/schema.rb.
30
29
  cattr_accessor :chk_ignore_pattern, default: /^chk_rails_[0-9a-f]{10}$/
31
30
 
31
+ ##
32
+ # :singleton-method:
33
+ # Specify a custom regular expression matching exclusion constraints which name
34
+ # should not be dumped to db/schema.rb.
35
+ cattr_accessor :excl_ignore_pattern, default: /^excl_rails_[0-9a-f]{10}$/
36
+
37
+ ##
38
+ # :singleton-method:
39
+ # Specify a custom regular expression matching unique constraints which name
40
+ # should not be dumped to db/schema.rb.
41
+ cattr_accessor :unique_ignore_pattern, default: /^uniq_rails_[0-9a-f]{10}$/
42
+
32
43
  class << self
33
44
  def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base)
34
45
  connection.create_schema_dumper(generate_options(config)).dump(stream)
@@ -60,6 +71,11 @@ module ActiveRecord
60
71
  @connection = connection
61
72
  @version = connection.migration_context.current_version rescue nil
62
73
  @options = options
74
+ @ignore_tables = [
75
+ ActiveRecord::Base.schema_migrations_table_name,
76
+ ActiveRecord::Base.internal_metadata_table_name,
77
+ self.class.ignore_tables
78
+ ].flatten
63
79
  end
64
80
 
65
81
  # turns 20170404131909 into "2017_04_04_131909"
@@ -111,7 +127,7 @@ module ActiveRecord
111
127
  end
112
128
 
113
129
  # dump foreign keys at the end to make sure all dependent tables exist.
114
- if @connection.supports_foreign_keys?
130
+ if @connection.use_foreign_keys?
115
131
  sorted_tables.each do |tbl|
116
132
  foreign_keys(tbl, stream) unless ignored?(tbl)
117
133
  end
@@ -171,12 +187,13 @@ module ActiveRecord
171
187
 
172
188
  indexes_in_create(table, tbl)
173
189
  check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
190
+ exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
191
+ unique_keys_in_create(table, tbl) if @connection.supports_unique_keys?
174
192
 
175
193
  tbl.puts " end"
176
194
  tbl.puts
177
195
 
178
- tbl.rewind
179
- stream.print tbl.read
196
+ stream.print tbl.string
180
197
  rescue => e
181
198
  stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
182
199
  stream.puts "# #{e.message}"
@@ -201,6 +218,18 @@ module ActiveRecord
201
218
 
202
219
  def indexes_in_create(table, stream)
203
220
  if (indexes = @connection.indexes(table)).any?
221
+ if @connection.supports_exclusion_constraints? && (exclusion_constraints = @connection.exclusion_constraints(table)).any?
222
+ exclusion_constraint_names = exclusion_constraints.collect(&:name)
223
+
224
+ indexes = indexes.reject { |index| exclusion_constraint_names.include?(index.name) }
225
+ end
226
+
227
+ if @connection.supports_unique_keys? && (unique_keys = @connection.unique_keys(table)).any?
228
+ unique_key_names = unique_keys.collect(&:name)
229
+
230
+ indexes = indexes.reject { |index| unique_key_names.include?(index.name) }
231
+ end
232
+
204
233
  index_statements = indexes.map do |index|
205
234
  " t.index #{index_parts(index).join(', ')}"
206
235
  end
@@ -219,6 +248,8 @@ module ActiveRecord
219
248
  index_parts << "opclass: #{format_index_parts(index.opclasses)}" if index.opclasses.present?
220
249
  index_parts << "where: #{index.where.inspect}" if index.where
221
250
  index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index)
251
+ index_parts << "include: #{index.include.inspect}" if index.include
252
+ index_parts << "nulls_not_distinct: #{index.nulls_not_distinct.inspect}" if index.nulls_not_distinct
222
253
  index_parts << "type: #{index.type.inspect}" if index.type
223
254
  index_parts << "comment: #{index.comment.inspect}" if index.comment
224
255
  index_parts
@@ -235,6 +266,8 @@ module ActiveRecord
235
266
  parts << "name: #{check_constraint.name.inspect}"
236
267
  end
237
268
 
269
+ parts << "validate: #{check_constraint.validate?.inspect}" unless check_constraint.validate?
270
+
238
271
  " #{parts.join(', ')}"
239
272
  end
240
273
 
@@ -250,7 +283,7 @@ module ActiveRecord
250
283
  remove_prefix_and_suffix(foreign_key.to_table).inspect,
251
284
  ]
252
285
 
253
- if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table)
286
+ if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table, "id")
254
287
  parts << "column: #{foreign_key.column.inspect}"
255
288
  end
256
289
 
@@ -265,6 +298,7 @@ module ActiveRecord
265
298
  parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
266
299
  parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
267
300
  parts << "deferrable: #{foreign_key.deferrable.inspect}" if foreign_key.deferrable
301
+ parts << "validate: #{foreign_key.validate?.inspect}" unless foreign_key.validate?
268
302
 
269
303
  " #{parts.join(', ')}"
270
304
  end
@@ -302,7 +336,7 @@ module ActiveRecord
302
336
  end
303
337
 
304
338
  def ignored?(table_name)
305
- [ActiveRecord::Base.schema_migrations_table_name, ActiveRecord::Base.internal_metadata_table_name, ignore_tables].flatten.any? do |ignored|
339
+ @ignore_tables.any? do |ignored|
306
340
  ignored === remove_prefix_and_suffix(table_name)
307
341
  end
308
342
  end