redis-bitops 0.2
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +193 -0
- data/lib/redis/bitops.rb +26 -0
- data/lib/redis/bitops/bitmap.rb +107 -0
- data/lib/redis/bitops/configuration.rb +38 -0
- data/lib/redis/bitops/queries/binary_operator.rb +71 -0
- data/lib/redis/bitops/queries/lazy_evaluation.rb +47 -0
- data/lib/redis/bitops/queries/materialization_helpers.rb +53 -0
- data/lib/redis/bitops/queries/tree_building_helpers.rb +46 -0
- data/lib/redis/bitops/queries/unary_operator.rb +48 -0
- data/lib/redis/bitops/sparse_bitmap.rb +125 -0
- data/spec/redis/bitops/bitmap_spec.rb +9 -0
- data/spec/redis/bitops/queries/binary_operator_spec.rb +24 -0
- data/spec/redis/bitops/queries/unary_operator_spec.rb +27 -0
- data/spec/redis/bitops/sparse_bitmap_spec.rb +99 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/bitmap_examples.rb +313 -0
- metadata +173 -0
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$: << "../../lib"
|
2
|
+
|
3
|
+
require 'redis/bitops'
|
4
|
+
|
5
|
+
require_relative './support/bitmap_examples'
|
6
|
+
|
7
|
+
require 'pry'
|
8
|
+
require 'awesome_print'
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.after(:each, redis_cleanup: true) do
|
12
|
+
key_prefix = example.metadata[:redis_key_prefix] or raise "Specify the key prefix using RSpec metadata (e.g. redis_key_prefix: 'rsb:')."
|
13
|
+
keys = redis.keys("#{key_prefix}*")
|
14
|
+
redis.del(*keys) unless keys.empty?
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,313 @@
|
|
1
|
+
shared_examples_for "a bitmap" do |creation_method|
|
2
|
+
let(:redis) { Redis.new }
|
3
|
+
let(:a) { redis.send(creation_method, "rsb:a") }
|
4
|
+
let(:b) { redis.send(creation_method, "rsb:b") }
|
5
|
+
let(:c) { redis.send(creation_method, "rsb:c") }
|
6
|
+
let(:result) { redis.send(creation_method, "rsb:output") }
|
7
|
+
|
8
|
+
describe "#[]" do
|
9
|
+
it "sets individual bits" do
|
10
|
+
b[0] = true
|
11
|
+
b[99] = true
|
12
|
+
b[0].should be_true
|
13
|
+
b[99].should be_true
|
14
|
+
end
|
15
|
+
|
16
|
+
it "resizes the bitmap as necessary" do
|
17
|
+
expect { b[10_000_000] = 1 }.to_not raise_error
|
18
|
+
b[10_000_000].should be_true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "doesn't zeroes unset bits" do
|
22
|
+
max_bit_pos = 5_000
|
23
|
+
approx_percent_bits_set = 0.2
|
24
|
+
|
25
|
+
# TODO: There are definitely faster ways to generate the data.
|
26
|
+
Inf = 1.0/0.0 unless Kernel.const_defined? "Inf"
|
27
|
+
set = Set.new((0..Inf).lazy.map {|i| (rand * max_bit_pos).to_i}.take(approx_percent_bits_set * max_bit_pos))
|
28
|
+
|
29
|
+
set.each do |pos|
|
30
|
+
b[pos] = true
|
31
|
+
end
|
32
|
+
|
33
|
+
set.each do |pos|
|
34
|
+
b[pos].should be_true
|
35
|
+
end
|
36
|
+
|
37
|
+
(0..Inf).lazy.take(max_bit_pos).each do |pos|
|
38
|
+
b[pos].should eq set.include?(pos)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#bitcount" do
|
44
|
+
it "returns the number of set bits" do
|
45
|
+
b.bitcount.should == 0
|
46
|
+
b[1] = true
|
47
|
+
b[2] = true
|
48
|
+
b[100] = true
|
49
|
+
b.bitcount.should == 3
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#operator &" do
|
54
|
+
it "returns a result query that can be materialized" do
|
55
|
+
a[0] = true
|
56
|
+
a[1] = true
|
57
|
+
a[2] = true
|
58
|
+
a[100] = true
|
59
|
+
a[110] = true
|
60
|
+
|
61
|
+
b[0] = true
|
62
|
+
b[100] = true
|
63
|
+
|
64
|
+
q = a & b
|
65
|
+
|
66
|
+
result << q
|
67
|
+
result.bitcount.should == 2
|
68
|
+
end
|
69
|
+
|
70
|
+
it "allows operator nesting" do
|
71
|
+
a[0] = true
|
72
|
+
a[1] = true
|
73
|
+
a[2] = true
|
74
|
+
a[100] = true
|
75
|
+
a[110] = true
|
76
|
+
|
77
|
+
b[0] = true
|
78
|
+
b[100] = true
|
79
|
+
|
80
|
+
c[0] = true
|
81
|
+
|
82
|
+
q = a & b & (c & a)
|
83
|
+
|
84
|
+
result << q
|
85
|
+
result.bitcount.should == 1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "#operator |" do
|
90
|
+
it "returns a result query that can be materialized" do
|
91
|
+
a[0] = true
|
92
|
+
a[1] = true
|
93
|
+
a[2] = true
|
94
|
+
a[100] = true
|
95
|
+
|
96
|
+
b[0] = true
|
97
|
+
b[100] = true
|
98
|
+
b[110] = true
|
99
|
+
|
100
|
+
q = a | b
|
101
|
+
|
102
|
+
result << q
|
103
|
+
result.bitcount.should == 5
|
104
|
+
end
|
105
|
+
|
106
|
+
it "allows operator nesting" do
|
107
|
+
a[0] = true
|
108
|
+
a[1] = true
|
109
|
+
a[2] = true
|
110
|
+
a[100] = true
|
111
|
+
a[110] = true
|
112
|
+
|
113
|
+
b[0] = true
|
114
|
+
b[100] = true
|
115
|
+
|
116
|
+
c[0] = true
|
117
|
+
|
118
|
+
q = a | b | (c | a)
|
119
|
+
|
120
|
+
result << q
|
121
|
+
result.bitcount.should == 5
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "#operator ~" do
|
126
|
+
it "returns a result query that can be materialized" do
|
127
|
+
a[0] = true
|
128
|
+
a[1] = true
|
129
|
+
a[2] = true
|
130
|
+
a[100] = true
|
131
|
+
|
132
|
+
q = ~a
|
133
|
+
|
134
|
+
result << q
|
135
|
+
result[0].should be_false
|
136
|
+
result[1].should be_false
|
137
|
+
result[2].should be_false
|
138
|
+
result[3].should be_true
|
139
|
+
result[99].should be_true
|
140
|
+
result[100].should be_false
|
141
|
+
end
|
142
|
+
|
143
|
+
it "allows operator nesting" do
|
144
|
+
a[0] = true
|
145
|
+
a[1] = true
|
146
|
+
a[2] = true
|
147
|
+
a[100] = true
|
148
|
+
a[110] = true
|
149
|
+
|
150
|
+
b[0] = true
|
151
|
+
b[100] = true
|
152
|
+
|
153
|
+
c[0] = true
|
154
|
+
|
155
|
+
q = ~a | b & c
|
156
|
+
|
157
|
+
result << q
|
158
|
+
result[0].should be_true
|
159
|
+
result[1].should be_false
|
160
|
+
result[2].should be_false
|
161
|
+
result[3].should be_true
|
162
|
+
result[99].should be_true
|
163
|
+
result[100].should be_false
|
164
|
+
result[109].should be_true
|
165
|
+
result[110].should be_false
|
166
|
+
result[111].should be_true
|
167
|
+
end
|
168
|
+
|
169
|
+
# Commented out because this is how redis BITOP NOT works:
|
170
|
+
# it pads the results to the full byte thus messing up with
|
171
|
+
# the operation.
|
172
|
+
|
173
|
+
# it "returns result with the correct bitcount" do
|
174
|
+
# pending
|
175
|
+
# a[0] = true
|
176
|
+
# a[1] = true
|
177
|
+
# a[2] = true
|
178
|
+
# a[100] = true
|
179
|
+
#
|
180
|
+
# q = ~a
|
181
|
+
#
|
182
|
+
# result << q
|
183
|
+
# result.bitcount.should == 96
|
184
|
+
# end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "#operator ^" do
|
188
|
+
it "returns a result query that can be materialized" do
|
189
|
+
a[0] = true
|
190
|
+
a[1] = true
|
191
|
+
a[2] = true
|
192
|
+
a[100] = true
|
193
|
+
|
194
|
+
b[0] = true
|
195
|
+
b[100] = true
|
196
|
+
b[110] = true
|
197
|
+
|
198
|
+
q = a ^ b
|
199
|
+
|
200
|
+
result << q
|
201
|
+
result.bitcount.should == 3
|
202
|
+
end
|
203
|
+
|
204
|
+
it "allows operator nesting" do
|
205
|
+
a[0] = true
|
206
|
+
a[1] = true
|
207
|
+
a[2] = true
|
208
|
+
a[100] = true
|
209
|
+
a[110] = true
|
210
|
+
|
211
|
+
b[0] = true
|
212
|
+
b[100] = true
|
213
|
+
|
214
|
+
c[0] = true
|
215
|
+
|
216
|
+
q = a ^ (b ^ c)
|
217
|
+
|
218
|
+
result << q
|
219
|
+
result.bitcount.should == 4
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe "#delete!" do
|
224
|
+
it "removes all bitmap's keys" do
|
225
|
+
a[0] = true
|
226
|
+
a[10_000] = true
|
227
|
+
a.delete!
|
228
|
+
redis.keys("rsb:*").should be_empty
|
229
|
+
end
|
230
|
+
|
231
|
+
it "effectively sets all bitmap's keys to zero" do
|
232
|
+
a[0] = true
|
233
|
+
a[10_000] = true
|
234
|
+
a.delete!
|
235
|
+
a[0].should be_false
|
236
|
+
a[10_000].should be_false
|
237
|
+
b[0] = true
|
238
|
+
result << (a & b)
|
239
|
+
result.bitcount.should == 0
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
describe "#<<" do
|
244
|
+
before do
|
245
|
+
a[0] = true
|
246
|
+
a[1] = true
|
247
|
+
a[2] = true
|
248
|
+
a[3] = true
|
249
|
+
|
250
|
+
b[0] = true
|
251
|
+
b[1] = true
|
252
|
+
b[2] = true
|
253
|
+
|
254
|
+
c[0] = true
|
255
|
+
c[1] = true
|
256
|
+
end
|
257
|
+
|
258
|
+
after do
|
259
|
+
@temp.delete! if @temp
|
260
|
+
end
|
261
|
+
|
262
|
+
it "materializes an arbitrarily-complicated expression" do
|
263
|
+
result << (a & (a & b) | c & b & a)
|
264
|
+
result.bitcount.should == 3
|
265
|
+
end
|
266
|
+
|
267
|
+
it "is lazy-invoked when expression is evaluated" do
|
268
|
+
result = (a & (a & b) | c & b & a)
|
269
|
+
result.should be_a Redis::Bitops::Queries::BinaryOperator
|
270
|
+
@temp = result
|
271
|
+
result.bitcount.should == 3
|
272
|
+
end
|
273
|
+
|
274
|
+
it "takes into account modifications made to the result" do
|
275
|
+
output = (a & (a & b) | c & b & a)
|
276
|
+
output[100] = true
|
277
|
+
@temp = output
|
278
|
+
result << output
|
279
|
+
result.bitcount.should == 4
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
describe "#copy_to" do
|
284
|
+
it "overrides the target bitmap" do
|
285
|
+
# Fix expression with bits set using [] after evaluation doesn't materialize the newly set bits.
|
286
|
+
result[1000] = true
|
287
|
+
a[0] = true
|
288
|
+
a[1] = true
|
289
|
+
a.copy_to(result)
|
290
|
+
result.bitcount.should == a.bitcount
|
291
|
+
result[1000].should be_false
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
shared_examples_for "a bitmap factory method" do |creation_method, bitmap_class|
|
297
|
+
let(:redis) { Redis.new }
|
298
|
+
|
299
|
+
after do
|
300
|
+
@bitmap.delete! if @bitmap
|
301
|
+
end
|
302
|
+
|
303
|
+
it "creates a new bitmap" do
|
304
|
+
@bitmap = redis.send(creation_method, "rsb:xxx")
|
305
|
+
@bitmap.should be_a bitmap_class
|
306
|
+
end
|
307
|
+
|
308
|
+
it "doesn't add keys until the bitmap is modified" do
|
309
|
+
@bitmap = redis.send(creation_method, "rsb:xxx")
|
310
|
+
expect { @bitmap }.to_not change { redis.keys.size }
|
311
|
+
expect { @bitmap[1] = true }.to change { redis.keys.size }
|
312
|
+
end
|
313
|
+
end
|
metadata
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redis-bitops
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.2'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Martin Bilski
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: redis
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: hiredis
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: awesome_print
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: byebug
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: pry-byebug
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: Optimized operations on Redis bitmaps using built-in Ruby operators.
|
126
|
+
Supports sparse bitmaps to preserve storage.
|
127
|
+
email: gyamtso@gmail.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- MIT-LICENSE
|
133
|
+
- README.md
|
134
|
+
- lib/redis/bitops.rb
|
135
|
+
- lib/redis/bitops/bitmap.rb
|
136
|
+
- lib/redis/bitops/configuration.rb
|
137
|
+
- lib/redis/bitops/queries/binary_operator.rb
|
138
|
+
- lib/redis/bitops/queries/lazy_evaluation.rb
|
139
|
+
- lib/redis/bitops/queries/materialization_helpers.rb
|
140
|
+
- lib/redis/bitops/queries/tree_building_helpers.rb
|
141
|
+
- lib/redis/bitops/queries/unary_operator.rb
|
142
|
+
- lib/redis/bitops/sparse_bitmap.rb
|
143
|
+
- spec/redis/bitops/bitmap_spec.rb
|
144
|
+
- spec/redis/bitops/queries/binary_operator_spec.rb
|
145
|
+
- spec/redis/bitops/queries/unary_operator_spec.rb
|
146
|
+
- spec/redis/bitops/sparse_bitmap_spec.rb
|
147
|
+
- spec/spec_helper.rb
|
148
|
+
- spec/support/bitmap_examples.rb
|
149
|
+
homepage: http://github.com/bilus/redis-bitops
|
150
|
+
licenses:
|
151
|
+
- MIT
|
152
|
+
metadata: {}
|
153
|
+
post_install_message:
|
154
|
+
rdoc_options: []
|
155
|
+
require_paths:
|
156
|
+
- lib
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
requirements: []
|
168
|
+
rubyforge_project:
|
169
|
+
rubygems_version: 2.2.2
|
170
|
+
signing_key:
|
171
|
+
specification_version: 4
|
172
|
+
summary: Bitmap and sparse bitmap operations for Redis.
|
173
|
+
test_files: []
|