stal 0.1.0 → 0.2.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.
Files changed (9) hide show
  1. checksums.yaml +4 -4
  2. data/.gems +1 -1
  3. data/CONTRIBUTING +19 -0
  4. data/README.md +6 -25
  5. data/data/stal.lua +88 -0
  6. data/lib/stal.rb +11 -73
  7. data/stal.gemspec +3 -3
  8. data/test/all.rb +3 -21
  9. metadata +14 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e0c73d92dba5eab545ca813c4e47885b77c78e34
4
- data.tar.gz: daf465500d116a4ead3f79d5532aabecda8a67d3
3
+ metadata.gz: 6d5e97a625f23834a34722015dd285abbd3759b2
4
+ data.tar.gz: 4579c7dc32e11a5be18f0f1e42d405a4c197319b
5
5
  SHA512:
6
- metadata.gz: 6a656ad854d7bb0027d45988772d3b81dfe49775344301424f4dc876977027d6ec1a207835e9570f8cf15434abf66e402e75e646ec5fdf9ab2ebdce23443911d
7
- data.tar.gz: 30851cd6f82f3072f2526359b73608a90dc07d1459944e681591453d02101f37c420feac75879b2dfcdb327bc00c799741a7147c316ed0f3048937953f4042e7
6
+ metadata.gz: 3bd4c9ce1448d9737fbbf730598facfe4a6119a4e5f2a3a513592a7760364b9eb8fd1a90fad61bfec0030ed2abf9763cf05ab339c12f563bc9d383b8d6d469dd
7
+ data.tar.gz: 6e8ae54b05c800ae9be844835b48a3c08d396799db7fdf5fe7423200d28fa1790da7d7a0339c776fad52f5c33b1d5c89d3f54a7e84cebd2c730c75138b374a76
data/.gems CHANGED
@@ -1,2 +1,2 @@
1
- redic -v 1.3.0
2
1
  cutest -v 1.2.2
2
+ redic -v 1.5.0
@@ -0,0 +1,19 @@
1
+ This code tries to solve a particular problem with a very simple
2
+ implementation. We try to keep the code to a minimum while making
3
+ it as clear as possible. The design is very likely finished, and
4
+ if some feature is missing it is possible that it was left out on
5
+ purpose. That said, new usage patterns may arise, and when that
6
+ happens we are ready to adapt if necessary.
7
+
8
+ A good first step for contributing is to meet us on IRC and discuss
9
+ ideas. We spend a lot of time on #lesscode at freenode, always ready
10
+ to talk about code and simplicity. If connecting to IRC is not an
11
+ option, you can create an issue explaining the proposed change and
12
+ a use case. We pay a lot of attention to use cases, because our
13
+ goal is to keep the code base simple. Usually the result of a
14
+ conversation is the creation of a different tool.
15
+
16
+ Please don't start the conversation with a pull request. The code
17
+ should come at last, and even though it may help to convey an idea,
18
+ more often than not it draws the attention to a particular
19
+ implementation.
data/README.md CHANGED
@@ -49,39 +49,19 @@ 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 = [:SUNION, "qux", [:SDIFF, [:SINTER, "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 internal calls to `:SUNION`, `:SDIFF` and
59
- `:SINTER` into `SDIFFSTORE`, `SINTERSTORE` and `SUNIONSTORE` to
58
+ `Stal` translates the internal calls to `SUNION`, `SDIFF` and
59
+ `SINTER` into `SDIFFSTORE`, `SINTERSTORE` and `SUNIONSTORE` to
60
60
  perform the underlying operations, and it takes care of generating
61
61
  and deleting any temporary keys.
62
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:
67
-
68
- ```ruby
69
- expr = [:SCARD, [:SINTER, "foo", "bar"]]
70
-
71
- Stal.solve(redis, expr)
72
- #=> 2
73
- ```
74
-
75
- If you want to preview the commands `Stal` will send to generate
76
- the results, you can use `Stal.compile`:
77
-
78
- ```ruby
79
- Stal.explain([:SINTER, [:SUNION, "foo", "bar"], "baz"])
80
- # [["SUNIONSTORE", "stal:0", "foo", "bar"],
81
- # [:SINTER, "stal:0", "baz"]]
82
- ```
83
-
84
- All commands are pipelined and wrapped in a `MULTI/EXEC` transaction.
63
+ For more information, refer to the repository of the [Stal][stal]
64
+ script.
85
65
 
86
66
  Installation
87
67
  ------------
@@ -92,3 +72,4 @@ $ gem install stal
92
72
 
93
73
  [redis]: http://redis.io
94
74
  [redic]: https://github.com/amakawa/redic
75
+ [stal]: https://github.com/soveran/stal
@@ -0,0 +1,88 @@
1
+ -- Copyright (c) 2016 Michel Martens
2
+
3
+ local expr = cjson.decode(ARGV[1])
4
+
5
+ local tr = {
6
+ SDIFF = "SDIFFSTORE",
7
+ SINTER = "SINTERSTORE",
8
+ SUNION = "SUNIONSTORE",
9
+ ZINTER = "ZINTERSTORE",
10
+ ZUNION = "ZUNIONSTORE",
11
+ }
12
+
13
+ local function append(t1, t2)
14
+ for _, item in ipairs(t2) do
15
+ table.insert(t1, item)
16
+ end
17
+ end
18
+
19
+ local function map(t, f)
20
+ local nt = {}
21
+
22
+ for k, v in pairs(t) do
23
+ nt[k] = f(v)
24
+ end
25
+
26
+ return nt
27
+ end
28
+
29
+ local compile, convert
30
+
31
+ function compile(expr, ids, ops)
32
+ return map(expr, function(v)
33
+ if (type(v) == "table") then
34
+ return convert(v, ids, ops)
35
+ else
36
+ return v
37
+ end
38
+ end)
39
+ end
40
+
41
+ function convert(expr, ids, ops)
42
+ local tail = {unpack(expr)}
43
+ local head = table.remove(tail, 1)
44
+
45
+ -- Key where partial results will be stored
46
+ local id = "stal:" .. #ids
47
+
48
+ -- Keep a reference to clean it up later
49
+ table.insert(ids, id)
50
+
51
+ -- Translate into command and destination key
52
+ local op = {tr[head] or head, id}
53
+
54
+ -- Compile the rest recursively
55
+ append(op, compile(tail, ids, ops))
56
+
57
+ -- Append the outermost operation
58
+ table.insert(ops, op)
59
+
60
+ return id
61
+ end
62
+
63
+ local function solve(expr)
64
+ local ids = {}
65
+ local ops = {}
66
+ local res = nil
67
+
68
+ table.insert(ops, compile(expr, ids, ops))
69
+
70
+ if (#ops == 1) then
71
+ return redis.call(unpack(ops[1]))
72
+ else
73
+ for _, op in ipairs(ops) do
74
+ if (#op > 1) then
75
+ res = redis.call(unpack(op))
76
+ end
77
+ end
78
+
79
+ redis.call("DEL", unpack(ids))
80
+
81
+ return res
82
+ end
83
+ end
84
+
85
+ redis.replicate_commands()
86
+ redis.set_repl(redis.REPL_NONE)
87
+
88
+ return solve(expr)
@@ -1,85 +1,23 @@
1
1
  # encoding: UTF-8
2
2
 
3
+ require "json"
3
4
  require "redic"
4
5
 
5
6
  module Stal
6
- class InvalidCommand < ArgumentError; end
7
-
8
- COMMANDS = {
9
- :SDIFF => 'SDIFFSTORE',
10
- :SINTER => 'SINTERSTORE',
11
- :SUNION => 'SUNIONSTORE',
12
- }
13
-
14
- def self.command(term)
15
- COMMANDS.fetch(term) do
16
- raise(InvalidCommand, term)
17
- end
18
- end
19
-
20
- # Compile expression into Redis commands
21
- def self.compile(expr, ids, ops)
22
- expr.map do |item|
23
- if Array === item
24
- convert(item, ids, ops)
25
- else
26
- item
27
- end
28
- end
29
- end
30
-
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
-
36
- # Key where partial results will be stored
37
- id = sprintf("stal:%s", ids.size)
38
-
39
- # Keep a reference to clean it up later
40
- ids.push(id)
41
-
42
- # Translate into command and destination key
43
- op = [command(head), id]
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)
56
- ids = []
57
- ops = []
58
-
59
- ops.push(compile(expr, ids, ops))
60
-
61
- return ops
62
- end
7
+ LUA = File.expand_path("../../data/stal.lua", __FILE__)
8
+ SHA = "e98658a3aca397c69e8b452a73c6826cdd9f0577"
63
9
 
64
10
  # Evaluate expression `expr` in the Redis client `c`.
65
11
  def self.solve(c, expr)
66
- ids = []
67
- ops = []
68
-
69
- ops.push(compile(expr, ids, ops))
70
-
71
- if ops.one?
72
- c.call(*ops[0])
73
- else
74
- c.queue("MULTI")
75
-
76
- ops.each do |op|
77
- c.queue(*op)
12
+ begin
13
+ c.call!("EVALSHA", SHA, 0, JSON.dump(expr))
14
+ rescue RuntimeError
15
+ if $!.message["NOSCRIPT"]
16
+ c.call!("SCRIPT", "LOAD", File.read(LUA))
17
+ c.call!("EVALSHA", SHA, 0, JSON.dump(expr))
18
+ else
19
+ raise $!
78
20
  end
79
-
80
- c.queue("DEL", *ids)
81
- c.queue("EXEC")
82
- c.commit[-1][-2]
83
21
  end
84
22
  end
85
23
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "stal"
3
- s.version = "0.1.0"
3
+ s.version = "0.2.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"]
@@ -10,6 +10,6 @@ Gem::Specification.new do |s|
10
10
 
11
11
  s.files = `git ls-files`.split("\n")
12
12
 
13
- s.add_dependency "redic"
14
- s.add_development_dependency "cutest"
13
+ s.add_dependency "redic", "~> 1.5"
14
+ s.add_development_dependency "cutest", "~> 0"
15
15
  end
@@ -3,6 +3,7 @@ require_relative "helper"
3
3
  setup do
4
4
  Redic.new.tap do |c|
5
5
  c.call("FLUSHDB")
6
+ c.call("SCRIPT", "FLUSH")
6
7
  c.call("SADD", "foo", "a", "b", "c")
7
8
  c.call("SADD", "bar", "b", "c", "d")
8
9
  c.call("SADD", "baz", "c", "d", "e")
@@ -13,36 +14,17 @@ end
13
14
  test do |c|
14
15
 
15
16
  # Example expression
16
- expr = ["SUNION", "qux", [:SDIFF, [:SINTER, "foo", "bar"], "baz"]]
17
-
18
- assert_equal ["b", "x", "y", "z"], Stal.solve(c, expr).sort
19
-
20
- # Commands in sub expressions must be symbols
21
17
  expr = ["SUNION", "qux", ["SDIFF", ["SINTER", "foo", "bar"], "baz"]]
22
18
 
23
- assert_raise(Stal::InvalidCommand) do
24
- Stal.solve(c, expr)
25
- end
19
+ assert_equal ["b", "x", "y", "z"], Stal.solve(c, expr).sort
26
20
 
27
21
  # Commands without sub expressions also work
28
22
  expr = ["SINTER", "foo", "bar"]
29
23
 
30
24
  assert_equal ["b", "c"], Stal.solve(c, expr).sort
31
25
 
32
- # Only :SUNION, :SDIFF and :SINTER are supported in sub expressions
33
- expr = ["SUNION", ["DEL", "foo"]]
34
-
35
- assert_raise(Stal::InvalidCommand) do
36
- Stal.solve(c, expr)
37
- end
38
-
39
26
  # Verify there's no keyspace pollution
40
27
  assert_equal ["bar", "baz", "foo", "qux"], c.call("KEYS", "*").sort
41
28
 
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)
29
+ expr = ["SCARD", ["SINTER", "foo", "bar"]]
48
30
  end
metadata CHANGED
@@ -1,41 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.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-09 00:00:00.000000000 Z
11
+ date: 2016-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redic
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '1.5'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '1.5'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: cutest
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  description: Stal receives s-expressions and resolves the set operations in Redis
@@ -45,9 +45,11 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
- - .gems
48
+ - ".gems"
49
+ - CONTRIBUTING
49
50
  - LICENSE
50
51
  - README.md
52
+ - data/stal.lua
51
53
  - lib/stal.rb
52
54
  - makefile
53
55
  - stal.gemspec
@@ -63,17 +65,17 @@ require_paths:
63
65
  - lib
64
66
  required_ruby_version: !ruby/object:Gem::Requirement
65
67
  requirements:
66
- - - '>='
68
+ - - ">="
67
69
  - !ruby/object:Gem::Version
68
70
  version: '0'
69
71
  required_rubygems_version: !ruby/object:Gem::Requirement
70
72
  requirements:
71
- - - '>='
73
+ - - ">="
72
74
  - !ruby/object:Gem::Version
73
75
  version: '0'
74
76
  requirements: []
75
77
  rubyforge_project:
76
- rubygems_version: 2.0.14
78
+ rubygems_version: 2.4.5.1
77
79
  signing_key:
78
80
  specification_version: 4
79
81
  summary: Set algebra solver for Redis