sequel-seek-pagination 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|