sequel-seek-pagination 0.4.0 → 0.4.1
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 +5 -5
- data/lib/sequel/extensions/seek_pagination.rb +100 -60
- data/lib/sequel/extensions/seek_pagination/version.rb +1 -1
- data/spec/seek_pagination_spec.rb +34 -2
- data/spec/spec_helper.rb +1 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9dfe03d36fb53800c54b429a7930afe26d146b7bb97262dd3295b2e2fefd8376
|
4
|
+
data.tar.gz: 3b26d4ac8282c81fcc08d0ccb16f40612281bacc3aabe0cb205692aa1b3fdf73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 920966063e123eec91588025445af46127c68deda371b0b73f60e14907648cc9cb70c503a32939981d3f87414c741673c0cb870a2295f0cf1509ac04c6ecec7d
|
7
|
+
data.tar.gz: ba980e3f6991239d7c3c10396f96beff84952ca0d3ef51a175bb817f0dfbbe25bdf42dd1354dc24963659dc98c8ae125e869782823e1745772e75b818f26c5a0
|
@@ -5,7 +5,27 @@ module Sequel
|
|
5
5
|
module SeekPagination
|
6
6
|
class Error < StandardError; end
|
7
7
|
|
8
|
-
def seek(
|
8
|
+
def seek(missing_pk: :raise, **args)
|
9
|
+
if c = seek_conditions(raise_on_missing_pk: missing_pk == :raise, **args)
|
10
|
+
where(c)
|
11
|
+
else
|
12
|
+
case missing_pk
|
13
|
+
when :ignore then self
|
14
|
+
when :nullify then nullify
|
15
|
+
when :return_nil then nil
|
16
|
+
else raise Error, "passed an invalid argument for missing_pk: #{missing_pk.inspect}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def seek_conditions(
|
22
|
+
value: nil,
|
23
|
+
pk: nil,
|
24
|
+
include_exact_match: false,
|
25
|
+
not_null: nil,
|
26
|
+
raise_on_missing_pk: false
|
27
|
+
)
|
28
|
+
|
9
29
|
order = opts[:order]
|
10
30
|
model = opts[:model]
|
11
31
|
|
@@ -13,32 +33,19 @@ module Sequel
|
|
13
33
|
raise Error, "must pass exactly one of :value and :pk to #seek"
|
14
34
|
elsif order.nil? || order.length.zero?
|
15
35
|
raise Error, "cannot call #seek on a dataset with no order"
|
16
|
-
elsif model.nil? && pk
|
17
|
-
raise Error, "attempted a primary key lookup on a dataset that doesn't have an associated model"
|
18
36
|
end
|
19
37
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
# Dataset#get won't like it if we pass it expressions that aren't
|
26
|
-
# simple columns, so we need to give it aliases for everything.
|
27
|
-
al = :a
|
28
|
-
gettable = order.map do |o|
|
29
|
-
expression = Sequel::SQL::OrderedExpression === o ? o.expression : o
|
30
|
-
Sequel.as(expression, (al = al.next))
|
38
|
+
values =
|
39
|
+
if pk
|
40
|
+
get_order_values_for_pk(pk, raise_on_failure: raise_on_missing_pk)
|
41
|
+
else
|
42
|
+
Array(value)
|
31
43
|
end
|
32
44
|
|
33
|
-
|
34
|
-
raise NoMatchingRow.new(target_ds)
|
35
|
-
end
|
36
|
-
else
|
37
|
-
values = Array(value)
|
45
|
+
return unless values
|
38
46
|
|
39
|
-
|
40
|
-
|
41
|
-
end
|
47
|
+
if values.length != order.length
|
48
|
+
raise Error, "passed the wrong number of values to #seek"
|
42
49
|
end
|
43
50
|
|
44
51
|
if not_null.nil?
|
@@ -47,8 +54,11 @@ module Sequel
|
|
47
54
|
# If the dataset was chained off a model, use its stored schema
|
48
55
|
# information to figure out what columns are not null.
|
49
56
|
if model
|
57
|
+
table = model.table_name
|
58
|
+
|
50
59
|
model.db_schema.each do |column, schema|
|
51
|
-
|
60
|
+
next if schema[:allow_null]
|
61
|
+
not_null << column << Sequel.qualify(table, column)
|
52
62
|
end
|
53
63
|
end
|
54
64
|
end
|
@@ -57,7 +67,40 @@ module Sequel
|
|
57
67
|
order.zip(values),
|
58
68
|
include_exact_match: include_exact_match,
|
59
69
|
not_null: not_null
|
60
|
-
).
|
70
|
+
).build_conditions
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_order_values_for_pk(pk, raise_on_failure: false)
|
74
|
+
order = opts[:order]
|
75
|
+
|
76
|
+
unless model = opts[:model]
|
77
|
+
raise Error, "attempted a primary key lookup on a dataset that doesn't have an associated model"
|
78
|
+
end
|
79
|
+
|
80
|
+
al = nil
|
81
|
+
aliases = order.map { al = al ? al.next : :a }
|
82
|
+
|
83
|
+
ds =
|
84
|
+
cached_dataset(:_seek_pagination_get_order_values_ds) do
|
85
|
+
# Need to load the values to order from for that pk from the DB, so we
|
86
|
+
# need to fetch the actual expressions being ordered by. Also,
|
87
|
+
# Dataset#get won't like it if we pass it expressions that aren't
|
88
|
+
# simple columns, so we need to give it aliases for everything.
|
89
|
+
naked.limit(1).select(
|
90
|
+
*order.map.with_index { |o, i|
|
91
|
+
expression = Sequel::SQL::OrderedExpression === o ? o.expression : o
|
92
|
+
Sequel.as(expression, aliases[i])
|
93
|
+
}
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
condition = model.qualified_primary_key_hash(pk)
|
98
|
+
|
99
|
+
if result = ds.where_all(condition).first
|
100
|
+
result.values_at(*aliases)
|
101
|
+
elsif raise_on_failure
|
102
|
+
raise NoMatchingRow.new(ds.where(condition))
|
103
|
+
end
|
61
104
|
end
|
62
105
|
|
63
106
|
private
|
@@ -71,46 +114,43 @@ module Sequel
|
|
71
114
|
@orders = order_values.map { |order, value| OrderedColumn.new(self, order, value) }
|
72
115
|
end
|
73
116
|
|
74
|
-
def
|
117
|
+
def build_conditions
|
75
118
|
length = orders.length
|
76
119
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
orders.map(&:value)
|
86
|
-
)
|
87
|
-
end
|
88
|
-
else
|
89
|
-
Sequel.&(
|
90
|
-
*length.times.map { |i|
|
91
|
-
allow_equal = include_exact_match || i != (length - 1)
|
92
|
-
conditions = orders[0..i]
|
93
|
-
|
94
|
-
if i.zero?
|
95
|
-
conditions[0].inequality_condition(allow_equal: allow_equal)
|
96
|
-
else
|
97
|
-
c = conditions[-2]
|
98
|
-
|
99
|
-
list = if filter = conditions[-1].inequality_condition(allow_equal: allow_equal)
|
100
|
-
[Sequel.&(c.eq_filter, filter)]
|
101
|
-
else
|
102
|
-
[c.eq_filter]
|
103
|
-
end
|
104
|
-
|
105
|
-
list += conditions[0..-2].map { |c| c.inequality_condition(allow_equal: false) }
|
106
|
-
|
107
|
-
Sequel.|(*list.compact)
|
108
|
-
end
|
109
|
-
}.compact
|
120
|
+
# Handle the common case where we can do a simpler (and faster)
|
121
|
+
# WHERE (non_nullable_1, non_nullable_2) > (1, 2) clause.
|
122
|
+
if length > 1 && orders.all?(&:not_null) && has_uniform_order_direction?
|
123
|
+
Sequel.virtual_row do |o|
|
124
|
+
o.__send__(
|
125
|
+
orders.first.inequality_method(include_exact_match),
|
126
|
+
orders.map(&:name),
|
127
|
+
orders.map(&:value)
|
110
128
|
)
|
111
129
|
end
|
112
|
-
|
113
|
-
|
130
|
+
else
|
131
|
+
Sequel.&(
|
132
|
+
*length.times.map { |i|
|
133
|
+
allow_equal = include_exact_match || i != (length - 1)
|
134
|
+
conditions = orders[0..i]
|
135
|
+
|
136
|
+
if i.zero?
|
137
|
+
conditions[0].inequality_condition(allow_equal: allow_equal)
|
138
|
+
else
|
139
|
+
c = conditions[-2]
|
140
|
+
|
141
|
+
list = if filter = conditions[-1].inequality_condition(allow_equal: allow_equal)
|
142
|
+
[Sequel.&(c.eq_filter, filter)]
|
143
|
+
else
|
144
|
+
[c.eq_filter]
|
145
|
+
end
|
146
|
+
|
147
|
+
list += conditions[0..-2].map { |c| c.inequality_condition(allow_equal: false) }
|
148
|
+
|
149
|
+
Sequel.|(*list.compact)
|
150
|
+
end
|
151
|
+
}.compact
|
152
|
+
)
|
153
|
+
end
|
114
154
|
end
|
115
155
|
|
116
156
|
private
|
@@ -200,8 +200,40 @@ class SeekPaginationSpec < Minitest::Spec
|
|
200
200
|
SeekModel.order(:not_nullable_1, :not_nullable_2, :id).seek(value: [1, 2, 3]).limit(5).sql
|
201
201
|
end
|
202
202
|
|
203
|
-
it "
|
204
|
-
|
203
|
+
it "shouldn't be fooled by table-qualified orderings" do
|
204
|
+
assert_equal %(SELECT * FROM "seek" WHERE (("seek"."not_nullable_1", "seek"."not_nullable_2", "seek"."id") > (1, 2, 3)) ORDER BY "seek"."not_nullable_1", "seek"."not_nullable_2", "seek"."id" LIMIT 5),
|
205
|
+
SeekModel.order(Sequel.qualify(:seek, :not_nullable_1), Sequel.qualify(:seek, :not_nullable_2), Sequel.qualify(:seek, :id)).seek(value: [1, 2, 3]).limit(5).sql
|
206
|
+
end
|
207
|
+
|
208
|
+
describe "when passed a pk and no record is found" do
|
209
|
+
it "should default to raising an error" do
|
210
|
+
assert_raises(Sequel::NoMatchingRow) do
|
211
|
+
SeekModel.order(:id).seek(pk: -45)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should support returning nil" do
|
216
|
+
assert_nil SeekModel.order(:id).seek(pk: -45, missing_pk: :return_nil)
|
217
|
+
end
|
218
|
+
|
219
|
+
it "should support ignoring the condition" do
|
220
|
+
ds = SeekModel.order(:id).seek(pk: -45, missing_pk: :ignore)
|
221
|
+
|
222
|
+
assert_equal SeekModel.order(:id), ds
|
223
|
+
end
|
224
|
+
|
225
|
+
it "should support nullifying the dataset" do
|
226
|
+
ds = SeekModel.order(:id).seek(pk: -45, missing_pk: :nullify)
|
227
|
+
|
228
|
+
assert_equal [], ds.all
|
229
|
+
assert_equal 0, ds.count
|
230
|
+
end
|
231
|
+
|
232
|
+
it "should raise when an unsupported option is passed" do
|
233
|
+
assert_error_message "passed an invalid argument for missing_pk: :nonexistent_option" do
|
234
|
+
SeekModel.order(:id).seek(pk: -45, missing_pk: :nonexistent_option)
|
235
|
+
end
|
236
|
+
end
|
205
237
|
end
|
206
238
|
end
|
207
239
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel-seek-pagination
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Hanks
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -107,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
107
|
version: '0'
|
108
108
|
requirements: []
|
109
109
|
rubyforge_project:
|
110
|
-
rubygems_version: 2.
|
110
|
+
rubygems_version: 2.7.3
|
111
111
|
signing_key:
|
112
112
|
specification_version: 4
|
113
113
|
summary: Seek pagination for Sequel + PostgreSQL
|