activerecord 8.0.5 → 8.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 (157) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +421 -652
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/belongs_to_association.rb +0 -2
  7. data/lib/active_record/associations/builder/association.rb +16 -5
  8. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  9. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  10. data/lib/active_record/associations/builder/has_one.rb +1 -1
  11. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  12. data/lib/active_record/associations/collection_proxy.rb +22 -4
  13. data/lib/active_record/associations/deprecation.rb +88 -0
  14. data/lib/active_record/associations/errors.rb +3 -0
  15. data/lib/active_record/associations/join_dependency.rb +4 -2
  16. data/lib/active_record/associations/preloader/batch.rb +1 -7
  17. data/lib/active_record/associations/preloader/branch.rb +1 -0
  18. data/lib/active_record/associations.rb +159 -21
  19. data/lib/active_record/attribute_methods/serialization.rb +16 -3
  20. data/lib/active_record/attribute_methods.rb +1 -1
  21. data/lib/active_record/attributes.rb +3 -0
  22. data/lib/active_record/autosave_association.rb +2 -2
  23. data/lib/active_record/base.rb +2 -3
  24. data/lib/active_record/coders/json.rb +14 -5
  25. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
  26. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
  27. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  28. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +383 -52
  29. data/lib/active_record/connection_adapters/abstract/database_statements.rb +27 -31
  30. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -18
  31. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  32. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  33. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
  34. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  35. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +88 -25
  36. data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
  37. data/lib/active_record/connection_adapters/abstract_adapter.rb +96 -49
  38. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
  39. data/lib/active_record/connection_adapters/column.rb +17 -4
  40. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  41. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  42. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  43. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
  44. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
  45. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  46. data/lib/active_record/connection_adapters/postgresql/column.rb +2 -4
  47. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -23
  48. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  49. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  50. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  51. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
  52. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
  53. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
  54. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
  55. data/lib/active_record/connection_adapters/postgresql_adapter.rb +10 -12
  56. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  57. data/lib/active_record/connection_adapters/sqlite3/column.rb +2 -8
  58. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
  59. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  60. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +3 -15
  61. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
  62. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +57 -33
  63. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  64. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  65. data/lib/active_record/connection_adapters.rb +1 -0
  66. data/lib/active_record/connection_handling.rb +8 -12
  67. data/lib/active_record/core.rb +5 -4
  68. data/lib/active_record/counter_cache.rb +33 -8
  69. data/lib/active_record/database_configurations/database_config.rb +5 -1
  70. data/lib/active_record/database_configurations/hash_config.rb +50 -9
  71. data/lib/active_record/database_configurations/url_config.rb +13 -3
  72. data/lib/active_record/database_configurations.rb +7 -3
  73. data/lib/active_record/delegated_type.rb +1 -1
  74. data/lib/active_record/dynamic_matchers.rb +54 -69
  75. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  76. data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
  77. data/lib/active_record/encryption/scheme.rb +1 -1
  78. data/lib/active_record/enum.rb +24 -8
  79. data/lib/active_record/errors.rb +23 -7
  80. data/lib/active_record/explain_registry.rb +0 -1
  81. data/lib/active_record/filter_attribute_handler.rb +73 -0
  82. data/lib/active_record/fixtures.rb +2 -2
  83. data/lib/active_record/future_result.rb +0 -2
  84. data/lib/active_record/gem_version.rb +3 -3
  85. data/lib/active_record/inheritance.rb +1 -1
  86. data/lib/active_record/insert_all.rb +14 -9
  87. data/lib/active_record/locking/optimistic.rb +7 -0
  88. data/lib/active_record/locking/pessimistic.rb +5 -0
  89. data/lib/active_record/log_subscriber.rb +1 -5
  90. data/lib/active_record/middleware/shard_selector.rb +34 -17
  91. data/lib/active_record/migration/command_recorder.rb +15 -2
  92. data/lib/active_record/migration/compatibility.rb +34 -24
  93. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  94. data/lib/active_record/migration.rb +26 -16
  95. data/lib/active_record/model_schema.rb +10 -7
  96. data/lib/active_record/nested_attributes.rb +2 -0
  97. data/lib/active_record/persistence.rb +34 -3
  98. data/lib/active_record/query_cache.rb +22 -15
  99. data/lib/active_record/query_logs.rb +3 -7
  100. data/lib/active_record/railtie.rb +32 -3
  101. data/lib/active_record/railties/databases.rake +16 -4
  102. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  103. data/lib/active_record/railties/job_runtime.rb +10 -11
  104. data/lib/active_record/reflection.rb +42 -3
  105. data/lib/active_record/relation/batches.rb +26 -12
  106. data/lib/active_record/relation/calculations.rb +20 -9
  107. data/lib/active_record/relation/delegation.rb +0 -1
  108. data/lib/active_record/relation/finder_methods.rb +28 -12
  109. data/lib/active_record/relation/merger.rb +2 -2
  110. data/lib/active_record/relation/predicate_builder/array_handler.rb +1 -3
  111. data/lib/active_record/relation/predicate_builder.rb +2 -2
  112. data/lib/active_record/relation/query_attribute.rb +3 -1
  113. data/lib/active_record/relation/query_methods.rb +40 -31
  114. data/lib/active_record/relation/where_clause.rb +2 -11
  115. data/lib/active_record/relation.rb +26 -14
  116. data/lib/active_record/result.rb +44 -21
  117. data/lib/active_record/sanitization.rb +2 -0
  118. data/lib/active_record/schema_dumper.rb +12 -10
  119. data/lib/active_record/scoping.rb +0 -1
  120. data/lib/active_record/signed_id.rb +43 -15
  121. data/lib/active_record/statement_cache.rb +13 -9
  122. data/lib/active_record/store.rb +44 -19
  123. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  124. data/lib/active_record/tasks/database_tasks.rb +2 -21
  125. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  126. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
  127. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  128. data/lib/active_record/test_databases.rb +10 -2
  129. data/lib/active_record/test_fixtures.rb +27 -2
  130. data/lib/active_record/testing/query_assertions.rb +8 -2
  131. data/lib/active_record/timestamp.rb +4 -2
  132. data/lib/active_record/transaction.rb +2 -5
  133. data/lib/active_record/transactions.rb +32 -10
  134. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  135. data/lib/active_record/type/internal/timezone.rb +7 -0
  136. data/lib/active_record/type/json.rb +15 -2
  137. data/lib/active_record/type/serialized.rb +11 -9
  138. data/lib/active_record/type/type_map.rb +1 -1
  139. data/lib/active_record/type_caster/connection.rb +2 -1
  140. data/lib/active_record/validations/associated.rb +1 -1
  141. data/lib/active_record.rb +65 -3
  142. data/lib/arel/alias_predication.rb +2 -0
  143. data/lib/arel/crud.rb +6 -11
  144. data/lib/arel/nodes/count.rb +2 -2
  145. data/lib/arel/nodes/function.rb +4 -10
  146. data/lib/arel/nodes/named_function.rb +2 -2
  147. data/lib/arel/nodes/node.rb +1 -1
  148. data/lib/arel/nodes.rb +0 -2
  149. data/lib/arel/predications.rb +3 -1
  150. data/lib/arel/select_manager.rb +7 -2
  151. data/lib/arel/visitors/dot.rb +0 -3
  152. data/lib/arel/visitors/postgresql.rb +55 -0
  153. data/lib/arel/visitors/sqlite.rb +55 -8
  154. data/lib/arel/visitors/to_sql.rb +3 -21
  155. data/lib/arel.rb +3 -1
  156. metadata +14 -10
  157. data/lib/active_record/normalization.rb +0 -163
@@ -35,9 +35,12 @@ module ActiveRecord
35
35
  config.active_record.query_log_tags = [ :application ]
36
36
  config.active_record.query_log_tags_format = :legacy
37
37
  config.active_record.cache_query_log_tags = false
38
+ config.active_record.query_log_tags_prepend_comment = false
38
39
  config.active_record.raise_on_assign_to_attr_readonly = false
39
40
  config.active_record.belongs_to_required_validates_foreign_key = true
40
41
  config.active_record.generate_secure_token_on = :create
42
+ config.active_record.use_legacy_signed_id_verifier = :generate_and_verify
43
+ config.active_record.deprecated_associations_options = { mode: :warn, backtrace: false }
41
44
 
42
45
  config.active_record.queues = ActiveSupport::InheritableOptions.new
43
46
 
@@ -229,10 +232,12 @@ To keep using the current cache store, you can turn off cache versioning entirel
229
232
  :query_log_tags,
230
233
  :query_log_tags_format,
231
234
  :cache_query_log_tags,
235
+ :query_log_tags_prepend_comment,
232
236
  :sqlite3_adapter_strict_strings_by_default,
233
237
  :check_schema_cache_dump_version,
234
238
  :use_schema_cache_dump,
235
239
  :postgresql_adapter_decode_dates,
240
+ :use_legacy_signed_id_verifier,
236
241
  )
237
242
 
238
243
  configs_used_in_other_initializers.each do |k, v|
@@ -274,6 +279,13 @@ To keep using the current cache store, you can turn off cache versioning entirel
274
279
  end
275
280
  end
276
281
 
282
+ initializer "active_record.job_checkpoints" do
283
+ require "active_record/railties/job_checkpoints"
284
+ ActiveSupport.on_load(:active_job_continuable) do
285
+ prepend ActiveRecord::Railties::JobCheckpoints
286
+ end
287
+ end
288
+
277
289
  initializer "active_record.set_reloader_hooks" do
278
290
  ActiveSupport.on_load(:active_record) do
279
291
  ActiveSupport::Reloader.before_class_unload do
@@ -319,9 +331,22 @@ To keep using the current cache store, you can turn off cache versioning entirel
319
331
  end
320
332
  end
321
333
 
322
- initializer "active_record.set_signed_id_verifier_secret" do
323
- ActiveSupport.on_load(:active_record) do
324
- self.signed_id_verifier_secret ||= -> { Rails.application.key_generator.generate_key("active_record/signed_id") }
334
+ initializer "active_record.filter_attributes_as_log_parameters" do |app|
335
+ ActiveRecord::FilterAttributeHandler.new(app).enable
336
+ end
337
+
338
+ initializer "active_record.configure_message_verifiers" do |app|
339
+ ActiveRecord.message_verifiers = app.message_verifiers
340
+
341
+ use_legacy_signed_id_verifier = app.config.active_record.use_legacy_signed_id_verifier
342
+ legacy_options = { digest: "SHA256", serializer: JSON, url_safe: true }
343
+
344
+ if use_legacy_signed_id_verifier == :generate_and_verify
345
+ app.message_verifiers.prepend { |salt| legacy_options if salt == "active_record/signed_id" }
346
+ elsif use_legacy_signed_id_verifier == :verify
347
+ app.message_verifiers.rotate { |salt| legacy_options if salt == "active_record/signed_id" }
348
+ elsif use_legacy_signed_id_verifier
349
+ raise ArgumentError, "Unrecognized value for config.active_record.use_legacy_signed_id_verifier: #{use_legacy_signed_id_verifier.inspect}"
325
350
  end
326
351
  end
327
352
 
@@ -387,6 +412,10 @@ To keep using the current cache store, you can turn off cache versioning entirel
387
412
  if app.config.active_record.cache_query_log_tags
388
413
  ActiveRecord::QueryLogs.cache_query_log_tags = true
389
414
  end
415
+
416
+ if app.config.active_record.query_log_tags_prepend_comment
417
+ ActiveRecord::QueryLogs.prepend_comment = true
418
+ end
390
419
  end
391
420
  end
392
421
  end
@@ -163,6 +163,18 @@ db_namespace = namespace :db do
163
163
  desc "Resets your database using your migrations for the current environment"
164
164
  task reset: ["db:drop", "db:create", "db:schema:dump", "db:migrate"]
165
165
 
166
+ namespace :reset do
167
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
168
+ desc "Drop and recreate the #{name} database using migrations"
169
+ task name => :load_config do
170
+ db_namespace["drop:#{name}"].invoke
171
+ db_namespace["create:#{name}"].invoke
172
+ db_namespace["schema:dump:#{name}"].invoke
173
+ db_namespace["migrate:#{name}"].invoke
174
+ end
175
+ end
176
+ end
177
+
166
178
  desc 'Run the "up" for a given migration VERSION.'
167
179
  task up: :load_config do
168
180
  ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:up")
@@ -333,7 +345,7 @@ db_namespace = namespace :db do
333
345
  pending_migrations << pool.migration_context.open.pending_migrations
334
346
  end
335
347
 
336
- pending_migrations = pending_migrations.flatten!
348
+ pending_migrations.flatten!
337
349
 
338
350
  if pending_migrations.any?
339
351
  puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}"
@@ -454,12 +466,12 @@ db_namespace = namespace :db do
454
466
 
455
467
  desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) into the database"
456
468
  task load: [:load_config, :check_protected_environments] do
457
- ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ENV["SCHEMA_FORMAT"], ENV["SCHEMA"])
469
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_current(nil, ENV["SCHEMA"])
458
470
  end
459
471
 
460
472
  namespace :dump do
461
473
  ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
462
- desc "Create a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) for #{name} database"
474
+ desc "Create a database schema file (either db/schema.rb or db/structure.sql, depending on configuration) for #{name} database"
463
475
  task name => :load_config do
464
476
  ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(name: name) do |pool|
465
477
  db_config = pool.db_config
@@ -474,7 +486,7 @@ db_namespace = namespace :db do
474
486
  namespace :load do
475
487
  ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
476
488
  desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on configuration) into the #{name} database"
477
- task name => "db:test:purge:#{name}" do
489
+ task name => [:load_config, :check_protected_environments] do
478
490
  ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(name: name) do |pool|
479
491
  db_config = pool.db_config
480
492
  ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Railties # :nodoc:
5
+ module JobCheckpoints # :nodoc:
6
+ def checkpoint!
7
+ if ActiveRecord.all_open_transactions.any?
8
+ raise ActiveJob::Continuation::CheckpointError, "Cannot checkpoint job with open transactions"
9
+ else
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -5,19 +5,18 @@ require "active_record/runtime_registry"
5
5
  module ActiveRecord
6
6
  module Railties # :nodoc:
7
7
  module JobRuntime # :nodoc:
8
- private
9
- def instrument(operation, payload = {}, &block)
10
- if operation == :perform && block
11
- super(operation, payload) do
12
- db_runtime_before_perform = ActiveRecord::RuntimeRegistry.sql_runtime
13
- result = block.call
14
- payload[:db_runtime] = ActiveRecord::RuntimeRegistry.sql_runtime - db_runtime_before_perform
15
- result
16
- end
17
- else
18
- super
8
+ def instrument(operation, payload = {}, &block) # :nodoc:
9
+ if operation == :perform && block
10
+ super(operation, payload) do
11
+ db_runtime_before_perform = ActiveRecord::RuntimeRegistry.sql_runtime
12
+ result = block.call
13
+ payload[:db_runtime] = ActiveRecord::RuntimeRegistry.sql_runtime - db_runtime_before_perform
14
+ result
19
15
  end
16
+ else
17
+ super
20
18
  end
19
+ end
21
20
  end
22
21
  end
23
22
  end
@@ -425,10 +425,14 @@ module ActiveRecord
425
425
 
426
426
  def _klass(class_name) # :nodoc:
427
427
  if active_record.name.demodulize == class_name
428
- return compute_class("::#{class_name}") rescue NameError
428
+ begin
429
+ compute_class("::#{class_name}")
430
+ rescue NameError
431
+ compute_class(class_name)
432
+ end
433
+ else
434
+ compute_class(class_name)
429
435
  end
430
-
431
- compute_class(class_name)
432
436
  end
433
437
 
434
438
  def compute_class(name)
@@ -516,6 +520,8 @@ module ActiveRecord
516
520
 
517
521
  def initialize(name, scope, options, active_record)
518
522
  super
523
+
524
+ @validated = false
519
525
  @type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
520
526
  @foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
521
527
  @join_table = nil
@@ -534,6 +540,8 @@ module ActiveRecord
534
540
  options[:query_constraints] = options.delete(:foreign_key)
535
541
  end
536
542
 
543
+ @deprecated = !!options[:deprecated]
544
+
537
545
  ensure_option_not_given_as_class!(:class_name)
538
546
  end
539
547
 
@@ -616,6 +624,8 @@ module ActiveRecord
616
624
  end
617
625
 
618
626
  def check_validity!
627
+ return if @validated
628
+
619
629
  check_validity_of_inverse!
620
630
 
621
631
  if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
@@ -625,6 +635,8 @@ module ActiveRecord
625
635
  raise CompositePrimaryKeyMismatchError.new(self)
626
636
  end
627
637
  end
638
+
639
+ @validated = true
628
640
  end
629
641
 
630
642
  def check_eager_loadable!
@@ -742,6 +754,10 @@ module ActiveRecord
742
754
  Array(options[:extend])
743
755
  end
744
756
 
757
+ def deprecated?
758
+ @deprecated
759
+ end
760
+
745
761
  private
746
762
  # Attempts to find the inverse association name automatically.
747
763
  # If it cannot find a suitable inverse association name, it returns
@@ -975,6 +991,8 @@ module ActiveRecord
975
991
 
976
992
  def initialize(delegate_reflection)
977
993
  super()
994
+
995
+ @validated = false
978
996
  @delegate_reflection = delegate_reflection
979
997
  @klass = delegate_reflection.options[:anonymous_class]
980
998
  @source_reflection_name = delegate_reflection.options[:source]
@@ -1138,6 +1156,8 @@ module ActiveRecord
1138
1156
  end
1139
1157
 
1140
1158
  def check_validity!
1159
+ return if @validated
1160
+
1141
1161
  if through_reflection.nil?
1142
1162
  raise HasManyThroughAssociationNotFoundError.new(active_record, self)
1143
1163
  end
@@ -1175,6 +1195,8 @@ module ActiveRecord
1175
1195
  end
1176
1196
 
1177
1197
  check_validity_of_inverse!
1198
+
1199
+ @validated = true
1178
1200
  end
1179
1201
 
1180
1202
  def constraints
@@ -1195,6 +1217,10 @@ module ActiveRecord
1195
1217
  collect_join_reflections(seed + [self])
1196
1218
  end
1197
1219
 
1220
+ def deprecated_nested_reflections
1221
+ @deprecated_nested_reflections ||= collect_deprecated_nested_reflections
1222
+ end
1223
+
1198
1224
  protected
1199
1225
  def actual_source_reflection # FIXME: this is a horrible name
1200
1226
  source_reflection.actual_source_reflection
@@ -1219,6 +1245,19 @@ module ActiveRecord
1219
1245
  options[:source_type] || source_reflection.class_name
1220
1246
  end
1221
1247
 
1248
+ def collect_deprecated_nested_reflections
1249
+ result = []
1250
+ [through_reflection, source_reflection].each do |reflection|
1251
+ result << reflection if reflection.deprecated?
1252
+ # Both the through and the source reflections could be through
1253
+ # themselves. Nesting can go an arbitrary number of levels down.
1254
+ if reflection.through_reflection?
1255
+ result.concat(reflection.deprecated_nested_reflections)
1256
+ end
1257
+ end
1258
+ result
1259
+ end
1260
+
1222
1261
  delegate_methods = AssociationReflection.public_instance_methods -
1223
1262
  public_instance_methods
1224
1263
 
@@ -435,35 +435,49 @@ module ActiveRecord
435
435
  if load
436
436
  records = batch_relation.records
437
437
  values = records.pluck(*cursor)
438
- yielded_relation = where(cursor => values)
438
+ values_size = values.size
439
+ values_last = values.last
440
+ yielded_relation = where(cursor => values).order(batch_orders.to_h)
439
441
  yielded_relation.load_records(records)
440
442
  elsif (empty_scope && use_ranges != false) || use_ranges
441
- values = batch_relation.pluck(*cursor)
443
+ # Efficiently peak at the last value for the next batch using offset and limit.
444
+ values_size = batch_limit
445
+ values_last = batch_relation.offset(batch_limit - 1).pick(*cursor)
446
+
447
+ # If the last value is not found using offset, there is at most one more batch of size < batch_limit.
448
+ # Retry by getting the whole list of remaining values so that we have the exact size and last value.
449
+ unless values_last
450
+ values = batch_relation.pluck(*cursor)
451
+ values_size = values.size
452
+ values_last = values.last
453
+ end
442
454
 
443
- finish = values.last
444
- if finish
445
- yielded_relation = apply_finish_limit(batch_relation, cursor, finish, batch_orders)
446
- yielded_relation = yielded_relation.except(:limit, :order)
455
+ # Finally, build the yielded relation if at least one value found.
456
+ if values_last
457
+ yielded_relation = apply_finish_limit(batch_relation, cursor, values_last, batch_orders)
458
+ yielded_relation = yielded_relation.except(:limit).reorder(batch_orders.to_h)
447
459
  yielded_relation.skip_query_cache!(false)
448
460
  end
449
461
  else
450
462
  values = batch_relation.pluck(*cursor)
451
- yielded_relation = where(cursor => values)
463
+ values_size = values.size
464
+ values_last = values.last
465
+ yielded_relation = where(cursor => values).order(batch_orders.to_h)
452
466
  end
453
467
 
454
- break if values.empty?
468
+ break if values_size == 0
455
469
 
456
- if values.flatten.any?(nil)
470
+ if [values_last].flatten.any?(nil)
457
471
  raise ArgumentError, "Not all of the batch cursor columns were included in the custom select clause "\
458
472
  "or some columns contain nil."
459
473
  end
460
474
 
461
475
  yield yielded_relation
462
476
 
463
- break if values.length < batch_limit
477
+ break if values_size < batch_limit
464
478
 
465
479
  if limit_value
466
- remaining -= values.length
480
+ remaining -= values_size
467
481
 
468
482
  if remaining == 0
469
483
  # Saves a useless iteration when the limit is a multiple of the
@@ -481,7 +495,7 @@ module ActiveRecord
481
495
  end
482
496
  operators << (last_order == :desc ? :lt : :gt)
483
497
 
484
- cursor_value = values.last
498
+ cursor_value = values_last
485
499
  batch_relation = batch_condition(relation, cursor, cursor_value, operators)
486
500
  end
487
501
 
@@ -286,6 +286,11 @@ module ActiveRecord
286
286
  # # SELECT DATEDIFF(updated_at, created_at) FROM people
287
287
  # # => ['0', '27761', '173']
288
288
  #
289
+ # Be aware that #pluck ignores any previous select clauses
290
+ #
291
+ # Person.select(:name).pluck(:id)
292
+ # # SELECT people.id FROM people
293
+ #
289
294
  # See also #ids.
290
295
  def pluck(*column_names)
291
296
  if @none
@@ -314,7 +319,7 @@ module ActiveRecord
314
319
  columns = relation.arel_columns(column_names)
315
320
  relation.select_values = columns
316
321
  result = skip_query_cache_if_necessary do
317
- if where_clause.contradiction?
322
+ if where_clause.contradiction? && !possible_aggregation?(column_names)
318
323
  ActiveRecord::Result.empty(async: @async)
319
324
  else
320
325
  model.with_connection do |c|
@@ -417,7 +422,7 @@ module ActiveRecord
417
422
  when :all
418
423
  Arel.star
419
424
  else
420
- arel_column(column_name)
425
+ arel_column(column_name.to_s)
421
426
  end
422
427
  end
423
428
 
@@ -461,6 +466,16 @@ module ActiveRecord
461
466
  column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
462
467
  end
463
468
 
469
+ def possible_aggregation?(column_names)
470
+ column_names.all? do |column_name|
471
+ if column_name.is_a?(String)
472
+ column_name.include?("(")
473
+ else
474
+ Arel.arel_node?(column_name)
475
+ end
476
+ end
477
+ end
478
+
464
479
  def operation_over_aggregate_column(column, operation, distinct)
465
480
  operation == "count" ? column.count(distinct) : column.public_send(operation)
466
481
  end
@@ -512,7 +527,6 @@ module ActiveRecord
512
527
 
513
528
  def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
514
529
  group_fields = group_values
515
- group_fields = group_fields.uniq if group_fields.size > 1
516
530
 
517
531
  if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
518
532
  association = model._reflect_on_association(group_fields.first)
@@ -535,7 +549,7 @@ module ActiveRecord
535
549
  column = relation.aggregate_column(column_name)
536
550
  column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
537
551
  select_value = operation_over_aggregate_column(column, operation, distinct)
538
- select_value.as(model.adapter_class.quote_column_name(column_alias))
552
+ select_value = select_value.as(model.adapter_class.quote_column_name(column_alias))
539
553
 
540
554
  select_values = [select_value]
541
555
  select_values += self.select_values unless having_clause.empty?
@@ -662,6 +676,7 @@ module ActiveRecord
662
676
  if column_name == :all
663
677
  column_alias = Arel.star
664
678
  relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
679
+ relation.unscope!(:order)
665
680
  else
666
681
  column_alias = Arel.sql("count_column")
667
682
  relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
@@ -670,11 +685,7 @@ module ActiveRecord
670
685
  subquery_alias = Arel.sql("subquery_for_count", retryable: true)
671
686
  select_value = operation_over_aggregate_column(column_alias, "count", false)
672
687
 
673
- if column_name == :all
674
- relation.unscope(:order).build_subquery(subquery_alias, select_value)
675
- else
676
- relation.build_subquery(subquery_alias, select_value)
677
- end
688
+ relation.build_subquery(subquery_alias, select_value)
678
689
  end
679
690
  end
680
691
  end
@@ -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 Delegation # :nodoc:
@@ -141,14 +141,14 @@ module ActiveRecord
141
141
  #
142
142
  # Product.where(["price = %?", price]).sole
143
143
  def sole
144
- found, undesired = first(2)
144
+ found, undesired = take(2)
145
145
 
146
146
  if found.nil?
147
147
  raise_record_not_found_exception!
148
148
  elsif undesired.nil?
149
149
  found
150
150
  else
151
- raise ActiveRecord::SoleRecordExceeded.new(self)
151
+ raise ActiveRecord::SoleRecordExceeded.new(model)
152
152
  end
153
153
  end
154
154
 
@@ -442,7 +442,7 @@ module ActiveRecord
442
442
  if distinct_value && offset_value
443
443
  relation = except(:order).limit!(1)
444
444
  else
445
- relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
445
+ relation = except(:select, :distinct, :order)._select!(Arel.sql(ONE_AS_ONE, retryable: true)).limit!(1)
446
446
  end
447
447
 
448
448
  case conditions
@@ -639,24 +639,40 @@ module ActiveRecord
639
639
  end
640
640
 
641
641
  def ordered_relation
642
- if order_values.empty? && (model.implicit_order_column || !model.query_constraints_list.nil? || primary_key)
643
- order(_order_columns.map { |column| table[column].asc })
642
+ if order_values.empty?
643
+ if !_order_columns.empty?
644
+ return order(_order_columns.map { |column| table[column].asc })
645
+ end
646
+
647
+ if ActiveRecord.raise_on_missing_required_finder_order_columns
648
+ raise MissingRequiredOrderError, <<~MSG.squish
649
+ Relation has no order values, and #{model} has no order columns to use as a default.
650
+ Set at least one of `implicit_order_column`, `query_constraints` or `primary_key` on
651
+ the model when no `order `is specified on the relation.
652
+ MSG
653
+ else
654
+ ActiveRecord.deprecator.warn(<<~MSG)
655
+ Calling order dependent finder methods (e.g. `#first`, `#second`) without `order` values on the relation,
656
+ and on a model (#{model}) that does not have any order columns (`implicit_order_column`, `query_constraints`,
657
+ or `primary_key`) to fall back on is deprecated and will raise `ActiveRecord::MissingRequiredOrderError`
658
+ in Rails 8.2.
659
+ MSG
660
+
661
+ self
662
+ end
644
663
  else
645
664
  self
646
665
  end
647
666
  end
648
667
 
649
668
  def _order_columns
650
- oc = []
669
+ columns = Array(model.implicit_order_column)
651
670
 
652
- oc << model.implicit_order_column if model.implicit_order_column
653
- oc << model.query_constraints_list if model.query_constraints_list
671
+ return columns.compact if columns.length.positive? && columns.last.nil?
654
672
 
655
- if model.primary_key && model.query_constraints_list.nil?
656
- oc << model.primary_key
657
- end
673
+ columns += Array(model.query_constraints_list || model.primary_key)
658
674
 
659
- oc.flatten.uniq.compact
675
+ columns.uniq.compact
660
676
  end
661
677
  end
662
678
  end
@@ -85,9 +85,9 @@ module ActiveRecord
85
85
  return if other.select_values.empty?
86
86
 
87
87
  if other.model == relation.model
88
- relation.select_values |= other.select_values
88
+ relation.select_values += other.select_values if relation.select_values != other.select_values
89
89
  else
90
- relation.select_values |= other.instance_eval do
90
+ relation.select_values += other.instance_eval do
91
91
  arel_columns(select_values)
92
92
  end
93
93
  end
@@ -31,9 +31,7 @@ module ActiveRecord
31
31
  values_predicate
32
32
  else
33
33
  array_predicates = ranges.map! { |range| predicate_builder.build(attribute, range) }
34
- values_predicate.or(
35
- Arel::Nodes::Grouping.new Arel::Nodes::Or.new(array_predicates)
36
- )
34
+ array_predicates.inject(values_predicate, &:or)
37
35
  end
38
36
  end
39
37
 
@@ -37,7 +37,7 @@ module ActiveRecord
37
37
 
38
38
  # Define how a class is converted to Arel nodes when passed to +where+.
39
39
  # The handler can be any object that responds to +call+, and will be used
40
- # for any value that +===+ the class given. For example:
40
+ # for any value that <tt>===</tt> the class given. For example:
41
41
  #
42
42
  # MyCustomDateRange = Struct.new(:start, :end)
43
43
  # handler = proc do |column, range|
@@ -82,7 +82,7 @@ module ActiveRecord
82
82
  attr_writer :table
83
83
 
84
84
  def expand_from_hash(attributes, &block)
85
- return ["1=0"] if attributes.empty?
85
+ return [Arel.sql("1=0", retryable: true)] if attributes.empty?
86
86
 
87
87
  attributes.flat_map do |key, value|
88
88
  if key.is_a?(Array) && key.size == 1
@@ -15,7 +15,9 @@ module ActiveRecord
15
15
  elsif @type.serialized?
16
16
  value_for_database
17
17
  elsif @type.mutable? # If the type is simply mutable, we deep_dup it.
18
- @value_before_type_cast = @value_before_type_cast.deep_dup
18
+ unless @value_before_type_cast.frozen?
19
+ @value_before_type_cast = @value_before_type_cast.deep_dup
20
+ end
19
21
  end
20
22
  end
21
23