redis_recipes 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +28 -0
- data/README.md +26 -0
- data/Rakefile +8 -0
- data/lib/range_lookup/README.md +62 -0
- data/lib/range_lookup/add.lua +66 -0
- data/lib/range_lookup/lookup.lua +27 -0
- data/lib/range_lookup/remove.lua +62 -0
- data/lib/range_lookup_x/README.md +59 -0
- data/lib/range_lookup_x/add.lua +59 -0
- data/lib/range_lookup_x/lookup.lua +19 -0
- data/lib/range_lookup_x/remove.lua +51 -0
- data/redis_recipes.gemspec +23 -0
- data/spec/range_lookup/add_spec.rb +146 -0
- data/spec/range_lookup/lookup_spec.rb +64 -0
- data/spec/range_lookup/remove_spec.rb +189 -0
- data/spec/range_lookup_x/add_spec.rb +149 -0
- data/spec/range_lookup_x/lookup_spec.rb +65 -0
- data/spec/range_lookup_x/remove_spec.rb +186 -0
- data/spec/spec_helper.rb +64 -0
- metadata +121 -0
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
redis_recipes (0.3.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.1.3)
|
10
|
+
rake (0.9.2.2)
|
11
|
+
redis (3.0.2)
|
12
|
+
rspec (2.11.0)
|
13
|
+
rspec-core (~> 2.11.0)
|
14
|
+
rspec-expectations (~> 2.11.0)
|
15
|
+
rspec-mocks (~> 2.11.0)
|
16
|
+
rspec-core (2.11.1)
|
17
|
+
rspec-expectations (2.11.3)
|
18
|
+
diff-lcs (~> 1.1.3)
|
19
|
+
rspec-mocks (2.11.3)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
rake
|
26
|
+
redis (~> 3.0.0)
|
27
|
+
redis_recipes!
|
28
|
+
rspec
|
data/README.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Redis Recipes
|
2
|
+
|
3
|
+
Redis LUA recipes. Require Redis 2.6.0 or higher.
|
4
|
+
|
5
|
+
## License
|
6
|
+
|
7
|
+
Copyright (c) 2012 Black Square Media Ltd
|
8
|
+
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
10
|
+
a copy of this software and associated documentation files (the
|
11
|
+
"Software"), to deal in the Software without restriction, including
|
12
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
13
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
14
|
+
permit persons to whom the Software is furnished to do so, subject to
|
15
|
+
the following conditions:
|
16
|
+
|
17
|
+
The above copyright notice and this permission notice shall be
|
18
|
+
included in all copies or substantial portions of the Software.
|
19
|
+
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
21
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
22
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
23
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
24
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
25
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# Redis Recipes: Range Lookup
|
2
|
+
|
3
|
+
Data structure, optimised for fast lookups of (possibly overlapping) ranges
|
4
|
+
containing a given value. Works with any numericly representable ranges, e.g.
|
5
|
+
dates, times, IP addresses, etc. Inspired by: http://stackoverflow.com/questions/8622816/redis-or-mongo-for-determining-if-a-number-falls-within-ranges/8624231#8624231
|
6
|
+
|
7
|
+
### Example / Internals:
|
8
|
+
|
9
|
+
Consider the following use case: "Holiday System"
|
10
|
+
|
11
|
+
* [A]lice is on vacation from the 4th until the 11th
|
12
|
+
* [B]ob leaves an 7th and returns on the 21st
|
13
|
+
* [C]olin is also away from 4th, but returns only on the 18th
|
14
|
+
* [D]eborah's holidays are from 16th to the 23th
|
15
|
+
|
16
|
+
Our numeric ranges would therefore be:
|
17
|
+
|
18
|
+
A 4-11
|
19
|
+
B 7-21
|
20
|
+
C 4-18
|
21
|
+
D 16-23
|
22
|
+
|
23
|
+
To identify the people away for any specific date quickly, we create a
|
24
|
+
flattened index:
|
25
|
+
|
26
|
+
4 [A C]
|
27
|
+
7 [A B C]
|
28
|
+
11 [B C]
|
29
|
+
16 [B C D]
|
30
|
+
18 [B D]
|
31
|
+
21 [D]
|
32
|
+
23 []
|
33
|
+
|
34
|
+
For any given day, we can match the list (set) of members that are on vacation,
|
35
|
+
by:
|
36
|
+
|
37
|
+
1. Just reading the members for exact hits. For example: on the 18th [B C D]
|
38
|
+
match.
|
39
|
+
2. Creating an intersection between the preceeding and the following set. For
|
40
|
+
example: on the 19th INTER([B C D], [B D]) -> [B D] match.
|
41
|
+
|
42
|
+
### Usage:
|
43
|
+
|
44
|
+
Add ranges:
|
45
|
+
|
46
|
+
redis-cli --eval <path/to/add.lua> holidays , A 4 11
|
47
|
+
redis-cli --eval <path/to/add.lua> holidays , B 7 21
|
48
|
+
redis-cli --eval <path/to/add.lua> holidays , C 4 18
|
49
|
+
redis-cli --eval <path/to/add.lua> holidays , D 16 23
|
50
|
+
redis-cli --eval <path/to/add.lua> holidays , X 5 15
|
51
|
+
|
52
|
+
Remove a range:
|
53
|
+
|
54
|
+
redis-cli --eval <path/to/remove.lua> holidays , X 5 15
|
55
|
+
|
56
|
+
Lookup range:
|
57
|
+
|
58
|
+
redis-cli --eval <path/to/lookup.lua> holidays , 18 # => 1) "B"
|
59
|
+
# 2) "C"
|
60
|
+
# 3) "D"
|
61
|
+
redis-cli --eval <path/to/lookup.lua> holidays , 19 # => 1) "B"
|
62
|
+
# 2) "D"
|
@@ -0,0 +1,66 @@
|
|
1
|
+
if #KEYS ~= 1 or #ARGV ~= 3 then
|
2
|
+
return redis.error_reply("wrong number of arguments")
|
3
|
+
end
|
4
|
+
|
5
|
+
local prefix = KEYS[1]
|
6
|
+
local member = ARGV[1]
|
7
|
+
local min = tonumber(ARGV[2])
|
8
|
+
local max = tonumber(ARGV[3])
|
9
|
+
|
10
|
+
if min == nil or max == nil or min > max then
|
11
|
+
return redis.error_reply("min/max are not numeric or out of range")
|
12
|
+
end
|
13
|
+
|
14
|
+
local index = prefix .. ":~"
|
15
|
+
local minscr = redis.call('zscore', index, min)
|
16
|
+
local maxscr = redis.call('zscore', index, max)
|
17
|
+
local minwrp = {}
|
18
|
+
local maxwrp = {}
|
19
|
+
local window = redis.call('zrangebyscore', index, "(" .. min, "(" .. max)
|
20
|
+
|
21
|
+
-- Find existing members to be included in the new min
|
22
|
+
if not minscr then
|
23
|
+
local before = redis.call('zrevrangebyscore', index, "(" .. min, "-inf", "limit", 0, 1)[1]
|
24
|
+
if before then
|
25
|
+
local after = redis.call('zrangebyscore', index, "(" .. min, "inf", "limit", 0, 1)[1]
|
26
|
+
if after then
|
27
|
+
minwrp = redis.call('sinter', prefix .. ":" .. before, prefix .. ":" .. after)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
-- Find existing members to be included in the new max
|
33
|
+
if not maxscr then
|
34
|
+
local after = redis.call('zrangebyscore', index, "(" .. max, "inf", "limit", 0, 1)[1]
|
35
|
+
if after then
|
36
|
+
local before = redis.call('zrevrangebyscore', index, "(" .. max, "-inf", "limit", 0, 1)[1]
|
37
|
+
if before then
|
38
|
+
maxwrp = redis.call('sinter', prefix .. ":" .. before, prefix .. ":" .. after)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
-- Store members in min & max sets
|
44
|
+
redis.call('sadd', prefix .. ":" .. min, member)
|
45
|
+
redis.call('sadd', prefix .. ":" .. max, member)
|
46
|
+
|
47
|
+
-- Store new min & max indices
|
48
|
+
if not minscr then redis.call('zadd', index, min, min) end
|
49
|
+
if not maxscr then redis.call('zadd', index, max, max) end
|
50
|
+
|
51
|
+
-- Store member in all existing sets between min & max
|
52
|
+
for _,key in pairs(window) do
|
53
|
+
redis.call('sadd', prefix .. ":" .. key, member)
|
54
|
+
end
|
55
|
+
|
56
|
+
-- Merge existing members into min
|
57
|
+
if #minwrp > 0 then
|
58
|
+
redis.call('sadd', prefix .. ":" .. min, unpack(minwrp))
|
59
|
+
end
|
60
|
+
|
61
|
+
-- Merge existing members into max
|
62
|
+
if #maxwrp > 0 then
|
63
|
+
redis.call('sadd', prefix .. ":" .. max, unpack(maxwrp))
|
64
|
+
end
|
65
|
+
|
66
|
+
return redis.status_reply("OK")
|
@@ -0,0 +1,27 @@
|
|
1
|
+
if #KEYS ~= 1 or #ARGV ~= 1 then
|
2
|
+
return redis.error_reply("wrong number of arguments")
|
3
|
+
end
|
4
|
+
|
5
|
+
local prefix = KEYS[1]
|
6
|
+
local value = tonumber(ARGV[1])
|
7
|
+
|
8
|
+
if value == nil then
|
9
|
+
return redis.error_reply("value is not numeric or out of range")
|
10
|
+
end
|
11
|
+
|
12
|
+
local members = {}
|
13
|
+
local score = redis.call('zscore', prefix .. ":~", value)
|
14
|
+
|
15
|
+
if score then -- Do we have an exact match?
|
16
|
+
members = redis.call('smembers', prefix .. ":" .. score)
|
17
|
+
else
|
18
|
+
local before = redis.call('zrevrangebyscore', prefix .. ":~", value, "-inf", "limit", 0, 1)[1]
|
19
|
+
if before then
|
20
|
+
local after = redis.call('zrangebyscore', prefix .. ":~", value, "inf", "limit", 0, 1)[1]
|
21
|
+
if after then
|
22
|
+
members = redis.call('sinter', prefix .. ":" .. before, prefix .. ":" .. after)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
return members
|
@@ -0,0 +1,62 @@
|
|
1
|
+
if #KEYS ~= 1 or #ARGV ~= 3 then
|
2
|
+
return redis.error_reply("wrong number of arguments")
|
3
|
+
end
|
4
|
+
|
5
|
+
local prefix = KEYS[1]
|
6
|
+
local member = ARGV[1]
|
7
|
+
local min = tonumber(ARGV[2])
|
8
|
+
local max = tonumber(ARGV[3])
|
9
|
+
|
10
|
+
if min == nil or max == nil or min > max then
|
11
|
+
return redis.error_reply("min/max are not numeric or out of range")
|
12
|
+
end
|
13
|
+
|
14
|
+
local index = prefix .. ":~"
|
15
|
+
local window = redis.call('zrangebyscore', index, min, max)
|
16
|
+
local before = nil
|
17
|
+
local after = nil
|
18
|
+
local minwrp = {}
|
19
|
+
local maxwrp = {}
|
20
|
+
|
21
|
+
-- Remove the member from all sets between min & max
|
22
|
+
for _, val in pairs(window) do
|
23
|
+
redis.call('srem', prefix .. ":" .. val, member)
|
24
|
+
end
|
25
|
+
|
26
|
+
-- Identify members wrapped in min
|
27
|
+
before = redis.call('zrevrangebyscore', index, "(" .. min, "-inf", "limit", 0, 1)[1]
|
28
|
+
if before then
|
29
|
+
after = redis.call('zrangebyscore', index, "(" .. min, "inf", "limit", 0, 1)[1]
|
30
|
+
if after then
|
31
|
+
minwrp = redis.call('sinter', prefix .. ":" .. before, prefix .. ":" .. after)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
-- Identify members wrapped in max
|
36
|
+
after = redis.call('zrangebyscore', index, "(" .. max, "inf", "limit", 0, 1)[1]
|
37
|
+
if after then
|
38
|
+
before = redis.call('zrevrangebyscore', index, "(" .. max, "-inf", "limit", 0, 1)[1]
|
39
|
+
if before then
|
40
|
+
maxwrp = redis.call('sinter', prefix .. ":" .. before, prefix .. ":" .. after)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
-- Remove existing wrapped members from min
|
45
|
+
if #minwrp > 0 then
|
46
|
+
redis.call('srem', prefix .. ":" .. min, unpack(minwrp))
|
47
|
+
end
|
48
|
+
|
49
|
+
-- Remove existing wrapped members from max
|
50
|
+
if #maxwrp > 0 then
|
51
|
+
redis.call('srem', prefix .. ":" .. max, unpack(maxwrp))
|
52
|
+
end
|
53
|
+
|
54
|
+
-- Remove the min index, if no more items are left in the min set
|
55
|
+
local minlen = redis.call('scard', prefix .. ":" .. min)
|
56
|
+
if minlen == 0 then redis.call('zrem', index, min) end
|
57
|
+
|
58
|
+
-- Remove the max index, if no more items are left in the max set
|
59
|
+
local maxlen = redis.call('scard', prefix .. ":" .. max)
|
60
|
+
if maxlen == 0 then redis.call('zrem', index, max) end
|
61
|
+
|
62
|
+
return redis.status_reply("OK")
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Redis Recipes: Range Lookup X
|
2
|
+
|
3
|
+
A data structure, very similar to the "normal" Range Lookup, except that it
|
4
|
+
stores ranges that exclude the end value.
|
5
|
+
|
6
|
+
### Example / Internals:
|
7
|
+
|
8
|
+
Consider the following use case: "Holiday System"
|
9
|
+
|
10
|
+
* [A]lice is on vacation from the 4th until the 11th
|
11
|
+
* [B]ob leaves an 7th and returns on the 21st
|
12
|
+
* [C]olin is also away from 4th, but returns only on the 18th
|
13
|
+
* [D]eborah's holidays are from 16th to the 23th
|
14
|
+
|
15
|
+
Our numeric ranges would therefore be:
|
16
|
+
|
17
|
+
A 4...12
|
18
|
+
B 7...22
|
19
|
+
C 4...19
|
20
|
+
D 16...24
|
21
|
+
|
22
|
+
To identify the people away for any specific date quickly, we create a
|
23
|
+
flattened index:
|
24
|
+
|
25
|
+
4 [A C]
|
26
|
+
7 [A B C]
|
27
|
+
12 [B C]
|
28
|
+
16 [B C D]
|
29
|
+
19 [B D]
|
30
|
+
22 [D]
|
31
|
+
24 []
|
32
|
+
|
33
|
+
For any given day, we can match the list (set) of members that are on vacation,
|
34
|
+
by:
|
35
|
+
|
36
|
+
1. Finding the index <= the given value. For example: a search for the 18th would return 16.
|
37
|
+
2. Reading the members on the index. For example: for the 16th, [B C D] would be returned.
|
38
|
+
|
39
|
+
### Usage:
|
40
|
+
|
41
|
+
Add ranges:
|
42
|
+
|
43
|
+
redis-cli --eval <path/to/add.lua> holidays , A 4 12
|
44
|
+
redis-cli --eval <path/to/add.lua> holidays , B 7 22
|
45
|
+
redis-cli --eval <path/to/add.lua> holidays , C 4 19
|
46
|
+
redis-cli --eval <path/to/add.lua> holidays , D 16 24
|
47
|
+
redis-cli --eval <path/to/add.lua> holidays , X 5 16
|
48
|
+
|
49
|
+
Remove a range:
|
50
|
+
|
51
|
+
redis-cli --eval <path/to/remove.lua> holidays , X 5 16
|
52
|
+
|
53
|
+
Lookup range:
|
54
|
+
|
55
|
+
redis-cli --eval <path/to/lookup.lua> holidays , 18 # => 1) "B"
|
56
|
+
# 2) "C"
|
57
|
+
# 3) "D"
|
58
|
+
redis-cli --eval <path/to/lookup.lua> holidays , 19 # => 1) "B"
|
59
|
+
# 2) "D"
|
@@ -0,0 +1,59 @@
|
|
1
|
+
if #KEYS ~= 1 or #ARGV ~= 3 then
|
2
|
+
return redis.error_reply("wrong number of arguments")
|
3
|
+
end
|
4
|
+
|
5
|
+
local prefix = KEYS[1]
|
6
|
+
local member = ARGV[1]
|
7
|
+
local min = tonumber(ARGV[2])
|
8
|
+
local max = tonumber(ARGV[3])
|
9
|
+
|
10
|
+
if min == nil or max == nil or min >= max then
|
11
|
+
return redis.error_reply("min/max are not numeric or out of range")
|
12
|
+
end
|
13
|
+
|
14
|
+
local index = prefix .. ":~"
|
15
|
+
local minscr = redis.call('zscore', index, min)
|
16
|
+
local maxscr = redis.call('zscore', index, max)
|
17
|
+
local minwrp = {}
|
18
|
+
local maxwrp = {}
|
19
|
+
local window = redis.call('zrangebyscore', index, "(" .. min, "(" .. max)
|
20
|
+
|
21
|
+
-- Find existing members to be included in the new min
|
22
|
+
if not minscr then
|
23
|
+
local before = redis.call('zrevrangebyscore', index, "(" .. min, "-inf", "limit", 0, 1)[1]
|
24
|
+
if before then
|
25
|
+
minwrp = redis.call('smembers', prefix .. ":" .. before)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
-- Find existing members to be included in the new max
|
30
|
+
if not maxscr then
|
31
|
+
local before = redis.call('zrevrangebyscore', index, "(" .. max, "-inf", "limit", 0, 1)[1]
|
32
|
+
if before then
|
33
|
+
maxwrp = redis.call('smembers', prefix .. ":" .. before)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
-- Store members in min set
|
38
|
+
redis.call('sadd', prefix .. ":" .. min, member)
|
39
|
+
|
40
|
+
-- Store new min & max indices
|
41
|
+
if not minscr then redis.call('zadd', index, min, min) end
|
42
|
+
if not maxscr then redis.call('zadd', index, max, max) end
|
43
|
+
|
44
|
+
-- Store member in all existing sets between min & max
|
45
|
+
for _,key in pairs(window) do
|
46
|
+
redis.call('sadd', prefix .. ":" .. key, member)
|
47
|
+
end
|
48
|
+
|
49
|
+
-- Merge existing members into min
|
50
|
+
if #minwrp > 0 then
|
51
|
+
redis.call('sadd', prefix .. ":" .. min, unpack(minwrp))
|
52
|
+
end
|
53
|
+
|
54
|
+
-- Merge existing members into max
|
55
|
+
if #maxwrp > 0 then
|
56
|
+
redis.call('sadd', prefix .. ":" .. max, unpack(maxwrp))
|
57
|
+
end
|
58
|
+
|
59
|
+
return redis.status_reply("OK")
|
@@ -0,0 +1,19 @@
|
|
1
|
+
if #KEYS ~= 1 or #ARGV ~= 1 then
|
2
|
+
return redis.error_reply("wrong number of arguments")
|
3
|
+
end
|
4
|
+
|
5
|
+
local prefix = KEYS[1]
|
6
|
+
local value = tonumber(ARGV[1])
|
7
|
+
|
8
|
+
if value == nil then
|
9
|
+
return redis.error_reply("value is not numeric or out of range")
|
10
|
+
end
|
11
|
+
|
12
|
+
local members = {}
|
13
|
+
local pos = redis.call('zrevrangebyscore', prefix .. ":~", value, "-inf", "limit", 0, 1)[1]
|
14
|
+
|
15
|
+
if pos then
|
16
|
+
members = redis.call('smembers', prefix .. ":" .. pos)
|
17
|
+
end
|
18
|
+
|
19
|
+
return members
|
@@ -0,0 +1,51 @@
|
|
1
|
+
if #KEYS ~= 1 or #ARGV ~= 3 then
|
2
|
+
return redis.error_reply("wrong number of arguments")
|
3
|
+
end
|
4
|
+
|
5
|
+
local prefix = KEYS[1]
|
6
|
+
local member = ARGV[1]
|
7
|
+
local min = tonumber(ARGV[2])
|
8
|
+
local max = tonumber(ARGV[3])
|
9
|
+
|
10
|
+
if min == nil or max == nil or min >= max then
|
11
|
+
return redis.error_reply("min/max are not numeric or out of range")
|
12
|
+
end
|
13
|
+
|
14
|
+
local index = prefix .. ":~"
|
15
|
+
local window = redis.call('zrangebyscore', index, min, "(" .. max)
|
16
|
+
|
17
|
+
local minlen = redis.call('scard', prefix .. ":" .. min)
|
18
|
+
local maxlen = redis.call('scard', prefix .. ":" .. max)
|
19
|
+
|
20
|
+
-- Calculate cardinality of the set before min
|
21
|
+
local befmin = redis.call('zrevrangebyscore', index, "(" .. min, "-inf", "limit", 0, 1)[1]
|
22
|
+
local bminlen = 0
|
23
|
+
if befmin then
|
24
|
+
bminlen = redis.call('scard', prefix .. ":" .. befmin)
|
25
|
+
end
|
26
|
+
|
27
|
+
-- Calculate cardinality of the set before max
|
28
|
+
local befmax = redis.call('zrevrangebyscore', index, "(" .. max, "-inf", "limit", 0, 1)[1]
|
29
|
+
local bmaxlen = 0
|
30
|
+
if befmax then
|
31
|
+
bmaxlen = redis.call('scard', prefix .. ":" .. befmax)
|
32
|
+
end
|
33
|
+
|
34
|
+
-- Remove min if the cardinality between min and the set before min differs by 1
|
35
|
+
if minlen - bminlen == 1 then
|
36
|
+
redis.call('del', prefix .. ":" .. min)
|
37
|
+
redis.call('zrem', index, min)
|
38
|
+
end
|
39
|
+
|
40
|
+
-- Remove max if the cardinality between max and the set before max differs by -1
|
41
|
+
if bmaxlen - maxlen == 1 then
|
42
|
+
redis.call('del', prefix .. ":" .. max)
|
43
|
+
redis.call('zrem', index, max)
|
44
|
+
end
|
45
|
+
|
46
|
+
-- Remove the member from all sets between min & max
|
47
|
+
for _, val in pairs(window) do
|
48
|
+
redis.call('srem', prefix .. ":" .. val, member)
|
49
|
+
end
|
50
|
+
|
51
|
+
return redis.status_reply("OK")
|