activerecord 7.2.0 → 8.0.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 +189 -745
- data/README.rdoc +1 -1
- data/lib/active_record/associations/association.rb +25 -5
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +10 -8
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +5 -5
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +34 -4
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +9 -1
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
- data/lib/active_record/attributes.rb +6 -5
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +23 -44
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +53 -18
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +26 -5
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -25
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +20 -38
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +44 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +50 -6
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +38 -90
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -12
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
- data/lib/active_record/connection_handling.rb +22 -0
- data/lib/active_record/core.rb +16 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
- data/lib/active_record/encryption/encryptor.rb +16 -9
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/encryption.rb +2 -0
- data/lib/active_record/enum.rb +8 -0
- data/lib/active_record/errors.rb +13 -5
- data/lib/active_record/fixtures.rb +0 -1
- data/lib/active_record/future_result.rb +14 -10
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/insert_all.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +22 -5
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +35 -33
- data/lib/active_record/model_schema.rb +6 -3
- data/lib/active_record/nested_attributes.rb +11 -2
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_logs.rb +97 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +6 -6
- data/lib/active_record/railtie.rb +8 -14
- data/lib/active_record/reflection.rb +19 -10
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +135 -75
- data/lib/active_record/relation/calculations.rb +24 -19
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +18 -18
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +6 -1
- data/lib/active_record/relation/query_methods.rb +58 -37
- data/lib/active_record/relation/record_fetch_warning.rb +2 -2
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation.rb +72 -61
- data/lib/active_record/result.rb +68 -7
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +5 -0
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/statement_cache.rb +12 -12
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +36 -16
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +9 -8
- data/lib/active_record.rb +15 -0
- data/lib/arel/collectors/bind.rb +1 -1
- metadata +14 -14
@@ -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,24 +247,27 @@ 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
|
243
|
-
|
244
|
-
unless block
|
245
|
-
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, order: order, use_ranges: use_ranges)
|
246
|
-
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)
|
247
262
|
|
248
263
|
if arel.orders.present?
|
249
264
|
act_on_ignored_order(error_on_ignore)
|
250
265
|
end
|
251
266
|
|
267
|
+
unless block
|
268
|
+
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, cursor: cursor, order: order, use_ranges: use_ranges)
|
269
|
+
end
|
270
|
+
|
252
271
|
batch_limit = of
|
253
272
|
|
254
273
|
if limit_value
|
@@ -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,65 @@ 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
|
-
yielded_relation = where(
|
437
|
+
values = records.pluck(*cursor)
|
438
|
+
yielded_relation = where(cursor => values)
|
383
439
|
yielded_relation.load_records(records)
|
384
440
|
elsif (empty_scope && use_ranges != false) || use_ranges
|
385
|
-
|
386
|
-
|
441
|
+
values = batch_relation.pluck(*cursor)
|
442
|
+
|
443
|
+
finish = values.last
|
387
444
|
if finish
|
388
|
-
yielded_relation = apply_finish_limit(batch_relation, finish, batch_orders)
|
445
|
+
yielded_relation = apply_finish_limit(batch_relation, cursor, finish, batch_orders)
|
389
446
|
yielded_relation = yielded_relation.except(:limit, :order)
|
390
447
|
yielded_relation.skip_query_cache!(false)
|
391
448
|
end
|
392
449
|
else
|
393
|
-
|
394
|
-
yielded_relation = where(
|
450
|
+
values = batch_relation.pluck(*cursor)
|
451
|
+
yielded_relation = where(cursor => values)
|
395
452
|
end
|
396
453
|
|
397
|
-
break if
|
454
|
+
break if values.empty?
|
398
455
|
|
399
|
-
|
400
|
-
|
456
|
+
if values.flatten.any?(nil)
|
457
|
+
raise ArgumentError, "Not all of the batch cursor columns were included in the custom select clause "\
|
458
|
+
"or some columns contain nil."
|
459
|
+
end
|
401
460
|
|
402
461
|
yield yielded_relation
|
403
462
|
|
404
|
-
break if
|
463
|
+
break if values.length < batch_limit
|
405
464
|
|
406
465
|
if limit_value
|
407
|
-
remaining -=
|
466
|
+
remaining -= values.length
|
408
467
|
|
409
468
|
if remaining == 0
|
410
469
|
# Saves a useless iteration when the limit is a multiple of the
|
@@ -422,7 +481,8 @@ module ActiveRecord
|
|
422
481
|
end
|
423
482
|
operators << (last_order == :desc ? :lt : :gt)
|
424
483
|
|
425
|
-
|
484
|
+
cursor_value = values.last
|
485
|
+
batch_relation = batch_condition(relation, cursor, cursor_value, operators)
|
426
486
|
end
|
427
487
|
|
428
488
|
nil
|
@@ -234,7 +234,7 @@ module ActiveRecord
|
|
234
234
|
if operation == "count"
|
235
235
|
unless distinct_value || distinct_select?(column_name || select_for_count)
|
236
236
|
relation.distinct!
|
237
|
-
relation.select_values = Array(
|
237
|
+
relation.select_values = Array(model.primary_key || table[Arel.star])
|
238
238
|
end
|
239
239
|
# PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
|
240
240
|
relation.order_values = [] if group_values.empty?
|
@@ -306,7 +306,7 @@ module ActiveRecord
|
|
306
306
|
relation = apply_join_dependency
|
307
307
|
relation.pluck(*column_names)
|
308
308
|
else
|
309
|
-
|
309
|
+
model.disallow_raw_sql!(flattened_args(column_names))
|
310
310
|
columns = arel_columns(column_names)
|
311
311
|
relation = spawn
|
312
312
|
relation.select_values = columns
|
@@ -314,8 +314,8 @@ module ActiveRecord
|
|
314
314
|
if where_clause.contradiction?
|
315
315
|
ActiveRecord::Result.empty(async: @async)
|
316
316
|
else
|
317
|
-
|
318
|
-
c.select_all(relation.arel, "#{
|
317
|
+
model.with_connection do |c|
|
318
|
+
c.select_all(relation.arel, "#{model.name} Pluck", async: @async)
|
319
319
|
end
|
320
320
|
end
|
321
321
|
end
|
@@ -391,8 +391,8 @@ module ActiveRecord
|
|
391
391
|
ActiveRecord::Result.empty
|
392
392
|
else
|
393
393
|
skip_query_cache_if_necessary do
|
394
|
-
|
395
|
-
c.select_all(relation, "#{
|
394
|
+
model.with_connection do |c|
|
395
|
+
c.select_all(relation, "#{model.name} Ids", async: @async)
|
396
396
|
end
|
397
397
|
end
|
398
398
|
end
|
@@ -408,7 +408,7 @@ module ActiveRecord
|
|
408
408
|
|
409
409
|
private
|
410
410
|
def all_attributes?(column_names)
|
411
|
-
(column_names.map(&:to_s) -
|
411
|
+
(column_names.map(&:to_s) - model.attribute_names - model.attribute_aliases.keys).empty?
|
412
412
|
end
|
413
413
|
|
414
414
|
def has_include?(column_name)
|
@@ -482,8 +482,8 @@ module ActiveRecord
|
|
482
482
|
ActiveRecord::Result.empty
|
483
483
|
else
|
484
484
|
skip_query_cache_if_necessary do
|
485
|
-
|
486
|
-
c.select_all(query_builder, "#{
|
485
|
+
model.with_connection do |c|
|
486
|
+
c.select_all(query_builder, "#{model.name} #{operation.capitalize}", async: @async)
|
487
487
|
end
|
488
488
|
end
|
489
489
|
end
|
@@ -504,13 +504,13 @@ module ActiveRecord
|
|
504
504
|
group_fields = group_fields.uniq if group_fields.size > 1
|
505
505
|
|
506
506
|
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
507
|
-
association =
|
507
|
+
association = model._reflect_on_association(group_fields.first)
|
508
508
|
associated = association && association.belongs_to? # only count belongs_to associations
|
509
509
|
group_fields = Array(association.foreign_key) if associated
|
510
510
|
end
|
511
511
|
group_fields = arel_columns(group_fields)
|
512
512
|
|
513
|
-
|
513
|
+
model.with_connection do |connection|
|
514
514
|
column_alias_tracker = ColumnAliasTracker.new(connection)
|
515
515
|
|
516
516
|
group_aliases = group_fields.map { |field|
|
@@ -522,13 +522,13 @@ module ActiveRecord
|
|
522
522
|
column = aggregate_column(column_name)
|
523
523
|
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
524
524
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
525
|
-
select_value.as(adapter_class.quote_column_name(column_alias))
|
525
|
+
select_value.as(model.adapter_class.quote_column_name(column_alias))
|
526
526
|
|
527
527
|
select_values = [select_value]
|
528
528
|
select_values += self.select_values unless having_clause.empty?
|
529
529
|
|
530
530
|
select_values.concat group_columns.map { |aliaz, field|
|
531
|
-
aliaz = adapter_class.quote_column_name(aliaz)
|
531
|
+
aliaz = model.adapter_class.quote_column_name(aliaz)
|
532
532
|
if field.respond_to?(:as)
|
533
533
|
field.as(aliaz)
|
534
534
|
else
|
@@ -541,7 +541,7 @@ module ActiveRecord
|
|
541
541
|
relation.select_values = select_values
|
542
542
|
|
543
543
|
result = skip_query_cache_if_necessary do
|
544
|
-
connection.select_all(relation.arel, "#{
|
544
|
+
connection.select_all(relation.arel, "#{model.name} #{operation.capitalize}", async: @async)
|
545
545
|
end
|
546
546
|
|
547
547
|
result.then do |calculated_data|
|
@@ -583,7 +583,7 @@ module ActiveRecord
|
|
583
583
|
|
584
584
|
def type_for(field, &block)
|
585
585
|
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
|
586
|
-
|
586
|
+
model.type_for_attribute(field_name, &block)
|
587
587
|
end
|
588
588
|
|
589
589
|
def lookup_cast_type_from_join_dependencies(name, join_dependencies = build_join_dependencies)
|
@@ -596,12 +596,12 @@ module ActiveRecord
|
|
596
596
|
|
597
597
|
def type_cast_pluck_values(result, columns)
|
598
598
|
cast_types = if result.columns.size != columns.size
|
599
|
-
|
599
|
+
model.attribute_types
|
600
600
|
else
|
601
601
|
join_dependencies = nil
|
602
602
|
columns.map.with_index do |column, i|
|
603
603
|
column.try(:type_caster) ||
|
604
|
-
|
604
|
+
model.attribute_types.fetch(name = result.columns[i]) do
|
605
605
|
join_dependencies ||= build_join_dependencies
|
606
606
|
lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
|
607
607
|
result.column_types[i] || Type.default_value
|
@@ -633,9 +633,14 @@ module ActiveRecord
|
|
633
633
|
if select_values.present?
|
634
634
|
return select_values.first if select_values.one?
|
635
635
|
|
636
|
+
adapter_class = model.adapter_class
|
636
637
|
select_values.map do |field|
|
637
|
-
column =
|
638
|
-
|
638
|
+
column = if Arel.arel_node?(field)
|
639
|
+
field
|
640
|
+
else
|
641
|
+
arel_column(field.to_s) do |attr_name|
|
642
|
+
Arel.sql(attr_name)
|
643
|
+
end
|
639
644
|
end
|
640
645
|
|
641
646
|
if column.is_a?(Arel::Nodes::SqlLiteral)
|
@@ -22,6 +22,9 @@ module ActiveRecord
|
|
22
22
|
end
|
23
23
|
|
24
24
|
module DelegateCache # :nodoc:
|
25
|
+
@delegate_base_methods = true
|
26
|
+
singleton_class.attr_accessor :delegate_base_methods
|
27
|
+
|
25
28
|
def relation_delegate_class(klass)
|
26
29
|
@relation_delegate_cache[klass]
|
27
30
|
end
|
@@ -75,12 +78,12 @@ module ActiveRecord
|
|
75
78
|
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
|
76
79
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
77
80
|
def #{method}(...)
|
78
|
-
scoping {
|
81
|
+
scoping { model.#{method}(...) }
|
79
82
|
end
|
80
83
|
RUBY
|
81
84
|
else
|
82
85
|
define_method(method) do |*args, **kwargs, &block|
|
83
|
-
scoping {
|
86
|
+
scoping { model.public_send(method, *args, **kwargs, &block) }
|
84
87
|
end
|
85
88
|
end
|
86
89
|
end
|
@@ -92,15 +95,15 @@ module ActiveRecord
|
|
92
95
|
|
93
96
|
# This module creates compiled delegation methods dynamically at runtime, which makes
|
94
97
|
# subsequent calls to that method faster by avoiding method_missing. The delegations
|
95
|
-
# may vary depending on the
|
96
|
-
# for each different
|
98
|
+
# may vary depending on the model of a relation, so we create a subclass of Relation
|
99
|
+
# for each different model, and the delegations are compiled into that subclass only.
|
97
100
|
|
98
101
|
delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
|
99
102
|
:[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
|
100
103
|
:to_sentence, :to_fs, :to_formatted_s, :as_json,
|
101
104
|
:shuffle, :split, :slice, :index, :rindex, to: :records
|
102
105
|
|
103
|
-
delegate :primary_key, :
|
106
|
+
delegate :primary_key, :with_connection, :connection, :table_name, :transaction, :sanitize_sql_like, :unscoped, :name, to: :model
|
104
107
|
|
105
108
|
module ClassSpecificRelation # :nodoc:
|
106
109
|
extend ActiveSupport::Concern
|
@@ -113,11 +116,19 @@ module ActiveRecord
|
|
113
116
|
|
114
117
|
private
|
115
118
|
def method_missing(method, ...)
|
116
|
-
if
|
117
|
-
|
118
|
-
|
119
|
+
if model.respond_to?(method)
|
120
|
+
if !DelegateCache.delegate_base_methods && Base.respond_to?(method)
|
121
|
+
# A common mistake in Active Record's own code is to call `ActiveRecord::Base`
|
122
|
+
# class methods on Association. It works because it's automatically delegated, but
|
123
|
+
# can introduce subtle bugs because it sets the global scope.
|
124
|
+
# We can't deprecate this behavior because gems might depend on it, however we
|
125
|
+
# can ban it from Active Record's own test suite to avoid regressions.
|
126
|
+
raise NotImplementedError, "Active Record code shouldn't rely on association delegation into ActiveRecord::Base methods"
|
127
|
+
elsif !Delegation.uncacheable_methods.include?(method)
|
128
|
+
model.generate_relation_method(method)
|
119
129
|
end
|
120
|
-
|
130
|
+
|
131
|
+
scoping { model.public_send(method, ...) }
|
121
132
|
else
|
122
133
|
super
|
123
134
|
end
|
@@ -125,19 +136,19 @@ module ActiveRecord
|
|
125
136
|
end
|
126
137
|
|
127
138
|
module ClassMethods # :nodoc:
|
128
|
-
def create(
|
129
|
-
relation_class_for(
|
139
|
+
def create(model, ...)
|
140
|
+
relation_class_for(model).new(model, ...)
|
130
141
|
end
|
131
142
|
|
132
143
|
private
|
133
|
-
def relation_class_for(
|
134
|
-
|
144
|
+
def relation_class_for(model)
|
145
|
+
model.relation_delegate_class(self)
|
135
146
|
end
|
136
147
|
end
|
137
148
|
|
138
149
|
private
|
139
150
|
def respond_to_missing?(method, _)
|
140
|
-
super ||
|
151
|
+
super || model.respond_to?(method)
|
141
152
|
end
|
142
153
|
end
|
143
154
|
end
|