activerecord 5.0.7.2 → 5.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +389 -2252
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/examples/performance.rb +28 -28
- data/examples/simple.rb +3 -3
- data/lib/active_record.rb +20 -20
- data/lib/active_record/aggregations.rb +244 -244
- data/lib/active_record/association_relation.rb +5 -5
- data/lib/active_record/associations.rb +1579 -1569
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +23 -15
- data/lib/active_record/associations/association_scope.rb +83 -81
- data/lib/active_record/associations/belongs_to_association.rb +0 -1
- data/lib/active_record/associations/builder/belongs_to.rb +16 -14
- data/lib/active_record/associations/builder/collection_association.rb +1 -2
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +27 -27
- data/lib/active_record/associations/collection_association.rb +74 -241
- data/lib/active_record/associations/collection_proxy.rb +144 -70
- data/lib/active_record/associations/has_many_association.rb +15 -19
- data/lib/active_record/associations/has_many_through_association.rb +12 -5
- data/lib/active_record/associations/has_one_association.rb +22 -28
- data/lib/active_record/associations/has_one_through_association.rb +5 -1
- data/lib/active_record/associations/join_dependency.rb +117 -115
- data/lib/active_record/associations/join_dependency/join_association.rb +16 -13
- data/lib/active_record/associations/join_dependency/join_base.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/preloader.rb +94 -94
- data/lib/active_record/associations/preloader/association.rb +87 -64
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -2
- data/lib/active_record/associations/preloader/collection_association.rb +6 -6
- data/lib/active_record/associations/preloader/has_many.rb +0 -2
- data/lib/active_record/associations/preloader/singular_association.rb +6 -8
- data/lib/active_record/associations/preloader/through_association.rb +34 -41
- data/lib/active_record/associations/singular_association.rb +8 -25
- data/lib/active_record/associations/through_association.rb +3 -6
- data/lib/active_record/attribute.rb +98 -71
- data/lib/active_record/attribute/user_provided_default.rb +4 -2
- data/lib/active_record/attribute_assignment.rb +61 -61
- data/lib/active_record/attribute_decorators.rb +35 -13
- data/lib/active_record/attribute_methods.rb +56 -65
- data/lib/active_record/attribute_methods/before_type_cast.rb +7 -7
- data/lib/active_record/attribute_methods/dirty.rb +216 -34
- data/lib/active_record/attribute_methods/primary_key.rb +78 -73
- data/lib/active_record/attribute_methods/read.rb +39 -35
- data/lib/active_record/attribute_methods/serialization.rb +7 -7
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +35 -58
- data/lib/active_record/attribute_methods/write.rb +36 -30
- data/lib/active_record/attribute_mutation_tracker.rb +53 -10
- data/lib/active_record/attribute_set.rb +9 -6
- data/lib/active_record/attribute_set/builder.rb +41 -49
- data/lib/active_record/attribute_set/yaml_encoder.rb +41 -0
- data/lib/active_record/attributes.rb +21 -21
- data/lib/active_record/autosave_association.rb +13 -13
- data/lib/active_record/base.rb +24 -22
- data/lib/active_record/callbacks.rb +52 -14
- data/lib/active_record/coders/yaml_column.rb +9 -11
- data/lib/active_record/collection_cache_key.rb +6 -17
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +320 -278
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -34
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -27
- data/lib/active_record/connection_adapters/abstract/quoting.rb +44 -57
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +9 -19
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +78 -79
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +53 -41
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +99 -93
- data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +156 -128
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +424 -382
- data/lib/active_record/connection_adapters/column.rb +27 -5
- data/lib/active_record/connection_adapters/connection_specification.rb +128 -118
- data/lib/active_record/connection_adapters/mysql/column.rb +6 -31
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -43
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +22 -22
- data/lib/active_record/connection_adapters/mysql/quoting.rb +6 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +49 -45
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +16 -19
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +49 -31
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +5 -6
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +24 -26
- data/lib/active_record/connection_adapters/postgresql/column.rb +1 -28
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -35
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid.rb +22 -21
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +9 -9
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +16 -16
- data/lib/active_record/connection_adapters/postgresql/oid/{rails_5_1_point.rb → legacy_point.rb} +9 -16
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +28 -8
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +28 -30
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +51 -51
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +38 -36
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +37 -24
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +19 -23
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +161 -170
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -7
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +179 -152
- data/lib/active_record/connection_adapters/schema_cache.rb +16 -7
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +3 -3
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +16 -20
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +1 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +28 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +187 -130
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -7
- data/lib/active_record/connection_handling.rb +14 -26
- data/lib/active_record/core.rb +110 -93
- data/lib/active_record/counter_cache.rb +62 -13
- data/lib/active_record/define_callbacks.rb +20 -0
- data/lib/active_record/dynamic_matchers.rb +80 -79
- data/lib/active_record/enum.rb +8 -6
- data/lib/active_record/errors.rb +58 -15
- data/lib/active_record/explain.rb +1 -2
- data/lib/active_record/explain_registry.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +7 -4
- data/lib/active_record/fixture_set/file.rb +11 -8
- data/lib/active_record/fixtures.rb +66 -53
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +93 -79
- data/lib/active_record/integration.rb +7 -7
- data/lib/active_record/internal_metadata.rb +3 -16
- data/lib/active_record/legacy_yaml_adapter.rb +1 -1
- data/lib/active_record/locking/optimistic.rb +64 -56
- data/lib/active_record/locking/pessimistic.rb +10 -1
- data/lib/active_record/log_subscriber.rb +29 -29
- data/lib/active_record/migration.rb +155 -172
- data/lib/active_record/migration/command_recorder.rb +94 -94
- data/lib/active_record/migration/compatibility.rb +76 -37
- data/lib/active_record/migration/join_table.rb +6 -6
- data/lib/active_record/model_schema.rb +85 -119
- data/lib/active_record/nested_attributes.rb +200 -199
- data/lib/active_record/null_relation.rb +10 -33
- data/lib/active_record/persistence.rb +45 -38
- data/lib/active_record/query_cache.rb +4 -8
- data/lib/active_record/querying.rb +2 -3
- data/lib/active_record/railtie.rb +16 -17
- data/lib/active_record/railties/controller_runtime.rb +6 -2
- data/lib/active_record/railties/databases.rake +125 -140
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +2 -2
- data/lib/active_record/reflection.rb +79 -96
- data/lib/active_record/relation.rb +72 -115
- data/lib/active_record/relation/batches.rb +87 -58
- data/lib/active_record/relation/batches/batch_enumerator.rb +1 -1
- data/lib/active_record/relation/calculations.rb +154 -160
- data/lib/active_record/relation/delegation.rb +30 -29
- data/lib/active_record/relation/finder_methods.rb +195 -226
- data/lib/active_record/relation/merger.rb +58 -62
- data/lib/active_record/relation/predicate_builder.rb +92 -89
- data/lib/active_record/relation/predicate_builder/array_handler.rb +7 -5
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +23 -23
- data/lib/active_record/relation/predicate_builder/base_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +0 -8
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +12 -10
- data/lib/active_record/relation/predicate_builder/range_handler.rb +0 -8
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +247 -295
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +4 -5
- data/lib/active_record/relation/where_clause.rb +79 -65
- data/lib/active_record/relation/where_clause_factory.rb +47 -8
- data/lib/active_record/result.rb +29 -31
- data/lib/active_record/runtime_registry.rb +3 -3
- data/lib/active_record/sanitization.rb +182 -197
- data/lib/active_record/schema.rb +3 -3
- data/lib/active_record/schema_dumper.rb +14 -37
- data/lib/active_record/schema_migration.rb +3 -3
- data/lib/active_record/scoping.rb +9 -10
- data/lib/active_record/scoping/default.rb +87 -91
- data/lib/active_record/scoping/named.rb +16 -28
- data/lib/active_record/secure_token.rb +2 -2
- data/lib/active_record/statement_cache.rb +13 -15
- data/lib/active_record/store.rb +31 -32
- data/lib/active_record/suppressor.rb +2 -1
- data/lib/active_record/table_metadata.rb +9 -5
- data/lib/active_record/tasks/database_tasks.rb +72 -65
- data/lib/active_record/tasks/mysql_database_tasks.rb +75 -72
- data/lib/active_record/tasks/postgresql_database_tasks.rb +53 -48
- data/lib/active_record/tasks/sqlite_database_tasks.rb +18 -16
- data/lib/active_record/timestamp.rb +39 -25
- data/lib/active_record/touch_later.rb +1 -2
- data/lib/active_record/transactions.rb +98 -110
- data/lib/active_record/type.rb +17 -13
- data/lib/active_record/type/adapter_specific_registry.rb +46 -42
- data/lib/active_record/type/decimal_without_scale.rb +9 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +3 -3
- data/lib/active_record/type/serialized.rb +8 -8
- data/lib/active_record/type/text.rb +9 -0
- data/lib/active_record/type/time.rb +0 -1
- data/lib/active_record/type/type_map.rb +11 -15
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type_caster.rb +2 -2
- data/lib/active_record/type_caster/connection.rb +8 -6
- data/lib/active_record/type_caster/map.rb +3 -1
- data/lib/active_record/validations.rb +4 -4
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/presence.rb +2 -2
- data/lib/active_record/validations/uniqueness.rb +8 -39
- data/lib/active_record/version.rb +1 -1
- data/lib/rails/generators/active_record.rb +4 -4
- data/lib/rails/generators/active_record/migration.rb +2 -2
- data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -34
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -9
- metadata +22 -13
- data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
@@ -2,7 +2,7 @@ require "active_record/relation/batches/batch_enumerator"
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Batches
|
5
|
-
|
5
|
+
ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order."
|
6
6
|
|
7
7
|
# Looping through a collection of records from the database
|
8
8
|
# (using the Scoping::Named::ClassMethods.all method, for example)
|
@@ -30,19 +30,23 @@ module ActiveRecord
|
|
30
30
|
# end
|
31
31
|
#
|
32
32
|
# ==== Options
|
33
|
-
# * <tt>:batch_size</tt> - Specifies the size of the batch.
|
33
|
+
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
|
34
34
|
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
|
35
35
|
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
36
36
|
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
37
|
-
#
|
37
|
+
# an order is present in the relation.
|
38
38
|
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
|
42
|
-
# (by setting the +:start+ and +:finish+ option on each worker).
|
39
|
+
# Limits are honored, and if present there is no requirement for the batch
|
40
|
+
# size, it can be less than, equal, or greater than the limit.
|
43
41
|
#
|
44
|
-
#
|
45
|
-
#
|
42
|
+
# The options +start+ and +finish+ are especially useful if you want
|
43
|
+
# multiple workers dealing with the same processing queue. You can make
|
44
|
+
# worker 1 handle all the records between id 1 and 9999 and worker 2
|
45
|
+
# handle from 10000 and beyond by setting the +:start+ and +:finish+
|
46
|
+
# option on each worker.
|
47
|
+
#
|
48
|
+
# # Let's process from record 10_000 on.
|
49
|
+
# Person.find_each(start: 10_000) do |person|
|
46
50
|
# person.party_all_night!
|
47
51
|
# end
|
48
52
|
#
|
@@ -51,8 +55,8 @@ module ActiveRecord
|
|
51
55
|
# work. This also means that this method only works when the primary key is
|
52
56
|
# orderable (e.g. an integer or string).
|
53
57
|
#
|
54
|
-
# NOTE:
|
55
|
-
# the
|
58
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
59
|
+
# other processes are modifying the database.
|
56
60
|
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
|
57
61
|
if block_given?
|
58
62
|
find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
|
@@ -85,19 +89,23 @@ module ActiveRecord
|
|
85
89
|
# To be yielded each record one by one, use #find_each instead.
|
86
90
|
#
|
87
91
|
# ==== Options
|
88
|
-
# * <tt>:batch_size</tt> - Specifies the size of the batch.
|
92
|
+
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
|
89
93
|
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
|
90
94
|
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
91
95
|
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
92
|
-
#
|
96
|
+
# an order is present in the relation.
|
97
|
+
#
|
98
|
+
# Limits are honored, and if present there is no requirement for the batch
|
99
|
+
# size, it can be less than, equal, or greater than the limit.
|
93
100
|
#
|
94
|
-
#
|
95
|
-
# the same processing queue. You can make
|
96
|
-
# between id
|
97
|
-
#
|
101
|
+
# The options +start+ and +finish+ are especially useful if you want
|
102
|
+
# multiple workers dealing with the same processing queue. You can make
|
103
|
+
# worker 1 handle all the records between id 1 and 9999 and worker 2
|
104
|
+
# handle from 10000 and beyond by setting the +:start+ and +:finish+
|
105
|
+
# option on each worker.
|
98
106
|
#
|
99
|
-
# # Let's process
|
100
|
-
# Person.find_in_batches(start:
|
107
|
+
# # Let's process from record 10_000 on.
|
108
|
+
# Person.find_in_batches(start: 10_000) do |group|
|
101
109
|
# group.each { |person| person.party_all_night! }
|
102
110
|
# end
|
103
111
|
#
|
@@ -106,8 +114,8 @@ module ActiveRecord
|
|
106
114
|
# work. This also means that this method only works when the primary key is
|
107
115
|
# orderable (e.g. an integer or string).
|
108
116
|
#
|
109
|
-
# NOTE:
|
110
|
-
# the
|
117
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
118
|
+
# other processes are modifying the database.
|
111
119
|
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
|
112
120
|
relation = self
|
113
121
|
unless block_given?
|
@@ -132,9 +140,9 @@ module ActiveRecord
|
|
132
140
|
# If you do not provide a block to #in_batches, it will return a
|
133
141
|
# BatchEnumerator which is enumerable.
|
134
142
|
#
|
135
|
-
# Person.in_batches.
|
143
|
+
# Person.in_batches.with_index do |relation, batch_index|
|
136
144
|
# puts "Processing relation ##{batch_index}"
|
137
|
-
# relation.delete_all
|
145
|
+
# relation.each { |relation| relation.delete_all }
|
138
146
|
# end
|
139
147
|
#
|
140
148
|
# Examples of calling methods on the returned BatchEnumerator object:
|
@@ -144,22 +152,24 @@ module ActiveRecord
|
|
144
152
|
# Person.in_batches.each_record(&:party_all_night!)
|
145
153
|
#
|
146
154
|
# ==== Options
|
147
|
-
# * <tt>:of</tt> - Specifies the size of the batch.
|
148
|
-
# * <tt>:load</tt> - Specifies if the relation should be loaded.
|
155
|
+
# * <tt>:of</tt> - Specifies the size of the batch. Default to 1000.
|
156
|
+
# * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false.
|
149
157
|
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
|
150
158
|
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
151
159
|
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
152
|
-
#
|
160
|
+
# an order is present in the relation.
|
161
|
+
#
|
162
|
+
# Limits are honored, and if present there is no requirement for the batch
|
163
|
+
# size, it can be less than, equal, or greater than the limit.
|
153
164
|
#
|
154
|
-
#
|
155
|
-
#
|
156
|
-
#
|
157
|
-
#
|
158
|
-
#
|
159
|
-
# option on each worker).
|
165
|
+
# The options +start+ and +finish+ are especially useful if you want
|
166
|
+
# multiple workers dealing with the same processing queue. You can make
|
167
|
+
# worker 1 handle all the records between id 1 and 9999 and worker 2
|
168
|
+
# handle from 10000 and beyond by setting the +:start+ and +:finish+
|
169
|
+
# option on each worker.
|
160
170
|
#
|
161
|
-
# # Let's process
|
162
|
-
# Person.in_batches(
|
171
|
+
# # Let's process from record 10_000 on.
|
172
|
+
# Person.in_batches(start: 10_000).update_all(awesome: true)
|
163
173
|
#
|
164
174
|
# An example of calling where query method on the relation:
|
165
175
|
#
|
@@ -176,22 +186,28 @@ module ActiveRecord
|
|
176
186
|
#
|
177
187
|
# NOTE: It's not possible to set the order. That is automatically set to
|
178
188
|
# ascending on the primary key ("id ASC") to make the batch ordering
|
179
|
-
# consistent. Therefore the primary key must be orderable, e.g
|
189
|
+
# consistent. Therefore the primary key must be orderable, e.g an integer
|
180
190
|
# or a string.
|
181
191
|
#
|
182
|
-
# NOTE:
|
183
|
-
#
|
192
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
193
|
+
# other processes are modifying the database.
|
184
194
|
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
|
185
195
|
relation = self
|
186
196
|
unless block_given?
|
187
197
|
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
|
188
198
|
end
|
189
199
|
|
190
|
-
if arel.orders.present?
|
191
|
-
|
200
|
+
if arel.orders.present?
|
201
|
+
act_on_ignored_order(error_on_ignore)
|
192
202
|
end
|
193
203
|
|
194
|
-
|
204
|
+
batch_limit = of
|
205
|
+
if limit_value
|
206
|
+
remaining = limit_value
|
207
|
+
batch_limit = remaining if remaining < batch_limit
|
208
|
+
end
|
209
|
+
|
210
|
+
relation = relation.reorder(batch_order).limit(batch_limit)
|
195
211
|
relation = apply_limits(relation, start, finish)
|
196
212
|
batch_relation = relation
|
197
213
|
|
@@ -199,11 +215,11 @@ module ActiveRecord
|
|
199
215
|
if load
|
200
216
|
records = batch_relation.records
|
201
217
|
ids = records.map(&:id)
|
202
|
-
yielded_relation =
|
218
|
+
yielded_relation = where(primary_key => ids)
|
203
219
|
yielded_relation.load_records(records)
|
204
220
|
else
|
205
221
|
ids = batch_relation.pluck(primary_key)
|
206
|
-
yielded_relation =
|
222
|
+
yielded_relation = where(primary_key => ids)
|
207
223
|
end
|
208
224
|
|
209
225
|
break if ids.empty?
|
@@ -213,31 +229,44 @@ module ActiveRecord
|
|
213
229
|
|
214
230
|
yield yielded_relation
|
215
231
|
|
216
|
-
break if ids.length <
|
232
|
+
break if ids.length < batch_limit
|
233
|
+
|
234
|
+
if limit_value
|
235
|
+
remaining -= ids.length
|
236
|
+
|
237
|
+
if remaining == 0
|
238
|
+
# Saves a useless iteration when the limit is a multiple of the
|
239
|
+
# batch size.
|
240
|
+
break
|
241
|
+
elsif remaining < batch_limit
|
242
|
+
relation = relation.limit(remaining)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
217
246
|
batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
|
218
247
|
end
|
219
248
|
end
|
220
249
|
|
221
250
|
private
|
222
251
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
252
|
+
def apply_limits(relation, start, finish)
|
253
|
+
relation = relation.where(arel_attribute(primary_key).gteq(start)) if start
|
254
|
+
relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish
|
255
|
+
relation
|
256
|
+
end
|
228
257
|
|
229
|
-
|
230
|
-
|
231
|
-
|
258
|
+
def batch_order
|
259
|
+
"#{quoted_table_name}.#{quoted_primary_key} ASC"
|
260
|
+
end
|
232
261
|
|
233
|
-
|
234
|
-
|
262
|
+
def act_on_ignored_order(error_on_ignore)
|
263
|
+
raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
|
235
264
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
265
|
+
if raise_error
|
266
|
+
raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
|
267
|
+
elsif logger
|
268
|
+
logger.warn(ORDER_IGNORE_MESSAGE)
|
269
|
+
end
|
240
270
|
end
|
241
|
-
end
|
242
271
|
end
|
243
272
|
end
|
@@ -37,7 +37,11 @@ module ActiveRecord
|
|
37
37
|
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
|
38
38
|
# between databases. In invalid cases, an error from the database is thrown.
|
39
39
|
def count(column_name = nil)
|
40
|
-
|
40
|
+
if block_given?
|
41
|
+
to_a.count { |*block_args| yield(*block_args) }
|
42
|
+
else
|
43
|
+
calculate(:count, column_name)
|
44
|
+
end
|
41
45
|
end
|
42
46
|
|
43
47
|
# Calculates the average value on a given column. Returns +nil+ if there's
|
@@ -108,10 +112,6 @@ module ActiveRecord
|
|
108
112
|
# ...
|
109
113
|
# end
|
110
114
|
def calculate(operation, column_name)
|
111
|
-
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
|
112
|
-
column_name = attribute_alias(column_name)
|
113
|
-
end
|
114
|
-
|
115
115
|
if has_include?(column_name)
|
116
116
|
relation = construct_relation_for_association_calculations
|
117
117
|
relation = relation.distinct if operation.to_s.downcase == "count"
|
@@ -184,202 +184,196 @@ module ActiveRecord
|
|
184
184
|
|
185
185
|
private
|
186
186
|
|
187
|
-
|
188
|
-
|
189
|
-
|
187
|
+
def has_include?(column_name)
|
188
|
+
eager_loading? || (includes_values.present? && column_name && column_name != :all)
|
189
|
+
end
|
190
190
|
|
191
|
-
|
192
|
-
|
191
|
+
def perform_calculation(operation, column_name)
|
192
|
+
operation = operation.to_s.downcase
|
193
193
|
|
194
|
-
|
195
|
-
|
196
|
-
|
194
|
+
# If #count is used with #distinct (i.e. `relation.distinct.count`) it is
|
195
|
+
# considered distinct.
|
196
|
+
distinct = distinct_value
|
197
197
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
198
|
+
if operation == "count"
|
199
|
+
column_name ||= select_for_count
|
200
|
+
column_name = primary_key if column_name == :all && distinct
|
201
|
+
distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
|
202
|
+
end
|
203
203
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
204
|
+
if group_values.any?
|
205
|
+
execute_grouped_calculation(operation, column_name, distinct)
|
206
|
+
else
|
207
|
+
execute_simple_calculation(operation, column_name, distinct)
|
208
|
+
end
|
208
209
|
end
|
209
|
-
end
|
210
210
|
|
211
|
-
|
212
|
-
|
211
|
+
def aggregate_column(column_name)
|
212
|
+
return column_name if Arel::Expressions === column_name
|
213
213
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
214
|
+
if @klass.has_attribute?(column_name.to_s) || @klass.attribute_alias?(column_name.to_s)
|
215
|
+
@klass.arel_attribute(column_name)
|
216
|
+
else
|
217
|
+
Arel.sql(column_name == :all ? "*" : column_name.to_s)
|
218
|
+
end
|
218
219
|
end
|
219
|
-
end
|
220
|
-
|
221
|
-
def operation_over_aggregate_column(column, operation, distinct)
|
222
|
-
operation == 'count' ? column.count(distinct) : column.send(operation)
|
223
|
-
end
|
224
|
-
|
225
|
-
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
|
226
|
-
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
227
|
-
relation = unscope(:order)
|
228
220
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
# Shortcut when limit is zero.
|
233
|
-
return 0 if relation.limit_value == 0
|
221
|
+
def operation_over_aggregate_column(column, operation, distinct)
|
222
|
+
operation == "count" ? column.count(distinct) : column.send(operation)
|
223
|
+
end
|
234
224
|
|
235
|
-
|
236
|
-
|
237
|
-
column = aggregate_column(column_name)
|
225
|
+
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
|
226
|
+
column_alias = column_name
|
238
227
|
|
239
|
-
|
228
|
+
if operation == "count" && (limit_value || offset_value)
|
229
|
+
# Shortcut when limit is zero.
|
230
|
+
return 0 if limit_value == 0
|
240
231
|
|
241
|
-
|
242
|
-
|
243
|
-
|
232
|
+
query_builder = build_count_subquery(spawn, column_name, distinct)
|
233
|
+
else
|
234
|
+
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
235
|
+
relation = unscope(:order)
|
244
236
|
|
245
|
-
|
246
|
-
column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
|
247
|
-
relation.select_values = [select_value]
|
237
|
+
column = aggregate_column(column_name)
|
248
238
|
|
249
|
-
|
250
|
-
end
|
239
|
+
select_value = operation_over_aggregate_column(column, operation, distinct)
|
251
240
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
column = result.column_types.fetch(column_alias) do
|
256
|
-
type_for(column_name)
|
257
|
-
end
|
241
|
+
column_alias = select_value.alias
|
242
|
+
column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
|
243
|
+
relation.select_values = [select_value]
|
258
244
|
|
259
|
-
|
260
|
-
|
245
|
+
query_builder = relation.arel
|
246
|
+
end
|
261
247
|
|
262
|
-
|
263
|
-
|
248
|
+
result = @klass.connection.select_all(query_builder, nil, bound_attributes)
|
249
|
+
row = result.first
|
250
|
+
value = row && row.values.first
|
251
|
+
type = result.column_types.fetch(column_alias) do
|
252
|
+
type_for(column_name)
|
253
|
+
end
|
264
254
|
|
265
|
-
|
266
|
-
association = @klass._reflect_on_association(group_attrs.first)
|
267
|
-
associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
|
268
|
-
group_fields = Array(associated ? association.foreign_key : group_attrs)
|
269
|
-
else
|
270
|
-
group_fields = group_attrs
|
255
|
+
type_cast_calculated_value(value, type, operation)
|
271
256
|
end
|
272
|
-
group_fields = arel_columns(group_fields)
|
273
|
-
|
274
|
-
group_aliases = group_fields.map { |field| column_alias_for(field) }
|
275
|
-
group_columns = group_aliases.zip(group_fields)
|
276
257
|
|
277
|
-
|
278
|
-
|
279
|
-
else
|
280
|
-
aggregate_alias = column_alias_for([operation, column_name].join(' '))
|
281
|
-
end
|
258
|
+
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
|
259
|
+
group_attrs = group_values
|
282
260
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
distinct).as(aggregate_alias)
|
288
|
-
]
|
289
|
-
select_values += self.select_values unless having_clause.empty?
|
290
|
-
|
291
|
-
select_values.concat group_columns.map { |aliaz, field|
|
292
|
-
if field.respond_to?(:as)
|
293
|
-
field.as(aliaz)
|
261
|
+
if group_attrs.first.respond_to?(:to_sym)
|
262
|
+
association = @klass._reflect_on_association(group_attrs.first)
|
263
|
+
associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
|
264
|
+
group_fields = Array(associated ? association.foreign_key : group_attrs)
|
294
265
|
else
|
295
|
-
|
266
|
+
group_fields = group_attrs
|
296
267
|
end
|
297
|
-
|
268
|
+
group_fields = arel_columns(group_fields)
|
298
269
|
|
299
|
-
|
300
|
-
|
301
|
-
relation.select_values = select_values
|
270
|
+
group_aliases = group_fields.map { |field| column_alias_for(field) }
|
271
|
+
group_columns = group_aliases.zip(group_fields)
|
302
272
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
key_records = Hash[key_records.map { |r| [r.id, r] }]
|
309
|
-
end
|
273
|
+
if operation == "count" && column_name == :all
|
274
|
+
aggregate_alias = "count_all"
|
275
|
+
else
|
276
|
+
aggregate_alias = column_alias_for([operation, column_name].join(" "))
|
277
|
+
end
|
310
278
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
279
|
+
select_values = [
|
280
|
+
operation_over_aggregate_column(
|
281
|
+
aggregate_column(column_name),
|
282
|
+
operation,
|
283
|
+
distinct).as(aggregate_alias)
|
284
|
+
]
|
285
|
+
select_values += select_values unless having_clause.empty?
|
286
|
+
|
287
|
+
select_values.concat group_columns.map { |aliaz, field|
|
288
|
+
if field.respond_to?(:as)
|
289
|
+
field.as(aliaz)
|
290
|
+
else
|
291
|
+
"#{field} AS #{aliaz}"
|
317
292
|
end
|
318
|
-
type_cast_calculated_value(row[aliaz], column)
|
319
293
|
}
|
320
|
-
key = key.first if key.size == 1
|
321
|
-
key = key_records[key] if associated
|
322
294
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
end
|
295
|
+
relation = except(:group)
|
296
|
+
relation.group_values = group_fields
|
297
|
+
relation.select_values = select_values
|
327
298
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
299
|
+
calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes)
|
300
|
+
|
301
|
+
if association
|
302
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
303
|
+
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
304
|
+
key_records = Hash[key_records.map { |r| [r.id, r] }]
|
305
|
+
end
|
306
|
+
|
307
|
+
Hash[calculated_data.map do |row|
|
308
|
+
key = group_columns.map { |aliaz, col_name|
|
309
|
+
type = type_for(col_name) do
|
310
|
+
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
311
|
+
end
|
312
|
+
type_cast_calculated_value(row[aliaz], type)
|
313
|
+
}
|
314
|
+
key = key.first if key.size == 1
|
315
|
+
key = key_records[key] if associated
|
316
|
+
|
317
|
+
type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
|
318
|
+
[key, type_cast_calculated_value(row[aggregate_alias], type, operation)]
|
319
|
+
end]
|
338
320
|
end
|
339
321
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
322
|
+
# Converts the given keys to the value that the database adapter returns as
|
323
|
+
# a usable column name:
|
324
|
+
#
|
325
|
+
# column_alias_for("users.id") # => "users_id"
|
326
|
+
# column_alias_for("sum(id)") # => "sum_id"
|
327
|
+
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
328
|
+
# column_alias_for("count(*)") # => "count_all"
|
329
|
+
def column_alias_for(keys)
|
330
|
+
if keys.respond_to? :name
|
331
|
+
keys = "#{keys.relation.name}.#{keys.name}"
|
332
|
+
end
|
345
333
|
|
346
|
-
|
347
|
-
|
334
|
+
table_name = keys.to_s.downcase
|
335
|
+
table_name.gsub!(/\*/, "all")
|
336
|
+
table_name.gsub!(/\W+/, " ")
|
337
|
+
table_name.strip!
|
338
|
+
table_name.gsub!(/ +/, "_")
|
348
339
|
|
349
|
-
|
350
|
-
|
351
|
-
@klass.type_for_attribute(field_name, &block)
|
352
|
-
end
|
340
|
+
@klass.connection.table_alias_for(table_name)
|
341
|
+
end
|
353
342
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
343
|
+
def type_for(field, &block)
|
344
|
+
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
|
345
|
+
@klass.type_for_attribute(field_name, &block)
|
346
|
+
end
|
347
|
+
|
348
|
+
def type_cast_calculated_value(value, type, operation = nil)
|
349
|
+
case operation
|
350
|
+
when "count" then value.to_i
|
351
|
+
when "sum" then type.deserialize(value || 0)
|
352
|
+
when "average" then value.respond_to?(:to_d) ? value.to_d : value
|
359
353
|
else type.deserialize(value)
|
354
|
+
end
|
360
355
|
end
|
361
|
-
end
|
362
356
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
357
|
+
def select_for_count
|
358
|
+
if select_values.present?
|
359
|
+
return select_values.first if select_values.one?
|
360
|
+
select_values.join(", ")
|
361
|
+
else
|
362
|
+
:all
|
363
|
+
end
|
369
364
|
end
|
370
|
-
end
|
371
365
|
|
372
|
-
|
373
|
-
|
374
|
-
|
366
|
+
def build_count_subquery(relation, column_name, distinct)
|
367
|
+
column_alias = Arel.sql("count_column")
|
368
|
+
subquery_alias = Arel.sql("subquery_for_count")
|
375
369
|
|
376
|
-
|
377
|
-
|
378
|
-
|
370
|
+
aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
|
371
|
+
relation.select_values = [aliased_column]
|
372
|
+
subquery = relation.arel.as(subquery_alias)
|
379
373
|
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
374
|
+
sm = Arel::SelectManager.new relation.engine
|
375
|
+
select_value = operation_over_aggregate_column(column_alias, "count", distinct)
|
376
|
+
sm.project(select_value).from(subquery)
|
377
|
+
end
|
384
378
|
end
|
385
379
|
end
|