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 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