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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9751ce847311d620172aefa80a83148dc6a2dd83
4
- data.tar.gz: a525864295cf2d3a2680bcc0eea4c88be7d94f3a
2
+ SHA256:
3
+ metadata.gz: 9dfe03d36fb53800c54b429a7930afe26d146b7bb97262dd3295b2e2fefd8376
4
+ data.tar.gz: 3b26d4ac8282c81fcc08d0ccb16f40612281bacc3aabe0cb205692aa1b3fdf73
5
5
  SHA512:
6
- metadata.gz: 8be865383b88eb55f9e7b6b19fa9a79a885a630c5a61cedc736196a450ff495af6e36ce381a959775bf42e4fff05807964abe8bfd194d406bf8246cadaa5a2c3
7
- data.tar.gz: e526f7d0531401db2b8931d3cc7b5a92fe3592b7d5d9c062c57f1c0f12ea349e47cbb0376e6615b7565e3b60d9daf4efec3f671f51c851762f89d97145fd0948
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(value: nil, pk: nil, include_exact_match: false, not_null: nil)
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
- if pk
21
- target_ds = where(model.qualified_primary_key_hash(pk))
22
-
23
- # Need to load the values to order from for that pk from the DB, so we
24
- # need to fetch the actual expressions being ordered by. Also,
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
- unless values = target_ds.get(gettable)
34
- raise NoMatchingRow.new(target_ds)
35
- end
36
- else
37
- values = Array(value)
45
+ return unless values
38
46
 
39
- if values.length != order.length
40
- raise Error, "passed the wrong number of values to #seek"
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
- not_null << column if schema[:allow_null] == false
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
- ).apply(self)
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 apply(dataset)
117
+ def build_conditions
75
118
  length = orders.length
76
119
 
77
- conditions =
78
- # Handle the common case where we can do a simpler (and faster)
79
- # WHERE (non_nullable_1, non_nullable_2) > (1, 2) clause.
80
- if length > 1 && orders.all?(&:not_null) && has_uniform_order_direction?
81
- Sequel.virtual_row do |o|
82
- o.__send__(
83
- orders.first.inequality_method(include_exact_match),
84
- orders.map(&:name),
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
- dataset.where(conditions)
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
@@ -1,5 +1,5 @@
1
1
  module Sequel
2
2
  module SeekPagination
3
- VERSION = '0.4.0'
3
+ VERSION = '0.4.1'
4
4
  end
5
5
  end
@@ -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 "should raise an error when passed a pk for a record that doesn't exist in the dataset" do
204
- assert_raises(Sequel::NoMatchingRow) { SeekModel.order(:id).seek(pk: -45) }
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
@@ -3,6 +3,7 @@ require 'sequel'
3
3
  $: << File.join(File.dirname(__FILE__), '..', 'lib')
4
4
 
5
5
  Sequel::Database.extension :seek_pagination
6
+ Sequel::Database.extension :null_dataset
6
7
 
7
8
  DB = Sequel.connect "postgres:///sequel-seek-pagination-test"
8
9
 
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.0
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: 2017-09-05 00:00:00.000000000 Z
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.5.1
110
+ rubygems_version: 2.7.3
111
111
  signing_key:
112
112
  specification_version: 4
113
113
  summary: Seek pagination for Sequel + PostgreSQL