restruct 0.0.3 → 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/.ruby-version +1 -1
- data/lib/restruct.rb +7 -2
- data/lib/restruct/array.rb +9 -9
- data/lib/restruct/connection.rb +71 -0
- data/lib/restruct/errors.rb +54 -0
- data/lib/restruct/hash.rb +8 -8
- data/lib/restruct/locker.rb +43 -0
- data/lib/restruct/marshal_queue.rb +5 -0
- data/lib/restruct/nested_hash.rb +2 -2
- data/lib/restruct/queue.rb +38 -0
- data/lib/restruct/set.rb +5 -5
- data/lib/restruct/structure.rb +10 -6
- data/lib/restruct/version.rb +1 -1
- data/lua/register.lua +21 -0
- data/lua/unregister.lua +10 -0
- data/restruct.gemspec +2 -1
- data/spec/array_spec.rb +37 -1
- data/spec/connection_spec.rb +116 -0
- data/spec/hash_spec.rb +17 -3
- data/spec/locker_spec.rb +161 -0
- data/spec/minitest_helper.rb +4 -3
- data/spec/nested_hash_spec.rb +2 -2
- data/spec/queue_spec.rb +41 -0
- data/spec/set_spec.rb +18 -1
- metadata +55 -31
- data/lib/restruct/batch.rb +0 -39
- data/spec/batch_spec.rb +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 220d7c61ca9fe2daff93071982a18b7934967c71
|
4
|
+
data.tar.gz: bc038fb7d2126b6d30d770ab1d28687b031111a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3357b7800bb6da26bdb54675d097300cd9f33b652f63ad7c5e9d1a0a71f4038459f20e8bade86f12d2b6562f57e08af5ed74400139307cbaea20d35fa5b2273f
|
7
|
+
data.tar.gz: 5740e1bc9699cef63a9c5632c2c6198a48164b99aaf78e3b60bef61983352b5fcc6d48ce3a8b8524af50b673a905caa65c1655e97c3bc4500ee9e0e21fc5b4f5
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby 2.
|
1
|
+
ruby 2.0
|
data/lib/restruct.rb
CHANGED
@@ -4,23 +4,28 @@ require 'forwardable'
|
|
4
4
|
require 'securerandom'
|
5
5
|
|
6
6
|
require_relative 'restruct/version'
|
7
|
+
require_relative 'restruct/errors'
|
7
8
|
require_relative 'restruct/structure'
|
8
9
|
require_relative 'restruct/id'
|
9
10
|
require_relative 'restruct/array'
|
10
11
|
require_relative 'restruct/set'
|
11
12
|
require_relative 'restruct/hash'
|
13
|
+
require_relative 'restruct/queue'
|
12
14
|
require_relative 'restruct/nested_hash'
|
13
15
|
require_relative 'restruct/marshalizable'
|
14
16
|
require_relative 'restruct/marshal_array'
|
15
17
|
require_relative 'restruct/marshal_set'
|
16
18
|
require_relative 'restruct/marshal_hash'
|
17
|
-
require_relative 'restruct/
|
19
|
+
require_relative 'restruct/marshal_queue'
|
20
|
+
require_relative 'restruct/locker'
|
21
|
+
require_relative 'restruct/connection'
|
22
|
+
|
18
23
|
|
19
24
|
module Restruct
|
20
25
|
|
21
26
|
extend ClassConfig
|
22
27
|
|
23
|
-
attr_config :
|
28
|
+
attr_config :connection, Connection.new
|
24
29
|
attr_config :id_separator, ':'
|
25
30
|
attr_config :id_generator, ->() { Id.new(:restruct)[SecureRandom.uuid] }
|
26
31
|
|
data/lib/restruct/array.rb
CHANGED
@@ -7,7 +7,7 @@ module Restruct
|
|
7
7
|
def_delegators :to_a, :uniq, :join, :reverse, :+, :-, :&, :|
|
8
8
|
|
9
9
|
def at(index)
|
10
|
-
deserialize
|
10
|
+
deserialize connection.call('LINDEX', id, index)
|
11
11
|
end
|
12
12
|
|
13
13
|
def values_at(*args)
|
@@ -48,11 +48,11 @@ module Restruct
|
|
48
48
|
validate_index_type! index
|
49
49
|
validate_index_bounds! index
|
50
50
|
|
51
|
-
|
51
|
+
connection.lazy 'LSET', id, index, serialize(element)
|
52
52
|
end
|
53
53
|
|
54
54
|
def push(*elements)
|
55
|
-
|
55
|
+
connection.lazy 'RPUSH', id, *elements.map { |e| serialize e }
|
56
56
|
self
|
57
57
|
end
|
58
58
|
alias_method :<<, :push
|
@@ -70,7 +70,7 @@ module Restruct
|
|
70
70
|
|
71
71
|
def pop(count=1)
|
72
72
|
if count == 1
|
73
|
-
deserialize
|
73
|
+
deserialize connection.lazy('RPOP', id)
|
74
74
|
else
|
75
75
|
[count, size].min.times.map { pop }.reverse
|
76
76
|
end
|
@@ -78,15 +78,15 @@ module Restruct
|
|
78
78
|
|
79
79
|
def shift(count=1)
|
80
80
|
if count == 1
|
81
|
-
deserialize
|
81
|
+
deserialize connection.lazy('LPOP', id)
|
82
82
|
else
|
83
83
|
[count, size].min.times.map { shift }
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
87
|
def delete(element)
|
88
|
-
removed_count =
|
89
|
-
removed_count > 0 ? element : nil
|
88
|
+
removed_count = connection.lazy 'LREM', id, 0, serialize(element)
|
89
|
+
removed_count && removed_count > 0 ? element : nil
|
90
90
|
end
|
91
91
|
|
92
92
|
def delete_at(index)
|
@@ -117,7 +117,7 @@ module Restruct
|
|
117
117
|
end
|
118
118
|
|
119
119
|
def size
|
120
|
-
|
120
|
+
connection.call 'LLEN', id
|
121
121
|
end
|
122
122
|
alias_method :count, :size
|
123
123
|
alias_method :length, :size
|
@@ -161,7 +161,7 @@ module Restruct
|
|
161
161
|
|
162
162
|
def range(start, stop)
|
163
163
|
return nil if start > size
|
164
|
-
|
164
|
+
connection.call('LRANGE', id, start, stop).map { |e| deserialize e }
|
165
165
|
end
|
166
166
|
|
167
167
|
def validate_index_type!(index)
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Restruct
|
2
|
+
class Connection
|
3
|
+
|
4
|
+
def initialize(*args)
|
5
|
+
@redis = Redic.new *args
|
6
|
+
@scripts = {}
|
7
|
+
@nesting = ::Hash.new { |h,k| h[k] = 0 }
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(*args)
|
11
|
+
raise ArgumentError if args.empty?
|
12
|
+
redis.call! *args
|
13
|
+
rescue RuntimeError => ex
|
14
|
+
raise ConnectionErrorFactory.create(ex)
|
15
|
+
end
|
16
|
+
|
17
|
+
def lazy(*args)
|
18
|
+
if nested?
|
19
|
+
redis.queue *args
|
20
|
+
nil
|
21
|
+
else
|
22
|
+
call *args
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def script(lua_src, *args)
|
27
|
+
scripts[lua_src] ||= call 'SCRIPT', 'LOAD', lua_src
|
28
|
+
call 'EVALSHA', scripts[lua_src], *args
|
29
|
+
rescue NoScriptError
|
30
|
+
scripts.delete lua_src
|
31
|
+
retry
|
32
|
+
end
|
33
|
+
|
34
|
+
def batch
|
35
|
+
incr_nesting
|
36
|
+
begin
|
37
|
+
result = yield
|
38
|
+
ensure
|
39
|
+
decr_nesting
|
40
|
+
end
|
41
|
+
commit unless nested?
|
42
|
+
rescue => ex
|
43
|
+
redis.clear unless nested?
|
44
|
+
raise ex
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :redis, :scripts
|
50
|
+
|
51
|
+
def nested?
|
52
|
+
@nesting[Thread.current.object_id] > 0
|
53
|
+
end
|
54
|
+
|
55
|
+
def incr_nesting
|
56
|
+
@nesting[Thread.current.object_id] += 1
|
57
|
+
end
|
58
|
+
|
59
|
+
def decr_nesting
|
60
|
+
@nesting[Thread.current.object_id] -= 1
|
61
|
+
end
|
62
|
+
|
63
|
+
def commit
|
64
|
+
results = redis.commit
|
65
|
+
error = results.detect { |r| r === RuntimeError }
|
66
|
+
raise ConnectionErrorFactory.create(error) if error
|
67
|
+
results
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Restruct
|
2
|
+
class Error < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
class ConnectionErrorFactory
|
6
|
+
def self.create(exception)
|
7
|
+
error_type = NoScriptError.match?(exception) ? NoScriptError : ConnectionError
|
8
|
+
error_type.new exception
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ConnectionError < Error
|
13
|
+
|
14
|
+
def initialize(inner_exception)
|
15
|
+
@inner_exception = inner_exception
|
16
|
+
end
|
17
|
+
|
18
|
+
def message
|
19
|
+
@inner_exception.message
|
20
|
+
end
|
21
|
+
|
22
|
+
def backtrace
|
23
|
+
@inner_exception.backtrace
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
class NoScriptError < ConnectionError
|
29
|
+
def self.match?(exception)
|
30
|
+
exception.message.start_with? 'NOSCRIPT No matching script'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class LockerError < Error
|
35
|
+
|
36
|
+
def initialize(inner_exception)
|
37
|
+
@inner_exception = inner_exception
|
38
|
+
end
|
39
|
+
|
40
|
+
def message
|
41
|
+
@parsed_message ||= parse_message @inner_exception.message
|
42
|
+
end
|
43
|
+
|
44
|
+
def backtrace
|
45
|
+
@inner_exception.backtrace
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_message(message)
|
49
|
+
match_data = /^(ERR Error running script.*@user_script.*user_script.*: )/.match message
|
50
|
+
match_data ? message.gsub(match_data.captures.first,'').strip : message
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
data/lib/restruct/hash.rb
CHANGED
@@ -7,7 +7,7 @@ module Restruct
|
|
7
7
|
def_delegators :to_h, :merge, :flatten, :invert
|
8
8
|
|
9
9
|
def [](key)
|
10
|
-
deserialize
|
10
|
+
deserialize connection.call('HGET', id, key)
|
11
11
|
end
|
12
12
|
|
13
13
|
def fetch(key, default=nil, &block)
|
@@ -25,7 +25,7 @@ module Restruct
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def store(key, value)
|
28
|
-
|
28
|
+
connection.lazy 'HSET', id, key, serialize(value)
|
29
29
|
value
|
30
30
|
end
|
31
31
|
alias_method :[]=, :store
|
@@ -38,7 +38,7 @@ module Restruct
|
|
38
38
|
|
39
39
|
def delete(key)
|
40
40
|
value = self[key]
|
41
|
-
|
41
|
+
connection.lazy 'HDEL', id, key
|
42
42
|
value
|
43
43
|
end
|
44
44
|
|
@@ -59,11 +59,11 @@ module Restruct
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def keys
|
62
|
-
|
62
|
+
connection.call 'HKEYS', id
|
63
63
|
end
|
64
64
|
|
65
65
|
def values
|
66
|
-
|
66
|
+
connection.call('HVALS', id).map { |v| deserialize v }
|
67
67
|
end
|
68
68
|
|
69
69
|
def values_at(*keys)
|
@@ -71,7 +71,7 @@ module Restruct
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def key?(key)
|
74
|
-
|
74
|
+
connection.call('HEXISTS', id, key) == 1
|
75
75
|
end
|
76
76
|
alias_method :has_key?, :key?
|
77
77
|
|
@@ -81,7 +81,7 @@ module Restruct
|
|
81
81
|
alias_method :has_value?, :value?
|
82
82
|
|
83
83
|
def size
|
84
|
-
|
84
|
+
connection.call 'HLEN', id
|
85
85
|
end
|
86
86
|
alias_method :count, :size
|
87
87
|
alias_method :length, :size
|
@@ -104,7 +104,7 @@ module Restruct
|
|
104
104
|
end
|
105
105
|
|
106
106
|
def to_h
|
107
|
-
|
107
|
+
connection.call('HGETALL', id).each_slice(2).each_with_object({}) do |(k,v), hash|
|
108
108
|
hash[k] = deserialize v
|
109
109
|
end
|
110
110
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Restruct
|
2
|
+
class Locker < Structure
|
3
|
+
|
4
|
+
REGISTER_LUA = File.read "#{File.dirname(__FILE__)}/../../lua/register.lua"
|
5
|
+
UNREGISTER_LUA = File.read "#{File.dirname(__FILE__)}/../../lua/unregister.lua"
|
6
|
+
|
7
|
+
def lock(key, &block)
|
8
|
+
_lock key, false, &block
|
9
|
+
end
|
10
|
+
|
11
|
+
def lock!(key, &block)
|
12
|
+
_lock key, true, &block
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method :unlock!, :destroy
|
16
|
+
|
17
|
+
alias_method :locked?, :exists?
|
18
|
+
|
19
|
+
def key
|
20
|
+
connection.call('HGET', id, 'key')
|
21
|
+
end
|
22
|
+
alias_method :locked_by, :key
|
23
|
+
|
24
|
+
def to_h
|
25
|
+
::Hash[connection.call('HGETALL', id).each_slice(2).to_a]
|
26
|
+
end
|
27
|
+
alias_method :to_primitive, :to_h
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def _lock(key, exclusive)
|
32
|
+
connection.script REGISTER_LUA, 0, id, key, exclusive
|
33
|
+
begin
|
34
|
+
yield
|
35
|
+
ensure
|
36
|
+
connection.script UNREGISTER_LUA, 0, id
|
37
|
+
end
|
38
|
+
rescue Restruct::ConnectionError => ex
|
39
|
+
raise LockerError.new ex
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
data/lib/restruct/nested_hash.rb
CHANGED
@@ -9,7 +9,7 @@ module Restruct
|
|
9
9
|
const_set :TYPE, type
|
10
10
|
|
11
11
|
def [](key)
|
12
|
-
self.class::TYPE.new id: id[key],
|
12
|
+
self.class::TYPE.new id: id[key], connection: connection, parent: self
|
13
13
|
end
|
14
14
|
|
15
15
|
def fetch(key)
|
@@ -39,7 +39,7 @@ module Restruct
|
|
39
39
|
|
40
40
|
def keys
|
41
41
|
sections = id.sections.count + 1
|
42
|
-
|
42
|
+
connection.call('KEYS', id['*']).map do |k|
|
43
43
|
Id.new(k).sections.take(sections).last
|
44
44
|
end.uniq.sort
|
45
45
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Restruct
|
2
|
+
class Queue < Structure
|
3
|
+
|
4
|
+
def push(object)
|
5
|
+
connection.lazy 'RPUSH', id, serialize(object)
|
6
|
+
end
|
7
|
+
|
8
|
+
def pop
|
9
|
+
deserialize connection.lazy('LPOP', id)
|
10
|
+
end
|
11
|
+
|
12
|
+
def size
|
13
|
+
connection.call 'LLEN', id
|
14
|
+
end
|
15
|
+
alias_method :count, :size
|
16
|
+
alias_method :length, :size
|
17
|
+
|
18
|
+
def empty?
|
19
|
+
size == 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_a
|
23
|
+
connection.call('LRANGE', id, 0, -1).map { |o| deserialize o }
|
24
|
+
end
|
25
|
+
alias_method :to_primitive, :to_a
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def serialize(string)
|
30
|
+
string
|
31
|
+
end
|
32
|
+
|
33
|
+
def deserialize(string)
|
34
|
+
string
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/restruct/set.rb
CHANGED
@@ -57,7 +57,7 @@ module Restruct
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def size
|
60
|
-
|
60
|
+
connection.call 'SCARD', id
|
61
61
|
end
|
62
62
|
alias_method :count, :size
|
63
63
|
alias_method :length, :size
|
@@ -67,7 +67,7 @@ module Restruct
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def include?(member)
|
70
|
-
|
70
|
+
connection.call('SISMEMBER', id, serialize(member)) == 1
|
71
71
|
end
|
72
72
|
|
73
73
|
def each(&block)
|
@@ -75,7 +75,7 @@ module Restruct
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def to_a
|
78
|
-
|
78
|
+
connection.call('SMEMBERS', id).map { |e| deserialize e }
|
79
79
|
end
|
80
80
|
|
81
81
|
def to_set
|
@@ -99,11 +99,11 @@ module Restruct
|
|
99
99
|
private
|
100
100
|
|
101
101
|
def _add(*members)
|
102
|
-
|
102
|
+
connection.lazy 'SADD', id, *members.map { |m| serialize m }
|
103
103
|
end
|
104
104
|
|
105
105
|
def _delete(*members)
|
106
|
-
|
106
|
+
connection.lazy 'SREM', id, *members.map { |m| serialize m }
|
107
107
|
end
|
108
108
|
|
109
109
|
|