activerecord 8.0.0 → 8.1.4

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 (202) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1381 -241
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +35 -17
  7. data/lib/active_record/associations/association_scope.rb +1 -1
  8. data/lib/active_record/associations/belongs_to_association.rb +20 -4
  9. data/lib/active_record/associations/builder/association.rb +16 -5
  10. data/lib/active_record/associations/builder/belongs_to.rb +36 -8
  11. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  12. data/lib/active_record/associations/builder/has_one.rb +1 -1
  13. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  14. data/lib/active_record/associations/collection_association.rb +9 -5
  15. data/lib/active_record/associations/collection_proxy.rb +26 -4
  16. data/lib/active_record/associations/deprecation.rb +88 -0
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/foreign_association.rb +3 -1
  19. data/lib/active_record/associations/has_one_association.rb +1 -0
  20. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  21. data/lib/active_record/associations/join_dependency.rb +5 -4
  22. data/lib/active_record/associations/preloader/association.rb +1 -1
  23. data/lib/active_record/associations/preloader/batch.rb +7 -1
  24. data/lib/active_record/associations/preloader/branch.rb +1 -0
  25. data/lib/active_record/associations.rb +159 -21
  26. data/lib/active_record/attribute_methods/primary_key.rb +2 -1
  27. data/lib/active_record/attribute_methods/query.rb +34 -0
  28. data/lib/active_record/attribute_methods/serialization.rb +19 -6
  29. data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
  30. data/lib/active_record/attribute_methods.rb +24 -17
  31. data/lib/active_record/attributes.rb +40 -26
  32. data/lib/active_record/autosave_association.rb +22 -12
  33. data/lib/active_record/base.rb +3 -4
  34. data/lib/active_record/callbacks.rb +1 -2
  35. data/lib/active_record/coders/column_serializer.rb +1 -1
  36. data/lib/active_record/coders/json.rb +16 -6
  37. data/lib/active_record/coders/yaml_column.rb +2 -2
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +19 -18
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
  40. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -12
  41. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +474 -112
  42. data/lib/active_record/connection_adapters/abstract/database_statements.rb +68 -41
  43. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -9
  44. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  45. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  46. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +31 -35
  47. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  48. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +94 -28
  49. data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
  50. data/lib/active_record/connection_adapters/abstract_adapter.rb +166 -64
  51. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +133 -40
  52. data/lib/active_record/connection_adapters/column.rb +14 -4
  53. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  54. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
  55. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  56. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  57. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +40 -25
  58. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +66 -15
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -3
  60. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  61. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  62. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +29 -33
  63. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  64. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
  65. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  66. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  67. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
  69. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -33
  70. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +70 -32
  71. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +99 -54
  72. data/lib/active_record/connection_adapters/postgresql_adapter.rb +40 -12
  73. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  74. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -23
  75. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  76. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +15 -3
  77. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +7 -14
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +68 -37
  79. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  80. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +8 -3
  81. data/lib/active_record/connection_adapters/trilogy_adapter.rb +16 -2
  82. data/lib/active_record/connection_adapters.rb +1 -0
  83. data/lib/active_record/connection_handling.rb +15 -10
  84. data/lib/active_record/core.rb +44 -12
  85. data/lib/active_record/counter_cache.rb +36 -9
  86. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  87. data/lib/active_record/database_configurations/database_config.rb +5 -1
  88. data/lib/active_record/database_configurations/hash_config.rb +66 -9
  89. data/lib/active_record/database_configurations/url_config.rb +15 -3
  90. data/lib/active_record/database_configurations.rb +7 -3
  91. data/lib/active_record/delegated_type.rb +19 -19
  92. data/lib/active_record/disable_joins_association_relation.rb +5 -1
  93. data/lib/active_record/dynamic_matchers.rb +54 -69
  94. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  95. data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
  96. data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
  97. data/lib/active_record/encryption/encryptor.rb +39 -25
  98. data/lib/active_record/encryption/scheme.rb +1 -1
  99. data/lib/active_record/enum.rb +37 -20
  100. data/lib/active_record/errors.rb +23 -7
  101. data/lib/active_record/explain.rb +1 -1
  102. data/lib/active_record/explain_registry.rb +51 -2
  103. data/lib/active_record/filter_attribute_handler.rb +76 -0
  104. data/lib/active_record/fixture_set/table_row.rb +19 -2
  105. data/lib/active_record/fixtures.rb +2 -2
  106. data/lib/active_record/future_result.rb +5 -3
  107. data/lib/active_record/gem_version.rb +2 -2
  108. data/lib/active_record/inheritance.rb +9 -4
  109. data/lib/active_record/insert_all.rb +14 -9
  110. data/lib/active_record/locking/optimistic.rb +7 -0
  111. data/lib/active_record/locking/pessimistic.rb +5 -0
  112. data/lib/active_record/log_subscriber.rb +30 -22
  113. data/lib/active_record/message_pack.rb +13 -0
  114. data/lib/active_record/middleware/shard_selector.rb +34 -17
  115. data/lib/active_record/migration/command_recorder.rb +32 -6
  116. data/lib/active_record/migration/compatibility.rb +34 -24
  117. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  118. data/lib/active_record/migration.rb +31 -21
  119. data/lib/active_record/model_schema.rb +38 -12
  120. data/lib/active_record/nested_attributes.rb +10 -6
  121. data/lib/active_record/persistence.rb +35 -3
  122. data/lib/active_record/query_cache.rb +22 -15
  123. data/lib/active_record/query_logs.rb +7 -7
  124. data/lib/active_record/querying.rb +17 -5
  125. data/lib/active_record/railtie.rb +35 -6
  126. data/lib/active_record/railties/controller_runtime.rb +11 -6
  127. data/lib/active_record/railties/databases.rake +24 -20
  128. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  129. data/lib/active_record/railties/job_runtime.rb +10 -11
  130. data/lib/active_record/reflection.rb +36 -1
  131. data/lib/active_record/relation/batches.rb +25 -11
  132. data/lib/active_record/relation/calculations.rb +62 -41
  133. data/lib/active_record/relation/delegation.rb +0 -1
  134. data/lib/active_record/relation/finder_methods.rb +76 -45
  135. data/lib/active_record/relation/merger.rb +4 -2
  136. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  137. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  138. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
  139. data/lib/active_record/relation/predicate_builder.rb +10 -8
  140. data/lib/active_record/relation/query_attribute.rb +4 -2
  141. data/lib/active_record/relation/query_methods.rb +106 -45
  142. data/lib/active_record/relation/spawn_methods.rb +6 -6
  143. data/lib/active_record/relation/where_clause.rb +10 -11
  144. data/lib/active_record/relation.rb +63 -31
  145. data/lib/active_record/result.rb +44 -21
  146. data/lib/active_record/runtime_registry.rb +42 -58
  147. data/lib/active_record/sanitization.rb +2 -0
  148. data/lib/active_record/schema.rb +1 -1
  149. data/lib/active_record/schema_dumper.rb +41 -21
  150. data/lib/active_record/scoping.rb +0 -1
  151. data/lib/active_record/secure_token.rb +3 -3
  152. data/lib/active_record/signed_id.rb +48 -19
  153. data/lib/active_record/statement_cache.rb +16 -12
  154. data/lib/active_record/store.rb +46 -19
  155. data/lib/active_record/structured_event_subscriber.rb +85 -0
  156. data/lib/active_record/table_metadata.rb +5 -20
  157. data/lib/active_record/tasks/abstract_tasks.rb +83 -0
  158. data/lib/active_record/tasks/database_tasks.rb +44 -45
  159. data/lib/active_record/tasks/mysql_database_tasks.rb +4 -56
  160. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  161. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  162. data/lib/active_record/test_databases.rb +18 -4
  163. data/lib/active_record/test_fixtures.rb +40 -6
  164. data/lib/active_record/testing/query_assertions.rb +8 -2
  165. data/lib/active_record/timestamp.rb +4 -2
  166. data/lib/active_record/transaction.rb +2 -5
  167. data/lib/active_record/transactions.rb +58 -32
  168. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  169. data/lib/active_record/type/internal/timezone.rb +7 -0
  170. data/lib/active_record/type/json.rb +13 -2
  171. data/lib/active_record/type/serialized.rb +16 -4
  172. data/lib/active_record/type/type_map.rb +1 -1
  173. data/lib/active_record/type_caster/connection.rb +2 -1
  174. data/lib/active_record/validations/associated.rb +1 -1
  175. data/lib/active_record.rb +72 -7
  176. data/lib/arel/alias_predication.rb +2 -0
  177. data/lib/arel/collectors/bind.rb +1 -1
  178. data/lib/arel/collectors/sql_string.rb +1 -1
  179. data/lib/arel/collectors/substitute_binds.rb +2 -2
  180. data/lib/arel/crud.rb +8 -11
  181. data/lib/arel/delete_manager.rb +5 -0
  182. data/lib/arel/nodes/binary.rb +1 -1
  183. data/lib/arel/nodes/count.rb +2 -2
  184. data/lib/arel/nodes/delete_statement.rb +4 -2
  185. data/lib/arel/nodes/function.rb +4 -10
  186. data/lib/arel/nodes/named_function.rb +2 -2
  187. data/lib/arel/nodes/node.rb +2 -2
  188. data/lib/arel/nodes/sql_literal.rb +1 -1
  189. data/lib/arel/nodes/update_statement.rb +4 -2
  190. data/lib/arel/nodes.rb +0 -2
  191. data/lib/arel/predications.rb +1 -3
  192. data/lib/arel/select_manager.rb +13 -4
  193. data/lib/arel/update_manager.rb +5 -0
  194. data/lib/arel/visitors/dot.rb +2 -3
  195. data/lib/arel/visitors/postgresql.rb +55 -0
  196. data/lib/arel/visitors/sqlite.rb +55 -8
  197. data/lib/arel/visitors/to_sql.rb +7 -23
  198. data/lib/arel.rb +3 -1
  199. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  200. metadata +16 -15
  201. data/lib/active_record/explain_subscriber.rb +0 -34
  202. data/lib/active_record/normalization.rb +0 -163
@@ -60,7 +60,7 @@ module ActiveRecord
60
60
  :reverse_order, :distinct, :create_with, :skip_query_cache]
61
61
 
62
62
  CLAUSE_METHODS = [:where, :having, :from]
63
- INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :with, :with_recursive]
63
+ INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL = [:distinct, :with, :with_recursive]
64
64
 
65
65
  VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
66
66
 
@@ -272,10 +272,15 @@ module ActiveRecord
272
272
  # such situation.
273
273
  def create_or_find_by(attributes, &block)
274
274
  with_connection do |connection|
275
- transaction(requires_new: true) { create(attributes, &block) }
275
+ record = nil
276
+ transaction(requires_new: true) do
277
+ record = create(attributes, &block)
278
+ record._last_transaction_return_status || raise(ActiveRecord::Rollback)
279
+ end
280
+ record
276
281
  rescue ActiveRecord::RecordNotUnique
277
282
  if connection.transaction_open?
278
- where(attributes).lock.find_by!(attributes)
283
+ rewhere(attributes).lock(connection.create_or_find_by_lock).take!
279
284
  else
280
285
  find_by!(attributes)
281
286
  end
@@ -287,17 +292,22 @@ module ActiveRecord
287
292
  # is raised if the created record is invalid.
288
293
  def create_or_find_by!(attributes, &block)
289
294
  with_connection do |connection|
290
- transaction(requires_new: true) { create!(attributes, &block) }
295
+ record = nil
296
+ transaction(requires_new: true) do
297
+ record = create!(attributes, &block)
298
+ record._last_transaction_return_status || raise(ActiveRecord::Rollback)
299
+ end
300
+ record
291
301
  rescue ActiveRecord::RecordNotUnique
292
302
  if connection.transaction_open?
293
- where(attributes).lock.find_by!(attributes)
303
+ rewhere(attributes).lock(connection.create_or_find_by_lock).take!
294
304
  else
295
305
  find_by!(attributes)
296
306
  end
297
307
  end
298
308
  end
299
309
 
300
- # Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
310
+ # Like #find_or_create_by, but calls {new}[rdoc-ref:Core.new]
301
311
  # instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
302
312
  def find_or_initialize_by(attributes, &block)
303
313
  find_by(attributes) || new(attributes, &block)
@@ -371,10 +381,11 @@ module ActiveRecord
371
381
 
372
382
  # Returns true if there are no records.
373
383
  #
374
- # When a pattern argument is given, this method checks whether elements in
375
- # the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
384
+ # When an argument is given, returns true if no records match the argument
385
+ # via the case-equality operator (<tt>===</tt>).
376
386
  #
377
- # posts.none?(Comment) # => true or false
387
+ # posts.none?(Post) # => true if posts is empty
388
+ # posts.none?(Comment) # => true
378
389
  def none?(*args)
379
390
  return true if @none
380
391
 
@@ -384,10 +395,12 @@ module ActiveRecord
384
395
 
385
396
  # Returns true if there are any records.
386
397
  #
387
- # When a pattern argument is given, this method checks whether elements in
388
- # the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
389
398
  #
390
- # posts.any?(Post) # => true or false
399
+ # When an argument is given, returns true if at least one records matches
400
+ # the argument via the case-equality operator (<tt>===</tt>).
401
+ #
402
+ # posts.any?(Post) # => true if at least one record
403
+ # posts.any?(Comment) # => false
391
404
  def any?(*args)
392
405
  return false if @none
393
406
 
@@ -397,10 +410,11 @@ module ActiveRecord
397
410
 
398
411
  # Returns true if there is exactly one record.
399
412
  #
400
- # When a pattern argument is given, this method checks whether elements in
401
- # the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
413
+ # When an argument is given, returns true if exactly one records matches the
414
+ # argument via the case-equality operator (<tt>===</tt>).
402
415
  #
403
- # posts.one?(Post) # => true or false
416
+ # posts.one?(Post) # => true if exactly one record
417
+ # posts.any?(Comment) # => false
404
418
  def one?(*args)
405
419
  return false if @none
406
420
 
@@ -475,7 +489,7 @@ module ActiveRecord
475
489
  if loaded?
476
490
  size = records.size
477
491
  if size > 0
478
- timestamp = records.map { |record| record.read_attribute(timestamp_column) }.max
492
+ timestamp = records.filter_map { |record| record.read_attribute(timestamp_column) }.max
479
493
  end
480
494
  else
481
495
  collection = eager_loading? ? apply_join_dependency : self
@@ -590,6 +604,18 @@ module ActiveRecord
590
604
 
591
605
  return 0 if @none
592
606
 
607
+ invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL.select do |method|
608
+ value = @values[method]
609
+ method == :distinct ? value : value&.any?
610
+ end
611
+ if invalid_methods.any?
612
+ ActiveRecord.deprecator.warn <<~MESSAGE
613
+ `#{invalid_methods.join(', ')}` is not supported by `update_all` and was never included in the generated query.
614
+
615
+ Calling `#{invalid_methods.join(', ')}` with `update_all` will raise an error in Rails 8.2.
616
+ MESSAGE
617
+ end
618
+
593
619
  if updates.is_a?(Hash)
594
620
  if model.locking_enabled? &&
595
621
  !updates.key?(model.locking_column) &&
@@ -603,17 +629,15 @@ module ActiveRecord
603
629
  end
604
630
 
605
631
  model.with_connection do |c|
606
- arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
632
+ arel = eager_loading? ? apply_join_dependency.arel : arel()
607
633
  arel.source.left = table
608
634
 
609
- group_values_arel_columns = arel_columns(group_values.uniq)
610
- having_clause_ast = having_clause.ast unless having_clause.empty?
611
635
  key = if model.composite_primary_key?
612
636
  primary_key.map { |pk| table[pk] }
613
637
  else
614
638
  table[primary_key]
615
639
  end
616
- stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns)
640
+ stmt = arel.compile_update(values, key)
617
641
  c.update(stmt, "#{model} Update All").tap { reset }
618
642
  end
619
643
  end
@@ -820,7 +844,7 @@ module ActiveRecord
820
844
  #
821
845
  # [:returning]
822
846
  # (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
823
- # inserted records, which by default is the primary key.
847
+ # upserted records, which by default is the primary key.
824
848
  # Pass <tt>returning: %w[ id name ]</tt> for both id and name
825
849
  # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
826
850
  # clause entirely.
@@ -849,7 +873,9 @@ module ActiveRecord
849
873
  # Active Record's schema_cache.
850
874
  #
851
875
  # [:on_duplicate]
852
- # Configure the SQL update sentence that will be used in case of conflict.
876
+ # Configure the behavior that will be used in case of conflict. Use `:skip`
877
+ # to ignore any conflicts or provide a safe SQL fragment wrapped with
878
+ # `Arel.sql`.
853
879
  #
854
880
  # NOTE: If you use this option you must provide all the columns you want to update
855
881
  # by yourself.
@@ -949,7 +975,7 @@ module ActiveRecord
949
975
  # If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
950
976
  # If no time argument is passed, the current time is used as default.
951
977
  #
952
- # === Examples
978
+ # ==== Examples
953
979
  #
954
980
  # # Touch all records
955
981
  # Person.all.touch_all
@@ -1000,7 +1026,7 @@ module ActiveRecord
1000
1026
  #
1001
1027
  # Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
1002
1028
  #
1003
- # Both calls delete the affected posts all at once with a single DELETE statement.
1029
+ # This call deletes the affected posts all at once with a single DELETE statement.
1004
1030
  # If you need to destroy dependent associations or call your <tt>before_*</tt> or
1005
1031
  # +after_destroy+ callbacks, use the #destroy_all method instead.
1006
1032
  #
@@ -1011,7 +1037,7 @@ module ActiveRecord
1011
1037
  def delete_all
1012
1038
  return 0 if @none
1013
1039
 
1014
- invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
1040
+ invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL.select do |method|
1015
1041
  value = @values[method]
1016
1042
  method == :distinct ? value : value&.any?
1017
1043
  end
@@ -1020,17 +1046,15 @@ module ActiveRecord
1020
1046
  end
1021
1047
 
1022
1048
  model.with_connection do |c|
1023
- arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
1049
+ arel = eager_loading? ? apply_join_dependency.arel : arel()
1024
1050
  arel.source.left = table
1025
1051
 
1026
- group_values_arel_columns = arel_columns(group_values.uniq)
1027
- having_clause_ast = having_clause.ast unless having_clause.empty?
1028
1052
  key = if model.composite_primary_key?
1029
1053
  primary_key.map { |pk| table[pk] }
1030
1054
  else
1031
1055
  table[primary_key]
1032
1056
  end
1033
- stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns)
1057
+ stmt = arel.compile_delete(key)
1034
1058
 
1035
1059
  c.delete(stmt, "#{model} Delete All").tap { reset }
1036
1060
  end
@@ -1057,6 +1081,10 @@ module ActiveRecord
1057
1081
  def delete(id_or_array)
1058
1082
  return 0 if id_or_array.nil? || (id_or_array.is_a?(Array) && id_or_array.empty?)
1059
1083
 
1084
+ if model.composite_primary_key? && !id_or_array.first.is_a?(Array)
1085
+ id_or_array = [id_or_array]
1086
+ end
1087
+
1060
1088
  where(model.primary_key => id_or_array).delete_all
1061
1089
  end
1062
1090
 
@@ -1395,12 +1423,16 @@ module ActiveRecord
1395
1423
 
1396
1424
  def _increment_attribute(attribute, value = 1)
1397
1425
  bind = predicate_builder.build_bind_attribute(attribute.name, value.abs)
1398
- expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attribute), 0)
1426
+ expr = table.coalesce(attribute, 0)
1399
1427
  expr = value < 0 ? expr - bind : expr + bind
1400
1428
  expr.expr
1401
1429
  end
1402
1430
 
1403
1431
  def exec_queries(&block)
1432
+ if lock_value && model.current_preventing_writes
1433
+ raise ActiveRecord::ReadOnlyError, "Lock query attempted while in readonly mode"
1434
+ end
1435
+
1404
1436
  skip_query_cache_if_necessary do
1405
1437
  rows = if scheduled?
1406
1438
  future = @future_result
@@ -1440,7 +1472,7 @@ module ActiveRecord
1440
1472
  else
1441
1473
  relation = join_dependency.apply_column_aliases(relation)
1442
1474
  @_join_dependency = join_dependency
1443
- c.select_all(relation.arel, "SQL", async: async)
1475
+ c.select_all(relation.arel, "#{model.name} Eager Load", async: async)
1444
1476
  end
1445
1477
  end
1446
1478
  end
@@ -29,6 +29,11 @@ module ActiveRecord
29
29
  # ...
30
30
  # ]
31
31
  #
32
+ # # Get the number of rows affected by the query:
33
+ # result = ActiveRecord::Base.lease_connection.exec_query('INSERT INTO posts (title, body) VALUES ("title_3", "body_3"), ("title_4", "body_4")')
34
+ # result.affected_rows
35
+ # # => 2
36
+ #
32
37
  # # ActiveRecord::Result also includes Enumerable.
33
38
  # result.each do |row|
34
39
  # puts row['title'] + " " + row['body']
@@ -89,24 +94,26 @@ module ActiveRecord
89
94
  alias_method :to_hash, :to_h
90
95
  end
91
96
 
92
- attr_reader :columns, :rows, :column_types
97
+ attr_reader :columns, :rows, :affected_rows
93
98
 
94
- def self.empty(async: false) # :nodoc:
99
+ def self.empty(async: false, affected_rows: nil) # :nodoc:
95
100
  if async
96
- EMPTY_ASYNC
101
+ FutureResult.wrap(new(EMPTY_ARRAY, EMPTY_ARRAY, EMPTY_HASH, affected_rows: affected_rows)).freeze
97
102
  else
98
- EMPTY
103
+ new(EMPTY_ARRAY, EMPTY_ARRAY, EMPTY_HASH, affected_rows: affected_rows).freeze
99
104
  end
100
105
  end
101
106
 
102
- def initialize(columns, rows, column_types = nil)
107
+ def initialize(columns, rows, column_types = nil, affected_rows: nil)
103
108
  # We freeze the strings to prevent them getting duped when
104
109
  # used as keys in ActiveRecord::Base's @attributes hash
105
110
  @columns = columns.each(&:-@).freeze
106
111
  @rows = rows
107
112
  @hash_rows = nil
108
- @column_types = column_types || EMPTY_HASH
113
+ @column_types = column_types.freeze
114
+ @types_hash = nil
109
115
  @column_indexes = nil
116
+ @affected_rows = affected_rows
110
117
  end
111
118
 
112
119
  # Returns true if this result set includes the column named +name+
@@ -154,6 +161,24 @@ module ActiveRecord
154
161
  n ? hash_rows.last(n) : hash_rows.last
155
162
  end
156
163
 
164
+ # Returns the +ActiveRecord::Type+ type of all columns.
165
+ # Note that not all database adapters return the result types,
166
+ # so the hash may be empty.
167
+ def column_types
168
+ if @column_types
169
+ @types_hash ||= begin
170
+ types = {}
171
+ @columns.each_with_index do |name, index|
172
+ type = @column_types[index] || Type.default_value
173
+ types[name] = types[index] = type
174
+ end
175
+ types.freeze
176
+ end
177
+ else
178
+ EMPTY_HASH
179
+ end
180
+ end
181
+
157
182
  def result # :nodoc:
158
183
  self
159
184
  end
@@ -162,7 +187,7 @@ module ActiveRecord
162
187
  self
163
188
  end
164
189
 
165
- def cast_values(type_overrides = {}) # :nodoc:
190
+ def cast_values(type_overrides = nil) # :nodoc:
166
191
  if columns.one?
167
192
  # Separated to avoid allocating an array per row
168
193
 
@@ -190,13 +215,13 @@ module ActiveRecord
190
215
 
191
216
  def initialize_copy(other)
192
217
  @rows = rows.dup
193
- @column_types = column_types.dup
194
218
  @hash_rows = nil
195
219
  end
196
220
 
197
221
  def freeze # :nodoc:
198
222
  hash_rows.freeze
199
- indexed_rows.freeze
223
+ indexed_rows
224
+ column_types
200
225
  super
201
226
  end
202
227
 
@@ -204,7 +229,7 @@ module ActiveRecord
204
229
  @column_indexes ||= begin
205
230
  index = 0
206
231
  hash = {}
207
- length = columns.length
232
+ length = columns.length
208
233
  while index < length
209
234
  hash[columns[index]] = index
210
235
  index += 1
@@ -222,10 +247,14 @@ module ActiveRecord
222
247
 
223
248
  private
224
249
  def column_type(name, index, type_overrides)
225
- type_overrides.fetch(name) do
226
- column_types.fetch(index) do
227
- column_types.fetch(name, Type.default_value)
250
+ if type_overrides
251
+ type_overrides.fetch(name) do
252
+ column_type(name, index, nil)
228
253
  end
254
+ elsif @column_types
255
+ @column_types[index] || Type.default_value
256
+ else
257
+ Type.default_value
229
258
  end
230
259
  end
231
260
 
@@ -237,14 +266,8 @@ module ActiveRecord
237
266
  end
238
267
  end
239
268
 
240
- empty_array = [].freeze
269
+ EMPTY_ARRAY = [].freeze
241
270
  EMPTY_HASH = {}.freeze
242
- private_constant :EMPTY_HASH
243
-
244
- EMPTY = new(empty_array, empty_array, EMPTY_HASH).freeze
245
- private_constant :EMPTY
246
-
247
- EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze
248
- private_constant :EMPTY_ASYNC
271
+ private_constant :EMPTY_ARRAY, :EMPTY_HASH
249
272
  end
250
273
  end
@@ -3,80 +3,64 @@
3
3
  module ActiveRecord
4
4
  # This is a thread locals registry for Active Record. For example:
5
5
  #
6
- # ActiveRecord::RuntimeRegistry.sql_runtime
6
+ # ActiveRecord::RuntimeRegistry.stats.sql_runtime
7
7
  #
8
8
  # returns the connection handler local to the current unit of execution (either thread of fiber).
9
9
  module RuntimeRegistry # :nodoc:
10
- extend self
11
-
12
- def sql_runtime
13
- ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] ||= 0.0
10
+ class Stats
11
+ attr_accessor :sql_runtime, :async_sql_runtime, :queries_count, :cached_queries_count
12
+
13
+ def initialize
14
+ @sql_runtime = 0.0
15
+ @async_sql_runtime = 0.0
16
+ @queries_count = 0
17
+ @cached_queries_count = 0
18
+ end
19
+
20
+ def reset_runtimes
21
+ sql_runtime_was = @sql_runtime
22
+ @sql_runtime = 0.0
23
+ @async_sql_runtime = 0.0
24
+ sql_runtime_was
25
+ end
26
+
27
+ public alias_method :reset, :initialize
14
28
  end
15
29
 
16
- def sql_runtime=(runtime)
17
- ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] = runtime
18
- end
19
-
20
- def async_sql_runtime
21
- ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] ||= 0.0
22
- end
30
+ extend self
23
31
 
24
- def async_sql_runtime=(runtime)
25
- ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] = runtime
32
+ def call(name, start, finish, id, payload)
33
+ record(
34
+ payload[:name],
35
+ (finish - start) * 1_000.0,
36
+ cached: payload[:cached],
37
+ async: payload[:async],
38
+ lock_wait: payload[:lock_wait],
39
+ )
26
40
  end
27
41
 
28
- def queries_count
29
- ActiveSupport::IsolatedExecutionState[:active_record_queries_count] ||= 0
30
- end
42
+ def record(query_name, runtime, cached: false, async: false, lock_wait: nil)
43
+ stats = self.stats
31
44
 
32
- def queries_count=(count)
33
- ActiveSupport::IsolatedExecutionState[:active_record_queries_count] = count
34
- end
45
+ unless query_name == "TRANSACTION" || query_name == "SCHEMA"
46
+ stats.queries_count += 1
47
+ stats.cached_queries_count += 1 if cached
48
+ end
35
49
 
36
- def cached_queries_count
37
- ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] ||= 0
50
+ if async
51
+ stats.async_sql_runtime += (runtime - lock_wait)
52
+ end
53
+ stats.sql_runtime += runtime
38
54
  end
39
55
 
40
- def cached_queries_count=(count)
41
- ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] = count
56
+ def stats
57
+ ActiveSupport::IsolatedExecutionState[:active_record_runtime] ||= Stats.new
42
58
  end
43
59
 
44
60
  def reset
45
- reset_runtimes
46
- reset_queries_count
47
- reset_cached_queries_count
48
- end
49
-
50
- def reset_runtimes
51
- rt, self.sql_runtime = sql_runtime, 0.0
52
- self.async_sql_runtime = 0.0
53
- rt
54
- end
55
-
56
- def reset_queries_count
57
- qc = queries_count
58
- self.queries_count = 0
59
- qc
60
- end
61
-
62
- def reset_cached_queries_count
63
- qc = cached_queries_count
64
- self.cached_queries_count = 0
65
- qc
61
+ stats.reset
66
62
  end
67
63
  end
68
64
  end
69
65
 
70
- ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload|
71
- unless ["SCHEMA", "TRANSACTION"].include?(payload[:name])
72
- ActiveRecord::RuntimeRegistry.queries_count += 1
73
- ActiveRecord::RuntimeRegistry.cached_queries_count += 1 if payload[:cached]
74
- end
75
-
76
- runtime = (finish - start) * 1_000.0
77
-
78
- if payload[:async]
79
- ActiveRecord::RuntimeRegistry.async_sql_runtime += (runtime - payload[:lock_wait])
80
- end
81
- ActiveRecord::RuntimeRegistry.sql_runtime += runtime
82
- end
66
+ ActiveSupport::Notifications.monotonic_subscribe("sql.active_record", ActiveRecord::RuntimeRegistry)
@@ -161,6 +161,8 @@ module ActiveRecord
161
161
  #
162
162
  # sanitize_sql_array(["role = ?", 0])
163
163
  # # => "role = '0'"
164
+ #
165
+ # Before using this method, please consider if Arel.sql would be better for your use-case
164
166
  def sanitize_sql_array(ary)
165
167
  statement, *values = ary
166
168
  if values.first.is_a?(Hash) && /:\w+/.match?(statement)
@@ -15,7 +15,7 @@ module ActiveRecord
15
15
  # t.string :name, null: false
16
16
  # end
17
17
  #
18
- # add_index :authors, :name, :unique
18
+ # add_index :authors, :name, unique: true
19
19
  #
20
20
  # create_table :posts do |t|
21
21
  # t.integer :author_id, null: false
@@ -165,7 +165,7 @@ module ActiveRecord
165
165
  # first dump primary key column
166
166
  pk = @connection.primary_key(table)
167
167
 
168
- tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
168
+ tbl.print " create_table #{relation_name(remove_prefix_and_suffix(table)).inspect}"
169
169
 
170
170
  case pk
171
171
  when String
@@ -207,12 +207,17 @@ module ActiveRecord
207
207
  end
208
208
 
209
209
  indexes_in_create(table, tbl)
210
- check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
210
+ remaining = check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
211
211
  exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
212
212
  unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
213
213
 
214
214
  tbl.puts " end"
215
215
 
216
+ if remaining
217
+ tbl.puts
218
+ tbl.print remaining.string
219
+ end
220
+
216
221
  stream.print tbl.string
217
222
  rescue => e
218
223
  stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
@@ -227,8 +232,8 @@ module ActiveRecord
227
232
  def indexes(table, stream)
228
233
  if (indexes = @connection.indexes(table)).any?
229
234
  add_index_statements = indexes.map do |index|
230
- table_name = remove_prefix_and_suffix(index.table).inspect
231
- " add_index #{([table_name] + index_parts(index)).join(', ')}"
235
+ table_name = remove_prefix_and_suffix(index.table)
236
+ " add_index #{([relation_name(table_name).inspect] + index_parts(index)).join(', ')}"
232
237
  end
233
238
 
234
239
  stream.puts add_index_statements.sort.join("\n")
@@ -272,35 +277,49 @@ module ActiveRecord
272
277
  index_parts << "nulls_not_distinct: #{index.nulls_not_distinct.inspect}" if index.nulls_not_distinct
273
278
  index_parts << "type: #{index.type.inspect}" if index.type
274
279
  index_parts << "comment: #{index.comment.inspect}" if index.comment
280
+ index_parts << "enabled: #{index.enabled.inspect}" if @connection.supports_disabling_indexes? && index.disabled?
275
281
  index_parts
276
282
  end
277
283
 
278
284
  def check_constraints_in_create(table, stream)
279
285
  if (check_constraints = @connection.check_constraints(table)).any?
280
- add_check_constraint_statements = check_constraints.map do |check_constraint|
281
- parts = [
282
- "t.check_constraint #{check_constraint.expression.inspect}"
283
- ]
286
+ check_valid, check_invalid = check_constraints.partition { |chk| chk.validate? }
284
287
 
285
- if check_constraint.export_name_on_schema_dump?
286
- parts << "name: #{check_constraint.name.inspect}"
288
+ unless check_valid.empty?
289
+ check_constraint_statements = check_valid.map do |check|
290
+ " t.check_constraint #{check_parts(check).join(', ')}"
287
291
  end
288
292
 
289
- parts << "validate: #{check_constraint.validate?.inspect}" unless check_constraint.validate?
290
-
291
- " #{parts.join(', ')}"
293
+ stream.puts check_constraint_statements.sort.join("\n")
292
294
  end
293
295
 
294
- stream.puts add_check_constraint_statements.sort.join("\n")
296
+ unless check_invalid.empty?
297
+ remaining = StringIO.new
298
+ table_name = remove_prefix_and_suffix(table).inspect
299
+
300
+ add_check_constraint_statements = check_invalid.map do |check|
301
+ " add_check_constraint #{([table_name] + check_parts(check)).join(', ')}"
302
+ end
303
+
304
+ remaining.puts add_check_constraint_statements.sort.join("\n")
305
+ remaining
306
+ end
295
307
  end
296
308
  end
297
309
 
310
+ def check_parts(check)
311
+ check_parts = [ check.expression.inspect ]
312
+ check_parts << "name: #{check.name.inspect}" if check.export_name_on_schema_dump?
313
+ check_parts << "validate: #{check.validate?.inspect}" unless check.validate?
314
+ check_parts
315
+ end
316
+
298
317
  def foreign_keys(table, stream)
299
318
  if (foreign_keys = @connection.foreign_keys(table)).any?
300
319
  add_foreign_key_statements = foreign_keys.map do |foreign_key|
301
320
  parts = [
302
- "add_foreign_key #{remove_prefix_and_suffix(foreign_key.from_table).inspect}",
303
- remove_prefix_and_suffix(foreign_key.to_table).inspect,
321
+ relation_name(remove_prefix_and_suffix(foreign_key.from_table)).inspect,
322
+ relation_name(remove_prefix_and_suffix(foreign_key.to_table)).inspect,
304
323
  ]
305
324
 
306
325
  if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table, "id")
@@ -311,16 +330,13 @@ module ActiveRecord
311
330
  parts << "primary_key: #{foreign_key.primary_key.inspect}"
312
331
  end
313
332
 
314
- if foreign_key.export_name_on_schema_dump?
315
- parts << "name: #{foreign_key.name.inspect}"
316
- end
317
-
333
+ parts << "name: #{foreign_key.name.inspect}" if foreign_key.export_name_on_schema_dump?
318
334
  parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
319
335
  parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
320
336
  parts << "deferrable: #{foreign_key.deferrable.inspect}" if foreign_key.deferrable
321
337
  parts << "validate: #{foreign_key.validate?.inspect}" unless foreign_key.validate?
322
338
 
323
- " #{parts.join(', ')}"
339
+ " add_foreign_key #{parts.join(', ')}"
324
340
  end
325
341
 
326
342
  stream.puts add_foreign_key_statements.sort.join("\n")
@@ -345,6 +361,10 @@ module ActiveRecord
345
361
  end
346
362
  end
347
363
 
364
+ def relation_name(name)
365
+ name
366
+ end
367
+
348
368
  def remove_prefix_and_suffix(table)
349
369
  # This method appears at the top when profiling active_record test cases run.
350
370
  # Avoid costly calculation when there are no prefix and suffix.
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/module/delegation"
4
3
 
5
4
  module ActiveRecord
6
5
  module Scoping
@@ -30,13 +30,13 @@ module ActiveRecord
30
30
  # {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can.
31
31
  # You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.
32
32
  #
33
- # === Options
33
+ # ==== Options
34
34
  #
35
- # [:length]
35
+ # [+:length+]
36
36
  # Length of the Secure Random, with a minimum of 24 characters. It will
37
37
  # default to 24.
38
38
  #
39
- # [:on]
39
+ # [+:on+]
40
40
  # The callback when the value is generated. When called with <tt>on:
41
41
  # :initialize</tt>, the value is generated in an
42
42
  # <tt>after_initialize</tt> callback, otherwise the value will be used