ohm 1.4.0 → 2.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gems +4 -4
- data/CHANGELOG +0 -8
- data/README.md +4 -6
- data/examples/chaining.rb +52 -56
- data/lib/ohm.rb +145 -358
- data/lib/ohm/command.rb +14 -10
- data/lib/ohm/lua/delete.lua +51 -0
- data/lib/ohm/lua/save.lua +104 -0
- data/ohm.gemspec +8 -6
- data/test/command.rb +22 -24
- data/test/connection.rb +5 -89
- data/test/filtering.rb +140 -8
- data/test/helper.rb +3 -1
- data/test/indices.rb +1 -1
- data/test/issue-52.rb +2 -2
- data/test/json.rb +0 -16
- data/test/list.rb +1 -1
- data/test/model.rb +70 -194
- data/test/uniques.rb +2 -32
- metadata +23 -26
- data/lib/ohm/transaction.rb +0 -133
- data/test/1.8.6_test.rb +0 -25
- data/test/pipeline-performance.rb +0 -67
- data/test/transactions.rb +0 -240
- data/test/validations.rb +0 -195
data/lib/ohm/command.rb
CHANGED
@@ -16,14 +16,17 @@ module Ohm
|
|
16
16
|
@keys = []
|
17
17
|
end
|
18
18
|
|
19
|
-
def call(
|
20
|
-
newkey(
|
21
|
-
|
19
|
+
def call(nido, redis)
|
20
|
+
newkey(nido, redis) do |key|
|
21
|
+
redis.call(@operation, key, *params(nido, redis))
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
def clean
|
26
|
-
keys.each
|
26
|
+
keys.each do |key, redis|
|
27
|
+
redis.call("DEL", key)
|
28
|
+
end
|
29
|
+
|
27
30
|
subcommands.each { |cmd| cmd.clean }
|
28
31
|
end
|
29
32
|
|
@@ -32,15 +35,16 @@ module Ohm
|
|
32
35
|
args.select { |arg| arg.respond_to?(:call) }
|
33
36
|
end
|
34
37
|
|
35
|
-
def params(
|
36
|
-
args.map { |arg| arg.respond_to?(:call) ? arg.call(
|
38
|
+
def params(nido, redis)
|
39
|
+
args.map { |arg| arg.respond_to?(:call) ? arg.call(nido, redis) : arg }
|
37
40
|
end
|
38
41
|
|
39
|
-
def newkey(
|
40
|
-
key =
|
41
|
-
keys << key
|
42
|
+
def newkey(nido, redis)
|
43
|
+
key = nido[SecureRandom.hex(32)]
|
44
|
+
keys << [key, redis]
|
45
|
+
|
46
|
+
yield key
|
42
47
|
|
43
|
-
yield key
|
44
48
|
return key
|
45
49
|
end
|
46
50
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
local model = cmsgpack.unpack(ARGV[1])
|
2
|
+
local uniques = cmsgpack.unpack(ARGV[2])
|
3
|
+
local collections = cmsgpack.unpack(ARGV[3])
|
4
|
+
|
5
|
+
local function remove_indices(model)
|
6
|
+
local memo = model.key .. ":_indices"
|
7
|
+
local existing = redis.call("SMEMBERS", memo)
|
8
|
+
|
9
|
+
for _, key in ipairs(existing) do
|
10
|
+
redis.call("SREM", key, model.id)
|
11
|
+
redis.call("SREM", memo, key)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
local function remove_uniques(model, uniques)
|
16
|
+
local memo = model.key .. ":_uniques"
|
17
|
+
|
18
|
+
for field, _ in pairs(uniques) do
|
19
|
+
local key = model.name .. ":uniques:" .. field
|
20
|
+
|
21
|
+
redis.call("HDEL", key, redis.call("HGET", memo, key))
|
22
|
+
redis.call("HDEL", memo, key)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
local function remove_collections(model, collections)
|
27
|
+
for _, collection in ipairs(collections) do
|
28
|
+
local key = model.key .. ":" .. collection
|
29
|
+
|
30
|
+
redis.call("DEL", key)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
local function delete(model)
|
35
|
+
local keys = {
|
36
|
+
model.key .. ":counters",
|
37
|
+
model.key .. ":_indices",
|
38
|
+
model.key .. ":_uniques",
|
39
|
+
model.key
|
40
|
+
}
|
41
|
+
|
42
|
+
redis.call("SREM", model.name .. ":all", model.id)
|
43
|
+
redis.call("DEL", unpack(keys))
|
44
|
+
end
|
45
|
+
|
46
|
+
remove_indices(model)
|
47
|
+
remove_uniques(model, uniques)
|
48
|
+
remove_collections(model, collections)
|
49
|
+
delete(model)
|
50
|
+
|
51
|
+
return model.id
|
@@ -0,0 +1,104 @@
|
|
1
|
+
local model = cmsgpack.unpack(ARGV[1])
|
2
|
+
local attrs = cmsgpack.unpack(ARGV[2])
|
3
|
+
local indices = cmsgpack.unpack(ARGV[3])
|
4
|
+
local uniques = cmsgpack.unpack(ARGV[4])
|
5
|
+
|
6
|
+
local function save(model, attrs)
|
7
|
+
redis.call("SADD", model.name .. ":all", model.id)
|
8
|
+
redis.call("DEL", model.key)
|
9
|
+
|
10
|
+
if math.mod(#attrs, 2) == 1 then
|
11
|
+
error("Wrong number of attribute/value pairs")
|
12
|
+
end
|
13
|
+
|
14
|
+
if #attrs > 0 then
|
15
|
+
redis.call("HMSET", model.key, unpack(attrs))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
local function index(model, indices)
|
20
|
+
for field, enum in pairs(indices) do
|
21
|
+
for _, val in ipairs(enum) do
|
22
|
+
local key = model.name .. ":indices:" .. field .. ":" .. tostring(val)
|
23
|
+
|
24
|
+
redis.call("SADD", model.key .. ":_indices", key)
|
25
|
+
redis.call("SADD", key, model.id)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
local function remove_indices(model)
|
31
|
+
local memo = model.key .. ":_indices"
|
32
|
+
local existing = redis.call("SMEMBERS", memo)
|
33
|
+
|
34
|
+
for _, key in ipairs(existing) do
|
35
|
+
redis.call("SREM", key, model.id)
|
36
|
+
redis.call("SREM", memo, key)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
local function unique(model, uniques)
|
41
|
+
for field, value in pairs(uniques) do
|
42
|
+
local key = model.name .. ":uniques:" .. field
|
43
|
+
|
44
|
+
redis.call("HSET", model.key .. ":_uniques", key, value)
|
45
|
+
redis.call("HSET", key, value, model.id)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
local function remove_uniques(model, uniques)
|
50
|
+
local memo = model.key .. ":_uniques"
|
51
|
+
|
52
|
+
for field, _ in pairs(uniques) do
|
53
|
+
local key = model.name .. ":uniques:" .. field
|
54
|
+
|
55
|
+
redis.call("HDEL", key, redis.call("HGET", memo, key))
|
56
|
+
redis.call("HDEL", memo, key)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
local function verify(model, uniques)
|
61
|
+
local duplicates = {}
|
62
|
+
|
63
|
+
for field, value in pairs(uniques) do
|
64
|
+
local key = model.name .. ":uniques:" .. field
|
65
|
+
local id = redis.call("HGET", key, tostring(value))
|
66
|
+
|
67
|
+
if id and id ~= tostring(model.id) then
|
68
|
+
duplicates[#duplicates + 1] = field
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
return duplicates, #duplicates ~= 0
|
73
|
+
end
|
74
|
+
|
75
|
+
local duplicates, err = verify(model, uniques)
|
76
|
+
|
77
|
+
if err then
|
78
|
+
error("UniqueIndexViolation: " .. duplicates[1])
|
79
|
+
end
|
80
|
+
|
81
|
+
local function convertBooleans(list)
|
82
|
+
for index, value in ipairs(list) do
|
83
|
+
if type(value) == "boolean" then
|
84
|
+
if value then
|
85
|
+
list[index] = 1
|
86
|
+
else
|
87
|
+
list[index] = nil
|
88
|
+
list[index - 1] = nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
convertBooleans(attrs)
|
95
|
+
|
96
|
+
save(model, attrs)
|
97
|
+
|
98
|
+
remove_indices(model)
|
99
|
+
index(model, indices)
|
100
|
+
|
101
|
+
remove_uniques(model, uniques)
|
102
|
+
unique(model, uniques)
|
103
|
+
|
104
|
+
return model.id
|
data/ohm.gemspec
CHANGED
@@ -1,18 +1,20 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "ohm"
|
3
|
-
s.version = "
|
3
|
+
s.version = "2.0.0.alpha1"
|
4
4
|
s.summary = %{Object-hash mapping library for Redis.}
|
5
5
|
s.description = %Q{Ohm is a library that allows to store an object in Redis, a persistent key-value database. It includes an extensible list of validations and has very good performance.}
|
6
6
|
s.authors = ["Michel Martens", "Damian Janowski", "Cyril David"]
|
7
7
|
s.email = ["michel@soveran.com", "djanowski@dimaion.com", "me@cyrildavid.com"]
|
8
|
-
s.homepage = "http://soveran.github.
|
8
|
+
s.homepage = "http://soveran.github.io/ohm/"
|
9
9
|
s.license = "MIT"
|
10
10
|
|
11
11
|
s.files = `git ls-files`.split("\n")
|
12
12
|
|
13
13
|
s.rubyforge_project = "ohm"
|
14
|
-
|
15
|
-
s.add_dependency "
|
16
|
-
s.add_dependency "
|
17
|
-
s.
|
14
|
+
|
15
|
+
s.add_dependency "redic"
|
16
|
+
s.add_dependency "nido"
|
17
|
+
s.add_dependency "msgpack"
|
18
|
+
|
19
|
+
s.add_development_dependency "cutest"
|
18
20
|
end
|
data/test/command.rb
CHANGED
@@ -2,56 +2,54 @@ require_relative "helper"
|
|
2
2
|
|
3
3
|
scope do
|
4
4
|
setup do
|
5
|
-
redis =
|
6
|
-
redis.
|
5
|
+
redis = Redic.new
|
6
|
+
redis.call("FLUSHDB")
|
7
7
|
|
8
|
-
|
9
|
-
# redis.client.logger = Logger.new(STDOUT)
|
10
|
-
nest = Nest.new("User:tmp", redis)
|
8
|
+
nido = Nido.new("User:tmp")
|
11
9
|
|
12
|
-
[1, 2, 3].each { |i| redis.
|
13
|
-
[1, 4, 5].each { |i| redis.
|
10
|
+
[1, 2, 3].each { |i| redis.call("SADD", "A", i) }
|
11
|
+
[1, 4, 5].each { |i| redis.call("SADD", "B", i) }
|
14
12
|
|
15
|
-
[10, 11, 12].each { |i| redis.
|
16
|
-
[11, 12, 13].each { |i| redis.
|
17
|
-
[12, 13, 14].each { |i| redis.
|
13
|
+
[10, 11, 12].each { |i| redis.call("SADD", "C", i) }
|
14
|
+
[11, 12, 13].each { |i| redis.call("SADD", "D", i) }
|
15
|
+
[12, 13, 14].each { |i| redis.call("SADD", "E", i) }
|
18
16
|
|
19
|
-
[10, 11, 12].each { |i| redis.
|
20
|
-
[11, 12, 13].each { |i| redis.
|
21
|
-
[12, 13, 14].each { |i| redis.
|
17
|
+
[10, 11, 12].each { |i| redis.call("SADD", "F", i) }
|
18
|
+
[11, 12, 13].each { |i| redis.call("SADD", "G", i) }
|
19
|
+
[12, 13, 14].each { |i| redis.call("SADD", "H", i) }
|
22
20
|
|
23
|
-
[redis,
|
21
|
+
[redis, nido]
|
24
22
|
end
|
25
23
|
|
26
24
|
test "special condition: single argument returns that arg" do
|
27
25
|
assert_equal "A", Ohm::Command[:sinterstore, "A"]
|
28
26
|
end
|
29
27
|
|
30
|
-
test "full stack test" do |redis,
|
28
|
+
test "full stack test" do |redis, nido|
|
31
29
|
cmd1 = Ohm::Command[:sinterstore, "A", "B"]
|
32
30
|
|
33
|
-
res = cmd1.call(
|
34
|
-
assert_equal ["1"], res
|
31
|
+
res = cmd1.call(nido, redis)
|
32
|
+
assert_equal ["1"], redis.call("SMEMBERS", res)
|
35
33
|
|
36
34
|
cmd1.clean
|
37
|
-
|
35
|
+
assert_equal 0, redis.call("EXISTS", res)
|
38
36
|
|
39
37
|
cmd2 = Ohm::Command[:sinterstore, "C", "D", "E"]
|
40
38
|
cmd3 = Ohm::Command[:sunionstore, cmd1, cmd2]
|
41
39
|
|
42
|
-
res = cmd3.call(
|
43
|
-
assert_equal ["1", "12"], res
|
40
|
+
res = cmd3.call(nido, redis)
|
41
|
+
assert_equal ["1", "12"], redis.call("SMEMBERS", res)
|
44
42
|
|
45
43
|
cmd3.clean
|
46
|
-
assert redis.
|
44
|
+
assert redis.call("KEYS", nido["*"]).empty?
|
47
45
|
|
48
46
|
cmd4 = Ohm::Command[:sinterstore, "F", "G", "H"]
|
49
47
|
cmd5 = Ohm::Command[:sdiffstore, cmd3, cmd4]
|
50
48
|
|
51
|
-
res = cmd5.call(
|
52
|
-
assert_equal ["1"], res
|
49
|
+
res = cmd5.call(nido, redis)
|
50
|
+
assert_equal ["1"], redis.call("SMEMBERS", res)
|
53
51
|
|
54
52
|
cmd5.clean
|
55
|
-
assert redis.
|
53
|
+
assert redis.call("KEYS", nido["*"]).empty?
|
56
54
|
end
|
57
55
|
end
|
data/test/connection.rb
CHANGED
@@ -2,105 +2,21 @@
|
|
2
2
|
|
3
3
|
require File.expand_path("./helper", File.dirname(__FILE__))
|
4
4
|
|
5
|
-
unless defined?(
|
6
|
-
|
7
|
-
end
|
8
|
-
|
9
|
-
prepare.clear
|
10
|
-
|
11
|
-
test "no rewriting of settings hash when using Ohm.connect" do
|
12
|
-
settings = { :url => "redis://127.0.0.1:6379/15" }.freeze
|
13
|
-
|
14
|
-
ex = nil
|
15
|
-
|
16
|
-
begin
|
17
|
-
Ohm.connect(settings)
|
18
|
-
rescue RuntimeError => e
|
19
|
-
ex = e
|
20
|
-
end
|
21
|
-
|
22
|
-
assert_equal ex, nil
|
23
|
-
end
|
24
|
-
|
25
|
-
test "connects lazily" do
|
26
|
-
Ohm.connect(:port => 9876)
|
27
|
-
|
28
|
-
begin
|
29
|
-
Ohm.redis.get "foo"
|
30
|
-
rescue => e
|
31
|
-
assert_equal Redis::CannotConnectError, e.class
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
test "provides a separate connection for each thread" do
|
36
|
-
assert Ohm.redis == Ohm.redis
|
37
|
-
|
38
|
-
conn1, conn2 = nil
|
39
|
-
|
40
|
-
threads = []
|
41
|
-
|
42
|
-
threads << Thread.new do
|
43
|
-
conn1 = Ohm.redis
|
44
|
-
end
|
45
|
-
|
46
|
-
threads << Thread.new do
|
47
|
-
conn2 = Ohm.redis
|
48
|
-
end
|
49
|
-
|
50
|
-
threads.each { |t| t.join }
|
51
|
-
|
52
|
-
assert conn1 != conn2
|
53
|
-
end
|
54
|
-
|
55
|
-
test "supports connecting by URL" do
|
56
|
-
Ohm.connect(:url => "redis://localhost:9876")
|
57
|
-
|
58
|
-
begin
|
59
|
-
Ohm.redis.get "foo"
|
60
|
-
rescue => e
|
61
|
-
assert_equal Redis::CannotConnectError, e.class
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
setup do
|
66
|
-
Ohm.connect(:url => "redis://localhost:6379/0")
|
67
|
-
end
|
68
|
-
|
69
|
-
test "connection class" do
|
70
|
-
conn = Ohm::Connection.new(:foo, :url => "redis://localhost:6379/0")
|
71
|
-
|
72
|
-
assert conn.redis.kind_of?(Redis)
|
73
|
-
end
|
74
|
-
|
75
|
-
test "issue #46" do
|
76
|
-
class B < Ohm::Model
|
77
|
-
connect(:url => "redis://localhost:6379/15")
|
78
|
-
end
|
79
|
-
|
80
|
-
# We do this since we did prepare.clear above.
|
81
|
-
B.db.flushall
|
82
|
-
|
83
|
-
b1, b2 = nil, nil
|
84
|
-
|
85
|
-
Thread.new { b1 = B.create }.join
|
86
|
-
Thread.new { b2 = B.create }.join
|
87
|
-
|
88
|
-
assert_equal [b1, b2], B.all.sort.to_a
|
5
|
+
unless defined?(Redic::CannotConnectError)
|
6
|
+
Redic::CannotConnectError = Errno::ECONNREFUSED
|
89
7
|
end
|
90
8
|
|
91
9
|
test "model can define its own connection" do
|
92
10
|
class B < Ohm::Model
|
93
|
-
|
11
|
+
self.redis = Redic.new("redis://localhost:6379/1")
|
94
12
|
end
|
95
13
|
|
96
|
-
|
97
|
-
assert_equal Ohm.conn.options, {:url=>"redis://localhost:6379/0"}
|
14
|
+
assert B.redis.url != Ohm.redis.url
|
98
15
|
end
|
99
16
|
|
100
17
|
test "model inherits Ohm.redis connection by default" do
|
101
|
-
Ohm.connect(:url => "redis://localhost:9876")
|
102
18
|
class C < Ohm::Model
|
103
19
|
end
|
104
20
|
|
105
|
-
assert_equal C.
|
21
|
+
assert_equal C.redis.url, Ohm.redis.url
|
106
22
|
end
|
data/test/filtering.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
__END__
|
1
2
|
require File.expand_path("./helper", File.dirname(__FILE__))
|
2
3
|
|
3
4
|
class User < Ohm::Model
|
@@ -95,14 +96,6 @@ test "#union" do |john, jane|
|
|
95
96
|
assert res.any? { |e| e.status == "inactive" }
|
96
97
|
end
|
97
98
|
|
98
|
-
test "#combine" do |john, jane|
|
99
|
-
res = User.find(:status => "active").combine(fname: ["John", "Jane"])
|
100
|
-
|
101
|
-
assert_equal 2, res.size
|
102
|
-
assert res.include?(john)
|
103
|
-
assert res.include?(jane)
|
104
|
-
end
|
105
|
-
|
106
99
|
# book author thing via @myobie
|
107
100
|
scope do
|
108
101
|
class Book < Ohm::Model
|
@@ -167,3 +160,142 @@ scope do
|
|
167
160
|
assert_equal 2, res.size
|
168
161
|
end
|
169
162
|
end
|
163
|
+
|
164
|
+
# test precision of filtering commands
|
165
|
+
require "logger"
|
166
|
+
require "stringio"
|
167
|
+
scope do
|
168
|
+
class Post < Ohm::Model
|
169
|
+
attribute :author
|
170
|
+
index :author
|
171
|
+
|
172
|
+
attribute :mood
|
173
|
+
index :mood
|
174
|
+
end
|
175
|
+
|
176
|
+
setup do
|
177
|
+
io = StringIO.new
|
178
|
+
|
179
|
+
Post.connect(:logger => Logger.new(io))
|
180
|
+
|
181
|
+
Post.create(author: "matz", mood: "happy")
|
182
|
+
Post.create(author: "rich", mood: "mad")
|
183
|
+
|
184
|
+
io
|
185
|
+
end
|
186
|
+
|
187
|
+
def read(io)
|
188
|
+
io.rewind
|
189
|
+
io.read
|
190
|
+
end
|
191
|
+
|
192
|
+
test "SINTERSTORE a b" do |io|
|
193
|
+
Post.find(author: "matz").find(mood: "happy").to_a
|
194
|
+
|
195
|
+
# This is the simple case. We should only do one SINTERSTORE
|
196
|
+
# given two direct keys. Anything more and we're performing badly.
|
197
|
+
expected = "SINTERSTORE Post:tmp:[a-f0-9]{64} " +
|
198
|
+
"Post:indices:author:matz Post:indices:mood:happy"
|
199
|
+
|
200
|
+
assert(read(io) =~ Regexp.new(expected))
|
201
|
+
end
|
202
|
+
|
203
|
+
test "SUNIONSTORE a b" do |io|
|
204
|
+
Post.find(author: "matz").union(mood: "happy").to_a
|
205
|
+
|
206
|
+
# Another simple case where we must only do one operation at maximum.
|
207
|
+
expected = "SUNIONSTORE Post:tmp:[a-f0-9]{64} " +
|
208
|
+
"Post:indices:author:matz Post:indices:mood:happy"
|
209
|
+
|
210
|
+
assert(read(io) =~ Regexp.new(expected))
|
211
|
+
end
|
212
|
+
|
213
|
+
test "SUNIONSTORE c (SINTERSTORE a b)" do |io|
|
214
|
+
Post.find(author: "matz").find(mood: "happy").union(author: "rich").to_a
|
215
|
+
|
216
|
+
# For this case we need an intermediate key. This will
|
217
|
+
# contain the intersection of matz + happy.
|
218
|
+
expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
|
219
|
+
"Post:indices:author:matz Post:indices:mood:happy"
|
220
|
+
|
221
|
+
assert(read(io) =~ Regexp.new(expected))
|
222
|
+
|
223
|
+
# The next operation is simply doing a UNION of the previously
|
224
|
+
# generated intermediate key and the additional single key.
|
225
|
+
expected = "SUNIONSTORE (Post:tmp:[a-f0-9]{64}) " +
|
226
|
+
"%s Post:indices:author:rich" % $1
|
227
|
+
|
228
|
+
assert(read(io) =~ Regexp.new(expected))
|
229
|
+
end
|
230
|
+
|
231
|
+
test "SUNIONSTORE (SINTERSTORE c d) (SINTERSTORE a b)" do |io|
|
232
|
+
Post.find(author: "matz").find(mood: "happy").
|
233
|
+
union(author: "rich", mood: "sad").to_a
|
234
|
+
|
235
|
+
# Similar to the previous case, we need to do an intermediate
|
236
|
+
# operation.
|
237
|
+
expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
|
238
|
+
"Post:indices:author:matz Post:indices:mood:happy"
|
239
|
+
|
240
|
+
match1 = read(io).match(Regexp.new(expected))
|
241
|
+
assert match1
|
242
|
+
|
243
|
+
# But now, we need to also hold another intermediate key for the
|
244
|
+
# condition of author: rich AND mood: sad.
|
245
|
+
expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
|
246
|
+
"Post:indices:author:rich Post:indices:mood:sad"
|
247
|
+
|
248
|
+
match2 = read(io).match(Regexp.new(expected))
|
249
|
+
assert match2
|
250
|
+
|
251
|
+
# Now we expect that it does a UNION of those two previous
|
252
|
+
# intermediate keys.
|
253
|
+
expected = sprintf(
|
254
|
+
"SUNIONSTORE (Post:tmp:[a-f0-9]{64}) %s %s",
|
255
|
+
match1[1], match2[1]
|
256
|
+
)
|
257
|
+
|
258
|
+
assert(read(io) =~ Regexp.new(expected))
|
259
|
+
end
|
260
|
+
|
261
|
+
test do |io|
|
262
|
+
Post.create(author: "kent", mood: "sad")
|
263
|
+
|
264
|
+
Post.find(author: "kent", mood: "sad").
|
265
|
+
union(author: "matz", mood: "happy").
|
266
|
+
except(mood: "sad", author: "rich").to_a
|
267
|
+
|
268
|
+
expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
|
269
|
+
"Post:indices:author:kent Post:indices:mood:sad"
|
270
|
+
|
271
|
+
match1 = read(io).match(Regexp.new(expected))
|
272
|
+
assert match1
|
273
|
+
|
274
|
+
expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
|
275
|
+
"Post:indices:author:matz Post:indices:mood:happy"
|
276
|
+
|
277
|
+
match2 = read(io).match(Regexp.new(expected))
|
278
|
+
assert match2
|
279
|
+
|
280
|
+
expected = sprintf(
|
281
|
+
"SUNIONSTORE (Post:tmp:[a-f0-9]{64}) %s %s",
|
282
|
+
match1[1], match2[1]
|
283
|
+
)
|
284
|
+
|
285
|
+
match3 = read(io).match(Regexp.new(expected))
|
286
|
+
assert match3
|
287
|
+
|
288
|
+
expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
|
289
|
+
"Post:indices:mood:sad Post:indices:author:rich"
|
290
|
+
|
291
|
+
match4 = read(io).match(Regexp.new(expected))
|
292
|
+
assert match4
|
293
|
+
|
294
|
+
expected = sprintf(
|
295
|
+
"SDIFFSTORE (Post:tmp:[a-f0-9]{64}) %s %s",
|
296
|
+
match3[1], match4[1]
|
297
|
+
)
|
298
|
+
|
299
|
+
assert(read(io) =~ Regexp.new(expected))
|
300
|
+
end
|
301
|
+
end
|