Skip to content

Commit 8a93d28

Browse files
jeremyevansdavekaro
andcommitted
Use a qualified primary key for Model.paged_each and for the paged_operations plugin when using joined datasets
This adds an unambiguous_primary_key private model dataset method, which returns the qualified primary key for the model if the dataset is joined, or the unqualified primary key for the model otherwise. This changes the _force_primary_key_order private model dataset method to use unambiguous_primary_key, so that it will use a qualified version of the primary key automatically if the dataset is joined. It makes the same change to the _paged_operations_pk method in the paged_operations plugin. Fixes #2300 Co-authored-by: Dave Kroondyk <[email protected]>
1 parent cf4466b commit 8a93d28

File tree

5 files changed

+77
-3
lines changed

5 files changed

+77
-3
lines changed

CHANGELOG

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
=== master
22

3+
* Use a qualified primary key for Model.paged_each and for the paged_operations plugin when using joined datasets (jeremyevans, davekaro) (#2299, #2300)
4+
35
* Add Model.qualified_primary_key for returning a qualified version of the model's primary key (jeremyevans)
46

57
* Support sql_literal_allow_caching? method on objects that respond to sql_literal_append/sql_literal (jeremyevans)

lib/sequel/model/base.rb

+10-1
Original file line numberDiff line numberDiff line change
@@ -2278,7 +2278,7 @@ def with_pk!(pk)
22782278
# Return the dataset ordered by the model's primary key. This should not
22792279
# be used if the model does not have a primary key.
22802280
def _force_primary_key_order
2281-
cached_dataset(:_pk_order_ds){order(*model.primary_key)}
2281+
cached_dataset(:_pk_order_ds){order(*unambiguous_primary_key)}
22822282
end
22832283

22842284
# If the dataset is not already ordered, and the model has a primary key,
@@ -2306,6 +2306,15 @@ def _with_pk_loader
23062306
end
23072307
end
23082308

2309+
# The primary key for the dataset's model, qualified if the dataset is joined.
2310+
def unambiguous_primary_key
2311+
if joined_dataset?
2312+
model.qualified_primary_key
2313+
else
2314+
model.primary_key
2315+
end
2316+
end
2317+
23092318
def non_sql_option?(key)
23102319
super || key == :model
23112320
end

lib/sequel/plugins/paged_operations.rb

+5-2
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,16 @@ def _paged_operations_pk(meth)
157157
raise Error, "the paged_operations plugin is not supported on DB2 when using emulated offsets, set the :offset_strategy Database option to 'limit_offset' or 'offset_fetch'"
158158
end
159159

160-
case pk = model.primary_key
160+
case pk = unambiguous_primary_key
161161
when Symbol
162162
Sequel.identifier(pk)
163163
when Array
164164
raise Error, "cannot use #{meth} on a model with a composite primary key"
165-
else
165+
when nil
166166
raise Error, "cannot use #{meth} on a model without a primary key"
167+
else
168+
# Likely SQL::QualifiedIdentifier, if the dataset is joined.
169+
pk
167170
end
168171
end
169172

spec/extensions/paged_operations_spec.rb

+55
Original file line numberDiff line numberDiff line change
@@ -262,3 +262,58 @@ def db.server_version(_=nil); 11020002; end
262262
]
263263
end
264264
end
265+
266+
describe "paged_operations plugin with joined dataset" do
267+
before do
268+
@db = Sequel.connect("mock://postgres")
269+
@c = Class.new(Sequel::Model(@db[:albums]))
270+
@c.plugin :paged_operations
271+
@db.sqls
272+
@db.fetch = [[{:id=>1002}], [{:id=>2002}]]
273+
@db.numrows = [1000, 1000, 2]
274+
@ds = @c.dataset.
275+
qualify.
276+
from(:albums, :artists).
277+
where{albums[:id] =~ artists[:album_id]}.
278+
with_quote_identifiers(false)
279+
end
280+
281+
it "#paged_delete should use qualified columns when joining" do
282+
@ds.paged_delete.must_equal 2002
283+
@db.sqls.must_equal [
284+
"SELECT albums.id FROM albums, artists WHERE (albums.id = artists.album_id) ORDER BY albums.id LIMIT 1 OFFSET 1000",
285+
"DELETE FROM albums USING artists WHERE ((albums.id = artists.album_id) AND (albums.id < 1002))",
286+
"SELECT albums.id FROM albums, artists WHERE (albums.id = artists.album_id) ORDER BY albums.id LIMIT 1 OFFSET 1000",
287+
"DELETE FROM albums USING artists WHERE ((albums.id = artists.album_id) AND (albums.id < 2002))",
288+
"SELECT albums.id FROM albums, artists WHERE (albums.id = artists.album_id) ORDER BY albums.id LIMIT 1 OFFSET 1000",
289+
"DELETE FROM albums USING artists WHERE (albums.id = artists.album_id)"
290+
]
291+
end
292+
293+
it "#paged_update should update using multiple queries" do
294+
@ds.paged_update({x: Sequel[:artists][:y]}).must_equal 2002
295+
@db.sqls.must_equal [
296+
"SELECT albums.id FROM albums, artists WHERE (albums.id = artists.album_id) ORDER BY albums.id LIMIT 1 OFFSET 1000",
297+
"UPDATE albums SET x = artists.y FROM artists WHERE ((albums.id = artists.album_id) AND (albums.id < 1002))",
298+
"SELECT albums.id FROM albums, artists WHERE ((albums.id = artists.album_id) AND (albums.id >= 1002)) ORDER BY albums.id LIMIT 1 OFFSET 1000",
299+
"UPDATE albums SET x = artists.y FROM artists WHERE ((albums.id = artists.album_id) AND (albums.id < 2002) AND (albums.id >= 1002))",
300+
"SELECT albums.id FROM albums, artists WHERE ((albums.id = artists.album_id) AND (albums.id >= 2002)) ORDER BY albums.id LIMIT 1 OFFSET 1000",
301+
"UPDATE albums SET x = artists.y FROM artists WHERE ((albums.id = artists.album_id) AND (albums.id >= 2002))"
302+
]
303+
end
304+
305+
it "#paged_datasets should yield multiple datasets making up dataset" do
306+
sqls = []
307+
@ds.paged_datasets{|ds| sqls << ds.sql}
308+
sqls.must_equal [
309+
"SELECT albums.* FROM albums, artists WHERE ((albums.id = artists.album_id) AND (albums.id < 1002))",
310+
"SELECT albums.* FROM albums, artists WHERE ((albums.id = artists.album_id) AND (albums.id < 2002) AND (albums.id >= 1002))",
311+
"SELECT albums.* FROM albums, artists WHERE ((albums.id = artists.album_id) AND (albums.id >= 2002))"
312+
]
313+
@db.sqls.must_equal [
314+
"SELECT albums.id FROM albums, artists WHERE (albums.id = artists.album_id) ORDER BY albums.id LIMIT 1 OFFSET 1000",
315+
"SELECT albums.id FROM albums, artists WHERE ((albums.id = artists.album_id) AND (albums.id >= 1002)) ORDER BY albums.id LIMIT 1 OFFSET 1000",
316+
"SELECT albums.id FROM albums, artists WHERE ((albums.id = artists.album_id) AND (albums.id >= 2002)) ORDER BY albums.id LIMIT 1 OFFSET 1000"
317+
]
318+
end
319+
end

spec/model/dataset_methods_spec.rb

+5
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ def destroy
122122
@c.db.sqls.must_equal ['BEGIN', 'SELECT * FROM items ORDER BY id LIMIT 5 OFFSET 0', 'COMMIT']
123123
end
124124

125+
it "#paged_each should order by qualified primary key if the dataset is joined" do
126+
@c.join(:x, :a=>:b).paged_each{|r| r.must_be_kind_of(@c)}
127+
@c.db.sqls.must_equal ['BEGIN', 'SELECT * FROM items INNER JOIN x ON (x.a = items.b) ORDER BY items.id LIMIT 1000 OFFSET 0', 'COMMIT']
128+
end
129+
125130
it "#paged_each should use existing order if there is one" do
126131
@c.order(:foo).paged_each{|r| r.must_be_kind_of(@c)}
127132
@c.db.sqls.must_equal ['BEGIN', 'SELECT * FROM items ORDER BY foo LIMIT 1000 OFFSET 0', 'COMMIT']

0 commit comments

Comments
 (0)