stal 0.0.1 → 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/README.md +15 -20
- data/lib/stal.rb +54 -38
- data/stal.gemspec +1 -1
- data/test/all.rb +24 -13
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e0c73d92dba5eab545ca813c4e47885b77c78e34
|
4
|
+
data.tar.gz: daf465500d116a4ead3f79d5532aabecda8a67d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6a656ad854d7bb0027d45988772d3b81dfe49775344301424f4dc876977027d6ec1a207835e9570f8cf15434abf66e402e75e646ec5fdf9ab2ebdce23443911d
|
7
|
+
data.tar.gz: 30851cd6f82f3072f2526359b73608a90dc07d1459944e681591453d02101f37c420feac75879b2dfcdb327bc00c799741a7147c316ed0f3048937953f4042e7
|
data/README.md
CHANGED
@@ -49,44 +49,39 @@ redis.call("SADD", "qux", "x", "y", "z")
|
|
49
49
|
Now we can perform some set operations with `Stal`:
|
50
50
|
|
51
51
|
```ruby
|
52
|
-
expr = [:
|
52
|
+
expr = [:SUNION, "qux", [:SDIFF, [:SINTER, "foo", "bar"], "baz"]]
|
53
53
|
|
54
54
|
Stal.solve(redis, expr)
|
55
55
|
#=> ["b", "x", "y", "z"]
|
56
56
|
```
|
57
57
|
|
58
|
-
`Stal` translates the
|
59
|
-
`SDIFFSTORE`, `SINTERSTORE` and `SUNIONSTORE` to
|
60
|
-
underlying operations
|
61
|
-
|
58
|
+
`Stal` translates the internal calls to `:SUNION`, `:SDIFF` and
|
59
|
+
`:SINTER` into `SDIFFSTORE`, `SINTERSTORE` and `SUNIONSTORE` to
|
60
|
+
perform the underlying operations, and it takes care of generating
|
61
|
+
and deleting any temporary keys.
|
62
|
+
|
63
|
+
Note that the only valid names for the internal commands are
|
64
|
+
`:SUNION`, `:SDIFF` and `:SINTER`. Any other internal command will
|
65
|
+
raise an error. The outmost command can be any set operation, for
|
66
|
+
example:
|
62
67
|
|
63
68
|
```ruby
|
64
|
-
expr = [:
|
69
|
+
expr = [:SCARD, [:SINTER, "foo", "bar"]]
|
65
70
|
|
66
71
|
Stal.solve(redis, expr)
|
67
|
-
#=>
|
72
|
+
#=> 2
|
68
73
|
```
|
69
74
|
|
70
75
|
If you want to preview the commands `Stal` will send to generate
|
71
76
|
the results, you can use `Stal.compile`:
|
72
77
|
|
73
78
|
```ruby
|
74
|
-
Stal.
|
75
|
-
# [[
|
76
|
-
# [:
|
77
|
-
# "stal:fe5aaec9-...",
|
78
|
-
# "stal:55f631dc-...",
|
79
|
-
# "baz"],
|
80
|
-
# [:SMEMBERS, "stal:fe5aaec9-..."],
|
81
|
-
# [:DEL,
|
82
|
-
# "stal:fe5aaec9-...",
|
83
|
-
# "stal:55f631dc-..."]]
|
79
|
+
Stal.explain([:SINTER, [:SUNION, "foo", "bar"], "baz"])
|
80
|
+
# [["SUNIONSTORE", "stal:0", "foo", "bar"],
|
81
|
+
# [:SINTER, "stal:0", "baz"]]
|
84
82
|
```
|
85
83
|
|
86
84
|
All commands are pipelined and wrapped in a `MULTI/EXEC` transaction.
|
87
|
-
The temporary keys, which have been shortened in the example, are
|
88
|
-
deleted immediately.
|
89
|
-
|
90
85
|
|
91
86
|
Installation
|
92
87
|
------------
|
data/lib/stal.rb
CHANGED
@@ -1,69 +1,85 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
3
|
require "redic"
|
4
|
-
require "securerandom"
|
5
4
|
|
6
5
|
module Stal
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
:
|
6
|
+
class InvalidCommand < ArgumentError; end
|
7
|
+
|
8
|
+
COMMANDS = {
|
9
|
+
:SDIFF => 'SDIFFSTORE',
|
10
|
+
:SINTER => 'SINTERSTORE',
|
11
|
+
:SUNION => 'SUNIONSTORE',
|
11
12
|
}
|
12
13
|
|
13
|
-
def self.
|
14
|
-
|
14
|
+
def self.command(term)
|
15
|
+
COMMANDS.fetch(term) do
|
16
|
+
raise(InvalidCommand, term)
|
17
|
+
end
|
15
18
|
end
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
# Keys we need to clean up later
|
21
|
-
ids.push(id)
|
22
|
-
|
23
|
-
# Add command with destination key
|
24
|
-
cmd = [tr(expr[0]), id]
|
25
|
-
|
26
|
-
expr[1..-1].each do |item|
|
20
|
+
# Compile expression into Redis commands
|
21
|
+
def self.compile(expr, ids, ops)
|
22
|
+
expr.map do |item|
|
27
23
|
if Array === item
|
28
|
-
|
24
|
+
convert(item, ids, ops)
|
29
25
|
else
|
30
|
-
|
26
|
+
item
|
31
27
|
end
|
32
28
|
end
|
29
|
+
end
|
33
30
|
|
34
|
-
|
31
|
+
# Transform :SDIFF, :SINTER and :SUNION commands
|
32
|
+
# into SDIFFSTORE, SINTERSTORE and SUNIONSTORE.
|
33
|
+
def self.convert(expr, ids, ops)
|
34
|
+
head, *tail = expr
|
35
35
|
|
36
|
-
|
37
|
-
|
36
|
+
# Key where partial results will be stored
|
37
|
+
id = sprintf("stal:%s", ids.size)
|
38
38
|
|
39
|
-
|
39
|
+
# Keep a reference to clean it up later
|
40
|
+
ids.push(id)
|
40
41
|
|
41
|
-
#
|
42
|
-
|
42
|
+
# Translate into command and destination key
|
43
|
+
op = [command(head), id]
|
43
44
|
|
44
|
-
#
|
45
|
+
# Compile the rest recursively
|
46
|
+
op.concat(compile(tail, ids, ops))
|
47
|
+
|
48
|
+
# Append the outermost operation
|
49
|
+
ops.push(op)
|
50
|
+
|
51
|
+
return id
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return commands without any wrapping added by `solve`
|
55
|
+
def self.explain(expr)
|
45
56
|
ids = []
|
57
|
+
ops = []
|
46
58
|
|
47
|
-
|
59
|
+
ops.push(compile(expr, ids, ops))
|
48
60
|
|
49
|
-
|
50
|
-
acc.push([:DEL, *ids])
|
51
|
-
acc
|
61
|
+
return ops
|
52
62
|
end
|
53
63
|
|
54
64
|
# Evaluate expression `expr` in the Redis client `c`.
|
55
65
|
def self.solve(c, expr)
|
56
|
-
|
66
|
+
ids = []
|
67
|
+
ops = []
|
57
68
|
|
58
|
-
|
69
|
+
ops.push(compile(expr, ids, ops))
|
59
70
|
|
60
|
-
|
61
|
-
c.
|
62
|
-
|
71
|
+
if ops.one?
|
72
|
+
c.call(*ops[0])
|
73
|
+
else
|
74
|
+
c.queue("MULTI")
|
63
75
|
|
64
|
-
|
76
|
+
ops.each do |op|
|
77
|
+
c.queue(*op)
|
78
|
+
end
|
65
79
|
|
66
|
-
|
67
|
-
|
80
|
+
c.queue("DEL", *ids)
|
81
|
+
c.queue("EXEC")
|
82
|
+
c.commit[-1][-2]
|
83
|
+
end
|
68
84
|
end
|
69
85
|
end
|
data/stal.gemspec
CHANGED
data/test/all.rb
CHANGED
@@ -12,26 +12,37 @@ end
|
|
12
12
|
|
13
13
|
test do |c|
|
14
14
|
|
15
|
-
#
|
16
|
-
expr = [
|
17
|
-
|
18
|
-
# Explicit syntax
|
19
|
-
expr = [:SUNIONSTORE, "qux", [:SDIFFSTORE, [:SINTERSTORE, "foo", "bar"], "baz"]]
|
15
|
+
# Example expression
|
16
|
+
expr = ["SUNION", "qux", [:SDIFF, [:SINTER, "foo", "bar"], "baz"]]
|
20
17
|
|
21
18
|
assert_equal ["b", "x", "y", "z"], Stal.solve(c, expr).sort
|
22
19
|
|
23
|
-
#
|
24
|
-
expr = ["
|
20
|
+
# Commands in sub expressions must be symbols
|
21
|
+
expr = ["SUNION", "qux", ["SDIFF", ["SINTER", "foo", "bar"], "baz"]]
|
25
22
|
|
26
|
-
|
23
|
+
assert_raise(Stal::InvalidCommand) do
|
24
|
+
Stal.solve(c, expr)
|
25
|
+
end
|
27
26
|
|
28
|
-
#
|
29
|
-
expr = ["
|
27
|
+
# Commands without sub expressions also work
|
28
|
+
expr = ["SINTER", "foo", "bar"]
|
30
29
|
|
31
|
-
assert_equal ["b", "
|
30
|
+
assert_equal ["b", "c"], Stal.solve(c, expr).sort
|
32
31
|
|
33
|
-
|
32
|
+
# Only :SUNION, :SDIFF and :SINTER are supported in sub expressions
|
33
|
+
expr = ["SUNION", ["DEL", "foo"]]
|
34
34
|
|
35
|
-
|
35
|
+
assert_raise(Stal::InvalidCommand) do
|
36
|
+
Stal.solve(c, expr)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Verify there's no keyspace pollution
|
36
40
|
assert_equal ["bar", "baz", "foo", "qux"], c.call("KEYS", "*").sort
|
41
|
+
|
42
|
+
expr = ["SCARD", [:SINTER, "foo", "bar"]]
|
43
|
+
|
44
|
+
# Explain returns an array of Redis commands
|
45
|
+
expected = [["SINTERSTORE", "stal:0", "foo", "bar"], ["SCARD", "stal:0"]]
|
46
|
+
|
47
|
+
assert_equal expected, Stal.explain(expr)
|
37
48
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michel Martens
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redic
|