rediska 0.0.11 → 0.1.0
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 +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
|