queris 0.8.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 +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +34 -0
- data/README.md +53 -0
- data/Rakefile +1 -0
- data/data/redis_scripts/add_low_ttl.lua +10 -0
- data/data/redis_scripts/copy_key_if_absent.lua +13 -0
- data/data/redis_scripts/copy_ttl.lua +13 -0
- data/data/redis_scripts/create_page_if_absent.lua +24 -0
- data/data/redis_scripts/debuq.lua +20 -0
- data/data/redis_scripts/delete_if_string.lua +8 -0
- data/data/redis_scripts/delete_matching_keys.lua +7 -0
- data/data/redis_scripts/expire_temp_query_keys.lua +7 -0
- data/data/redis_scripts/make_rangehack_if_needed.lua +30 -0
- data/data/redis_scripts/master_expire.lua +15 -0
- data/data/redis_scripts/match_key_type.lua +9 -0
- data/data/redis_scripts/move_key.lua +11 -0
- data/data/redis_scripts/multisize.lua +19 -0
- data/data/redis_scripts/paged_query_ready.lua +35 -0
- data/data/redis_scripts/periodic_zremrangebyscore.lua +9 -0
- data/data/redis_scripts/persist_reusable_temp_query_keys.lua +14 -0
- data/data/redis_scripts/query_ensure_existence.lua +23 -0
- data/data/redis_scripts/query_intersect_optimization.lua +31 -0
- data/data/redis_scripts/remove_from_keyspace.lua +27 -0
- data/data/redis_scripts/remove_from_sets.lua +13 -0
- data/data/redis_scripts/results_from_hash.lua +54 -0
- data/data/redis_scripts/results_with_ttl.lua +20 -0
- data/data/redis_scripts/subquery_intersect_optimization.lua +25 -0
- data/data/redis_scripts/subquery_intersect_optimization_cleanup.lua +5 -0
- data/data/redis_scripts/undo_add_low_ttl.lua +8 -0
- data/data/redis_scripts/unpaged_query_ready.lua +17 -0
- data/data/redis_scripts/unpersist_reusable_temp_query_keys.lua +11 -0
- data/data/redis_scripts/update_live_expiring_presence_index.lua +20 -0
- data/data/redis_scripts/update_query.lua +126 -0
- data/data/redis_scripts/update_rangehacks.lua +94 -0
- data/data/redis_scripts/zrangestore.lua +12 -0
- data/lib/queris.rb +400 -0
- data/lib/queris/errors.rb +8 -0
- data/lib/queris/indices.rb +735 -0
- data/lib/queris/mixin/active_record.rb +74 -0
- data/lib/queris/mixin/object.rb +398 -0
- data/lib/queris/mixin/ohm.rb +81 -0
- data/lib/queris/mixin/queris_model.rb +59 -0
- data/lib/queris/model.rb +455 -0
- data/lib/queris/profiler.rb +275 -0
- data/lib/queris/query.rb +1215 -0
- data/lib/queris/query/operations.rb +398 -0
- data/lib/queris/query/page.rb +101 -0
- data/lib/queris/query/timer.rb +42 -0
- data/lib/queris/query/trace.rb +108 -0
- data/lib/queris/query_store.rb +137 -0
- data/lib/queris/version.rb +3 -0
- data/lib/rails/log_subscriber.rb +22 -0
- data/lib/rails/request_timing.rb +29 -0
- data/lib/tasks/queris.rake +138 -0
- data/queris.gemspec +41 -0
- data/test.rb +39 -0
- data/test/current.rb +74 -0
- data/test/dsl.rb +35 -0
- data/test/ohm.rb +37 -0
- metadata +161 -0
@@ -0,0 +1,398 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Queris
|
3
|
+
class Query
|
4
|
+
private
|
5
|
+
class Op #query operation
|
6
|
+
|
7
|
+
class Operand #boilerplate
|
8
|
+
attr_accessor :index, :value, :temp_keys
|
9
|
+
def initialize(op_index, val)
|
10
|
+
@temp_keys = []
|
11
|
+
@index = op_index
|
12
|
+
@value = val
|
13
|
+
end
|
14
|
+
def marshal_dump
|
15
|
+
[(Query === index ? index.id : index.name).to_sym, value]
|
16
|
+
end
|
17
|
+
def key
|
18
|
+
if is_query? && @optimize_subquery_key
|
19
|
+
@temp_key ||= index.results_key(:as_optimized_subquery)
|
20
|
+
else
|
21
|
+
index.key_for_query value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
def optimized_key(whichkey=nil)
|
25
|
+
k=whichkey || key
|
26
|
+
@optimized||={}
|
27
|
+
if Enumerable===k
|
28
|
+
k.map! { |ky| @optimized[ky] || ky }
|
29
|
+
else
|
30
|
+
k = @optimized[k] || k
|
31
|
+
end
|
32
|
+
k
|
33
|
+
end
|
34
|
+
|
35
|
+
def split
|
36
|
+
if Array === key && Enumerable === value
|
37
|
+
raise ClientError, "Sanity check failed - different number of keys and values, bailing." if key.length != value.length
|
38
|
+
value.map do |val|
|
39
|
+
self.class.new(index, val)
|
40
|
+
end
|
41
|
+
else
|
42
|
+
[ self ]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
def is_query?
|
46
|
+
Query === @index
|
47
|
+
end
|
48
|
+
def gather_key_sizes(redis)
|
49
|
+
@size={}
|
50
|
+
k=key #we assume the key already exists on redis (it should have been created by the responsible index earlier)
|
51
|
+
if Enumerable === k
|
52
|
+
k.each { |k| @size[k]=index.key_size(k, redis) }
|
53
|
+
else
|
54
|
+
@size[k]=index.key_size(k, redis)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
def key_size(redis_key)
|
58
|
+
raise ClientError "Attepted to get query operand key size, but it's not ready yet." unless Hash === @size
|
59
|
+
s = @size[redis_key]
|
60
|
+
return Redis::Future === s ? s.value : s
|
61
|
+
end
|
62
|
+
def preintersect(smallkey, mykey)
|
63
|
+
#puts "preintersect #{self} with #{smallkey}"
|
64
|
+
@preintersect||={}
|
65
|
+
@optimized||={}
|
66
|
+
@preintersect[mykey]=smallkey
|
67
|
+
preintersect_key = "#{mykey}:optimized:#{Queris.digest smallkey}"
|
68
|
+
@optimized[mykey]=preintersect_key
|
69
|
+
temp_keys << preintersect_key
|
70
|
+
preintersect_key
|
71
|
+
end
|
72
|
+
def delayed_optimize_query(smallkey, multiplier)
|
73
|
+
@optimize_subquery_key = smallkey
|
74
|
+
@optimize_threshold_multiplier = multiplier
|
75
|
+
end
|
76
|
+
|
77
|
+
def optimized?
|
78
|
+
@optimized && !@optimized.empty?
|
79
|
+
end
|
80
|
+
attr_reader :optimization_key
|
81
|
+
|
82
|
+
def run_optimization(redis)
|
83
|
+
if @preintersect
|
84
|
+
@preintersect.each do |k, smallkey|
|
85
|
+
#puts "running optimization - preintersecting #{k} and #{smallkey}"
|
86
|
+
Queris.run_script(:query_intersect_optimization, redis, [@optimized[k], k, smallkey])
|
87
|
+
end
|
88
|
+
#puts "preintersected some stuff"
|
89
|
+
elsif is_query? && @optimize_subquery_key
|
90
|
+
Queris.run_script(:subquery_intersect_optimization, redis, [ key, index.key, @optimize_subquery_key], [ @optimize_threshold_multiplier ])
|
91
|
+
#puts "no optimizations to run"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
def cleanup_optimization(redis)
|
95
|
+
if is_query? && @optimize_subquery_key
|
96
|
+
Queris.run_script(:subquery_intersect_optimization_cleanup, redis, [ key, index.key ])
|
97
|
+
end
|
98
|
+
end
|
99
|
+
def json_redis_dump(op_name = nil)
|
100
|
+
ret = []
|
101
|
+
miniop = {}
|
102
|
+
if is_query?
|
103
|
+
miniop = {query: index.json_redis_dump}
|
104
|
+
else
|
105
|
+
index.json_redis_dump miniop
|
106
|
+
if Range === @value && @index.handle_range?
|
107
|
+
miniop[:min] = @index.val(@value.begin)
|
108
|
+
miniop[@value.exclude_end? ? :max : :max_or_equal] = @index.val(@value.end)
|
109
|
+
miniop[:key] = @index.key
|
110
|
+
elsif Enumerable === @value
|
111
|
+
@value.each do |val|
|
112
|
+
miniop = { equal: @index.val(val), key: @index.key(val) }
|
113
|
+
miniop[:op] = op_name if op_name
|
114
|
+
ret << miniop
|
115
|
+
end
|
116
|
+
return ret
|
117
|
+
else
|
118
|
+
miniop[:equal] = @index.val(@value) unless miniop[:nocompare]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
miniop[:key] = @index.key(@value)
|
122
|
+
miniop[:op]=op_name if op_name
|
123
|
+
ret << miniop
|
124
|
+
ret
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
attr_accessor :operands, :fragile
|
129
|
+
def initialize(fragile=false)
|
130
|
+
@operands = []
|
131
|
+
@keys = []
|
132
|
+
@weights = []
|
133
|
+
@subqueries = []
|
134
|
+
@fragile = fragile
|
135
|
+
end
|
136
|
+
|
137
|
+
def query_run_stage_inspect(r, q)
|
138
|
+
operands.each do |op|
|
139
|
+
#this logic belongs elsewhere but is here for premature optimization
|
140
|
+
if Queris::RangeIndex === op.index && op.index.rangehack?(op.value)
|
141
|
+
op.index.ensure_rangehack_exists(r, op.value, q)
|
142
|
+
end
|
143
|
+
op.gather_key_sizes(r)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
def query_run_stage_release(r,q)
|
147
|
+
operands.each do |op|
|
148
|
+
op.index.clear_rangehack_keys if Queris::RangeIndex === op.index
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def notready!
|
153
|
+
@ready=nil
|
154
|
+
self
|
155
|
+
end
|
156
|
+
def push(index, val) # push operand
|
157
|
+
@ready = nil
|
158
|
+
@operands << Operand.new(index,val)
|
159
|
+
self
|
160
|
+
end
|
161
|
+
def symbol
|
162
|
+
@symbol || self.class::SYMBOL
|
163
|
+
end
|
164
|
+
def command
|
165
|
+
@command || self.class::COMMAND
|
166
|
+
end
|
167
|
+
def keys(target_key=nil, first = nil)
|
168
|
+
prepare
|
169
|
+
@keys[0]=target_key unless target_key.nil?
|
170
|
+
first ? @keys[1..-1] : @keys
|
171
|
+
end
|
172
|
+
def weights(first = nil)
|
173
|
+
prepare
|
174
|
+
first ? @weights[1..-1] : @weights
|
175
|
+
end
|
176
|
+
def target_key_weight
|
177
|
+
1
|
178
|
+
end
|
179
|
+
def operand_key_weight(op)
|
180
|
+
1
|
181
|
+
end
|
182
|
+
def temp_keys
|
183
|
+
optimized = []
|
184
|
+
operands.each { |op| optimized |= op.temp_keys }
|
185
|
+
optimized
|
186
|
+
end
|
187
|
+
def temp_keys?
|
188
|
+
operands.each { |op| return true unless op.temp_keys.empty? }
|
189
|
+
nil
|
190
|
+
end
|
191
|
+
def subqueries
|
192
|
+
prepare
|
193
|
+
@subqueries || []
|
194
|
+
end
|
195
|
+
def optimize(smallkey, smallsize, page=nil)
|
196
|
+
#optimization walker. doesn't really do much unless given a decision block
|
197
|
+
@optimized = nil
|
198
|
+
operands.each do |op|
|
199
|
+
key = op.key
|
200
|
+
if Enumerable === key
|
201
|
+
key.each do |k|
|
202
|
+
yield k, op.key_size(k), op if block_given?
|
203
|
+
end
|
204
|
+
else
|
205
|
+
yield key, op.key_size(key), op if block_given?
|
206
|
+
end
|
207
|
+
if op.optimized?
|
208
|
+
@optimized = true
|
209
|
+
notready!
|
210
|
+
end
|
211
|
+
end
|
212
|
+
return smallkey, smallsize
|
213
|
+
end
|
214
|
+
def optimized?
|
215
|
+
@optimized
|
216
|
+
end
|
217
|
+
def notready!
|
218
|
+
@ready=nil; self
|
219
|
+
end
|
220
|
+
private :notready!
|
221
|
+
def prepare
|
222
|
+
return if @ready
|
223
|
+
@keys, @weights, @subqueries = [:result_key], [target_key_weight], []
|
224
|
+
operands.each do |op|
|
225
|
+
k = block_given? ? yield(op) : op.optimized_key
|
226
|
+
num_keys = @keys.length
|
227
|
+
if Array === k
|
228
|
+
@keys |= k
|
229
|
+
else
|
230
|
+
@keys << k
|
231
|
+
end
|
232
|
+
if (@keys.length - num_keys) < 0
|
233
|
+
raise ArgumentError, "something really wrong here"
|
234
|
+
end
|
235
|
+
@weights += [ operand_key_weight(op) ] * (@keys.length - num_keys)
|
236
|
+
@subqueries << op.index if Query === op.index
|
237
|
+
end
|
238
|
+
@ready = true
|
239
|
+
end
|
240
|
+
def notready!
|
241
|
+
@ready = nil
|
242
|
+
self
|
243
|
+
end
|
244
|
+
def operand_key(op)
|
245
|
+
op.index.key_for_query op.value
|
246
|
+
end
|
247
|
+
|
248
|
+
def run(redis, target, first=false, trace_callback=false)
|
249
|
+
subqueries_on_slave = !subqueries.empty? && redis != Queris.redis(:master)
|
250
|
+
|
251
|
+
redis.multi do |r|
|
252
|
+
r=redis
|
253
|
+
if subqueries_on_slave || optimized?
|
254
|
+
#prevent dummy result string on master from race-conditioning its way into the query
|
255
|
+
|
256
|
+
Queris.run_script :delete_if_string, redis, subqueries.map{|s| s.key} if subqueries_on_slave
|
257
|
+
end
|
258
|
+
operands.each do |op|
|
259
|
+
op.run_optimization(redis)
|
260
|
+
op.index.before_query_op(redis, target, op.value, op) if op.index.respond_to? :before_query_op
|
261
|
+
op.cleanup_optimization(redis)
|
262
|
+
end
|
263
|
+
unless trace_callback
|
264
|
+
redis.send self.class::COMMAND, target, keys(target, first), :weights => weights(first)
|
265
|
+
else
|
266
|
+
operands.each do |operand|
|
267
|
+
operand.split.each do |op| #ensure one key per operand
|
268
|
+
keys = [ target, op.key ]
|
269
|
+
weights = [target_key_weight, operand_key_weight(op)]
|
270
|
+
if first
|
271
|
+
keys.shift; weights.shift
|
272
|
+
first = false
|
273
|
+
end
|
274
|
+
redis.send self.class::COMMAND, target, keys, :weights => weights
|
275
|
+
trace_callback.call(self, op, target) if trace_callback
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
operands.each { |op| op.index.after_query_op(redis, target, op.value, op) if op.index.respond_to? :after_query_op }
|
281
|
+
end
|
282
|
+
|
283
|
+
def marshal_dump
|
284
|
+
[self.class::SYMBOL, operands.map {|op| op.marshal_dump}]
|
285
|
+
end
|
286
|
+
def json_redis_dump(etc={})
|
287
|
+
all_ops = []
|
288
|
+
operands.map do |op|
|
289
|
+
all_ops.concat op.json_redis_dump(self.class::NAME)
|
290
|
+
end
|
291
|
+
all_ops
|
292
|
+
end
|
293
|
+
def to_s
|
294
|
+
"#{symbol} #{operands.map{|o| Query === o.index ? o.index : "#{o.index.name}<#{o.value}>"}.join(" #{symbol} ")}"
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
class UnionOp < Op
|
299
|
+
COMMAND = :zunionstore
|
300
|
+
SYMBOL = :'∪'
|
301
|
+
NAME = :union
|
302
|
+
|
303
|
+
OPTIMIZATION_THRESHOLD_MULTIPLIER = 3
|
304
|
+
def optimize(smallkey, smallsize, page=nil)
|
305
|
+
m = self.class::OPTIMIZATION_THRESHOLD_MULTIPLIER
|
306
|
+
super do |key, size, op|
|
307
|
+
if op.is_query? && !op.index.paged?
|
308
|
+
#puts "optimizing unpaged subquery #{op.index} later"
|
309
|
+
op.delayed_optimize_query(smallkey, m)
|
310
|
+
elsif smallsize * m < size
|
311
|
+
#puts "optimization reduced union(?) operand #{op} from #{size} to #{smallsize}"
|
312
|
+
op.preintersect(smallkey, key)
|
313
|
+
elsif page && page.size * m < size
|
314
|
+
#puts "paging reduced union(?) operand #{op} from #{size} to #{page.size}"
|
315
|
+
op.preintersect(page.key, key)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
return smallkey, smallsize
|
319
|
+
end
|
320
|
+
end
|
321
|
+
class IntersectOp < Op
|
322
|
+
COMMAND = :zinterstore
|
323
|
+
SYMBOL = :'∩'
|
324
|
+
NAME = :intersect
|
325
|
+
|
326
|
+
OPTIMIZATION_THRESHOLD_MULTIPLIER = 5
|
327
|
+
def optimize(smallkey, smallsize, page=nil)
|
328
|
+
smallestkey, smallestsize, smallestop = Float::INFINITY, Float::INFINITY, nil
|
329
|
+
m = self.class::OPTIMIZATION_THRESHOLD_MULTIPLIER
|
330
|
+
#subquery_ops=[]
|
331
|
+
super do |key, size, op|
|
332
|
+
smallestkey, smallestsize, smallestop = key, size, op if size < smallestsize
|
333
|
+
#subquery_ops << op if op.is_query?
|
334
|
+
end
|
335
|
+
#no need to preintersect subqueries for intersects - it's not trivial (size may not be available before query subquery is run), and it's not terribly advantageous
|
336
|
+
if smallsize * m < smallestsize
|
337
|
+
puts "optimization reduced intersect operand from #{smallestsize} to #{smallsize}"
|
338
|
+
smallestop.preintersect(smallkey, smallestkey)
|
339
|
+
elsif page && page.size * m < smallestsize
|
340
|
+
smallestop.preintersect(page.key, smallestkey)
|
341
|
+
end
|
342
|
+
if smallestsize < smallsize
|
343
|
+
#puts "found a smaller intersect key: |#{smallestkey}|=#{smallestsize}"
|
344
|
+
return smallestkey, smallestsize
|
345
|
+
else
|
346
|
+
return smallkey, smallsize
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
class DiffOp < UnionOp
|
351
|
+
SYMBOL = :'∖'
|
352
|
+
NAME = :diff
|
353
|
+
|
354
|
+
def target_key_weight; 0; end
|
355
|
+
def operand_key_weight(op=nil); :'-inf'; end
|
356
|
+
def run(redis, result_key, *arg)
|
357
|
+
super redis, result_key, *arg
|
358
|
+
redis.zremrangebyscore result_key, :'-inf', :'-inf'
|
359
|
+
# BUG: sorted sets with -inf scores will be treated incorrectly when diffing
|
360
|
+
end
|
361
|
+
end
|
362
|
+
class SortOp < Op
|
363
|
+
COMMAND = :zinterstore
|
364
|
+
SYMBOL = :sortby
|
365
|
+
def json_redis_dump
|
366
|
+
operands.map do |op|
|
367
|
+
{key: op.index.key, multiplier: op.value}
|
368
|
+
end
|
369
|
+
end
|
370
|
+
def push(index, reverse=nil)
|
371
|
+
super(index, reverse ? -1 : 1)
|
372
|
+
end
|
373
|
+
def target_key_weight; 0; end
|
374
|
+
def operand_key_weight(op); op.value; end
|
375
|
+
def prepare
|
376
|
+
#don't trigger the rangehack
|
377
|
+
super { |op| op.index.key op.value }
|
378
|
+
end
|
379
|
+
def operand_key(op)
|
380
|
+
op.index.key op.value
|
381
|
+
end
|
382
|
+
def query_run_stage_inspect(r, q)
|
383
|
+
operands.each do |op|
|
384
|
+
#don't trigger the rangehack
|
385
|
+
op.gather_key_sizes(r)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
def run(redis, target, first=false, trace_callback=nil)
|
389
|
+
sort_keys = keys(target, first)
|
390
|
+
redis.send self.class::COMMAND, target, sort_keys, :weights => weights(first)
|
391
|
+
if trace_callback
|
392
|
+
raise NotImplemented, "Can't trace multi-sorts yet." if sort_keys.count > 2 || operands.count > 1
|
393
|
+
trace_callback.call(self, operands.first, target)
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Queris
|
2
|
+
class Query
|
3
|
+
class Page
|
4
|
+
attr_accessor :page, :range
|
5
|
+
def initialize(prefix, sortops, page_size, ttl)
|
6
|
+
@prefix=prefix
|
7
|
+
@ops=sortops
|
8
|
+
@pagesize=page_size
|
9
|
+
@ttl=ttl
|
10
|
+
@page=0
|
11
|
+
end
|
12
|
+
def key
|
13
|
+
"#{@prefix}page:#{Queris.digest(@ops.join)}:#{@pagesize}:#{@page}"
|
14
|
+
end
|
15
|
+
def size; @pagesize; end
|
16
|
+
def volatile_query_keys(q)
|
17
|
+
[ q.results_key(:last_loaded_page) ]
|
18
|
+
end
|
19
|
+
def gather_data(redis, results_key, pagecount_key)
|
20
|
+
#puts "gather page data for key #{results_key}"
|
21
|
+
@current_count= Queris.run_script :multisize, redis, [results_key]
|
22
|
+
@last_loaded_page ||= redis.get pagecount_key
|
23
|
+
@total_count ||= redis.zcard source_key
|
24
|
+
end
|
25
|
+
def inspect_query(r, q)
|
26
|
+
gather_data(r, q.results_key, q.results_key(:last_loaded_page))
|
27
|
+
gather_ready_data(r, q)
|
28
|
+
end
|
29
|
+
def query_run_stage_after_run(r, q)
|
30
|
+
puts "write last_loaded_page for #{q}"
|
31
|
+
llp = fluxcap(@last_loaded_page)
|
32
|
+
llp=llp.to_i if llp
|
33
|
+
@last_loaded_page = @page
|
34
|
+
r.set q.results_key(:last_loaded_page), fluxcap(@last_loaded_page)
|
35
|
+
inspect_query(r, q)
|
36
|
+
end
|
37
|
+
def gather_ready_data(r, q)
|
38
|
+
@ready = Queris.run_script(:paged_query_ready, r, [q.results_key, q.results_key(:exists), source_key, q.runstate_key(:ready)])
|
39
|
+
end
|
40
|
+
|
41
|
+
def seek
|
42
|
+
last, cur_count = fluxcap(@last_loaded_page), fluxcap(@current_count)
|
43
|
+
last=nil if last == ""
|
44
|
+
last=last.to_i unless last.nil?
|
45
|
+
cur_count=cur_count.to_i unless cur_count.nil?
|
46
|
+
@key=nil
|
47
|
+
if last.nil?
|
48
|
+
@page=0
|
49
|
+
puts "seeking next page... will be #{@page} last_loaded = #{last}, cur_count = #{cur_count}, total max = #{fluxcap @total_count}"
|
50
|
+
false
|
51
|
+
elsif cur_count < range.max && !no_more_pages?
|
52
|
+
@page = last + 1
|
53
|
+
puts "seeking next page... will be #{@page} last_loaded = #{last}, cur_count = #{cur_count}, total max = #{fluxcap @total_count}"
|
54
|
+
false
|
55
|
+
else
|
56
|
+
puts "no need to seek, we are here"
|
57
|
+
true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
def no_more_pages?
|
61
|
+
last, cur_count = fluxcap(@last_loaded_page), fluxcap(@current_count)
|
62
|
+
return nil if last == "" || last.nil?
|
63
|
+
last=last.to_i
|
64
|
+
(last + 1) * size > fluxcap(@total_count)
|
65
|
+
end
|
66
|
+
def ready?
|
67
|
+
raise Error, "Asked if a page was ready without having set a desired range first" unless @range
|
68
|
+
ready = (fluxcap(@current_count) || -Float::INFINITY) >= @range.max || no_more_pages?
|
69
|
+
yield(self) if !ready && block_given?
|
70
|
+
ready
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def source_key
|
75
|
+
raise NotImplemented, "paging by multiple sorts not yet implemented" if @ops.count > 1
|
76
|
+
binding.pry if @ops.first.nil?
|
77
|
+
@ops.first.keys[1]
|
78
|
+
end
|
79
|
+
def source_id
|
80
|
+
Queris.digest source_key
|
81
|
+
end
|
82
|
+
def created?; @created==@page; end
|
83
|
+
def create_page(redis)
|
84
|
+
llp = fluxcap(@last_loaded_page)
|
85
|
+
llp=nil if llp==""
|
86
|
+
return if (llp && llp == @page)
|
87
|
+
redis.eval("redis.log(redis.LOG_WARNING, 'want page #{@page}!!!!!!!!1')")
|
88
|
+
Queris.run_script(:create_page_if_absent, redis, [key, source_key], [@pagesize * @page, @pagesize * (@page + 1) -1, @ttl])
|
89
|
+
end
|
90
|
+
private
|
91
|
+
def fluxcap(val)#possible future value
|
92
|
+
begin
|
93
|
+
Redis::Future === val ? val.value : val
|
94
|
+
rescue Redis::FutureNotReady
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|