redis-file 0.4.2
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/.gitignore +7 -0
- data/.rspec +1 -0
- data/.travis.yml +8 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.md +86 -0
- data/Rakefile +27 -0
- data/lib/redis-file.rb +6 -0
- data/lib/redis-file/expiring_hash.rb +70 -0
- data/lib/redis-file/rspec.rb +23 -0
- data/lib/redis-file/sorted_set_argument_handler.rb +74 -0
- data/lib/redis-file/sorted_set_store.rb +80 -0
- data/lib/redis-file/version.rb +3 -0
- data/lib/redis-file/zset.rb +29 -0
- data/lib/redis/connection/file.rb +960 -0
- data/redis-file.gemspec +23 -0
- data/spec/compatibility_spec.rb +9 -0
- data/spec/connection_spec.rb +85 -0
- data/spec/hashes_spec.rb +182 -0
- data/spec/keys_spec.rb +248 -0
- data/spec/lists_spec.rb +195 -0
- data/spec/server_spec.rb +100 -0
- data/spec/sets_spec.rb +178 -0
- data/spec/sorted_sets_spec.rb +425 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/spec_helper_live_redis.rb +14 -0
- data/spec/strings_spec.rb +236 -0
- data/spec/transactions_spec.rb +19 -0
- data/spec/upcase_method_name_spec.rb +18 -0
- metadata +103 -0
@@ -0,0 +1,960 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'redis/connection/registry'
|
3
|
+
require 'redis/connection/command_helper'
|
4
|
+
require "redis-file/expiring_hash"
|
5
|
+
require "redis-file/sorted_set_argument_handler"
|
6
|
+
require "redis-file/sorted_set_store"
|
7
|
+
require "redis-file/zset"
|
8
|
+
require 'digest/md5'
|
9
|
+
|
10
|
+
class Redis
|
11
|
+
module Connection
|
12
|
+
class File
|
13
|
+
|
14
|
+
# try use "db" folder, if the folder not exists, use the same folder
|
15
|
+
FILENAME_SUFIX = "redis"
|
16
|
+
FILENAME_PREFIX = Rails.env + "." rescue "db."
|
17
|
+
DB_FOLDER = ::File.directory?("db") ? "db/" : "./"
|
18
|
+
DB_PATH = DB_FOLDER + FILENAME_PREFIX + FILENAME_SUFIX
|
19
|
+
::File.open(DB_PATH, 'wb') { |file| Marshal.dump({}, file) } unless ::File.exists?(DB_PATH)
|
20
|
+
|
21
|
+
include Redis::Connection::CommandHelper
|
22
|
+
include RedisFile
|
23
|
+
|
24
|
+
attr_accessor :buffer, :options
|
25
|
+
|
26
|
+
# Used for resetting everything in specs
|
27
|
+
def self.reset_all_databases
|
28
|
+
::File.open(DB_PATH, 'wb') { |file| Marshal.dump({}, file) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.connect(options = {})
|
32
|
+
new(options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(options = {})
|
36
|
+
self.options = options
|
37
|
+
end
|
38
|
+
|
39
|
+
def database_id
|
40
|
+
@database_id ||= 0
|
41
|
+
end
|
42
|
+
attr_writer :database_id
|
43
|
+
|
44
|
+
def database_instance_key
|
45
|
+
Digest::MD5.hexdigest([options[:host], options[:port]].to_s)
|
46
|
+
end
|
47
|
+
|
48
|
+
def find_database id=database_id
|
49
|
+
@databases[database_instance_key] ||= {}
|
50
|
+
@databases[database_instance_key][id] ||= ExpiringHash.new
|
51
|
+
end
|
52
|
+
|
53
|
+
def data
|
54
|
+
find_database
|
55
|
+
end
|
56
|
+
|
57
|
+
def replies
|
58
|
+
@replies ||= []
|
59
|
+
end
|
60
|
+
attr_writer :replies
|
61
|
+
|
62
|
+
def connected?
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
def connect_unix(path, timeout)
|
67
|
+
end
|
68
|
+
|
69
|
+
def disconnect
|
70
|
+
end
|
71
|
+
|
72
|
+
def timeout=(usecs)
|
73
|
+
end
|
74
|
+
|
75
|
+
def write(command)
|
76
|
+
meffod = command.shift.to_s.downcase.to_sym
|
77
|
+
if respond_to?(meffod)
|
78
|
+
@databases = ::File.open(DB_PATH) { |file| Marshal.load(file) }
|
79
|
+
reply = send(meffod, *command)
|
80
|
+
::File.open(DB_PATH, 'wb') { |file| Marshal.dump(@databases, file) }
|
81
|
+
else
|
82
|
+
raise Redis::CommandError, "ERR unknown command '#{meffod}'"
|
83
|
+
end
|
84
|
+
|
85
|
+
if reply == true
|
86
|
+
reply = 1
|
87
|
+
elsif reply == false
|
88
|
+
reply = 0
|
89
|
+
end
|
90
|
+
|
91
|
+
replies << reply
|
92
|
+
buffer << reply if buffer && meffod != :multi
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
|
96
|
+
def read
|
97
|
+
replies.shift
|
98
|
+
end
|
99
|
+
|
100
|
+
# NOT IMPLEMENTED:
|
101
|
+
# * blpop
|
102
|
+
# * brpop
|
103
|
+
# * brpoplpush
|
104
|
+
# * discard
|
105
|
+
# * sort
|
106
|
+
# * subscribe
|
107
|
+
# * psubscribe
|
108
|
+
# * publish
|
109
|
+
|
110
|
+
def flushdb
|
111
|
+
@databases[database_instance_key].delete(database_id)
|
112
|
+
"OK"
|
113
|
+
end
|
114
|
+
|
115
|
+
def flushall
|
116
|
+
@databases[database_instance_key] = {}
|
117
|
+
"OK"
|
118
|
+
end
|
119
|
+
|
120
|
+
def auth(password)
|
121
|
+
"OK"
|
122
|
+
end
|
123
|
+
|
124
|
+
def select(index)
|
125
|
+
data_type_check(index, Integer)
|
126
|
+
self.database_id = index
|
127
|
+
"OK"
|
128
|
+
end
|
129
|
+
|
130
|
+
def info
|
131
|
+
{
|
132
|
+
"redis_version" => "0.07",
|
133
|
+
"connected_clients" => "1",
|
134
|
+
"connected_slaves" => "0",
|
135
|
+
"used_memory" => "3187",
|
136
|
+
"changes_since_last_save" => "0",
|
137
|
+
"last_save_time" => "1237655729",
|
138
|
+
"total_connections_received" => "1",
|
139
|
+
"total_commands_processed" => "1",
|
140
|
+
"uptime_in_seconds" => "36000",
|
141
|
+
"uptime_in_days" => 0
|
142
|
+
}
|
143
|
+
end
|
144
|
+
|
145
|
+
def monitor; end
|
146
|
+
|
147
|
+
def save; end
|
148
|
+
|
149
|
+
def bgsave ; end
|
150
|
+
|
151
|
+
def bgreriteaof ; end
|
152
|
+
|
153
|
+
def move key, destination_id
|
154
|
+
raise Redis::CommandError, "ERR source and destination objects are the same" if destination_id == database_id
|
155
|
+
destination = find_database(destination_id)
|
156
|
+
return false unless data.has_key?(key)
|
157
|
+
return false if destination.has_key?(key)
|
158
|
+
destination[key] = data.delete(key)
|
159
|
+
true
|
160
|
+
end
|
161
|
+
|
162
|
+
def get(key)
|
163
|
+
data_type_check(key, String)
|
164
|
+
data[key]
|
165
|
+
end
|
166
|
+
|
167
|
+
def getbit(key, offset)
|
168
|
+
return unless data[key]
|
169
|
+
data[key].unpack('B*')[0].split("")[offset].to_i
|
170
|
+
end
|
171
|
+
|
172
|
+
def getrange(key, start, ending)
|
173
|
+
return unless data[key]
|
174
|
+
data[key][start..ending]
|
175
|
+
end
|
176
|
+
alias :substr :getrange
|
177
|
+
|
178
|
+
def getset(key, value)
|
179
|
+
data_type_check(key, String)
|
180
|
+
data[key].tap do
|
181
|
+
set(key, value)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def mget(*keys)
|
186
|
+
raise_argument_error('mget') if keys.empty?
|
187
|
+
# We work with either an array, or list of arguments
|
188
|
+
keys = keys.first if keys.size == 1
|
189
|
+
data.values_at(*keys)
|
190
|
+
end
|
191
|
+
|
192
|
+
def append(key, value)
|
193
|
+
data[key] = (data[key] || "")
|
194
|
+
data[key] = data[key] + value.to_s
|
195
|
+
end
|
196
|
+
|
197
|
+
def strlen(key)
|
198
|
+
return unless data[key]
|
199
|
+
data[key].size
|
200
|
+
end
|
201
|
+
|
202
|
+
def hgetall(key)
|
203
|
+
data_type_check(key, Hash)
|
204
|
+
data[key].to_a.flatten || {}
|
205
|
+
end
|
206
|
+
|
207
|
+
def hget(key, field)
|
208
|
+
data_type_check(key, Hash)
|
209
|
+
data[key] && data[key][field.to_s]
|
210
|
+
end
|
211
|
+
|
212
|
+
def hdel(key, field)
|
213
|
+
field = field.to_s
|
214
|
+
data_type_check(key, Hash)
|
215
|
+
data[key] && data[key].delete(field)
|
216
|
+
remove_key_for_empty_collection(key)
|
217
|
+
end
|
218
|
+
|
219
|
+
def hkeys(key)
|
220
|
+
data_type_check(key, Hash)
|
221
|
+
return [] if data[key].nil?
|
222
|
+
data[key].keys
|
223
|
+
end
|
224
|
+
|
225
|
+
def keys(pattern = "*")
|
226
|
+
regexp = Regexp.new(pattern.split("*").map { |r| Regexp.escape(r) }.join(".*"))
|
227
|
+
data.keys.select { |key| key =~ regexp }
|
228
|
+
end
|
229
|
+
|
230
|
+
def randomkey
|
231
|
+
data.keys[rand(dbsize)]
|
232
|
+
end
|
233
|
+
|
234
|
+
def echo(string)
|
235
|
+
string
|
236
|
+
end
|
237
|
+
|
238
|
+
def ping
|
239
|
+
"PONG"
|
240
|
+
end
|
241
|
+
|
242
|
+
def lastsave
|
243
|
+
Time.now.to_i
|
244
|
+
end
|
245
|
+
|
246
|
+
def dbsize
|
247
|
+
data.keys.count
|
248
|
+
end
|
249
|
+
|
250
|
+
def exists(key)
|
251
|
+
data.key?(key)
|
252
|
+
end
|
253
|
+
|
254
|
+
def llen(key)
|
255
|
+
data_type_check(key, Array)
|
256
|
+
return 0 unless data[key]
|
257
|
+
data[key].size
|
258
|
+
end
|
259
|
+
|
260
|
+
def lrange(key, startidx, endidx)
|
261
|
+
data_type_check(key, Array)
|
262
|
+
(data[key] && data[key][startidx..endidx]) || []
|
263
|
+
end
|
264
|
+
|
265
|
+
def ltrim(key, start, stop)
|
266
|
+
data_type_check(key, Array)
|
267
|
+
return unless data[key]
|
268
|
+
|
269
|
+
if start < 0 && data[key].count < start.abs
|
270
|
+
# Example: we have a list of 3 elements and
|
271
|
+
# we give it a ltrim list, -5, -1. This means
|
272
|
+
# it should trim to a max of 5. Since 3 < 5
|
273
|
+
# we should not touch the list. This is consistent
|
274
|
+
# with behavior of real Redis's ltrim with a negative
|
275
|
+
# start argument.
|
276
|
+
data[key]
|
277
|
+
else
|
278
|
+
data[key] = data[key][start..stop]
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def lindex(key, index)
|
283
|
+
data_type_check(key, Array)
|
284
|
+
data[key] && data[key][index]
|
285
|
+
end
|
286
|
+
|
287
|
+
def linsert(key, where, pivot, value)
|
288
|
+
data_type_check(key, Array)
|
289
|
+
return unless data[key]
|
290
|
+
index = data[key].index(pivot)
|
291
|
+
case where
|
292
|
+
when :before then data[key].insert(index, value)
|
293
|
+
when :after then data[key].insert(index + 1, value)
|
294
|
+
else raise_syntax_error
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def lset(key, index, value)
|
299
|
+
data_type_check(key, Array)
|
300
|
+
return unless data[key]
|
301
|
+
raise Redis::CommandError, "ERR index out of range" if index >= data[key].size
|
302
|
+
data[key][index] = value
|
303
|
+
end
|
304
|
+
|
305
|
+
def lrem(key, count, value)
|
306
|
+
data_type_check(key, Array)
|
307
|
+
return unless data[key]
|
308
|
+
old_size = data[key].size
|
309
|
+
diff =
|
310
|
+
if count == 0
|
311
|
+
data[key].delete(value)
|
312
|
+
old_size - data[key].size
|
313
|
+
else
|
314
|
+
array = count > 0 ? data[key].dup : data[key].reverse
|
315
|
+
count.abs.times{ array.delete_at(array.index(value) || array.length) }
|
316
|
+
data[key] = count > 0 ? array.dup : array.reverse
|
317
|
+
old_size - data[key].size
|
318
|
+
end
|
319
|
+
remove_key_for_empty_collection(key)
|
320
|
+
diff
|
321
|
+
end
|
322
|
+
|
323
|
+
def rpush(key, value)
|
324
|
+
data_type_check(key, Array)
|
325
|
+
data[key] ||= []
|
326
|
+
[value].flatten.each do |val|
|
327
|
+
data[key].push(val.to_s)
|
328
|
+
end
|
329
|
+
data[key].size
|
330
|
+
end
|
331
|
+
|
332
|
+
def rpushx(key, value)
|
333
|
+
data_type_check(key, Array)
|
334
|
+
return unless data[key]
|
335
|
+
rpush(key, value)
|
336
|
+
end
|
337
|
+
|
338
|
+
def lpush(key, value)
|
339
|
+
data_type_check(key, Array)
|
340
|
+
data[key] ||= []
|
341
|
+
[value].flatten.each do |val|
|
342
|
+
data[key].unshift(val.to_s)
|
343
|
+
end
|
344
|
+
data[key].size
|
345
|
+
end
|
346
|
+
|
347
|
+
def lpushx(key, value)
|
348
|
+
data_type_check(key, Array)
|
349
|
+
return unless data[key]
|
350
|
+
lpush(key, value)
|
351
|
+
end
|
352
|
+
|
353
|
+
def rpop(key)
|
354
|
+
data_type_check(key, Array)
|
355
|
+
return unless data[key]
|
356
|
+
data[key].pop
|
357
|
+
end
|
358
|
+
|
359
|
+
def rpoplpush(key1, key2)
|
360
|
+
data_type_check(key1, Array)
|
361
|
+
rpop(key1).tap do |elem|
|
362
|
+
lpush(key2, elem)
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def lpop(key)
|
367
|
+
data_type_check(key, Array)
|
368
|
+
return unless data[key]
|
369
|
+
data[key].shift
|
370
|
+
end
|
371
|
+
|
372
|
+
def smembers(key)
|
373
|
+
data_type_check(key, ::Set)
|
374
|
+
return [] unless data[key]
|
375
|
+
data[key].to_a.reverse
|
376
|
+
end
|
377
|
+
|
378
|
+
def sismember(key, value)
|
379
|
+
data_type_check(key, ::Set)
|
380
|
+
return false unless data[key]
|
381
|
+
data[key].include?(value.to_s)
|
382
|
+
end
|
383
|
+
|
384
|
+
def sadd(key, value)
|
385
|
+
data_type_check(key, ::Set)
|
386
|
+
value = Array(value)
|
387
|
+
raise_argument_error('sadd') if value.empty?
|
388
|
+
|
389
|
+
result = if data[key]
|
390
|
+
old_set = data[key].dup
|
391
|
+
data[key].merge(value.map(&:to_s))
|
392
|
+
(data[key] - old_set).size
|
393
|
+
else
|
394
|
+
data[key] = ::Set.new(value.map(&:to_s))
|
395
|
+
data[key].size
|
396
|
+
end
|
397
|
+
|
398
|
+
# 0 = false, 1 = true, 2+ untouched
|
399
|
+
return result == 1 if result < 2
|
400
|
+
result
|
401
|
+
end
|
402
|
+
|
403
|
+
def srem(key, value)
|
404
|
+
data_type_check(key, ::Set)
|
405
|
+
deleted = !!(data[key] && data[key].delete?(value.to_s))
|
406
|
+
remove_key_for_empty_collection(key)
|
407
|
+
deleted
|
408
|
+
end
|
409
|
+
|
410
|
+
def smove(source, destination, value)
|
411
|
+
data_type_check(destination, ::Set)
|
412
|
+
result = self.srem(source, value)
|
413
|
+
self.sadd(destination, value) if result
|
414
|
+
result
|
415
|
+
end
|
416
|
+
|
417
|
+
def spop(key)
|
418
|
+
data_type_check(key, ::Set)
|
419
|
+
elem = srandmember(key)
|
420
|
+
srem(key, elem)
|
421
|
+
elem
|
422
|
+
end
|
423
|
+
|
424
|
+
def scard(key)
|
425
|
+
data_type_check(key, ::Set)
|
426
|
+
return 0 unless data[key]
|
427
|
+
data[key].size
|
428
|
+
end
|
429
|
+
|
430
|
+
def sinter(*keys)
|
431
|
+
raise_argument_error('sinter') if keys.empty?
|
432
|
+
|
433
|
+
keys.each { |k| data_type_check(k, ::Set) }
|
434
|
+
return ::Set.new if keys.any? { |k| data[k].nil? }
|
435
|
+
keys = keys.map { |k| data[k] || ::Set.new }
|
436
|
+
keys.inject do |set, key|
|
437
|
+
set & key
|
438
|
+
end.to_a
|
439
|
+
end
|
440
|
+
|
441
|
+
def sinterstore(destination, *keys)
|
442
|
+
data_type_check(destination, ::Set)
|
443
|
+
result = sinter(*keys)
|
444
|
+
data[destination] = ::Set.new(result)
|
445
|
+
end
|
446
|
+
|
447
|
+
def sunion(*keys)
|
448
|
+
keys.each { |k| data_type_check(k, ::Set) }
|
449
|
+
keys = keys.map { |k| data[k] || ::Set.new }
|
450
|
+
keys.inject(::Set.new) do |set, key|
|
451
|
+
set | key
|
452
|
+
end.to_a
|
453
|
+
end
|
454
|
+
|
455
|
+
def sunionstore(destination, *keys)
|
456
|
+
data_type_check(destination, ::Set)
|
457
|
+
result = sunion(*keys)
|
458
|
+
data[destination] = ::Set.new(result)
|
459
|
+
end
|
460
|
+
|
461
|
+
def sdiff(key1, *keys)
|
462
|
+
[key1, *keys].each { |k| data_type_check(k, ::Set) }
|
463
|
+
keys = keys.map { |k| data[k] || ::Set.new }
|
464
|
+
keys.inject(data[key1]) do |memo, set|
|
465
|
+
memo - set
|
466
|
+
end.to_a
|
467
|
+
end
|
468
|
+
|
469
|
+
def sdiffstore(destination, key1, *keys)
|
470
|
+
data_type_check(destination, ::Set)
|
471
|
+
result = sdiff(key1, *keys)
|
472
|
+
data[destination] = ::Set.new(result)
|
473
|
+
end
|
474
|
+
|
475
|
+
def srandmember(key)
|
476
|
+
data_type_check(key, ::Set)
|
477
|
+
return nil unless data[key]
|
478
|
+
data[key].to_a[rand(data[key].size)]
|
479
|
+
end
|
480
|
+
|
481
|
+
def del(*keys)
|
482
|
+
keys = keys.flatten(1)
|
483
|
+
raise_argument_error('del') if keys.empty?
|
484
|
+
|
485
|
+
old_count = data.keys.size
|
486
|
+
keys.each do |key|
|
487
|
+
data.delete(key)
|
488
|
+
end
|
489
|
+
old_count - data.keys.size
|
490
|
+
end
|
491
|
+
|
492
|
+
def setnx(key, value)
|
493
|
+
if exists(key)
|
494
|
+
false
|
495
|
+
else
|
496
|
+
set(key, value)
|
497
|
+
true
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
def rename(key, new_key)
|
502
|
+
return unless data[key]
|
503
|
+
data[new_key] = data[key]
|
504
|
+
data.expires[new_key] = data.expires[key] if data.expires.include?(key)
|
505
|
+
data.delete(key)
|
506
|
+
end
|
507
|
+
|
508
|
+
def renamenx(key, new_key)
|
509
|
+
if exists(new_key)
|
510
|
+
false
|
511
|
+
else
|
512
|
+
rename(key, new_key)
|
513
|
+
true
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
def expire(key, ttl)
|
518
|
+
return unless data[key]
|
519
|
+
data.expires[key] = Time.now + ttl
|
520
|
+
true
|
521
|
+
end
|
522
|
+
|
523
|
+
def ttl(key)
|
524
|
+
if data.expires.include?(key) && (ttl = data.expires[key].to_i - Time.now.to_i) > 0
|
525
|
+
ttl
|
526
|
+
else
|
527
|
+
-1
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
def expireat(key, timestamp)
|
532
|
+
data.expires[key] = Time.at(timestamp)
|
533
|
+
true
|
534
|
+
end
|
535
|
+
|
536
|
+
def persist(key)
|
537
|
+
!!data.expires.delete(key)
|
538
|
+
end
|
539
|
+
|
540
|
+
def hset(key, field, value)
|
541
|
+
data_type_check(key, Hash)
|
542
|
+
field = field.to_s
|
543
|
+
if data[key]
|
544
|
+
result = !data[key].include?(field)
|
545
|
+
data[key][field] = value.to_s
|
546
|
+
result
|
547
|
+
else
|
548
|
+
data[key] = { field => value.to_s }
|
549
|
+
true
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
def hsetnx(key, field, value)
|
554
|
+
data_type_check(key, Hash)
|
555
|
+
field = field.to_s
|
556
|
+
return false if data[key] && data[key][field]
|
557
|
+
hset(key, field, value)
|
558
|
+
end
|
559
|
+
|
560
|
+
def hmset(key, *fields)
|
561
|
+
# mapped_hmset gives us [[:k1, "v1", :k2, "v2"]] for `fields`. Fix that.
|
562
|
+
fields = fields[0] if mapped_param?(fields)
|
563
|
+
raise_argument_error('hmset') if fields.empty?
|
564
|
+
|
565
|
+
is_list_of_arrays = fields.all?{|field| field.instance_of?(Array)}
|
566
|
+
|
567
|
+
raise_argument_error('hmset') if fields.size.odd? and !is_list_of_arrays
|
568
|
+
raise_argument_error('hmset') if is_list_of_arrays and !fields.all?{|field| field.length == 2}
|
569
|
+
|
570
|
+
data_type_check(key, Hash)
|
571
|
+
data[key] ||= {}
|
572
|
+
|
573
|
+
if is_list_of_arrays
|
574
|
+
fields.each do |pair|
|
575
|
+
data[key][pair[0].to_s] = pair[1].to_s
|
576
|
+
end
|
577
|
+
else
|
578
|
+
fields.each_slice(2) do |field|
|
579
|
+
data[key][field[0].to_s] = field[1].to_s
|
580
|
+
end
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
def hmget(key, *fields)
|
585
|
+
raise_argument_error('hmget') if fields.empty?
|
586
|
+
|
587
|
+
data_type_check(key, Hash)
|
588
|
+
fields.map do |field|
|
589
|
+
field = field.to_s
|
590
|
+
if data[key]
|
591
|
+
data[key][field]
|
592
|
+
else
|
593
|
+
nil
|
594
|
+
end
|
595
|
+
end
|
596
|
+
end
|
597
|
+
|
598
|
+
def hlen(key)
|
599
|
+
data_type_check(key, Hash)
|
600
|
+
return 0 unless data[key]
|
601
|
+
data[key].size
|
602
|
+
end
|
603
|
+
|
604
|
+
def hvals(key)
|
605
|
+
data_type_check(key, Hash)
|
606
|
+
return [] unless data[key]
|
607
|
+
data[key].values
|
608
|
+
end
|
609
|
+
|
610
|
+
def hincrby(key, field, increment)
|
611
|
+
data_type_check(key, Hash)
|
612
|
+
field = field.to_s
|
613
|
+
if data[key]
|
614
|
+
data[key][field] = (data[key][field].to_i + increment.to_i).to_s
|
615
|
+
else
|
616
|
+
data[key] = { field => increment.to_s }
|
617
|
+
end
|
618
|
+
data[key][field].to_i
|
619
|
+
end
|
620
|
+
|
621
|
+
def hexists(key, field)
|
622
|
+
data_type_check(key, Hash)
|
623
|
+
return false unless data[key]
|
624
|
+
data[key].key?(field.to_s)
|
625
|
+
end
|
626
|
+
|
627
|
+
def sync ; end
|
628
|
+
|
629
|
+
def [](key)
|
630
|
+
get(key)
|
631
|
+
end
|
632
|
+
|
633
|
+
def []=(key, value)
|
634
|
+
set(key, value)
|
635
|
+
end
|
636
|
+
|
637
|
+
def set(key, value)
|
638
|
+
data[key] = value.to_s
|
639
|
+
"OK"
|
640
|
+
end
|
641
|
+
|
642
|
+
def setbit(key, offset, bit)
|
643
|
+
old_val = data[key] ? data[key].unpack('B*')[0].split("") : []
|
644
|
+
size_increment = [((offset/8)+1)*8-old_val.length, 0].max
|
645
|
+
old_val += Array.new(size_increment).map{"0"}
|
646
|
+
original_val = old_val[offset]
|
647
|
+
old_val[offset] = bit.to_s
|
648
|
+
new_val = ""
|
649
|
+
old_val.each_slice(8){|b| new_val = new_val + b.join("").to_i(2).chr }
|
650
|
+
data[key] = new_val
|
651
|
+
original_val
|
652
|
+
end
|
653
|
+
|
654
|
+
def setex(key, seconds, value)
|
655
|
+
data[key] = value.to_s
|
656
|
+
expire(key, seconds)
|
657
|
+
"OK"
|
658
|
+
end
|
659
|
+
|
660
|
+
def setrange(key, offset, value)
|
661
|
+
return unless data[key]
|
662
|
+
s = data[key][offset,value.size]
|
663
|
+
data[key][s] = value
|
664
|
+
end
|
665
|
+
|
666
|
+
def mset(*pairs)
|
667
|
+
# Handle pairs for mapped_mset command
|
668
|
+
pairs = pairs[0] if mapped_param?(pairs)
|
669
|
+
raise_argument_error('mset') if pairs.empty? || pairs.size.odd?
|
670
|
+
|
671
|
+
pairs.each_slice(2) do |pair|
|
672
|
+
data[pair[0].to_s] = pair[1].to_s
|
673
|
+
end
|
674
|
+
"OK"
|
675
|
+
end
|
676
|
+
|
677
|
+
def msetnx(*pairs)
|
678
|
+
# Handle pairs for mapped_msetnx command
|
679
|
+
pairs = pairs[0] if mapped_param?(pairs)
|
680
|
+
keys = []
|
681
|
+
pairs.each_with_index{|item, index| keys << item.to_s if index % 2 == 0}
|
682
|
+
return false if keys.any?{|key| data.key?(key) }
|
683
|
+
mset(*pairs)
|
684
|
+
true
|
685
|
+
end
|
686
|
+
|
687
|
+
def sort(key)
|
688
|
+
# TODO: Implement
|
689
|
+
end
|
690
|
+
|
691
|
+
def incr(key)
|
692
|
+
data.merge!({ key => (data[key].to_i + 1).to_s || "1"})
|
693
|
+
data[key].to_i
|
694
|
+
end
|
695
|
+
|
696
|
+
def incrby(key, by)
|
697
|
+
data.merge!({ key => (data[key].to_i + by.to_i).to_s || by })
|
698
|
+
data[key].to_i
|
699
|
+
end
|
700
|
+
|
701
|
+
def decr(key)
|
702
|
+
data.merge!({ key => (data[key].to_i - 1).to_s || "-1"})
|
703
|
+
data[key].to_i
|
704
|
+
end
|
705
|
+
|
706
|
+
def decrby(key, by)
|
707
|
+
data.merge!({ key => ((data[key].to_i - by.to_i) || (by.to_i * -1)).to_s })
|
708
|
+
data[key].to_i
|
709
|
+
end
|
710
|
+
|
711
|
+
def type(key)
|
712
|
+
case data[key]
|
713
|
+
when nil then "none"
|
714
|
+
when String then "string"
|
715
|
+
when Hash then "hash"
|
716
|
+
when Array then "list"
|
717
|
+
when ::Set then "set"
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
def quit ; end
|
722
|
+
|
723
|
+
def shutdown; end
|
724
|
+
|
725
|
+
def slaveof(host, port) ; end
|
726
|
+
|
727
|
+
def exec
|
728
|
+
buffer.tap {|x| self.buffer = nil }
|
729
|
+
end
|
730
|
+
|
731
|
+
def multi
|
732
|
+
self.buffer = []
|
733
|
+
yield if block_given?
|
734
|
+
"OK"
|
735
|
+
end
|
736
|
+
|
737
|
+
def watch(_)
|
738
|
+
"OK"
|
739
|
+
end
|
740
|
+
|
741
|
+
def unwatch
|
742
|
+
"OK"
|
743
|
+
end
|
744
|
+
|
745
|
+
def zadd(key, *args)
|
746
|
+
if !args.first.is_a?(Array)
|
747
|
+
if args.size < 2
|
748
|
+
raise_argument_error('zadd')
|
749
|
+
elsif args.size.odd?
|
750
|
+
raise_syntax_error
|
751
|
+
end
|
752
|
+
else
|
753
|
+
unless args.all? {|pair| pair.size == 2 }
|
754
|
+
raise_syntax_error
|
755
|
+
end
|
756
|
+
end
|
757
|
+
|
758
|
+
data_type_check(key, ZSet)
|
759
|
+
data[key] ||= ZSet.new
|
760
|
+
|
761
|
+
if args.size == 2 && !(Array === args.first)
|
762
|
+
score, value = args
|
763
|
+
exists = !data[key].key?(value.to_s)
|
764
|
+
data[key][value.to_s] = score
|
765
|
+
else
|
766
|
+
# Turn [1, 2, 3, 4] into [[1, 2], [3, 4]] unless it is already
|
767
|
+
args = args.each_slice(2).to_a unless args.first.is_a?(Array)
|
768
|
+
exists = args.map(&:last).map { |el| data[key].key?(el.to_s) }.count(false)
|
769
|
+
args.each { |s, v| data[key][v.to_s] = s }
|
770
|
+
end
|
771
|
+
|
772
|
+
exists
|
773
|
+
end
|
774
|
+
|
775
|
+
def zrem(key, value)
|
776
|
+
data_type_check(key, ZSet)
|
777
|
+
values = Array(value)
|
778
|
+
return 0 unless data[key]
|
779
|
+
|
780
|
+
response = values.map do |v|
|
781
|
+
data[key].delete(v) if data[key].has_key?(v)
|
782
|
+
end.compact.size
|
783
|
+
|
784
|
+
remove_key_for_empty_collection(key)
|
785
|
+
response
|
786
|
+
end
|
787
|
+
|
788
|
+
def zcard(key)
|
789
|
+
data_type_check(key, ZSet)
|
790
|
+
data[key] ? data[key].size : 0
|
791
|
+
end
|
792
|
+
|
793
|
+
def zscore(key, value)
|
794
|
+
data_type_check(key, ZSet)
|
795
|
+
value = data[key] && data[key][value.to_s]
|
796
|
+
value && value.to_s
|
797
|
+
end
|
798
|
+
|
799
|
+
def zcount(key, min, max)
|
800
|
+
data_type_check(key, ZSet)
|
801
|
+
return 0 unless data[key]
|
802
|
+
data[key].select_by_score(min, max).size
|
803
|
+
end
|
804
|
+
|
805
|
+
def zincrby(key, num, value)
|
806
|
+
data_type_check(key, ZSet)
|
807
|
+
data[key] ||= ZSet.new
|
808
|
+
data[key][value.to_s] ||= 0
|
809
|
+
data[key].increment(value.to_s, num)
|
810
|
+
data[key][value.to_s].to_s
|
811
|
+
end
|
812
|
+
|
813
|
+
def zrank(key, value)
|
814
|
+
data_type_check(key, ZSet)
|
815
|
+
z = data[key]
|
816
|
+
return unless z
|
817
|
+
z.keys.sort_by {|k| z[k] }.index(value.to_s)
|
818
|
+
end
|
819
|
+
|
820
|
+
def zrevrank(key, value)
|
821
|
+
data_type_check(key, ZSet)
|
822
|
+
z = data[key]
|
823
|
+
return unless z
|
824
|
+
z.keys.sort_by {|k| -z[k] }.index(value.to_s)
|
825
|
+
end
|
826
|
+
|
827
|
+
def zrange(key, start, stop, with_scores = nil)
|
828
|
+
data_type_check(key, ZSet)
|
829
|
+
return [] unless data[key]
|
830
|
+
|
831
|
+
# Sort by score, or if scores are equal, key alphanum
|
832
|
+
results = data[key].sort do |(k1, v1), (k2, v2)|
|
833
|
+
if v1 == v2
|
834
|
+
k1 <=> k2
|
835
|
+
else
|
836
|
+
v1 <=> v2
|
837
|
+
end
|
838
|
+
end
|
839
|
+
# Select just the keys unless we want scores
|
840
|
+
results = results.map(&:first) unless with_scores
|
841
|
+
results[start..stop].flatten.map(&:to_s)
|
842
|
+
end
|
843
|
+
|
844
|
+
def zrevrange(key, start, stop, with_scores = nil)
|
845
|
+
data_type_check(key, ZSet)
|
846
|
+
return [] unless data[key]
|
847
|
+
|
848
|
+
if with_scores
|
849
|
+
data[key].sort_by {|_,v| -v }
|
850
|
+
else
|
851
|
+
data[key].keys.sort_by {|k| -data[key][k] }
|
852
|
+
end[start..stop].flatten.map(&:to_s)
|
853
|
+
end
|
854
|
+
|
855
|
+
def zrangebyscore(key, min, max, *opts)
|
856
|
+
data_type_check(key, ZSet)
|
857
|
+
return [] unless data[key]
|
858
|
+
|
859
|
+
range = data[key].select_by_score(min, max)
|
860
|
+
vals = if opts.include?('WITHSCORES')
|
861
|
+
range.sort_by {|_,v| v }
|
862
|
+
else
|
863
|
+
range.keys.sort_by {|k| range[k] }
|
864
|
+
end
|
865
|
+
|
866
|
+
limit = get_limit(opts, vals)
|
867
|
+
vals = vals[*limit] if limit
|
868
|
+
|
869
|
+
vals.flatten.map(&:to_s)
|
870
|
+
end
|
871
|
+
|
872
|
+
def zrevrangebyscore(key, max, min, *opts)
|
873
|
+
data_type_check(key, ZSet)
|
874
|
+
return [] unless data[key]
|
875
|
+
|
876
|
+
range = data[key].select_by_score(min, max)
|
877
|
+
vals = if opts.include?('WITHSCORES')
|
878
|
+
range.sort_by {|_,v| -v }
|
879
|
+
else
|
880
|
+
range.keys.sort_by {|k| -range[k] }
|
881
|
+
end
|
882
|
+
|
883
|
+
limit = get_limit(opts, vals)
|
884
|
+
vals = vals[*limit] if limit
|
885
|
+
|
886
|
+
vals.flatten.map(&:to_s)
|
887
|
+
end
|
888
|
+
|
889
|
+
def zremrangebyscore(key, min, max)
|
890
|
+
data_type_check(key, ZSet)
|
891
|
+
return 0 unless data[key]
|
892
|
+
|
893
|
+
range = data[key].select_by_score(min, max)
|
894
|
+
range.each {|k,_| data[key].delete(k) }
|
895
|
+
range.size
|
896
|
+
end
|
897
|
+
|
898
|
+
def zinterstore(out, *args)
|
899
|
+
data_type_check(out, ZSet)
|
900
|
+
args_handler = SortedSetArgumentHandler.new(args)
|
901
|
+
data[out] = SortedSetIntersectStore.new(args_handler, data).call
|
902
|
+
data[out].size
|
903
|
+
end
|
904
|
+
|
905
|
+
def zunionstore(out, *args)
|
906
|
+
data_type_check(out, ZSet)
|
907
|
+
args_handler = SortedSetArgumentHandler.new(args)
|
908
|
+
data[out] = SortedSetUnionStore.new(args_handler, data).call
|
909
|
+
data[out].size
|
910
|
+
end
|
911
|
+
|
912
|
+
def zremrangebyrank(key, start, stop)
|
913
|
+
sorted_elements = data[key].sort_by { |k, v| v }
|
914
|
+
start = sorted_elements.length if start > sorted_elements.length
|
915
|
+
elements_to_delete = sorted_elements[start..stop]
|
916
|
+
elements_to_delete.each { |elem, rank| data[key].delete(elem) }
|
917
|
+
elements_to_delete.size
|
918
|
+
end
|
919
|
+
|
920
|
+
private
|
921
|
+
def raise_argument_error command
|
922
|
+
raise Redis::CommandError, "ERR wrong number of arguments for '#{command}' command"
|
923
|
+
end
|
924
|
+
|
925
|
+
def raise_syntax_error
|
926
|
+
raise Redis::CommandError, "ERR syntax error"
|
927
|
+
end
|
928
|
+
|
929
|
+
def remove_key_for_empty_collection(key)
|
930
|
+
del(key) if data[key] && data[key].empty?
|
931
|
+
end
|
932
|
+
|
933
|
+
def data_type_check(key, klass)
|
934
|
+
if data[key] && !data[key].is_a?(klass)
|
935
|
+
warn "Operation against a key holding the wrong kind of value: Expected #{klass} at #{key}."
|
936
|
+
raise Redis::CommandError.new("ERR Operation against a key holding the wrong kind of value")
|
937
|
+
end
|
938
|
+
end
|
939
|
+
|
940
|
+
def get_limit(opts, vals)
|
941
|
+
index = opts.index('LIMIT')
|
942
|
+
|
943
|
+
if index
|
944
|
+
offset = opts[index + 1]
|
945
|
+
|
946
|
+
count = opts[index + 2]
|
947
|
+
count = vals.size if count < 0
|
948
|
+
|
949
|
+
[offset, count]
|
950
|
+
end
|
951
|
+
end
|
952
|
+
|
953
|
+
def mapped_param? param
|
954
|
+
param.size == 1 && param[0].is_a?(Array)
|
955
|
+
end
|
956
|
+
end
|
957
|
+
end
|
958
|
+
end
|
959
|
+
|
960
|
+
Redis::Connection.drivers << Redis::Connection::File
|