activerecord 7.0.8.7 → 7.1.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1339 -1572
- data/MIT-LICENSE +1 -1
- data/README.rdoc +15 -16
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +18 -3
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +16 -11
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +10 -8
- data/lib/active_record/associations/preloader/association.rb +27 -6
- data/lib/active_record/associations/preloader.rb +12 -9
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +193 -97
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +40 -26
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +105 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +55 -9
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +10 -24
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +109 -32
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -122
- data/lib/active_record/connection_adapters/abstract/transaction.rb +280 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +502 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +200 -108
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
- data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -12
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -29
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -6
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +351 -54
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -168
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +42 -36
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +162 -77
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +71 -94
- data/lib/active_record/core.rb +128 -138
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +86 -33
- data/lib/active_record/delegated_type.rb +8 -3
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +36 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +2 -2
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +19 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +113 -26
- data/lib/active_record/errors.rb +89 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +119 -71
- data/lib/active_record/future_result.rb +30 -5
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +55 -8
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +118 -30
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +5 -7
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +100 -4
- data/lib/active_record/migration/compatibility.rb +131 -5
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration.rb +213 -109
- data/lib/active_record/model_schema.rb +47 -27
- data/lib/active_record/nested_attributes.rb +28 -3
- data/lib/active_record/normalization.rb +158 -0
- data/lib/active_record/persistence.rb +183 -33
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +77 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +107 -45
- data/lib/active_record/railties/controller_runtime.rb +10 -5
- data/lib/active_record/railties/databases.rake +139 -145
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +169 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +152 -63
- data/lib/active_record/relation/delegation.rb +22 -8
- data/lib/active_record/relation/finder_methods.rb +85 -15
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +351 -62
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +76 -35
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +10 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +41 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +7 -5
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +10 -1
- data/lib/active_record/tasks/database_tasks.rb +127 -105
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -7
- data/lib/active_record/test_fixtures.rb +113 -96
- data/lib/active_record/timestamp.rb +26 -14
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +36 -10
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +121 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +0 -8
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +52 -17
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -3,8 +3,10 @@
|
|
3
3
|
require "active_record/relation/batches/batch_enumerator"
|
4
4
|
|
5
5
|
module ActiveRecord
|
6
|
+
# = Active Record \Batches
|
6
7
|
module Batches
|
7
8
|
ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order."
|
9
|
+
DEFAULT_ORDER = :asc
|
8
10
|
|
9
11
|
# Looping through a collection of records from the database
|
10
12
|
# (using the Scoping::Named::ClassMethods.all method, for example)
|
@@ -37,7 +39,16 @@ module ActiveRecord
|
|
37
39
|
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
38
40
|
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
39
41
|
# an order is present in the relation.
|
40
|
-
# * <tt>:order</tt> - Specifies the primary key order (can be +:asc+ or +:desc+
|
42
|
+
# * <tt>:order</tt> - Specifies the primary key order (can be +:asc+ or +:desc+ or an array consisting
|
43
|
+
# of :asc or :desc). Defaults to +:asc+.
|
44
|
+
#
|
45
|
+
# class Order < ActiveRecord::Base
|
46
|
+
# self.primary_key = [:id_1, :id_2]
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# Order.find_each(order: [:asc, :desc])
|
50
|
+
#
|
51
|
+
# In the above code, +id_1+ is sorted in ascending order and +id_2+ in descending order.
|
41
52
|
#
|
42
53
|
# Limits are honored, and if present there is no requirement for the batch
|
43
54
|
# size: it can be less than, equal to, or greater than the limit.
|
@@ -65,7 +76,7 @@ module ActiveRecord
|
|
65
76
|
#
|
66
77
|
# NOTE: By its nature, batch processing is subject to race conditions if
|
67
78
|
# other processes are modifying the database.
|
68
|
-
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order:
|
79
|
+
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: DEFAULT_ORDER, &block)
|
69
80
|
if block_given?
|
70
81
|
find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do |records|
|
71
82
|
records.each(&block)
|
@@ -73,7 +84,7 @@ module ActiveRecord
|
|
73
84
|
else
|
74
85
|
enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
|
75
86
|
relation = self
|
76
|
-
apply_limits(relation, start, finish, order).size
|
87
|
+
apply_limits(relation, start, finish, build_batch_orders(order)).size
|
77
88
|
end
|
78
89
|
end
|
79
90
|
end
|
@@ -102,7 +113,16 @@ module ActiveRecord
|
|
102
113
|
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
103
114
|
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
104
115
|
# an order is present in the relation.
|
105
|
-
# * <tt>:order</tt> - Specifies the primary key order (can be +:asc+ or +:desc+
|
116
|
+
# * <tt>:order</tt> - Specifies the primary key order (can be +:asc+ or +:desc+ or an array consisting
|
117
|
+
# of :asc or :desc). Defaults to +:asc+.
|
118
|
+
#
|
119
|
+
# class Order < ActiveRecord::Base
|
120
|
+
# self.primary_key = [:id_1, :id_2]
|
121
|
+
# end
|
122
|
+
#
|
123
|
+
# Order.find_in_batches(order: [:asc, :desc])
|
124
|
+
#
|
125
|
+
# In the above code, +id_1+ is sorted in ascending order and +id_2+ in descending order.
|
106
126
|
#
|
107
127
|
# Limits are honored, and if present there is no requirement for the batch
|
108
128
|
# size: it can be less than, equal to, or greater than the limit.
|
@@ -125,11 +145,11 @@ module ActiveRecord
|
|
125
145
|
#
|
126
146
|
# NOTE: By its nature, batch processing is subject to race conditions if
|
127
147
|
# other processes are modifying the database.
|
128
|
-
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order:
|
148
|
+
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: DEFAULT_ORDER)
|
129
149
|
relation = self
|
130
150
|
unless block_given?
|
131
151
|
return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
|
132
|
-
total = apply_limits(relation, start, finish, order).size
|
152
|
+
total = apply_limits(relation, start, finish, build_batch_orders(order)).size
|
133
153
|
(total - 1).div(batch_size) + 1
|
134
154
|
end
|
135
155
|
end
|
@@ -167,7 +187,22 @@ module ActiveRecord
|
|
167
187
|
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
168
188
|
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
169
189
|
# an order is present in the relation.
|
170
|
-
# * <tt>:order</tt> - Specifies the primary key order (can be +:asc+ or +:desc+
|
190
|
+
# * <tt>:order</tt> - Specifies the primary key order (can be +:asc+ or +:desc+ or an array consisting
|
191
|
+
# of :asc or :desc). Defaults to +:asc+.
|
192
|
+
#
|
193
|
+
# class Order < ActiveRecord::Base
|
194
|
+
# self.primary_key = [:id_1, :id_2]
|
195
|
+
# end
|
196
|
+
#
|
197
|
+
# Order.in_batches(order: [:asc, :desc])
|
198
|
+
#
|
199
|
+
# In the above code, +id_1+ is sorted in ascending order and +id_2+ in descending order.
|
200
|
+
#
|
201
|
+
# * <tt>:use_ranges</tt> - Specifies whether to use range iteration (id >= x AND id <= y).
|
202
|
+
# It can make iterating over the whole or almost whole tables several times faster.
|
203
|
+
# Only whole table iterations use this style of iteration by default. You can disable this behavior by passing +false+.
|
204
|
+
# If you iterate over the table and the only condition is, e.g., <tt>archived_at: nil</tt> (and only a tiny fraction
|
205
|
+
# of the records are archived), it makes sense to opt in to this approach.
|
171
206
|
#
|
172
207
|
# Limits are honored, and if present there is no requirement for the batch
|
173
208
|
# size, it can be less than, equal, or greater than the limit.
|
@@ -201,14 +236,13 @@ module ActiveRecord
|
|
201
236
|
#
|
202
237
|
# NOTE: By its nature, batch processing is subject to race conditions if
|
203
238
|
# other processes are modifying the database.
|
204
|
-
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: :
|
205
|
-
|
206
|
-
|
207
|
-
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
|
239
|
+
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: DEFAULT_ORDER, use_ranges: nil, &block)
|
240
|
+
unless Array(order).all? { |ord| [:asc, :desc].include?(ord) }
|
241
|
+
raise ArgumentError, ":order must be :asc or :desc or an array consisting of :asc or :desc, got #{order.inspect}"
|
208
242
|
end
|
209
243
|
|
210
|
-
unless
|
211
|
-
|
244
|
+
unless block
|
245
|
+
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, order: order, use_ranges: use_ranges)
|
212
246
|
end
|
213
247
|
|
214
248
|
if arel.orders.present?
|
@@ -216,71 +250,76 @@ module ActiveRecord
|
|
216
250
|
end
|
217
251
|
|
218
252
|
batch_limit = of
|
253
|
+
|
219
254
|
if limit_value
|
220
255
|
remaining = limit_value
|
221
256
|
batch_limit = remaining if remaining < batch_limit
|
222
257
|
end
|
223
258
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
yield yielded_relation
|
246
|
-
|
247
|
-
break if ids.length < batch_limit
|
248
|
-
|
249
|
-
if limit_value
|
250
|
-
remaining -= ids.length
|
251
|
-
|
252
|
-
if remaining == 0
|
253
|
-
# Saves a useless iteration when the limit is a multiple of the
|
254
|
-
# batch size.
|
255
|
-
break
|
256
|
-
elsif remaining < batch_limit
|
257
|
-
relation = relation.limit(remaining)
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
batch_relation = relation.where(
|
262
|
-
predicate_builder[primary_key, primary_key_offset, order == :desc ? :lt : :gt]
|
259
|
+
if self.loaded?
|
260
|
+
batch_on_loaded_relation(
|
261
|
+
relation: self,
|
262
|
+
start: start,
|
263
|
+
finish: finish,
|
264
|
+
order: order,
|
265
|
+
batch_limit: batch_limit,
|
266
|
+
&block
|
267
|
+
)
|
268
|
+
else
|
269
|
+
batch_on_unloaded_relation(
|
270
|
+
relation: self,
|
271
|
+
start: start,
|
272
|
+
finish: finish,
|
273
|
+
load: load,
|
274
|
+
order: order,
|
275
|
+
use_ranges: use_ranges,
|
276
|
+
remaining: remaining,
|
277
|
+
batch_limit: batch_limit,
|
278
|
+
&block
|
263
279
|
)
|
264
280
|
end
|
265
281
|
end
|
266
282
|
|
267
283
|
private
|
268
|
-
def apply_limits(relation, start, finish,
|
269
|
-
relation = apply_start_limit(relation, start,
|
270
|
-
relation = apply_finish_limit(relation, finish,
|
284
|
+
def apply_limits(relation, start, finish, batch_orders)
|
285
|
+
relation = apply_start_limit(relation, start, batch_orders) if start
|
286
|
+
relation = apply_finish_limit(relation, finish, batch_orders) if finish
|
271
287
|
relation
|
272
288
|
end
|
273
289
|
|
274
|
-
def apply_start_limit(relation, start,
|
275
|
-
|
290
|
+
def apply_start_limit(relation, start, batch_orders)
|
291
|
+
operators = batch_orders.map do |_column, order|
|
292
|
+
order == :desc ? :lteq : :gteq
|
293
|
+
end
|
294
|
+
batch_condition(relation, primary_key, start, operators)
|
295
|
+
end
|
296
|
+
|
297
|
+
def apply_finish_limit(relation, finish, batch_orders)
|
298
|
+
operators = batch_orders.map do |_column, order|
|
299
|
+
order == :desc ? :gteq : :lteq
|
300
|
+
end
|
301
|
+
batch_condition(relation, primary_key, finish, operators)
|
276
302
|
end
|
277
303
|
|
278
|
-
def
|
279
|
-
|
304
|
+
def batch_condition(relation, columns, values, operators)
|
305
|
+
cursor_positions = Array(columns).zip(Array(values), operators)
|
306
|
+
|
307
|
+
first_clause_column, first_clause_value, operator = cursor_positions.pop
|
308
|
+
where_clause = predicate_builder[first_clause_column, first_clause_value, operator]
|
309
|
+
|
310
|
+
cursor_positions.reverse_each do |column_name, value, operator|
|
311
|
+
where_clause = predicate_builder[column_name, value, operator == :lteq ? :lt : :gt].or(
|
312
|
+
predicate_builder[column_name, value, :eq].and(where_clause)
|
313
|
+
)
|
314
|
+
end
|
315
|
+
|
316
|
+
relation.where(where_clause)
|
280
317
|
end
|
281
318
|
|
282
|
-
def
|
283
|
-
|
319
|
+
def build_batch_orders(order)
|
320
|
+
get_the_order_of_primary_key(order).map do |column, ord|
|
321
|
+
[column, ord || DEFAULT_ORDER]
|
322
|
+
end
|
284
323
|
end
|
285
324
|
|
286
325
|
def act_on_ignored_order(error_on_ignore)
|
@@ -292,5 +331,95 @@ module ActiveRecord
|
|
292
331
|
logger.warn(ORDER_IGNORE_MESSAGE)
|
293
332
|
end
|
294
333
|
end
|
334
|
+
|
335
|
+
def get_the_order_of_primary_key(order)
|
336
|
+
Array(primary_key).zip(Array(order))
|
337
|
+
end
|
338
|
+
|
339
|
+
def batch_on_loaded_relation(relation:, start:, finish:, order:, batch_limit:)
|
340
|
+
records = relation.to_a
|
341
|
+
|
342
|
+
if start || finish
|
343
|
+
records = records.filter do |record|
|
344
|
+
(start.nil? || record.id >= start) && (finish.nil? || record.id <= finish)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
records = records.sort_by { |record| record.id }
|
349
|
+
|
350
|
+
if order == :desc
|
351
|
+
records.reverse!
|
352
|
+
end
|
353
|
+
|
354
|
+
(0...records.size).step(batch_limit).each do |start|
|
355
|
+
subrelation = relation.spawn
|
356
|
+
subrelation.load_records(records[start, batch_limit])
|
357
|
+
|
358
|
+
yield subrelation
|
359
|
+
end
|
360
|
+
|
361
|
+
nil
|
362
|
+
end
|
363
|
+
|
364
|
+
def batch_on_unloaded_relation(relation:, start:, finish:, load:, order:, use_ranges:, remaining:, batch_limit:)
|
365
|
+
batch_orders = build_batch_orders(order)
|
366
|
+
relation = relation.reorder(batch_orders.to_h).limit(batch_limit)
|
367
|
+
relation = apply_limits(relation, start, finish, batch_orders)
|
368
|
+
relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
|
369
|
+
batch_relation = relation
|
370
|
+
empty_scope = to_sql == klass.unscoped.all.to_sql
|
371
|
+
|
372
|
+
loop do
|
373
|
+
if load
|
374
|
+
records = batch_relation.records
|
375
|
+
ids = records.map(&:id)
|
376
|
+
yielded_relation = where(primary_key => ids)
|
377
|
+
yielded_relation.load_records(records)
|
378
|
+
elsif (empty_scope && use_ranges != false) || use_ranges
|
379
|
+
ids = batch_relation.ids
|
380
|
+
finish = ids.last
|
381
|
+
if finish
|
382
|
+
yielded_relation = apply_finish_limit(batch_relation, finish, batch_orders)
|
383
|
+
yielded_relation = yielded_relation.except(:limit, :order)
|
384
|
+
yielded_relation.skip_query_cache!(false)
|
385
|
+
end
|
386
|
+
else
|
387
|
+
ids = batch_relation.ids
|
388
|
+
yielded_relation = where(primary_key => ids)
|
389
|
+
end
|
390
|
+
|
391
|
+
break if ids.empty?
|
392
|
+
|
393
|
+
primary_key_offset = ids.last
|
394
|
+
raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
|
395
|
+
|
396
|
+
yield yielded_relation
|
397
|
+
|
398
|
+
break if ids.length < batch_limit
|
399
|
+
|
400
|
+
if limit_value
|
401
|
+
remaining -= ids.length
|
402
|
+
|
403
|
+
if remaining == 0
|
404
|
+
# Saves a useless iteration when the limit is a multiple of the
|
405
|
+
# batch size.
|
406
|
+
break
|
407
|
+
elsif remaining < batch_limit
|
408
|
+
relation = relation.limit(remaining)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
batch_orders_copy = batch_orders.dup
|
413
|
+
_last_column, last_order = batch_orders_copy.pop
|
414
|
+
operators = batch_orders_copy.map do |_column, order|
|
415
|
+
order == :desc ? :lteq : :gteq
|
416
|
+
end
|
417
|
+
operators << (last_order == :desc ? :lt : :gt)
|
418
|
+
|
419
|
+
batch_relation = batch_condition(relation, primary_key, primary_key_offset, operators)
|
420
|
+
end
|
421
|
+
|
422
|
+
nil
|
423
|
+
end
|
295
424
|
end
|
296
425
|
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "active_support/core_ext/enumerable"
|
4
4
|
|
5
5
|
module ActiveRecord
|
6
|
+
# = Active Record \Calculations
|
6
7
|
module Calculations
|
7
8
|
class ColumnAliasTracker # :nodoc:
|
8
9
|
def initialize(connection)
|
@@ -71,8 +72,7 @@ module ActiveRecord
|
|
71
72
|
# of each key would be the #count.
|
72
73
|
#
|
73
74
|
# Article.group(:status, :category).count
|
74
|
-
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
|
75
|
-
# # ["published", "business"]=>0, ["published", "technology"]=>2}
|
75
|
+
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
|
76
76
|
#
|
77
77
|
# If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
|
78
78
|
#
|
@@ -93,6 +93,11 @@ module ActiveRecord
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
|
+
# Same as <tt>#count</tt> but perform the query asynchronously and returns an ActiveRecord::Promise
|
97
|
+
def async_count(column_name = nil)
|
98
|
+
async.count(column_name)
|
99
|
+
end
|
100
|
+
|
96
101
|
# Calculates the average value on a given column. Returns +nil+ if there's
|
97
102
|
# no row. See #calculate for examples with options.
|
98
103
|
#
|
@@ -101,6 +106,11 @@ module ActiveRecord
|
|
101
106
|
calculate(:average, column_name)
|
102
107
|
end
|
103
108
|
|
109
|
+
# Same as <tt>#average</tt> but perform the query asynchronously and returns an ActiveRecord::Promise
|
110
|
+
def async_average(column_name)
|
111
|
+
async.average(column_name)
|
112
|
+
end
|
113
|
+
|
104
114
|
# Calculates the minimum value on a given column. The value is returned
|
105
115
|
# with the same data type of the column, or +nil+ if there's no row. See
|
106
116
|
# #calculate for examples with options.
|
@@ -110,6 +120,11 @@ module ActiveRecord
|
|
110
120
|
calculate(:minimum, column_name)
|
111
121
|
end
|
112
122
|
|
123
|
+
# Same as <tt>#minimum</tt> but perform the query asynchronously and returns an ActiveRecord::Promise
|
124
|
+
def async_minimum(column_name)
|
125
|
+
async.minimum(column_name)
|
126
|
+
end
|
127
|
+
|
113
128
|
# Calculates the maximum value on a given column. The value is returned
|
114
129
|
# with the same data type of the column, or +nil+ if there's no row. See
|
115
130
|
# #calculate for examples with options.
|
@@ -119,32 +134,29 @@ module ActiveRecord
|
|
119
134
|
calculate(:maximum, column_name)
|
120
135
|
end
|
121
136
|
|
137
|
+
# Same as <tt>#maximum</tt> but perform the query asynchronously and returns an ActiveRecord::Promise
|
138
|
+
def async_maximum(column_name)
|
139
|
+
async.maximum(column_name)
|
140
|
+
end
|
141
|
+
|
122
142
|
# Calculates the sum of values on a given column. The value is returned
|
123
143
|
# with the same data type of the column, +0+ if there's no row. See
|
124
144
|
# #calculate for examples with options.
|
125
145
|
#
|
126
146
|
# Person.sum(:age) # => 4562
|
127
|
-
def sum(
|
147
|
+
def sum(initial_value_or_column = 0, &block)
|
128
148
|
if block_given?
|
129
|
-
|
130
|
-
if identity_or_column.nil? && (values.first.is_a?(Numeric) || values.first(1) == [] || values.first.respond_to?(:coerce))
|
131
|
-
identity_or_column = 0
|
132
|
-
end
|
133
|
-
|
134
|
-
if identity_or_column.nil?
|
135
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
136
|
-
Rails 7.0 has deprecated Enumerable.sum in favor of Ruby's native implementation available since 2.4.
|
137
|
-
Sum of non-numeric elements requires an initial argument.
|
138
|
-
MSG
|
139
|
-
values.inject(:+) || 0
|
140
|
-
else
|
141
|
-
values.sum(identity_or_column)
|
142
|
-
end
|
149
|
+
map(&block).sum(initial_value_or_column)
|
143
150
|
else
|
144
|
-
calculate(:sum,
|
151
|
+
calculate(:sum, initial_value_or_column)
|
145
152
|
end
|
146
153
|
end
|
147
154
|
|
155
|
+
# Same as <tt>#sum</tt> but perform the query asynchronously and returns an ActiveRecord::Promise
|
156
|
+
def async_sum(identity_or_column = nil)
|
157
|
+
async.sum(identity_or_column)
|
158
|
+
end
|
159
|
+
|
148
160
|
# This calculates aggregate values in the given column. Methods for #count, #sum, #average,
|
149
161
|
# #minimum, and #maximum have been added as shortcuts.
|
150
162
|
#
|
@@ -177,10 +189,23 @@ module ActiveRecord
|
|
177
189
|
# ...
|
178
190
|
# end
|
179
191
|
def calculate(operation, column_name)
|
192
|
+
operation = operation.to_s.downcase
|
193
|
+
|
194
|
+
if @none
|
195
|
+
case operation
|
196
|
+
when "count", "sum"
|
197
|
+
result = group_values.any? ? Hash.new : 0
|
198
|
+
return @async ? Promise::Complete.new(result) : result
|
199
|
+
when "average", "minimum", "maximum"
|
200
|
+
result = group_values.any? ? Hash.new : nil
|
201
|
+
return @async ? Promise::Complete.new(result) : result
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
180
205
|
if has_include?(column_name)
|
181
206
|
relation = apply_join_dependency
|
182
207
|
|
183
|
-
if operation
|
208
|
+
if operation == "count"
|
184
209
|
unless distinct_value || distinct_select?(column_name || select_for_count)
|
185
210
|
relation.distinct!
|
186
211
|
relation.select_values = [ klass.primary_key || table[Arel.star] ]
|
@@ -229,31 +254,44 @@ module ActiveRecord
|
|
229
254
|
# # => ['0', '27761', '173']
|
230
255
|
#
|
231
256
|
# See also #ids.
|
232
|
-
#
|
233
257
|
def pluck(*column_names)
|
258
|
+
return [] if @none
|
259
|
+
|
234
260
|
if loaded? && all_attributes?(column_names)
|
235
|
-
|
261
|
+
result = records.pluck(*column_names)
|
262
|
+
if @async
|
263
|
+
return Promise::Complete.new(result)
|
264
|
+
else
|
265
|
+
return result
|
266
|
+
end
|
236
267
|
end
|
237
268
|
|
238
269
|
if has_include?(column_names.first)
|
239
270
|
relation = apply_join_dependency
|
240
271
|
relation.pluck(*column_names)
|
241
272
|
else
|
242
|
-
klass.disallow_raw_sql!(column_names)
|
273
|
+
klass.disallow_raw_sql!(column_names.flatten)
|
243
274
|
columns = arel_columns(column_names)
|
244
275
|
relation = spawn
|
245
276
|
relation.select_values = columns
|
246
277
|
result = skip_query_cache_if_necessary do
|
247
278
|
if where_clause.contradiction?
|
248
|
-
ActiveRecord::Result.empty
|
279
|
+
ActiveRecord::Result.empty(async: @async)
|
249
280
|
else
|
250
|
-
klass.connection.select_all(relation.arel, "#{klass.name} Pluck")
|
281
|
+
klass.connection.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
|
251
282
|
end
|
252
283
|
end
|
253
|
-
|
284
|
+
result.then do |result|
|
285
|
+
type_cast_pluck_values(result, columns)
|
286
|
+
end
|
254
287
|
end
|
255
288
|
end
|
256
289
|
|
290
|
+
# Same as <tt>#pluck</tt> but perform the query asynchronously and returns an ActiveRecord::Promise
|
291
|
+
def async_pluck(*column_names)
|
292
|
+
async.pluck(*column_names)
|
293
|
+
end
|
294
|
+
|
257
295
|
# Pick the value(s) from the named column(s) in the current relation.
|
258
296
|
# This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
|
259
297
|
# when you have a relation that's already narrowed down to a single row.
|
@@ -270,18 +308,59 @@ module ActiveRecord
|
|
270
308
|
# # => [ 'David', 'david@loudthinking.com' ]
|
271
309
|
def pick(*column_names)
|
272
310
|
if loaded? && all_attributes?(column_names)
|
273
|
-
|
311
|
+
result = records.pick(*column_names)
|
312
|
+
return @async ? Promise::Complete.new(result) : result
|
274
313
|
end
|
275
314
|
|
276
|
-
limit(1).pluck(*column_names).first
|
315
|
+
limit(1).pluck(*column_names).then(&:first)
|
277
316
|
end
|
278
317
|
|
279
|
-
#
|
318
|
+
# Same as <tt>#pick</tt> but perform the query asynchronously and returns an ActiveRecord::Promise
|
319
|
+
def async_pick(*column_names)
|
320
|
+
async.pick(*column_names)
|
321
|
+
end
|
322
|
+
|
323
|
+
# Returns the base model's ID's for the relation using the table's primary key
|
280
324
|
#
|
281
325
|
# Person.ids # SELECT people.id FROM people
|
282
|
-
# Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.
|
326
|
+
# Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
|
283
327
|
def ids
|
284
|
-
|
328
|
+
primary_key_array = Array(primary_key)
|
329
|
+
|
330
|
+
if loaded?
|
331
|
+
result = records.map do |record|
|
332
|
+
if primary_key_array.one?
|
333
|
+
record._read_attribute(primary_key_array.first)
|
334
|
+
else
|
335
|
+
primary_key_array.map { |column| record._read_attribute(column) }
|
336
|
+
end
|
337
|
+
end
|
338
|
+
return @async ? Promise::Complete.new(result) : result
|
339
|
+
end
|
340
|
+
|
341
|
+
if has_include?(primary_key)
|
342
|
+
relation = apply_join_dependency.group(*primary_key_array)
|
343
|
+
return relation.ids
|
344
|
+
end
|
345
|
+
|
346
|
+
columns = arel_columns(primary_key_array)
|
347
|
+
relation = spawn
|
348
|
+
relation.select_values = columns
|
349
|
+
|
350
|
+
result = if relation.where_clause.contradiction?
|
351
|
+
ActiveRecord::Result.empty
|
352
|
+
else
|
353
|
+
skip_query_cache_if_necessary do
|
354
|
+
klass.connection.select_all(relation, "#{klass.name} Ids", async: @async)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
result.then { |result| type_cast_pluck_values(result, columns) }
|
359
|
+
end
|
360
|
+
|
361
|
+
# Same as <tt>#ids</tt> but perform the query asynchronously and returns an ActiveRecord::Promise
|
362
|
+
def async_ids
|
363
|
+
async.ids
|
285
364
|
end
|
286
365
|
|
287
366
|
private
|
@@ -341,6 +420,7 @@ module ActiveRecord
|
|
341
420
|
# Shortcut when limit is zero.
|
342
421
|
return 0 if limit_value == 0
|
343
422
|
|
423
|
+
relation = self
|
344
424
|
query_builder = build_count_subquery(spawn, column_name, distinct)
|
345
425
|
else
|
346
426
|
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
@@ -355,15 +435,23 @@ module ActiveRecord
|
|
355
435
|
query_builder = relation.arel
|
356
436
|
end
|
357
437
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
438
|
+
query_result = if relation.where_clause.contradiction?
|
439
|
+
ActiveRecord::Result.empty
|
440
|
+
else
|
441
|
+
skip_query_cache_if_necessary do
|
442
|
+
@klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
|
443
|
+
end
|
364
444
|
end
|
365
445
|
|
366
|
-
|
446
|
+
query_result.then do |result|
|
447
|
+
if operation != "count"
|
448
|
+
type = column.try(:type_caster) ||
|
449
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
450
|
+
type = type.subtype if Enum::EnumType === type
|
451
|
+
end
|
452
|
+
|
453
|
+
type_cast_calculated_value(result.cast_values.first, operation, type)
|
454
|
+
end
|
367
455
|
end
|
368
456
|
|
369
457
|
def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
|
@@ -406,39 +494,40 @@ module ActiveRecord
|
|
406
494
|
relation.group_values = group_fields
|
407
495
|
relation.select_values = select_values
|
408
496
|
|
409
|
-
|
497
|
+
result = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async) }
|
498
|
+
result.then do |calculated_data|
|
499
|
+
if association
|
500
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
501
|
+
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
502
|
+
key_records = key_records.index_by(&:id)
|
503
|
+
end
|
410
504
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
505
|
+
key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
|
506
|
+
types[aliaz] = col_name.try(:type_caster) ||
|
507
|
+
type_for(col_name) do
|
508
|
+
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
509
|
+
end
|
510
|
+
end
|
416
511
|
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
512
|
+
hash_rows = calculated_data.cast_values(key_types).map! do |row|
|
513
|
+
calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
|
514
|
+
hash[col_name] = row[i]
|
421
515
|
end
|
422
|
-
end
|
423
|
-
|
424
|
-
hash_rows = calculated_data.cast_values(key_types).map! do |row|
|
425
|
-
calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
|
426
|
-
hash[col_name] = row[i]
|
427
516
|
end
|
428
|
-
end
|
429
517
|
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
518
|
+
if operation != "count"
|
519
|
+
type = column.try(:type_caster) ||
|
520
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
521
|
+
type = type.subtype if Enum::EnumType === type
|
522
|
+
end
|
435
523
|
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
524
|
+
hash_rows.each_with_object({}) do |row, result|
|
525
|
+
key = group_aliases.map { |aliaz| row[aliaz] }
|
526
|
+
key = key.first if key.size == 1
|
527
|
+
key = key_records[key] if associated
|
440
528
|
|
441
|
-
|
529
|
+
result[key] = type_cast_calculated_value(row[column_alias], operation, type)
|
530
|
+
end
|
442
531
|
end
|
443
532
|
end
|
444
533
|
|