rediska 0.0.11 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +18 -14
- data/lib/rediska/command_executor.rb +25 -0
- data/lib/rediska/connection.rb +8 -1
- data/lib/rediska/driver.rb +30 -56
- data/lib/rediska/sort_method.rb +108 -0
- data/lib/rediska/transaction_commands.rb +83 -0
- data/lib/rediska/version.rb +1 -1
- data/rediska.gemspec +1 -1
- data/spec/lib/rediska/driver_spec.rb +5 -5
- data/spec/redis_spec.rb +3 -0
- data/spec/support/connection.rb +15 -15
- data/spec/support/hashes.rb +51 -51
- data/spec/support/keys.rb +106 -61
- data/spec/support/lists.rb +37 -30
- data/spec/support/server.rb +23 -23
- data/spec/support/sets.rb +44 -44
- data/spec/support/sorted_method.rb +136 -0
- data/spec/support/sorted_sets.rb +140 -136
- data/spec/support/strings.rb +74 -74
- data/spec/support/transactions.rb +65 -12
- data/spec/support/upcase_method_names.rb +2 -2
- metadata +22 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e4ac28eb56bf3a2cab47dc9b091b7182acc88b8
|
4
|
+
data.tar.gz: 5d6d48bc843232644fbfbf92063a7785a6db947e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b149f856ed62eb989464acdf464424d2c870a752b5e00014e0e83b7c3cb48c66a375d819a792641b7989f19868dc60d5f6b86d7f89a4f5ce335d5e0d8255b99
|
7
|
+
data.tar.gz: 5c5cfffc7e706fe27dee1ea447d36a9c58cff8f63e906060c516f1c9e4b4d190f618cc79a2b4746ae464091231e2a3aa521ae4192ab7eddd3307a8febb994971
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rediska (0.0
|
4
|
+
rediska (0.1.0)
|
5
5
|
redis (~> 3.0.0)
|
6
6
|
|
7
7
|
GEM
|
@@ -16,22 +16,26 @@ GEM
|
|
16
16
|
diff-lcs (1.2.5)
|
17
17
|
docile (1.1.3)
|
18
18
|
json (1.8.1)
|
19
|
-
mime-types (2.
|
20
|
-
multi_json (1.
|
21
|
-
rake (10.
|
19
|
+
mime-types (2.3)
|
20
|
+
multi_json (1.10.1)
|
21
|
+
rake (10.3.2)
|
22
22
|
rdoc (4.1.1)
|
23
23
|
json (~> 1.4)
|
24
24
|
redis (3.0.7)
|
25
25
|
rest-client (1.6.7)
|
26
26
|
mime-types (>= 1.16)
|
27
|
-
rspec (
|
28
|
-
rspec-core (~>
|
29
|
-
rspec-expectations (~>
|
30
|
-
rspec-mocks (~>
|
31
|
-
rspec-core (
|
32
|
-
|
33
|
-
|
34
|
-
|
27
|
+
rspec (3.0.0)
|
28
|
+
rspec-core (~> 3.0.0)
|
29
|
+
rspec-expectations (~> 3.0.0)
|
30
|
+
rspec-mocks (~> 3.0.0)
|
31
|
+
rspec-core (3.0.0)
|
32
|
+
rspec-support (~> 3.0.0)
|
33
|
+
rspec-expectations (3.0.0)
|
34
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
35
|
+
rspec-support (~> 3.0.0)
|
36
|
+
rspec-mocks (3.0.0)
|
37
|
+
rspec-support (~> 3.0.0)
|
38
|
+
rspec-support (3.0.0)
|
35
39
|
simplecov (0.8.2)
|
36
40
|
docile (~> 1.1.0)
|
37
41
|
multi_json
|
@@ -40,7 +44,7 @@ GEM
|
|
40
44
|
term-ansicolor (1.3.0)
|
41
45
|
tins (~> 1.0)
|
42
46
|
thor (0.19.1)
|
43
|
-
tins (1.0
|
47
|
+
tins (1.3.0)
|
44
48
|
|
45
49
|
PLATFORMS
|
46
50
|
ruby
|
@@ -50,4 +54,4 @@ DEPENDENCIES
|
|
50
54
|
rake
|
51
55
|
rdoc
|
52
56
|
rediska!
|
53
|
-
rspec
|
57
|
+
rspec
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Rediska
|
2
|
+
module CommandExecutor
|
3
|
+
def write(command)
|
4
|
+
meffod = command.shift.to_s.downcase.to_sym
|
5
|
+
|
6
|
+
if in_multi && !(TRANSACTION_COMMANDS.include? meffod) # queue commands
|
7
|
+
queued_commands << [meffod, *command]
|
8
|
+
reply = 'QUEUED'
|
9
|
+
elsif respond_to?(meffod)
|
10
|
+
reply = send(meffod, *command)
|
11
|
+
else
|
12
|
+
raise Redis::CommandError, "ERR unknown command '#{meffod}'"
|
13
|
+
end
|
14
|
+
|
15
|
+
if reply == true
|
16
|
+
reply = 1
|
17
|
+
elsif reply == false
|
18
|
+
reply = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
replies << reply
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/rediska/connection.rb
CHANGED
@@ -1,13 +1,20 @@
|
|
1
1
|
require 'rediska/databases/memory'
|
2
2
|
require 'rediska/databases/pstore'
|
3
|
+
require 'rediska/sort_method'
|
3
4
|
require 'rediska/sorted_set_argument_handler'
|
4
5
|
require 'rediska/sorted_set_store'
|
5
6
|
require 'rediska/zset'
|
6
7
|
require 'rediska/driver'
|
8
|
+
require 'rediska/command_executor'
|
9
|
+
require 'rediska/transaction_commands'
|
7
10
|
|
8
11
|
module Rediska
|
9
12
|
class Connection
|
10
13
|
include Driver
|
14
|
+
include SortMethod
|
15
|
+
include TransactionCommands
|
16
|
+
include CommandExecutor
|
17
|
+
|
11
18
|
|
12
19
|
class << self
|
13
20
|
def databases
|
@@ -63,5 +70,5 @@ module Rediska
|
|
63
70
|
raise ArgumentError, "invalid database type: #{Rediska.configuration.database}"
|
64
71
|
end
|
65
72
|
end
|
66
|
-
|
73
|
+
end
|
67
74
|
end
|
data/lib/rediska/driver.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Rediska
|
2
2
|
module Driver
|
3
|
-
attr_accessor :
|
3
|
+
attr_accessor :database_id
|
4
4
|
attr_writer :replies
|
5
5
|
|
6
6
|
def replies
|
@@ -20,25 +20,6 @@ module Rediska
|
|
20
20
|
def timeout=(usecs)
|
21
21
|
end
|
22
22
|
|
23
|
-
def write(command)
|
24
|
-
meffod = command.shift.to_s.downcase.to_sym
|
25
|
-
if respond_to?(meffod)
|
26
|
-
reply = send(meffod, *command)
|
27
|
-
else
|
28
|
-
raise Redis::CommandError, "ERR unknown command #{meffod.upcase}"
|
29
|
-
end
|
30
|
-
|
31
|
-
if reply == true
|
32
|
-
reply = 1
|
33
|
-
elsif reply == false
|
34
|
-
reply = 0
|
35
|
-
end
|
36
|
-
|
37
|
-
replies << reply
|
38
|
-
buffer << reply if buffer && meffod != :multi
|
39
|
-
nil
|
40
|
-
end
|
41
|
-
|
42
23
|
def read
|
43
24
|
replies.shift
|
44
25
|
end
|
@@ -47,8 +28,6 @@ module Rediska
|
|
47
28
|
# * blpop
|
48
29
|
# * brpop
|
49
30
|
# * brpoplpush
|
50
|
-
# * discard
|
51
|
-
# * sort
|
52
31
|
# * subscribe
|
53
32
|
# * psubscribe
|
54
33
|
# * publish
|
@@ -159,8 +138,9 @@ module Rediska
|
|
159
138
|
def hdel(key, field)
|
160
139
|
field = field.to_s
|
161
140
|
data_type_check(key, Hash)
|
162
|
-
data[key] && data[key].delete(field)
|
141
|
+
deleted = data[key] && data[key].delete(field)
|
163
142
|
remove_key_for_empty_collection(key)
|
143
|
+
deleted ? 1 : 0
|
164
144
|
end
|
165
145
|
|
166
146
|
def hkeys(key)
|
@@ -307,7 +287,7 @@ module Rediska
|
|
307
287
|
def rpoplpush(key1, key2)
|
308
288
|
data_type_check(key1, Array)
|
309
289
|
rpop(key1).tap do |elem|
|
310
|
-
lpush(key2, elem)
|
290
|
+
lpush(key2, elem) unless elem.nil?
|
311
291
|
end
|
312
292
|
end
|
313
293
|
|
@@ -590,9 +570,23 @@ module Rediska
|
|
590
570
|
set(key, value)
|
591
571
|
end
|
592
572
|
|
593
|
-
def set(key, value)
|
573
|
+
def set(key, value, *array_options)
|
574
|
+
option_nx = array_options.delete('NX')
|
575
|
+
option_xx = array_options.delete('XX')
|
576
|
+
|
577
|
+
return false if option_nx && option_xx
|
578
|
+
|
579
|
+
return false if option_nx && exists(key)
|
580
|
+
return false if option_xx && !exists(key)
|
581
|
+
|
594
582
|
data[key] = value.to_s
|
595
583
|
|
584
|
+
options = Hash[array_options.each_slice(2).to_a]
|
585
|
+
ttl_in_seconds = options['EX'] if options['EX']
|
586
|
+
ttl_in_seconds = options['PX'] / 1000.0 if options['PX']
|
587
|
+
|
588
|
+
expire(key, ttl_in_seconds) if ttl_in_seconds
|
589
|
+
|
596
590
|
'OK'
|
597
591
|
end
|
598
592
|
|
@@ -646,10 +640,6 @@ module Rediska
|
|
646
640
|
true
|
647
641
|
end
|
648
642
|
|
649
|
-
def sort(key)
|
650
|
-
# TODO: Implement
|
651
|
-
end
|
652
|
-
|
653
643
|
def incr(key)
|
654
644
|
data.merge!({ key => (data[key].to_i + 1).to_s || '1'})
|
655
645
|
data[key].to_i
|
@@ -690,25 +680,6 @@ module Rediska
|
|
690
680
|
def slaveof(host, port)
|
691
681
|
end
|
692
682
|
|
693
|
-
def exec
|
694
|
-
buffer.tap {|x| self.buffer = nil }
|
695
|
-
end
|
696
|
-
|
697
|
-
def multi
|
698
|
-
self.buffer = []
|
699
|
-
yield if block_given?
|
700
|
-
|
701
|
-
'OK'
|
702
|
-
end
|
703
|
-
|
704
|
-
def watch(_)
|
705
|
-
'OK'
|
706
|
-
end
|
707
|
-
|
708
|
-
def unwatch
|
709
|
-
'OK'
|
710
|
-
end
|
711
|
-
|
712
683
|
def scan(start_cursor, *args)
|
713
684
|
match = '*'
|
714
685
|
count = 10
|
@@ -896,6 +867,17 @@ module Rediska
|
|
896
867
|
range.size
|
897
868
|
end
|
898
869
|
|
870
|
+
def zremrangebyrank(key, start, stop)
|
871
|
+
data_type_check(key, ZSet)
|
872
|
+
return 0 unless data[key]
|
873
|
+
|
874
|
+
sorted_elements = data[key].sort_by { |k, v| v }
|
875
|
+
start = sorted_elements.length if start > sorted_elements.length
|
876
|
+
elements_to_delete = sorted_elements[start..stop]
|
877
|
+
elements_to_delete.each { |elem, rank| data[key].delete(elem) }
|
878
|
+
elements_to_delete.size
|
879
|
+
end
|
880
|
+
|
899
881
|
def zinterstore(out, *args)
|
900
882
|
data_type_check(out, ZSet)
|
901
883
|
args_handler = SortedSetArgumentHandler.new(args)
|
@@ -910,14 +892,6 @@ module Rediska
|
|
910
892
|
data[out].size
|
911
893
|
end
|
912
894
|
|
913
|
-
def zremrangebyrank(key, start, stop)
|
914
|
-
sorted_elements = data[key].sort_by { |k, v| v }
|
915
|
-
start = sorted_elements.length if start > sorted_elements.length
|
916
|
-
elements_to_delete = sorted_elements[start..stop]
|
917
|
-
elements_to_delete.each { |elem, rank| data[key].delete(elem) }
|
918
|
-
elements_to_delete.size
|
919
|
-
end
|
920
|
-
|
921
895
|
private
|
922
896
|
def raise_argument_error(command, match_string = command)
|
923
897
|
error_message = if %w(hmset mset_odd).include?(match_string.downcase)
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# Codes are mostly referenced from MockRedis' implementation.
|
2
|
+
module Rediska
|
3
|
+
module SortMethod
|
4
|
+
def sort(key, *redis_options_array)
|
5
|
+
return [] unless key
|
6
|
+
|
7
|
+
unless %w(list set zset).include? type(key)
|
8
|
+
warn "Operation against a key holding the wrong kind of value: Expected list, set or zset at #{key}."
|
9
|
+
raise Redis::CommandError.new('WRONGTYPE Operation against a key holding the wrong kind of value')
|
10
|
+
end
|
11
|
+
|
12
|
+
options = extract_options_from(redis_options_array)
|
13
|
+
|
14
|
+
projected = project(data[key], options[:by], options[:get])
|
15
|
+
sorted = sort_by(projected, options[:order])
|
16
|
+
sliced = slice(sorted, options[:limit])
|
17
|
+
|
18
|
+
options[:store] ? rpush(options[:store], sliced) : sliced.flatten(1)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
ASCENDING_SORT = Proc.new { |a, b| a.first <=> b.first }
|
23
|
+
DESCENDING_SORT = Proc.new { |a, b| b.first <=> a.first }
|
24
|
+
|
25
|
+
def extract_options_from(options_array)
|
26
|
+
options = {
|
27
|
+
limit: [],
|
28
|
+
order: 'ASC',
|
29
|
+
get: []
|
30
|
+
}
|
31
|
+
|
32
|
+
if options_array.first == 'BY'
|
33
|
+
options_array.shift
|
34
|
+
options[:by] = options_array.shift
|
35
|
+
end
|
36
|
+
|
37
|
+
if options_array.first == 'LIMIT'
|
38
|
+
options_array.shift
|
39
|
+
options[:limit] = [options_array.shift, options_array.shift]
|
40
|
+
end
|
41
|
+
|
42
|
+
while options_array.first == 'GET'
|
43
|
+
options_array.shift
|
44
|
+
options[:get] << options_array.shift
|
45
|
+
end
|
46
|
+
|
47
|
+
if %w(ASC DESC ALPHA).include?(options_array.first)
|
48
|
+
options[:order] = options_array.shift
|
49
|
+
options[:order] = 'ASC' if options[:order] == 'ALPHA'
|
50
|
+
end
|
51
|
+
|
52
|
+
if options_array.first == 'STORE'
|
53
|
+
options_array.shift
|
54
|
+
options[:store] = options_array.shift
|
55
|
+
end
|
56
|
+
|
57
|
+
options
|
58
|
+
end
|
59
|
+
|
60
|
+
def project(enumerable, by, get_patterns)
|
61
|
+
enumerable.map do |*elements|
|
62
|
+
element = elements.flatten.first
|
63
|
+
weight = by ? lookup_from_pattern(by, element) : element
|
64
|
+
value = element
|
65
|
+
|
66
|
+
if get_patterns.length > 0
|
67
|
+
value = get_patterns.map do |pattern|
|
68
|
+
pattern == '#' ? element : lookup_from_pattern(pattern, element)
|
69
|
+
end
|
70
|
+
value = value.first if value.length == 1
|
71
|
+
end
|
72
|
+
|
73
|
+
[weight, value]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def sort_by(projected, direction)
|
78
|
+
sorter =
|
79
|
+
case direction.upcase
|
80
|
+
when 'DESC'
|
81
|
+
DESCENDING_SORT
|
82
|
+
when 'ASC', 'ALPHA'
|
83
|
+
ASCENDING_SORT
|
84
|
+
else
|
85
|
+
raise "Invalid direction '#{direction}'"
|
86
|
+
end
|
87
|
+
|
88
|
+
projected.sort(&sorter).map(&:last)
|
89
|
+
end
|
90
|
+
|
91
|
+
def slice(sorted, limit)
|
92
|
+
skip = limit.first || 0
|
93
|
+
take = limit.last || sorted.length
|
94
|
+
|
95
|
+
sorted[skip...(skip + take)] || sorted
|
96
|
+
end
|
97
|
+
|
98
|
+
def lookup_from_pattern(pattern, element)
|
99
|
+
key = pattern.sub('*', element)
|
100
|
+
|
101
|
+
if (hash_parts = key.split('->')).length > 1
|
102
|
+
hget hash_parts.first, hash_parts.last
|
103
|
+
else
|
104
|
+
get key
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Rediska
|
2
|
+
TRANSACTION_COMMANDS = [:discard, :exec, :multi, :watch, :unwatch]
|
3
|
+
|
4
|
+
module TransactionCommands
|
5
|
+
def self.included(klass)
|
6
|
+
klass.class_eval do
|
7
|
+
def self.queued_commands
|
8
|
+
@queued_commands ||= Hash.new {|h,k| h[k] = [] }
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.in_multi
|
12
|
+
@in_multi ||= Hash.new{|h,k| h[k] = false}
|
13
|
+
end
|
14
|
+
|
15
|
+
def queued_commands
|
16
|
+
self.class.queued_commands[database_instance_key]
|
17
|
+
end
|
18
|
+
|
19
|
+
def queued_commands=(cmds)
|
20
|
+
self.class.queued_commands[database_instance_key] = cmds
|
21
|
+
end
|
22
|
+
|
23
|
+
def in_multi
|
24
|
+
self.class.in_multi[database_instance_key]
|
25
|
+
end
|
26
|
+
|
27
|
+
def in_multi=(multi_state)
|
28
|
+
self.class.in_multi[database_instance_key] = multi_state
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def discard
|
34
|
+
unless in_multi
|
35
|
+
raise Redis::CommandError, 'ERR DISCARD without MULTI'
|
36
|
+
end
|
37
|
+
|
38
|
+
self.in_multi = false
|
39
|
+
self.queued_commands = []
|
40
|
+
|
41
|
+
'OK'
|
42
|
+
end
|
43
|
+
|
44
|
+
def exec
|
45
|
+
unless in_multi
|
46
|
+
raise Redis::CommandError, 'ERR EXEC without MULTI'
|
47
|
+
end
|
48
|
+
|
49
|
+
responses = queued_commands.map do |cmd|
|
50
|
+
begin
|
51
|
+
send(*cmd)
|
52
|
+
rescue => e
|
53
|
+
e
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
self.queued_commands = [] # reset queued_commands
|
58
|
+
self.in_multi = false # reset in_multi state
|
59
|
+
|
60
|
+
responses
|
61
|
+
end
|
62
|
+
|
63
|
+
def multi
|
64
|
+
if in_multi
|
65
|
+
raise Redis::CommandError, 'ERR MULTI calls can not be nested'
|
66
|
+
end
|
67
|
+
|
68
|
+
self.in_multi = true
|
69
|
+
|
70
|
+
yield(self) if block_given?
|
71
|
+
|
72
|
+
'OK'
|
73
|
+
end
|
74
|
+
|
75
|
+
def watch(_)
|
76
|
+
'OK'
|
77
|
+
end
|
78
|
+
|
79
|
+
def unwatch
|
80
|
+
'OK'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|