activerecord 7.2.2.1 → 8.1.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +564 -753
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +35 -11
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +10 -8
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +192 -24
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +17 -4
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attribute_methods.rb +24 -19
- data/lib/active_record/attributes.rb +40 -26
- data/lib/active_record/autosave_association.rb +91 -39
- data/lib/active_record/base.rb +3 -4
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -117
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -11
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +28 -45
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -19
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +37 -10
- data/lib/active_record/core.rb +61 -25
- data/lib/active_record/counter_cache.rb +34 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +67 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +19 -19
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +9 -9
- data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
- data/lib/active_record/encryption/encryptor.rb +49 -28
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +46 -42
- data/lib/active_record/errors.rb +36 -12
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +13 -9
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +12 -7
- data/lib/active_record/locking/optimistic.rb +8 -1
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -13
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +44 -11
- data/lib/active_record/migration/compatibility.rb +37 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +50 -43
- data/lib/active_record/model_schema.rb +38 -13
- data/lib/active_record/nested_attributes.rb +6 -6
- data/lib/active_record/persistence.rb +162 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +104 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +12 -12
- data/lib/active_record/railtie.rb +37 -32
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -37
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +53 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +147 -73
- data/lib/active_record/relation/calculations.rb +80 -63
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +54 -37
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +22 -7
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +156 -95
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +10 -11
- data/lib/active_record/relation.rb +122 -80
- data/lib/active_record/result.rb +109 -24
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +9 -6
- data/lib/active_record/schema_dumper.rb +47 -22
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +47 -18
- data/lib/active_record/statement_cache.rb +24 -20
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +6 -23
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +39 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +39 -16
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +11 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +85 -50
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +13 -4
- data/lib/arel/table.rb +3 -7
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +6 -22
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +17 -17
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -5,19 +5,18 @@ require "active_record/runtime_registry"
|
|
|
5
5
|
module ActiveRecord
|
|
6
6
|
module Railties # :nodoc:
|
|
7
7
|
module JobRuntime # :nodoc:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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.stats.sql_runtime
|
|
12
|
+
result = block.call
|
|
13
|
+
payload[:db_runtime] = ActiveRecord::RuntimeRegistry.stats.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
|
|
@@ -198,7 +198,7 @@ module ActiveRecord
|
|
|
198
198
|
end
|
|
199
199
|
|
|
200
200
|
def join_scope(table, foreign_table, foreign_klass)
|
|
201
|
-
predicate_builder = predicate_builder(table)
|
|
201
|
+
predicate_builder = klass.predicate_builder.with(TableMetadata.new(klass, table))
|
|
202
202
|
scope_chain_items = join_scopes(table, predicate_builder)
|
|
203
203
|
klass_scope = klass_join_scope(table, predicate_builder)
|
|
204
204
|
|
|
@@ -224,7 +224,7 @@ module ActiveRecord
|
|
|
224
224
|
klass_scope
|
|
225
225
|
end
|
|
226
226
|
|
|
227
|
-
def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
|
|
227
|
+
def join_scopes(table, predicate_builder = nil, klass = self.klass, record = nil) # :nodoc:
|
|
228
228
|
if scope
|
|
229
229
|
[scope_for(build_scope(table, predicate_builder, klass), record)]
|
|
230
230
|
else
|
|
@@ -232,7 +232,7 @@ module ActiveRecord
|
|
|
232
232
|
end
|
|
233
233
|
end
|
|
234
234
|
|
|
235
|
-
def klass_join_scope(table, predicate_builder) # :nodoc:
|
|
235
|
+
def klass_join_scope(table, predicate_builder = nil) # :nodoc:
|
|
236
236
|
relation = build_scope(table, predicate_builder)
|
|
237
237
|
klass.scope_for_association(relation)
|
|
238
238
|
end
|
|
@@ -333,12 +333,8 @@ module ActiveRecord
|
|
|
333
333
|
collect_join_chain
|
|
334
334
|
end
|
|
335
335
|
|
|
336
|
-
def build_scope(table, predicate_builder =
|
|
337
|
-
Relation.create(
|
|
338
|
-
klass,
|
|
339
|
-
table: table,
|
|
340
|
-
predicate_builder: predicate_builder
|
|
341
|
-
)
|
|
336
|
+
def build_scope(table, predicate_builder = nil, klass = self.klass)
|
|
337
|
+
Relation.create(klass, table:, predicate_builder:)
|
|
342
338
|
end
|
|
343
339
|
|
|
344
340
|
def strict_loading?
|
|
@@ -357,10 +353,6 @@ module ActiveRecord
|
|
|
357
353
|
end
|
|
358
354
|
|
|
359
355
|
private
|
|
360
|
-
def predicate_builder(table)
|
|
361
|
-
PredicateBuilder.new(TableMetadata.new(klass, table))
|
|
362
|
-
end
|
|
363
|
-
|
|
364
356
|
def primary_key(klass)
|
|
365
357
|
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
|
|
366
358
|
end
|
|
@@ -524,6 +516,8 @@ module ActiveRecord
|
|
|
524
516
|
|
|
525
517
|
def initialize(name, scope, options, active_record)
|
|
526
518
|
super
|
|
519
|
+
|
|
520
|
+
@validated = false
|
|
527
521
|
@type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
|
|
528
522
|
@foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
|
|
529
523
|
@join_table = nil
|
|
@@ -531,9 +525,9 @@ module ActiveRecord
|
|
|
531
525
|
@association_foreign_key = nil
|
|
532
526
|
@association_primary_key = nil
|
|
533
527
|
if options[:query_constraints]
|
|
534
|
-
|
|
535
|
-
Setting `query_constraints:` option on `#{active_record}.#{macro} :#{name}` is
|
|
536
|
-
To
|
|
528
|
+
raise ConfigurationError, <<~MSG.squish
|
|
529
|
+
Setting `query_constraints:` option on `#{active_record}.#{macro} :#{name}` is not allowed.
|
|
530
|
+
To get the same behavior, use the `foreign_key` option instead.
|
|
537
531
|
MSG
|
|
538
532
|
end
|
|
539
533
|
|
|
@@ -542,6 +536,8 @@ module ActiveRecord
|
|
|
542
536
|
options[:query_constraints] = options.delete(:foreign_key)
|
|
543
537
|
end
|
|
544
538
|
|
|
539
|
+
@deprecated = !!options[:deprecated]
|
|
540
|
+
|
|
545
541
|
ensure_option_not_given_as_class!(:class_name)
|
|
546
542
|
end
|
|
547
543
|
|
|
@@ -562,12 +558,12 @@ module ActiveRecord
|
|
|
562
558
|
def foreign_key(infer_from_inverse_of: true)
|
|
563
559
|
@foreign_key ||= if options[:foreign_key]
|
|
564
560
|
if options[:foreign_key].is_a?(Array)
|
|
565
|
-
options[:foreign_key].map { |fk| fk.to_s.freeze }.freeze
|
|
561
|
+
options[:foreign_key].map { |fk| -fk.to_s.freeze }.freeze
|
|
566
562
|
else
|
|
567
563
|
options[:foreign_key].to_s.freeze
|
|
568
564
|
end
|
|
569
565
|
elsif options[:query_constraints]
|
|
570
|
-
options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze
|
|
566
|
+
options[:query_constraints].map { |fk| -fk.to_s.freeze }.freeze
|
|
571
567
|
else
|
|
572
568
|
derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)
|
|
573
569
|
|
|
@@ -575,7 +571,12 @@ module ActiveRecord
|
|
|
575
571
|
derived_fk = derive_fk_query_constraints(derived_fk)
|
|
576
572
|
end
|
|
577
573
|
|
|
578
|
-
derived_fk
|
|
574
|
+
if derived_fk.is_a?(Array)
|
|
575
|
+
derived_fk.map! { |fk| -fk.freeze }
|
|
576
|
+
derived_fk.freeze
|
|
577
|
+
else
|
|
578
|
+
-derived_fk.freeze
|
|
579
|
+
end
|
|
579
580
|
end
|
|
580
581
|
end
|
|
581
582
|
|
|
@@ -619,6 +620,8 @@ module ActiveRecord
|
|
|
619
620
|
end
|
|
620
621
|
|
|
621
622
|
def check_validity!
|
|
623
|
+
return if @validated
|
|
624
|
+
|
|
622
625
|
check_validity_of_inverse!
|
|
623
626
|
|
|
624
627
|
if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
|
|
@@ -628,6 +631,8 @@ module ActiveRecord
|
|
|
628
631
|
raise CompositePrimaryKeyMismatchError.new(self)
|
|
629
632
|
end
|
|
630
633
|
end
|
|
634
|
+
|
|
635
|
+
@validated = true
|
|
631
636
|
end
|
|
632
637
|
|
|
633
638
|
def check_eager_loadable!
|
|
@@ -745,6 +750,10 @@ module ActiveRecord
|
|
|
745
750
|
Array(options[:extend])
|
|
746
751
|
end
|
|
747
752
|
|
|
753
|
+
def deprecated?
|
|
754
|
+
@deprecated
|
|
755
|
+
end
|
|
756
|
+
|
|
748
757
|
private
|
|
749
758
|
# Attempts to find the inverse association name automatically.
|
|
750
759
|
# If it cannot find a suitable inverse association name, it returns
|
|
@@ -978,6 +987,8 @@ module ActiveRecord
|
|
|
978
987
|
|
|
979
988
|
def initialize(delegate_reflection)
|
|
980
989
|
super()
|
|
990
|
+
|
|
991
|
+
@validated = false
|
|
981
992
|
@delegate_reflection = delegate_reflection
|
|
982
993
|
@klass = delegate_reflection.options[:anonymous_class]
|
|
983
994
|
@source_reflection_name = delegate_reflection.options[:source]
|
|
@@ -1065,7 +1076,7 @@ module ActiveRecord
|
|
|
1065
1076
|
source_reflection.scopes + super
|
|
1066
1077
|
end
|
|
1067
1078
|
|
|
1068
|
-
def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
|
|
1079
|
+
def join_scopes(table, predicate_builder = nil, klass = self.klass, record = nil) # :nodoc:
|
|
1069
1080
|
source_reflection.join_scopes(table, predicate_builder, klass, record) + super
|
|
1070
1081
|
end
|
|
1071
1082
|
|
|
@@ -1141,6 +1152,8 @@ module ActiveRecord
|
|
|
1141
1152
|
end
|
|
1142
1153
|
|
|
1143
1154
|
def check_validity!
|
|
1155
|
+
return if @validated
|
|
1156
|
+
|
|
1144
1157
|
if through_reflection.nil?
|
|
1145
1158
|
raise HasManyThroughAssociationNotFoundError.new(active_record, self)
|
|
1146
1159
|
end
|
|
@@ -1178,6 +1191,8 @@ module ActiveRecord
|
|
|
1178
1191
|
end
|
|
1179
1192
|
|
|
1180
1193
|
check_validity_of_inverse!
|
|
1194
|
+
|
|
1195
|
+
@validated = true
|
|
1181
1196
|
end
|
|
1182
1197
|
|
|
1183
1198
|
def constraints
|
|
@@ -1198,6 +1213,10 @@ module ActiveRecord
|
|
|
1198
1213
|
collect_join_reflections(seed + [self])
|
|
1199
1214
|
end
|
|
1200
1215
|
|
|
1216
|
+
def deprecated_nested_reflections
|
|
1217
|
+
@deprecated_nested_reflections ||= collect_deprecated_nested_reflections
|
|
1218
|
+
end
|
|
1219
|
+
|
|
1201
1220
|
protected
|
|
1202
1221
|
def actual_source_reflection # FIXME: this is a horrible name
|
|
1203
1222
|
source_reflection.actual_source_reflection
|
|
@@ -1222,6 +1241,19 @@ module ActiveRecord
|
|
|
1222
1241
|
options[:source_type] || source_reflection.class_name
|
|
1223
1242
|
end
|
|
1224
1243
|
|
|
1244
|
+
def collect_deprecated_nested_reflections
|
|
1245
|
+
result = []
|
|
1246
|
+
[through_reflection, source_reflection].each do |reflection|
|
|
1247
|
+
result << reflection if reflection.deprecated?
|
|
1248
|
+
# Both the through and the source reflections could be through
|
|
1249
|
+
# themselves. Nesting can go an arbitrary number of levels down.
|
|
1250
|
+
if reflection.through_reflection?
|
|
1251
|
+
result.concat(reflection.deprecated_nested_reflections)
|
|
1252
|
+
end
|
|
1253
|
+
end
|
|
1254
|
+
result
|
|
1255
|
+
end
|
|
1256
|
+
|
|
1225
1257
|
delegate_methods = AssociationReflection.public_instance_methods -
|
|
1226
1258
|
public_instance_methods
|
|
1227
1259
|
|
|
@@ -1238,7 +1270,7 @@ module ActiveRecord
|
|
|
1238
1270
|
@previous_reflection = previous_reflection
|
|
1239
1271
|
end
|
|
1240
1272
|
|
|
1241
|
-
def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
|
|
1273
|
+
def join_scopes(table, predicate_builder = nil, klass = self.klass, record = nil) # :nodoc:
|
|
1242
1274
|
scopes = super
|
|
1243
1275
|
unless @previous_reflection.through_reflection?
|
|
1244
1276
|
scopes += @previous_reflection.join_scopes(table, predicate_builder, klass, record)
|
|
@@ -5,11 +5,12 @@ module ActiveRecord
|
|
|
5
5
|
class BatchEnumerator
|
|
6
6
|
include Enumerable
|
|
7
7
|
|
|
8
|
-
def initialize(of: 1000, start: nil, finish: nil, relation:, order: :asc, use_ranges: nil) # :nodoc:
|
|
8
|
+
def initialize(of: 1000, start: nil, finish: nil, relation:, cursor:, order: :asc, use_ranges: nil) # :nodoc:
|
|
9
9
|
@of = of
|
|
10
10
|
@relation = relation
|
|
11
11
|
@start = start
|
|
12
12
|
@finish = finish
|
|
13
|
+
@cursor = cursor
|
|
13
14
|
@order = order
|
|
14
15
|
@use_ranges = use_ranges
|
|
15
16
|
end
|
|
@@ -52,7 +53,7 @@ module ActiveRecord
|
|
|
52
53
|
def each_record(&block)
|
|
53
54
|
return to_enum(:each_record) unless block_given?
|
|
54
55
|
|
|
55
|
-
@relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true, order: @order).each do |relation|
|
|
56
|
+
@relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true, cursor: @cursor, order: @order).each do |relation|
|
|
56
57
|
relation.records.each(&block)
|
|
57
58
|
end
|
|
58
59
|
end
|
|
@@ -105,7 +106,7 @@ module ActiveRecord
|
|
|
105
106
|
# relation.update_all(awesome: true)
|
|
106
107
|
# end
|
|
107
108
|
def each(&block)
|
|
108
|
-
enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false, order: @order, use_ranges: @use_ranges)
|
|
109
|
+
enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false, cursor: @cursor, order: @order, use_ranges: @use_ranges)
|
|
109
110
|
return enum.each(&block) if block_given?
|
|
110
111
|
enum
|
|
111
112
|
end
|
|
@@ -5,7 +5,7 @@ require "active_record/relation/batches/batch_enumerator"
|
|
|
5
5
|
module ActiveRecord
|
|
6
6
|
# = Active Record \Batches
|
|
7
7
|
module Batches
|
|
8
|
-
ORDER_IGNORE_MESSAGE = "Scoped order is ignored,
|
|
8
|
+
ORDER_IGNORE_MESSAGE = "Scoped order is ignored, use :cursor with :order to configure custom order."
|
|
9
9
|
DEFAULT_ORDER = :asc
|
|
10
10
|
|
|
11
11
|
# Looping through a collection of records from the database
|
|
@@ -35,11 +35,13 @@ module ActiveRecord
|
|
|
35
35
|
#
|
|
36
36
|
# ==== Options
|
|
37
37
|
# * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
|
|
38
|
-
# * <tt>:start</tt> - Specifies the
|
|
39
|
-
# * <tt>:finish</tt> - Specifies the
|
|
38
|
+
# * <tt>:start</tt> - Specifies the cursor column value to start from, inclusive of the value.
|
|
39
|
+
# * <tt>:finish</tt> - Specifies the cursor column value to end at, inclusive of the value.
|
|
40
40
|
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
|
41
41
|
# an order is present in the relation.
|
|
42
|
-
# * <tt>:
|
|
42
|
+
# * <tt>:cursor</tt> - Specifies the column to use for batching (can be a column name or an array
|
|
43
|
+
# of column names). Defaults to primary key.
|
|
44
|
+
# * <tt>:order</tt> - Specifies the cursor column order (can be +:asc+ or +:desc+ or an array consisting
|
|
43
45
|
# of :asc or :desc). Defaults to +:asc+.
|
|
44
46
|
#
|
|
45
47
|
# class Order < ActiveRecord::Base
|
|
@@ -71,20 +73,25 @@ module ActiveRecord
|
|
|
71
73
|
#
|
|
72
74
|
# NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
|
|
73
75
|
# ascending on the primary key ("id ASC").
|
|
74
|
-
# This also means that this method only works when the
|
|
76
|
+
# This also means that this method only works when the cursor column is
|
|
75
77
|
# orderable (e.g. an integer or string).
|
|
76
78
|
#
|
|
79
|
+
# NOTE: When using custom columns for batching, they should include at least one unique column
|
|
80
|
+
# (e.g. primary key) as a tiebreaker. Also, to reduce the likelihood of race conditions,
|
|
81
|
+
# all columns should be static (unchangeable after it was set).
|
|
82
|
+
#
|
|
77
83
|
# NOTE: By its nature, batch processing is subject to race conditions if
|
|
78
84
|
# other processes are modifying the database.
|
|
79
|
-
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: DEFAULT_ORDER, &block)
|
|
85
|
+
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER, &block)
|
|
80
86
|
if block_given?
|
|
81
|
-
find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do |records|
|
|
87
|
+
find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, cursor: cursor, order: order) do |records|
|
|
82
88
|
records.each(&block)
|
|
83
89
|
end
|
|
84
90
|
else
|
|
85
|
-
enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
|
|
91
|
+
enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, cursor: cursor, order: order) do
|
|
86
92
|
relation = self
|
|
87
|
-
|
|
93
|
+
cursor = Array(cursor)
|
|
94
|
+
apply_limits(relation, cursor, start, finish, build_batch_orders(cursor, order)).size
|
|
88
95
|
end
|
|
89
96
|
end
|
|
90
97
|
end
|
|
@@ -109,11 +116,13 @@ module ActiveRecord
|
|
|
109
116
|
#
|
|
110
117
|
# ==== Options
|
|
111
118
|
# * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
|
|
112
|
-
# * <tt>:start</tt> - Specifies the
|
|
113
|
-
# * <tt>:finish</tt> - Specifies the
|
|
119
|
+
# * <tt>:start</tt> - Specifies the cursor column value to start from, inclusive of the value.
|
|
120
|
+
# * <tt>:finish</tt> - Specifies the cursor column value to end at, inclusive of the value.
|
|
114
121
|
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
|
115
122
|
# an order is present in the relation.
|
|
116
|
-
# * <tt>:
|
|
123
|
+
# * <tt>:cursor</tt> - Specifies the column to use for batching (can be a column name or an array
|
|
124
|
+
# of column names). Defaults to primary key.
|
|
125
|
+
# * <tt>:order</tt> - Specifies the cursor column order (can be +:asc+ or +:desc+ or an array consisting
|
|
117
126
|
# of :asc or :desc). Defaults to +:asc+.
|
|
118
127
|
#
|
|
119
128
|
# class Order < ActiveRecord::Base
|
|
@@ -140,21 +149,26 @@ module ActiveRecord
|
|
|
140
149
|
#
|
|
141
150
|
# NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
|
|
142
151
|
# ascending on the primary key ("id ASC").
|
|
143
|
-
# This also means that this method only works when the
|
|
152
|
+
# This also means that this method only works when the cursor column is
|
|
144
153
|
# orderable (e.g. an integer or string).
|
|
145
154
|
#
|
|
155
|
+
# NOTE: When using custom columns for batching, they should include at least one unique column
|
|
156
|
+
# (e.g. primary key) as a tiebreaker. Also, to reduce the likelihood of race conditions,
|
|
157
|
+
# all columns should be static (unchangeable after it was set).
|
|
158
|
+
#
|
|
146
159
|
# NOTE: By its nature, batch processing is subject to race conditions if
|
|
147
160
|
# other processes are modifying the database.
|
|
148
|
-
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: DEFAULT_ORDER)
|
|
161
|
+
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER)
|
|
149
162
|
relation = self
|
|
150
163
|
unless block_given?
|
|
151
|
-
return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
|
|
152
|
-
|
|
164
|
+
return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, cursor: cursor, order: order) do
|
|
165
|
+
cursor = Array(cursor)
|
|
166
|
+
total = apply_limits(relation, cursor, start, finish, build_batch_orders(cursor, order)).size
|
|
153
167
|
(total - 1).div(batch_size) + 1
|
|
154
168
|
end
|
|
155
169
|
end
|
|
156
170
|
|
|
157
|
-
in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore, order: order) do |batch|
|
|
171
|
+
in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore, cursor: cursor, order: order) do |batch|
|
|
158
172
|
yield batch.to_a
|
|
159
173
|
end
|
|
160
174
|
end
|
|
@@ -183,11 +197,13 @@ module ActiveRecord
|
|
|
183
197
|
# ==== Options
|
|
184
198
|
# * <tt>:of</tt> - Specifies the size of the batch. Defaults to 1000.
|
|
185
199
|
# * <tt>:load</tt> - Specifies if the relation should be loaded. Defaults to false.
|
|
186
|
-
# * <tt>:start</tt> - Specifies the
|
|
187
|
-
# * <tt>:finish</tt> - Specifies the
|
|
200
|
+
# * <tt>:start</tt> - Specifies the cursor column value to start from, inclusive of the value.
|
|
201
|
+
# * <tt>:finish</tt> - Specifies the cursor column value to end at, inclusive of the value.
|
|
188
202
|
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
|
189
203
|
# an order is present in the relation.
|
|
190
|
-
# * <tt>:
|
|
204
|
+
# * <tt>:cursor</tt> - Specifies the column to use for batching (can be a column name or an array
|
|
205
|
+
# of column names). Defaults to primary key.
|
|
206
|
+
# * <tt>:order</tt> - Specifies the cursor column order (can be +:asc+ or +:desc+ or an array consisting
|
|
191
207
|
# of :asc or :desc). Defaults to +:asc+.
|
|
192
208
|
#
|
|
193
209
|
# class Order < ActiveRecord::Base
|
|
@@ -231,22 +247,25 @@ module ActiveRecord
|
|
|
231
247
|
#
|
|
232
248
|
# NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
|
|
233
249
|
# ascending on the primary key ("id ASC").
|
|
234
|
-
# This also means that this method only works when the
|
|
250
|
+
# This also means that this method only works when the cursor column is
|
|
235
251
|
# orderable (e.g. an integer or string).
|
|
236
252
|
#
|
|
253
|
+
# NOTE: When using custom columns for batching, they should include at least one unique column
|
|
254
|
+
# (e.g. primary key) as a tiebreaker. Also, to reduce the likelihood of race conditions,
|
|
255
|
+
# all columns should be static (unchangeable after it was set).
|
|
256
|
+
#
|
|
237
257
|
# NOTE: By its nature, batch processing is subject to race conditions if
|
|
238
258
|
# other processes are modifying the database.
|
|
239
|
-
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: DEFAULT_ORDER, use_ranges: nil, &block)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
end
|
|
259
|
+
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER, use_ranges: nil, &block)
|
|
260
|
+
cursor = Array(cursor).map(&:to_s)
|
|
261
|
+
ensure_valid_options_for_batching!(cursor, start, finish, order)
|
|
243
262
|
|
|
244
263
|
if arel.orders.present?
|
|
245
264
|
act_on_ignored_order(error_on_ignore)
|
|
246
265
|
end
|
|
247
266
|
|
|
248
267
|
unless block
|
|
249
|
-
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, order: order, use_ranges: use_ranges)
|
|
268
|
+
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, cursor: cursor, order: order, use_ranges: use_ranges)
|
|
250
269
|
end
|
|
251
270
|
|
|
252
271
|
batch_limit = of
|
|
@@ -261,6 +280,7 @@ module ActiveRecord
|
|
|
261
280
|
relation: self,
|
|
262
281
|
start: start,
|
|
263
282
|
finish: finish,
|
|
283
|
+
cursor: cursor,
|
|
264
284
|
order: order,
|
|
265
285
|
batch_limit: batch_limit,
|
|
266
286
|
&block
|
|
@@ -271,6 +291,7 @@ module ActiveRecord
|
|
|
271
291
|
start: start,
|
|
272
292
|
finish: finish,
|
|
273
293
|
load: load,
|
|
294
|
+
cursor: cursor,
|
|
274
295
|
order: order,
|
|
275
296
|
use_ranges: use_ranges,
|
|
276
297
|
remaining: remaining,
|
|
@@ -281,28 +302,51 @@ module ActiveRecord
|
|
|
281
302
|
end
|
|
282
303
|
|
|
283
304
|
private
|
|
284
|
-
def
|
|
285
|
-
|
|
286
|
-
|
|
305
|
+
def ensure_valid_options_for_batching!(cursor, start, finish, order)
|
|
306
|
+
if start && Array(start).size != cursor.size
|
|
307
|
+
raise ArgumentError, ":start must contain one value per cursor column"
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
if finish && Array(finish).size != cursor.size
|
|
311
|
+
raise ArgumentError, ":finish must contain one value per cursor column"
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
if (Array(primary_key) - cursor).any?
|
|
315
|
+
indexes = model.schema_cache.indexes(table_name)
|
|
316
|
+
unique_index = indexes.find { |index| index.unique && index.where.nil? && (Array(index.columns) - cursor).empty? }
|
|
317
|
+
|
|
318
|
+
unless unique_index
|
|
319
|
+
raise ArgumentError, ":cursor must include a primary key or other unique column(s)"
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
if (Array(order) - [:asc, :desc]).any?
|
|
324
|
+
raise ArgumentError, ":order must be :asc or :desc or an array consisting of :asc or :desc, got #{order.inspect}"
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def apply_limits(relation, cursor, start, finish, batch_orders)
|
|
329
|
+
relation = apply_start_limit(relation, cursor, start, batch_orders) if start
|
|
330
|
+
relation = apply_finish_limit(relation, cursor, finish, batch_orders) if finish
|
|
287
331
|
relation
|
|
288
332
|
end
|
|
289
333
|
|
|
290
|
-
def apply_start_limit(relation, start, batch_orders)
|
|
334
|
+
def apply_start_limit(relation, cursor, start, batch_orders)
|
|
291
335
|
operators = batch_orders.map do |_column, order|
|
|
292
336
|
order == :desc ? :lteq : :gteq
|
|
293
337
|
end
|
|
294
|
-
batch_condition(relation,
|
|
338
|
+
batch_condition(relation, cursor, start, operators)
|
|
295
339
|
end
|
|
296
340
|
|
|
297
|
-
def apply_finish_limit(relation, finish, batch_orders)
|
|
341
|
+
def apply_finish_limit(relation, cursor, finish, batch_orders)
|
|
298
342
|
operators = batch_orders.map do |_column, order|
|
|
299
343
|
order == :desc ? :gteq : :lteq
|
|
300
344
|
end
|
|
301
|
-
batch_condition(relation,
|
|
345
|
+
batch_condition(relation, cursor, finish, operators)
|
|
302
346
|
end
|
|
303
347
|
|
|
304
|
-
def batch_condition(relation,
|
|
305
|
-
cursor_positions =
|
|
348
|
+
def batch_condition(relation, cursor, values, operators)
|
|
349
|
+
cursor_positions = cursor.zip(Array(values), operators)
|
|
306
350
|
|
|
307
351
|
first_clause_column, first_clause_value, operator = cursor_positions.pop
|
|
308
352
|
where_clause = predicate_builder[first_clause_column, first_clause_value, operator]
|
|
@@ -316,9 +360,9 @@ module ActiveRecord
|
|
|
316
360
|
relation.where(where_clause)
|
|
317
361
|
end
|
|
318
362
|
|
|
319
|
-
def build_batch_orders(order)
|
|
320
|
-
|
|
321
|
-
[column,
|
|
363
|
+
def build_batch_orders(cursor, order)
|
|
364
|
+
cursor.zip(Array(order)).map do |column, order_|
|
|
365
|
+
[column, order_ || DEFAULT_ORDER]
|
|
322
366
|
end
|
|
323
367
|
end
|
|
324
368
|
|
|
@@ -327,34 +371,28 @@ module ActiveRecord
|
|
|
327
371
|
|
|
328
372
|
if raise_error
|
|
329
373
|
raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
|
|
330
|
-
elsif logger
|
|
331
|
-
logger.warn(ORDER_IGNORE_MESSAGE)
|
|
374
|
+
elsif model.logger
|
|
375
|
+
model.logger.warn(ORDER_IGNORE_MESSAGE)
|
|
332
376
|
end
|
|
333
377
|
end
|
|
334
378
|
|
|
335
|
-
def
|
|
336
|
-
Array(primary_key).zip(Array(order))
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
def batch_on_loaded_relation(relation:, start:, finish:, order:, batch_limit:)
|
|
379
|
+
def batch_on_loaded_relation(relation:, start:, finish:, cursor:, order:, batch_limit:)
|
|
340
380
|
records = relation.to_a
|
|
381
|
+
order = build_batch_orders(cursor, order).map(&:second)
|
|
341
382
|
|
|
342
383
|
if start || finish
|
|
343
384
|
records = records.filter do |record|
|
|
344
|
-
|
|
385
|
+
values = record_cursor_values(record, cursor)
|
|
345
386
|
|
|
346
|
-
|
|
347
|
-
(
|
|
348
|
-
else
|
|
349
|
-
(start.nil? || id <= start) && (finish.nil? || id >= finish)
|
|
350
|
-
end
|
|
387
|
+
(start.nil? || compare_values_for_order(values, Array(start), order) >= 0) &&
|
|
388
|
+
(finish.nil? || compare_values_for_order(values, Array(finish), order) <= 0)
|
|
351
389
|
end
|
|
352
390
|
end
|
|
353
391
|
|
|
354
|
-
records.
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
392
|
+
records.sort! do |record1, record2|
|
|
393
|
+
values1 = record_cursor_values(record1, cursor)
|
|
394
|
+
values2 = record_cursor_values(record2, cursor)
|
|
395
|
+
compare_values_for_order(values1, values2, order)
|
|
358
396
|
end
|
|
359
397
|
|
|
360
398
|
records.each_slice(batch_limit) do |subrecords|
|
|
@@ -367,44 +405,79 @@ module ActiveRecord
|
|
|
367
405
|
nil
|
|
368
406
|
end
|
|
369
407
|
|
|
370
|
-
def
|
|
371
|
-
|
|
408
|
+
def record_cursor_values(record, cursor)
|
|
409
|
+
record.attributes.slice(*cursor).values
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# This is a custom implementation of `<=>` operator,
|
|
413
|
+
# which also takes into account how the collection will be ordered.
|
|
414
|
+
def compare_values_for_order(values1, values2, order)
|
|
415
|
+
values1.each_with_index do |element1, index|
|
|
416
|
+
element2 = values2[index]
|
|
417
|
+
direction = order[index]
|
|
418
|
+
comparison = element1 <=> element2
|
|
419
|
+
comparison = -comparison if direction == :desc
|
|
420
|
+
return comparison if comparison != 0
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
0
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def batch_on_unloaded_relation(relation:, start:, finish:, load:, cursor:, order:, use_ranges:, remaining:, batch_limit:)
|
|
427
|
+
batch_orders = build_batch_orders(cursor, order)
|
|
372
428
|
relation = relation.reorder(batch_orders.to_h).limit(batch_limit)
|
|
373
|
-
relation = apply_limits(relation, start, finish, batch_orders)
|
|
429
|
+
relation = apply_limits(relation, cursor, start, finish, batch_orders)
|
|
374
430
|
relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
|
|
375
431
|
batch_relation = relation
|
|
376
|
-
empty_scope = to_sql ==
|
|
432
|
+
empty_scope = to_sql == model.unscoped.all.to_sql
|
|
377
433
|
|
|
378
434
|
loop do
|
|
379
435
|
if load
|
|
380
436
|
records = batch_relation.records
|
|
381
|
-
|
|
382
|
-
|
|
437
|
+
values = records.pluck(*cursor)
|
|
438
|
+
values_size = values.size
|
|
439
|
+
values_last = values.last
|
|
440
|
+
yielded_relation = rewhere(cursor => values)
|
|
383
441
|
yielded_relation.load_records(records)
|
|
384
442
|
elsif (empty_scope && use_ranges != false) || use_ranges
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
|
454
|
+
|
|
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)
|
|
389
458
|
yielded_relation = yielded_relation.except(:limit, :order)
|
|
390
459
|
yielded_relation.skip_query_cache!(false)
|
|
391
460
|
end
|
|
392
461
|
else
|
|
393
|
-
|
|
394
|
-
|
|
462
|
+
values = batch_relation.pluck(*cursor)
|
|
463
|
+
values_size = values.size
|
|
464
|
+
values_last = values.last
|
|
465
|
+
yielded_relation = rewhere(cursor => values)
|
|
395
466
|
end
|
|
396
467
|
|
|
397
|
-
break if
|
|
468
|
+
break if values_size == 0
|
|
398
469
|
|
|
399
|
-
|
|
400
|
-
|
|
470
|
+
if [values_last].flatten.any?(nil)
|
|
471
|
+
raise ArgumentError, "Not all of the batch cursor columns were included in the custom select clause "\
|
|
472
|
+
"or some columns contain nil."
|
|
473
|
+
end
|
|
401
474
|
|
|
402
475
|
yield yielded_relation
|
|
403
476
|
|
|
404
|
-
break if
|
|
477
|
+
break if values_size < batch_limit
|
|
405
478
|
|
|
406
479
|
if limit_value
|
|
407
|
-
remaining -=
|
|
480
|
+
remaining -= values_size
|
|
408
481
|
|
|
409
482
|
if remaining == 0
|
|
410
483
|
# Saves a useless iteration when the limit is a multiple of the
|
|
@@ -422,7 +495,8 @@ module ActiveRecord
|
|
|
422
495
|
end
|
|
423
496
|
operators << (last_order == :desc ? :lt : :gt)
|
|
424
497
|
|
|
425
|
-
|
|
498
|
+
cursor_value = values_last
|
|
499
|
+
batch_relation = batch_condition(relation, cursor, cursor_value, operators)
|
|
426
500
|
end
|
|
427
501
|
|
|
428
502
|
nil
|