Skip to content

Commit 23fdde8

Browse files
authored
Merge pull request rails#47901 from fatkodima/batches-composite-pks
Support batching using composite primary keys
2 parents c396d97 + 52f27f9 commit 23fdde8

File tree

2 files changed

+53
-9
lines changed

2 files changed

+53
-9
lines changed

activerecord/lib/active_record/relation/batches.rb

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore:
227227
batch_limit = remaining if remaining < batch_limit
228228
end
229229

230-
relation = relation.reorder(batch_order(order)).limit(batch_limit)
230+
relation = relation.reorder(*batch_order(order)).limit(batch_limit)
231231
relation = apply_limits(relation, start, finish, order)
232232
relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
233233
batch_relation = relation
@@ -240,15 +240,15 @@ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore:
240240
yielded_relation = where(primary_key => ids)
241241
yielded_relation.load_records(records)
242242
elsif (empty_scope && use_ranges != false) || use_ranges
243-
ids = batch_relation.pluck(primary_key)
243+
ids = batch_relation.ids
244244
finish = ids.last
245245
if finish
246246
yielded_relation = apply_finish_limit(batch_relation, finish, order)
247247
yielded_relation = yielded_relation.except(:limit, :order)
248248
yielded_relation.skip_query_cache!(false)
249249
end
250250
else
251-
ids = batch_relation.pluck(primary_key)
251+
ids = batch_relation.ids
252252
yielded_relation = where(primary_key => ids)
253253
end
254254

@@ -273,8 +273,8 @@ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore:
273273
end
274274
end
275275

276-
batch_relation = relation.where(
277-
predicate_builder[primary_key, primary_key_offset, order == :desc ? :lt : :gt]
276+
batch_relation = batch_condition(
277+
relation, primary_key, primary_key_offset, order == :desc ? :lt : :gt
278278
)
279279
end
280280
end
@@ -287,15 +287,32 @@ def apply_limits(relation, start, finish, order)
287287
end
288288

289289
def apply_start_limit(relation, start, order)
290-
relation.where(predicate_builder[primary_key, start, order == :desc ? :lteq : :gteq])
290+
batch_condition(relation, primary_key, start, order == :desc ? :lteq : :gteq)
291291
end
292292

293293
def apply_finish_limit(relation, finish, order)
294-
relation.where(predicate_builder[primary_key, finish, order == :desc ? :gteq : :lteq])
294+
batch_condition(relation, primary_key, finish, order == :desc ? :gteq : :lteq)
295+
end
296+
297+
def batch_condition(relation, columns, values, operator)
298+
columns = Array(columns)
299+
values = Array(values)
300+
cursor_positions = columns.zip(values)
301+
302+
first_clause_column, first_clause_value = cursor_positions.pop
303+
where_clause = predicate_builder[first_clause_column, first_clause_value, operator]
304+
305+
cursor_positions.reverse_each do |column_name, value|
306+
where_clause = predicate_builder[column_name, value, operator == :lteq ? :lt : :gt].or(
307+
predicate_builder[column_name, value, :eq].and(where_clause)
308+
)
309+
end
310+
311+
relation.where(where_clause)
295312
end
296313

297314
def batch_order(order)
298-
table[primary_key].public_send(order)
315+
Array(primary_key).map { |column| table[column].public_send(order) }
299316
end
300317

301318
def act_on_ignored_order(error_on_ignore)

activerecord/test/cases/batches_test.rb

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
require "models/comment"
55
require "models/post"
66
require "models/subscriber"
7+
require "models/cpk"
78

89
class EachTest < ActiveRecord::TestCase
9-
fixtures :posts, :subscribers
10+
fixtures :posts, :subscribers, :cpk_orders
1011

1112
def setup
1213
@posts = Post.order("id asc")
@@ -769,4 +770,30 @@ def test_find_in_batches_should_return_a_sized_enumerator
769770
end
770771
end
771772
end
773+
774+
test ".find_each iterates over composite primary key" do
775+
orders = Cpk::Order.order(*Cpk::Order.primary_key).to_a
776+
Cpk::Order.find_each(batch_size: 1).with_index do |order, index|
777+
assert_equal orders[index], order
778+
end
779+
end
780+
781+
test ".in_batches should start from the start option when using composite primary key" do
782+
order = Cpk::Order.second
783+
relation = Cpk::Order.in_batches(of: 1, start: order.id).first
784+
assert_equal order, relation.first
785+
end
786+
787+
test ".in_batches should end at the finish option when using composite primary key" do
788+
order = Cpk::Order.second_to_last
789+
relation = Cpk::Order.in_batches(of: 1, finish: order.id).reverse_each.first
790+
assert_equal order, relation.last
791+
end
792+
793+
test ".in_batches with scope and using composite primary key" do
794+
order1, order2 = Cpk::Order.first(2)
795+
shop_id, id = order1.id
796+
relation = Cpk::Order.where("shop_id > ? OR shop_id = ? AND id > ?", shop_id, shop_id, id).in_batches(of: 1).first
797+
assert_equal order2, relation.first
798+
end
772799
end

0 commit comments

Comments
 (0)