stal 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -20
  3. data/lib/stal.rb +54 -38
  4. data/stal.gemspec +1 -1
  5. data/test/all.rb +24 -13
  6. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 01f32692fdb539b8eebbc7d2964ba275aecef27b
4
- data.tar.gz: a971d29478038e640c0e94fd2636e0a57edd807f
3
+ metadata.gz: e0c73d92dba5eab545ca813c4e47885b77c78e34
4
+ data.tar.gz: daf465500d116a4ead3f79d5532aabecda8a67d3
5
5
  SHA512:
6
- metadata.gz: dc8a947141e7a9d84d8c78ba58097d5cdb002b2493fdbd5a9752cb98306cd661c4f49e6c2b353c4371bb28ff45f30368b255f200d56a467bfe265c69fe620bfd
7
- data.tar.gz: 47e6bb79573cde80bce0ed2554ef135de38f52c45ce5303761886d41f2b14b4bb4994632d45007d342685f21c530241d111dc8abe7a2984ef2beebc4361b082d
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 = [:union, "qux", [:diff, [:inter, "foo", "bar"], "baz"]]
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 shortcuts `:union`, `:diff` and `:inter` into
59
- `SDIFFSTORE`, `SINTERSTORE` and `SUNIONSTORE` to perform the
60
- underlying operations. You can also use the explicit command
61
- (lowercase works too).
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 = [:SUNIONSTORE, "qux", [:SDIFFSTORE, [:SINTERSTORE, "foo", "bar"], "baz"]]
69
+ expr = [:SCARD, [:SINTER, "foo", "bar"]]
65
70
 
66
71
  Stal.solve(redis, expr)
67
- #=> ["b", "x", "y", "z"]
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.compile([:inter, [:union, "foo", "bar"], "baz"])
75
- # [[:SUNIONSTORE, "stal:55f631dc-...", "foo", "bar"],
76
- # [:SINTERSTORE,
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
  ------------
@@ -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
- ALIASES = {
8
- :diff => "SDIFFSTORE",
9
- :inter => "SINTERSTORE",
10
- :union => "SUNIONSTORE",
6
+ class InvalidCommand < ArgumentError; end
7
+
8
+ COMMANDS = {
9
+ :SDIFF => 'SDIFFSTORE',
10
+ :SINTER => 'SINTERSTORE',
11
+ :SUNION => 'SUNIONSTORE',
11
12
  }
12
13
 
13
- def self.tr(term)
14
- ALIASES.fetch(term, term)
14
+ def self.command(term)
15
+ COMMANDS.fetch(term) do
16
+ raise(InvalidCommand, term)
17
+ end
15
18
  end
16
19
 
17
- def self.compute(expr, ids, acc)
18
- id = sprintf("stal:%s", SecureRandom.uuid)
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
- cmd.push(compute(item, ids, acc))
24
+ convert(item, ids, ops)
29
25
  else
30
- cmd.push(item)
26
+ item
31
27
  end
32
28
  end
29
+ end
33
30
 
34
- acc.push(cmd)
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
- return id
37
- end
36
+ # Key where partial results will be stored
37
+ id = sprintf("stal:%s", ids.size)
38
38
 
39
- def self.compile(expr)
39
+ # Keep a reference to clean it up later
40
+ ids.push(id)
40
41
 
41
- # Commands to process
42
- acc = []
42
+ # Translate into command and destination key
43
+ op = [command(head), id]
43
44
 
44
- # Keys to cleanup
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
- id = compute(expr, ids, acc)
59
+ ops.push(compile(expr, ids, ops))
48
60
 
49
- acc.push([:SMEMBERS, id])
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
- operations = compile(expr)
66
+ ids = []
67
+ ops = []
57
68
 
58
- c.queue("MULTI")
69
+ ops.push(compile(expr, ids, ops))
59
70
 
60
- operations.each do |op|
61
- c.queue(*op)
62
- end
71
+ if ops.one?
72
+ c.call(*ops[0])
73
+ else
74
+ c.queue("MULTI")
63
75
 
64
- c.queue("EXEC")
76
+ ops.each do |op|
77
+ c.queue(*op)
78
+ end
65
79
 
66
- # Return the result of SMEMBERS
67
- c.commit[-1][-2]
80
+ c.queue("DEL", *ids)
81
+ c.queue("EXEC")
82
+ c.commit[-1][-2]
83
+ end
68
84
  end
69
85
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "stal"
3
- s.version = "0.0.1"
3
+ s.version = "0.1.0"
4
4
  s.summary = %{Set algebra solver for Redis}
5
5
  s.description = %Q{Stal receives s-expressions and resolves the set operations in Redis}
6
6
  s.authors = ["Michel Martens"]
@@ -12,26 +12,37 @@ end
12
12
 
13
13
  test do |c|
14
14
 
15
- # Shortcut syntax
16
- expr = [:union, "qux", [:diff, [:inter, "foo", "bar"], "baz"]]
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
- # Explicit syntax with strings
24
- expr = ["SUNIONSTORE", "qux", ["SDIFFSTORE", ["SINTERSTORE", "foo", "bar"], "baz"]]
20
+ # Commands in sub expressions must be symbols
21
+ expr = ["SUNION", "qux", ["SDIFF", ["SINTER", "foo", "bar"], "baz"]]
25
22
 
26
- assert_equal ["b", "x", "y", "z"], Stal.solve(c, expr).sort
23
+ assert_raise(Stal::InvalidCommand) do
24
+ Stal.solve(c, expr)
25
+ end
27
26
 
28
- # Explicit syntax with lowercase strings
29
- expr = ["sunionstore", "qux", ["sdiffstore", ["sinterstore", "foo", "bar"], "baz"]]
27
+ # Commands without sub expressions also work
28
+ expr = ["SINTER", "foo", "bar"]
30
29
 
31
- assert_equal ["b", "x", "y", "z"], Stal.solve(c, expr).sort
30
+ assert_equal ["b", "c"], Stal.solve(c, expr).sort
32
31
 
33
- assert_equal ["b", "x", "y", "z"], Stal.solve(c, expr).sort
32
+ # Only :SUNION, :SDIFF and :SINTER are supported in sub expressions
33
+ expr = ["SUNION", ["DEL", "foo"]]
34
34
 
35
- # Verify there's no pollution
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.1
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-04 00:00:00.000000000 Z
11
+ date: 2015-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redic