activerecord 7.0.6 → 7.1.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (233) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1598 -1367
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +16 -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 +20 -4
  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 +16 -10
  15. data/lib/active_record/associations/collection_proxy.rb +20 -10
  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 -7
  20. data/lib/active_record/associations/join_dependency.rb +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +31 -7
  22. data/lib/active_record/associations/preloader.rb +13 -10
  23. data/lib/active_record/associations/singular_association.rb +6 -8
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +313 -217
  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 +52 -34
  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 +60 -18
  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 +74 -51
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  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 -124
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +505 -102
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +214 -113
  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 +23 -144
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +21 -14
  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 +18 -13
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -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 +14 -3
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
  71. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  72. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  73. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/quoting.rb +15 -8
  75. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  76. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  77. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  78. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +361 -60
  80. data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
  81. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  82. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  83. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  84. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
  85. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  86. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
  87. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +210 -83
  88. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  89. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  90. data/lib/active_record/connection_adapters/trilogy_adapter.rb +262 -0
  91. data/lib/active_record/connection_adapters.rb +3 -1
  92. data/lib/active_record/connection_handling.rb +72 -95
  93. data/lib/active_record/core.rb +175 -153
  94. data/lib/active_record/counter_cache.rb +46 -25
  95. data/lib/active_record/database_configurations/database_config.rb +9 -3
  96. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  97. data/lib/active_record/database_configurations/url_config.rb +17 -11
  98. data/lib/active_record/database_configurations.rb +86 -33
  99. data/lib/active_record/delegated_type.rb +9 -4
  100. data/lib/active_record/deprecator.rb +7 -0
  101. data/lib/active_record/destroy_association_async_job.rb +2 -0
  102. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  103. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  104. data/lib/active_record/encryption/config.rb +25 -1
  105. data/lib/active_record/encryption/configurable.rb +12 -19
  106. data/lib/active_record/encryption/context.rb +10 -3
  107. data/lib/active_record/encryption/contexts.rb +5 -1
  108. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  109. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +21 -6
  111. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  112. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  113. data/lib/active_record/encryption/key_generator.rb +12 -1
  114. data/lib/active_record/encryption/message_serializer.rb +2 -0
  115. data/lib/active_record/encryption/properties.rb +3 -3
  116. data/lib/active_record/encryption/scheme.rb +19 -22
  117. data/lib/active_record/encryption.rb +1 -0
  118. data/lib/active_record/enum.rb +112 -28
  119. data/lib/active_record/errors.rb +112 -18
  120. data/lib/active_record/explain.rb +23 -3
  121. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  122. data/lib/active_record/fixture_set/render_context.rb +2 -0
  123. data/lib/active_record/fixture_set/table_row.rb +29 -8
  124. data/lib/active_record/fixtures.rb +135 -71
  125. data/lib/active_record/future_result.rb +31 -5
  126. data/lib/active_record/gem_version.rb +4 -4
  127. data/lib/active_record/inheritance.rb +30 -16
  128. data/lib/active_record/insert_all.rb +57 -10
  129. data/lib/active_record/integration.rb +8 -8
  130. data/lib/active_record/internal_metadata.rb +120 -30
  131. data/lib/active_record/locking/pessimistic.rb +5 -2
  132. data/lib/active_record/log_subscriber.rb +29 -12
  133. data/lib/active_record/marshalling.rb +56 -0
  134. data/lib/active_record/message_pack.rb +124 -0
  135. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  136. data/lib/active_record/middleware/database_selector.rb +6 -8
  137. data/lib/active_record/middleware/shard_selector.rb +3 -1
  138. data/lib/active_record/migration/command_recorder.rb +104 -5
  139. data/lib/active_record/migration/compatibility.rb +150 -58
  140. data/lib/active_record/migration/default_strategy.rb +23 -0
  141. data/lib/active_record/migration/execution_strategy.rb +19 -0
  142. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  143. data/lib/active_record/migration.rb +271 -114
  144. data/lib/active_record/model_schema.rb +64 -44
  145. data/lib/active_record/nested_attributes.rb +24 -6
  146. data/lib/active_record/normalization.rb +167 -0
  147. data/lib/active_record/persistence.rb +191 -38
  148. data/lib/active_record/promise.rb +84 -0
  149. data/lib/active_record/query_cache.rb +3 -21
  150. data/lib/active_record/query_logs.rb +77 -52
  151. data/lib/active_record/query_logs_formatter.rb +41 -0
  152. data/lib/active_record/querying.rb +15 -2
  153. data/lib/active_record/railtie.rb +109 -47
  154. data/lib/active_record/railties/controller_runtime.rb +14 -9
  155. data/lib/active_record/railties/databases.rake +142 -148
  156. data/lib/active_record/railties/job_runtime.rb +23 -0
  157. data/lib/active_record/readonly_attributes.rb +32 -5
  158. data/lib/active_record/reflection.rb +174 -44
  159. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  160. data/lib/active_record/relation/batches.rb +190 -61
  161. data/lib/active_record/relation/calculations.rb +187 -63
  162. data/lib/active_record/relation/delegation.rb +23 -9
  163. data/lib/active_record/relation/finder_methods.rb +77 -16
  164. data/lib/active_record/relation/merger.rb +2 -0
  165. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  166. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  167. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  168. data/lib/active_record/relation/predicate_builder.rb +27 -16
  169. data/lib/active_record/relation/query_attribute.rb +25 -1
  170. data/lib/active_record/relation/query_methods.rb +378 -70
  171. data/lib/active_record/relation/spawn_methods.rb +18 -1
  172. data/lib/active_record/relation.rb +91 -35
  173. data/lib/active_record/result.rb +19 -5
  174. data/lib/active_record/runtime_registry.rb +24 -1
  175. data/lib/active_record/sanitization.rb +51 -11
  176. data/lib/active_record/schema.rb +2 -3
  177. data/lib/active_record/schema_dumper.rb +46 -7
  178. data/lib/active_record/schema_migration.rb +68 -33
  179. data/lib/active_record/scoping/default.rb +15 -5
  180. data/lib/active_record/scoping/named.rb +2 -2
  181. data/lib/active_record/scoping.rb +2 -1
  182. data/lib/active_record/secure_password.rb +60 -0
  183. data/lib/active_record/secure_token.rb +21 -3
  184. data/lib/active_record/signed_id.rb +7 -5
  185. data/lib/active_record/store.rb +8 -8
  186. data/lib/active_record/suppressor.rb +3 -1
  187. data/lib/active_record/table_metadata.rb +11 -2
  188. data/lib/active_record/tasks/database_tasks.rb +127 -105
  189. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  190. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  191. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  192. data/lib/active_record/test_fixtures.rb +113 -96
  193. data/lib/active_record/timestamp.rb +27 -15
  194. data/lib/active_record/token_for.rb +113 -0
  195. data/lib/active_record/touch_later.rb +11 -6
  196. data/lib/active_record/transactions.rb +39 -13
  197. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  198. data/lib/active_record/type/internal/timezone.rb +7 -2
  199. data/lib/active_record/type/serialized.rb +4 -0
  200. data/lib/active_record/type/time.rb +4 -0
  201. data/lib/active_record/validations/absence.rb +1 -1
  202. data/lib/active_record/validations/numericality.rb +5 -4
  203. data/lib/active_record/validations/presence.rb +5 -28
  204. data/lib/active_record/validations/uniqueness.rb +47 -2
  205. data/lib/active_record/validations.rb +8 -4
  206. data/lib/active_record/version.rb +1 -1
  207. data/lib/active_record.rb +121 -16
  208. data/lib/arel/errors.rb +10 -0
  209. data/lib/arel/factory_methods.rb +4 -0
  210. data/lib/arel/nodes/and.rb +4 -0
  211. data/lib/arel/nodes/binary.rb +6 -1
  212. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  213. data/lib/arel/nodes/cte.rb +36 -0
  214. data/lib/arel/nodes/fragments.rb +35 -0
  215. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  216. data/lib/arel/nodes/leading_join.rb +8 -0
  217. data/lib/arel/nodes/node.rb +111 -2
  218. data/lib/arel/nodes/sql_literal.rb +6 -0
  219. data/lib/arel/nodes/table_alias.rb +4 -0
  220. data/lib/arel/nodes.rb +4 -0
  221. data/lib/arel/predications.rb +2 -0
  222. data/lib/arel/table.rb +9 -5
  223. data/lib/arel/visitors/mysql.rb +8 -1
  224. data/lib/arel/visitors/to_sql.rb +81 -17
  225. data/lib/arel/visitors/visitor.rb +2 -2
  226. data/lib/arel.rb +16 -2
  227. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  228. data/lib/rails/generators/active_record/migration.rb +3 -1
  229. data/lib/rails/generators/active_record/model/USAGE +113 -0
  230. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  231. metadata +51 -15
  232. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  233. 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 does not find a record
168
+ # because a 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,49 @@ 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
+ #
295
+ # When a pattern argument is given, this method checks whether elements in
296
+ # the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
297
+ #
298
+ # posts.none?(Comment) # => true or false
299
+ def none?(*args)
300
+ return true if @none
301
+
302
+ return super if args.present? || block_given?
280
303
  empty?
281
304
  end
282
305
 
283
306
  # Returns true if there are any records.
284
- def any?
285
- return super if block_given?
307
+ #
308
+ # When a pattern argument is given, this method checks whether elements in
309
+ # the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
310
+ #
311
+ # posts.any?(Post) # => true or false
312
+ def any?(*args)
313
+ return false if @none
314
+
315
+ return super if args.present? || block_given?
286
316
  !empty?
287
317
  end
288
318
 
289
319
  # Returns true if there is exactly one record.
290
- def one?
291
- return super if block_given?
320
+ #
321
+ # When a pattern argument is given, this method checks whether elements in
322
+ # the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
323
+ #
324
+ # posts.one?(Post) # => true or false
325
+ def one?(*args)
326
+ return false if @none
327
+
328
+ return super if args.present? || block_given?
292
329
  return records.one? if loaded?
293
330
  limited_count == 1
294
331
  end
295
332
 
296
333
  # Returns true if there is more than one record.
297
334
  def many?
335
+ return false if @none
336
+
298
337
  return super if block_given?
299
338
  return records.many? if loaded?
300
339
  limited_count > 1
@@ -307,7 +346,7 @@ module ActiveRecord
307
346
  # # => "products/query-1850ab3d302391b85b8693e941286659"
308
347
  #
309
348
  # 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.
349
+ # in \Rails 6.0 and earlier, the cache key will also include a version.
311
350
  #
312
351
  # ActiveRecord::Base.collection_cache_versioning = false
313
352
  # Product.where("name like ?", "%Cosmic Encounter%").cache_key
@@ -409,7 +448,7 @@ module ActiveRecord
409
448
  # Comment.where(post_id: 1).scoping do
410
449
  # Comment.first
411
450
  # end
412
- # # => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
451
+ # # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
413
452
  #
414
453
  # If <tt>all_queries: true</tt> is passed, scoping will apply to all queries
415
454
  # for the relation including +update+ and +delete+ on instances.
@@ -446,7 +485,8 @@ module ActiveRecord
446
485
  #
447
486
  # ==== Parameters
448
487
  #
449
- # * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
488
+ # * +updates+ - A string, array, or hash representing the SET part of an SQL statement. Any strings provided will
489
+ # be type cast, unless you use +Arel.sql+. (Don't pass user-provided values to +Arel.sql+.)
450
490
  #
451
491
  # ==== Examples
452
492
  #
@@ -461,9 +501,14 @@ module ActiveRecord
461
501
  #
462
502
  # # Update all invoices and set the number column to its id value.
463
503
  # Invoice.update_all('number = id')
504
+ #
505
+ # # Update all books with 'Rails' in their title
506
+ # Book.where('title LIKE ?', '%Rails%').update_all(title: Arel.sql("title + ' - volume 1'"))
464
507
  def update_all(updates)
465
508
  raise ArgumentError, "Empty list of attributes to change" if updates.blank?
466
509
 
510
+ return 0 if @none
511
+
467
512
  if updates.is_a?(Hash)
468
513
  if klass.locking_enabled? &&
469
514
  !updates.key?(klass.locking_column) &&
@@ -599,6 +644,8 @@ module ActiveRecord
599
644
  # Post.distinct.delete_all
600
645
  # # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
601
646
  def delete_all
647
+ return 0 if @none
648
+
602
649
  invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
603
650
  value = @values[method]
604
651
  method == :distinct ? value : value&.any?
@@ -720,7 +767,7 @@ module ActiveRecord
720
767
  # Returns sql statement for the relation.
721
768
  #
722
769
  # User.where(name: 'Oscar').to_sql
723
- # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
770
+ # # SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
724
771
  def to_sql
725
772
  @to_sql ||= if eager_loading?
726
773
  apply_join_dependency do |relation, join_dependency|
@@ -774,8 +821,13 @@ module ActiveRecord
774
821
  end
775
822
  end
776
823
 
777
- def pretty_print(q)
778
- q.pp(records)
824
+ def pretty_print(pp)
825
+ subject = loaded? ? records : annotate("loading for pp")
826
+ entries = subject.take([limit_value, 11].compact.min)
827
+
828
+ entries[10] = "..." if entries.size == 11
829
+
830
+ pp.pp(entries)
779
831
  end
780
832
 
781
833
  # Returns true if relation is blank.
@@ -837,10 +889,6 @@ module ActiveRecord
837
889
  @loaded = true
838
890
  end
839
891
 
840
- def null_relation? # :nodoc:
841
- is_a?(NullRelation)
842
- end
843
-
844
892
  private
845
893
  def already_in_scope?(registry)
846
894
  @delegate_to_klass && registry.current_scope(klass, true)
@@ -925,6 +973,14 @@ module ActiveRecord
925
973
  end
926
974
 
927
975
  def exec_main_query(async: false)
976
+ if @none
977
+ if async
978
+ return FutureResult::Complete.new([])
979
+ else
980
+ return []
981
+ end
982
+ end
983
+
928
984
  skip_query_cache_if_necessary do
929
985
  if where_clause.contradiction?
930
986
  [].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,34 @@ 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 async_sql_runtime
21
+ ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] ||= 0.0
22
+ end
23
+
24
+ def async_sql_runtime=(runtime)
25
+ ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] = runtime
26
+ end
27
+
28
+ def reset
29
+ rt, self.sql_runtime = sql_runtime, 0.0
30
+ self.async_sql_runtime = 0.0
31
+ rt
32
+ end
33
+ end
34
+ end
35
+
36
+ ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload|
37
+ runtime = (finish - start) * 1_000.0
38
+
39
+ if payload[:async]
40
+ ActiveRecord::RuntimeRegistry.async_sql_runtime += (runtime - payload[:lock_wait])
19
41
  end
42
+ ActiveRecord::RuntimeRegistry.sql_runtime += runtime
20
43
  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