cassandra 0.2.3 → 0.4

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.
@@ -0,0 +1,39 @@
1
+
2
+ class Cassandra
3
+ # A temporally-ordered Long class for use in Cassandra column names
4
+ class Long < Comparable
5
+ ENTROPY = 2**12
6
+ MAX = 2**64
7
+
8
+ def initialize(bytes = nil)
9
+ case bytes
10
+ when String
11
+ raise TypeError, "8 bytes required" if bytes.size != 8
12
+ @bytes = bytes
13
+ when Integer
14
+ raise TypeError, "Integer must be between 0 and 2**64" if bytes < 0 or bytes > MAX
15
+ @bytes = [bytes].pack("Q")
16
+ when NilClass
17
+ # Time.stamp is 52 bytes, so we have 12 bytes of entropy left over
18
+ @bytes = [Time.stamp * ENTROPY + rand(ENTROPY)].pack("Q")
19
+ else
20
+ raise TypeError, "Can't convert from #{bytes.class}"
21
+ end
22
+ end
23
+
24
+ def to_i
25
+ @to_i ||= @bytes.unpack("Q")[0]
26
+ end
27
+
28
+ def inspect
29
+ ints = @bytes.unpack("Q")
30
+ "<Cassandra::Long##{object_id} time: #{
31
+ Time.at((ints[0] / ENTROPY) / 1_000_000).inspect
32
+ }, usecs: #{
33
+ (ints[0] / ENTROPY) % 1_000_000
34
+ }, jitter: #{
35
+ ints[0] % ENTROPY
36
+ }>"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,135 @@
1
+
2
+ class Cassandra
3
+ # Hash is ordered in Ruby 1.9!
4
+ if RUBY_VERSION >= '1.9'
5
+ OrderedHash = ::Hash
6
+ else
7
+ # Copyright (c) 2004-2009 David Heinemeier Hansson
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.
27
+
28
+ class OrderedHash < Hash
29
+ require 'enumerator'
30
+
31
+ def self.[](*array)
32
+ hash = new
33
+ array.each_slice(2) { |key, value| hash[key] = value }
34
+ hash
35
+ end
36
+
37
+ def initialize(*args, &block)
38
+ super
39
+ @keys = []
40
+ end
41
+
42
+ def initialize_copy(other)
43
+ super
44
+ # make a deep copy of keys
45
+ @keys = other.keys
46
+ end
47
+
48
+ def []=(key, value)
49
+ @keys << key if !has_key?(key)
50
+ super
51
+ end
52
+
53
+ def delete(key)
54
+ if has_key? key
55
+ index = @keys.index(key)
56
+ @keys.delete_at index
57
+ end
58
+ super
59
+ end
60
+
61
+ def delete_if
62
+ super
63
+ sync_keys!
64
+ self
65
+ end
66
+
67
+ def reject!
68
+ super
69
+ sync_keys!
70
+ self
71
+ end
72
+
73
+ def reject(&block)
74
+ dup.reject!(&block)
75
+ end
76
+
77
+ def keys
78
+ @keys.dup
79
+ end
80
+
81
+ def values
82
+ @keys.collect { |key| self[key] }
83
+ end
84
+
85
+ def to_hash
86
+ self
87
+ end
88
+
89
+ def each_key
90
+ @keys.each { |key| yield key }
91
+ end
92
+
93
+ def each_value
94
+ @keys.each { |key| yield self[key]}
95
+ end
96
+
97
+ def each
98
+ @keys.each {|key| yield [key, self[key]]}
99
+ end
100
+
101
+ alias_method :each_pair, :each
102
+
103
+ def clear
104
+ super
105
+ @keys.clear
106
+ self
107
+ end
108
+
109
+ def shift
110
+ k = @keys.first
111
+ v = delete(k)
112
+ [k, v]
113
+ end
114
+
115
+ def merge!(other_hash)
116
+ other_hash.each {|k,v| self[k] = v }
117
+ self
118
+ end
119
+
120
+ def merge(other_hash)
121
+ dup.merge!(other_hash)
122
+ end
123
+
124
+ def inspect
125
+ "#<OrderedHash #{super}>"
126
+ end
127
+
128
+ private
129
+
130
+ def sync_keys!
131
+ @keys.delete_if {|k| !has_key?(k)}
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,21 @@
1
+
2
+ module CassandraThrift
3
+ module Cassandra
4
+ class SafeClient
5
+ def initialize(client, transport)
6
+ @client = client
7
+ @transport = transport
8
+ end
9
+
10
+ def method_missing(*args)
11
+ @client.send(*args)
12
+ rescue IOError, UnavailableException
13
+ @transport.close rescue nil
14
+ @transport.open
15
+ raise if defined?(once)
16
+ once = true
17
+ retry
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,57 @@
1
+
2
+ class Cassandra
3
+ module Serialization
4
+ module String
5
+ def self.dump(object);
6
+ object.to_s
7
+ end
8
+
9
+ def self.load(object)
10
+ object
11
+ end
12
+ end
13
+
14
+ module Marshal
15
+ def self.dump(object)
16
+ ::Marshal.dump(object)
17
+ end
18
+
19
+ def self.load(object)
20
+ ::Marshal.load(object)
21
+ end
22
+ end
23
+
24
+ module JSON
25
+ def self.dump(object)
26
+ ::JSON.dump(object)
27
+ end
28
+
29
+ begin
30
+ require 'yajl/json_gem'
31
+ def self.load(object)
32
+ ::JSON.load(object)
33
+ end
34
+ rescue LoadError
35
+ require 'json/ext'
36
+ def self.load(object)
37
+ ::JSON.load("[#{object}]").first # :-(
38
+ end
39
+ end
40
+ end
41
+
42
+ module CompressedJSON
43
+ def self.dump(object)
44
+ Zlib::Deflate.deflate(JSON.dump(object))
45
+ end
46
+
47
+ def self.load(object)
48
+ JSON.load(Zlib::Inflate.inflate(object))
49
+ end
50
+ end
51
+
52
+ # module Avro
53
+ # # Someday!
54
+ # end
55
+ end
56
+ end
57
+
@@ -0,0 +1,9 @@
1
+
2
+ class << Time
3
+ def now_in_useconds
4
+ time = Time.now
5
+ time.to_i * 1_000_000 + time.usec
6
+ end
7
+ alias :stamp :now_in_useconds
8
+ end
9
+
@@ -0,0 +1,44 @@
1
+
2
+ class Cassandra
3
+ # A temporally-ordered UUID class for use in Cassandra column names
4
+ class UUID < Comparable
5
+ UINT = 2**32
6
+ LONG = 2**64
7
+ MAX = 2**128
8
+
9
+ def initialize(bytes = nil)
10
+ case bytes
11
+ when String
12
+ raise TypeError, "16 bytes required" if bytes.size != 16
13
+ @bytes = bytes
14
+ when Integer
15
+ raise TypeError, "Integer must be between 0 and 2**128" if bytes < 0 or bytes > MAX
16
+ @bytes = [bytes / LONG, bytes % LONG].pack("QQ")
17
+ when NilClass
18
+ @bytes = [Time.stamp, Process.pid, rand(UINT)].pack("QII")
19
+ else
20
+ raise TypeError, "Can't convert from #{bytes.class}"
21
+ end
22
+ end
23
+
24
+ def to_i
25
+ @to_i ||= begin
26
+ ints = @bytes.unpack("QQ")
27
+ ints[0] * 2**64 + ints[1]
28
+ end
29
+ end
30
+
31
+ def inspect
32
+ ints = @bytes.unpack("QII")
33
+ "<Cassandra::UUID##{object_id} time: #{
34
+ Time.at(ints[0] / 1_000_000).inspect
35
+ }, usecs: #{
36
+ ints[0] % 1_000_000
37
+ }, pid: #{
38
+ ints[1]
39
+ }, jitter: #{
40
+ ints[2]
41
+ }>"
42
+ end
43
+ end
44
+ end
data/quickstart.sh ADDED
@@ -0,0 +1,12 @@
1
+
2
+ if [ ! -e cassandra ]; then
3
+ rm -rf data
4
+ cd vendor
5
+ tar xjf cassandra.tar.bz2
6
+ mv cassandra ..
7
+ cd ../cassandra
8
+ ant
9
+ cd ..
10
+ fi
11
+
12
+ env CASSANDRA_INCLUDE=`pwd`/conf/cassandra.in.sh cassandra/bin/cassandra -f
@@ -0,0 +1,302 @@
1
+
2
+ require 'test/unit'
3
+ require "#{File.expand_path(File.dirname(__FILE__))}/../lib/cassandra"
4
+
5
+ begin; require 'ruby-debug'; rescue LoadError; end
6
+
7
+ class CassandraTest < Test::Unit::TestCase
8
+ include Cassandra::Constants
9
+
10
+ def setup
11
+ @twitter = Cassandra.new('Twitter', '127.0.0.1')
12
+ @twitter.clear_keyspace!
13
+ @blogs = Cassandra.new('Multiblog', '127.0.0.1')
14
+ @blogs.clear_keyspace!
15
+ end
16
+
17
+ def test_inspect
18
+ assert_nothing_raised do
19
+ @blogs.inspect
20
+ @twitter.inspect
21
+ end
22
+ end
23
+
24
+ def test_connection_reopens
25
+ assert_raises(NoMethodError) do
26
+ @twitter.insert(:Statuses, 1, {'body' => 'v'})
27
+ end
28
+ assert_nothing_raised do
29
+ @twitter.insert(:Statuses, key, {'body' => 'v'})
30
+ end
31
+ end
32
+
33
+ def test_get_key_name_sorted
34
+ @twitter.insert(:Users, key, {'body' => 'v', 'user' => 'v'})
35
+ assert_equal({'body' => 'v', 'user' => 'v'}, @twitter.get(:Users, key))
36
+ assert_equal({}, @twitter.get(:Users, 'bogus'))
37
+ end
38
+
39
+ def test_get_key_name_sorted_preserving_order
40
+ # In-order hash is preserved
41
+ hash = OrderedHash['a', nil, 'b', nil, 'c', nil, 'd', nil,]
42
+ @twitter.insert(:Users, key, hash)
43
+ assert_equal(hash.keys, @twitter.get(:Users, key).keys)
44
+
45
+ @twitter.remove(:Users, key)
46
+
47
+ # Out-of-order hash is returned sorted
48
+ hash = OrderedHash['b', nil, 'c', nil, 'd', nil, 'a', nil]
49
+ @twitter.insert(:Users, key, hash)
50
+ assert_equal(hash.keys.sort, @twitter.get(:Users, key).keys)
51
+ assert_not_equal(hash.keys, @twitter.get(:Users, key).keys)
52
+ end
53
+
54
+ def test_get_key_time_sorted
55
+ @twitter.insert(:Statuses, key, {'body' => 'v', 'user' => 'v'})
56
+ assert_equal({'body' => 'v', 'user' => 'v'}, @twitter.get(:Statuses, key))
57
+ assert_equal({}, @twitter.get(:Statuses, 'bogus'))
58
+ end
59
+
60
+ def test_get_value
61
+ @twitter.insert(:Statuses, key, {'body' => 'v'})
62
+ assert_equal 'v', @twitter.get(:Statuses, key, 'body')
63
+ assert_nil @twitter.get(:Statuses, 'bogus', 'body')
64
+
65
+ assert @twitter.exists?(:Statuses, key, 'body')
66
+ assert_nil @twitter.exists?(:Statuses, 'bogus', 'body')
67
+ end
68
+
69
+ def test_get_value_nil
70
+ @twitter.insert(:Statuses, key, {'body' => nil})
71
+ assert_nil @twitter.get(:Statuses, key, 'body')
72
+ assert @twitter.exists?(:Statuses, key, 'body')
73
+ end
74
+
75
+ def test_get_super_key
76
+ columns = {'user_timelines' => {Long.new => '4', Long.new => '5'}}
77
+ @twitter.insert(:StatusRelationships, key, columns)
78
+ assert_equal(columns, @twitter.get(:StatusRelationships, key))
79
+ assert_equal({}, @twitter.get(:StatusRelationships, 'bogus'))
80
+ end
81
+
82
+ def test_get_several_super_keys
83
+ columns = {
84
+ 'user_timelines' => {Long.new => 'v1'},
85
+ 'mentions_timelines' => {Long.new => 'v2'}}
86
+ @twitter.insert(:StatusRelationships, key, columns)
87
+
88
+ assert_equal(columns, @twitter.get(:StatusRelationships, key))
89
+ assert_equal({}, @twitter.get(:StatusRelationships, 'bogus'))
90
+ end
91
+
92
+ def test_get_super_sub_keys_with_limit
93
+ columns = {Long.new => 'v1'}
94
+ @twitter.insert(:StatusRelationships, key, {'user_timelines' => columns})
95
+ @twitter.insert(:StatusRelationships, key, {'user_timelines' => {Long.new => 'v2'}})
96
+ assert_equal(columns, @twitter.get(:StatusRelationships, key, "user_timelines", nil, 1))
97
+ end
98
+
99
+ def test_get_super_sub_keys_with_ranges
100
+ @twitter.insert(:StatusRelationships, key, {'user_timelines' => {Long.new => 'v1'}})
101
+ first_column = {Long.new => 'v2'}
102
+ @twitter.insert(:StatusRelationships, key, {'user_timelines' => first_column})
103
+ @twitter.insert(:StatusRelationships, key, {'user_timelines' => {Long.new => 'v3', Long.new => 'v4'}})
104
+ last_column = {Long.new => 'v5'}
105
+ @twitter.insert(:StatusRelationships, key, {'user_timelines' => last_column})
106
+
107
+ keys = @twitter.get(:StatusRelationships, key, "user_timelines").keys
108
+ assert_equal keys.sort, keys
109
+
110
+ assert_equal(first_column, @twitter.get(:StatusRelationships, key, "user_timelines", nil, 1, first_column.keys.first..''))
111
+ assert_equal(3, @twitter.get(:StatusRelationships, key, "user_timelines", nil, 100, last_column.keys.first..first_column.keys.first).size)
112
+ end
113
+
114
+ def test_get_super_sub_key
115
+ columns = {Long.new => 'v', Long.new => 'v'}
116
+ @twitter.insert(:StatusRelationships, key, {'user_timelines' => columns})
117
+ assert_equal(columns, @twitter.get(:StatusRelationships, key, 'user_timelines'))
118
+ assert_equal({}, @twitter.get(:StatusRelationships, 'bogus', 'user_timelines'))
119
+ end
120
+
121
+ def test_get_super_value
122
+ columns = {Long.new => 'v'}
123
+ @twitter.insert(:StatusRelationships, key, {'user_timelines' => columns})
124
+ assert_equal('v', @twitter.get(:StatusRelationships, key, 'user_timelines', columns.keys.first))
125
+ assert_nil @twitter.get(:StatusRelationships, 'bogus', 'user_timelines', columns.keys.first)
126
+ end
127
+
128
+ def test_get_key_range
129
+ @twitter.insert(:Statuses, '2', {'body' => '1'})
130
+ @twitter.insert(:Statuses, '3', {'body' => '1'})
131
+ @twitter.insert(:Statuses, '4', {'body' => '1'})
132
+ @twitter.insert(:Statuses, '5', {'body' => '1'})
133
+ @twitter.insert(:Statuses, '6', {'body' => '1'})
134
+ assert_equal(['3', '4', '5'], @twitter.get_key_range(:Statuses, '3'..'5'))
135
+ end
136
+
137
+ def test_multi_get
138
+ @twitter.insert(:Users, key + '1', {'body' => 'v1', 'user' => 'v1'})
139
+ @twitter.insert(:Users, key + '2', {'body' => 'v2', 'user' => 'v2'})
140
+ assert_equal(
141
+ OrderedHash[key + '1', {'body' => 'v1', 'user' => 'v1'}, key + '2', {'body' => 'v2', 'user' => 'v2'}, 'bogus', {}],
142
+ @twitter.multi_get(:Users, [key + '1', key + '2', 'bogus']))
143
+ assert_equal(
144
+ OrderedHash[key + '2', {'body' => 'v2', 'user' => 'v2'}, 'bogus', {}, key + '1', {'body' => 'v1', 'user' => 'v1'}],
145
+ @twitter.multi_get(:Users, [key + '2', 'bogus', key + '1']))
146
+ end
147
+
148
+ def test_remove_key
149
+ @twitter.insert(:Statuses, key, {'body' => 'v'})
150
+ assert_equal({'body' => 'v'}, @twitter.get(:Statuses, key))
151
+
152
+ @twitter.remove(:Statuses, key)
153
+ assert_equal({}, @twitter.get(:Statuses, key))
154
+ assert_equal 0, @twitter.count(:Statuses)
155
+ end
156
+
157
+ def test_remove_value
158
+ @twitter.insert(:Statuses, key, {'body' => 'v'})
159
+ @twitter.remove(:Statuses, key, 'body')
160
+ assert_nil @twitter.get(:Statuses, key, 'body')
161
+ end
162
+
163
+ def test_remove_super_key
164
+ @twitter.insert(:StatusRelationships, key, {'user_timelines' => {Long.new => 'v'}})
165
+ @twitter.remove(:StatusRelationships, key)
166
+ assert_equal({}, @twitter.get(:StatusRelationships, key))
167
+ end
168
+
169
+ def test_remove_super_sub_key
170
+ @twitter.insert(:StatusRelationships, key, {'user_timelines' => {Long.new => 'v'}})
171
+ @twitter.remove(:StatusRelationships, key, 'user_timelines')
172
+ assert_equal({}, @twitter.get(:StatusRelationships, key, 'user_timelines'))
173
+ end
174
+
175
+ def test_remove_super_value
176
+ columns = {Long.new => 'v'}
177
+ @twitter.insert(:StatusRelationships, key, {'user_timelines' => columns})
178
+ @twitter.remove(:StatusRelationships, key, 'user_timelines', columns.keys.first)
179
+ assert_nil @twitter.get(:StatusRelationships, key, 'user_timelines', columns.keys.first)
180
+ end
181
+
182
+ def test_clear_column_family
183
+ @twitter.insert(:Statuses, key + "1", {'body' => '1'})
184
+ @twitter.insert(:Statuses, key + "2", {'body' => '2'})
185
+ @twitter.insert(:Statuses, key + "3", {'body' => '3'})
186
+ @twitter.clear_column_family!(:Statuses)
187
+ assert_equal 0, @twitter.count(:Statuses)
188
+ end
189
+
190
+ def test_insert_key
191
+ @twitter.insert(:Statuses, key, {'body' => 'v', 'user' => 'v'})
192
+ assert_equal({'body' => 'v', 'user' => 'v'}, @twitter.get(:Statuses, key))
193
+ end
194
+
195
+ def test_insert_super_key
196
+ columns = {Long.new => 'v', Long.new => 'v'}
197
+ @twitter.insert(:StatusRelationships, key, {'user_timelines' => columns})
198
+ assert_equal(columns, @twitter.get(:StatusRelationships, key, 'user_timelines'))
199
+ end
200
+
201
+ def test_get_columns
202
+ @twitter.insert(:Statuses, key, {'body' => 'v1', 'user' => 'v2'})
203
+ assert_equal(['v1' , 'v2'], @twitter.get_columns(:Statuses, key, ['body', 'user']))
204
+ end
205
+
206
+ def test_get_column_values_super
207
+ user_columns, mentions_columns = {Long.new => 'v1'}, {Long.new => 'v2'}
208
+ @twitter.insert(:StatusRelationships, key,
209
+ {'user_timelines' => user_columns, 'mentions_timelines' => mentions_columns})
210
+ assert_equal [user_columns, mentions_columns],
211
+ @twitter.get_columns(:StatusRelationships, key, ['user_timelines', 'mentions_timelines'])
212
+ end
213
+
214
+ def test_multi_get_columns
215
+ @twitter.insert(:Users, key + '1', {'body' => 'v1', 'user' => 'v1'})
216
+ @twitter.insert(:Users, key + '2', {'body' => 'v2', 'user' => 'v2'})
217
+ assert_equal(
218
+ OrderedHash[key + '1', ['v1', 'v1'], key + '2', ['v2', 'v2'], 'bogus', [nil, nil]],
219
+ @twitter.multi_get_columns(:Users, [key + '1', key + '2', 'bogus'], ['body', 'user']))
220
+ assert_equal(
221
+ OrderedHash[key + '2', ['v2', 'v2'], 'bogus', [nil, nil], key + '1', ['v1', 'v1']],
222
+ @twitter.multi_get_columns(:Users, [key + '2', 'bogus', key + '1'], ['body', 'user']))
223
+ end
224
+
225
+ # Not supported
226
+ # def test_get_columns_super_sub
227
+ # @twitter.insert(:StatusRelationships, key, {
228
+ # 'user_timelines' => {Long.new => 'v1'},
229
+ # 'mentions_timelines' => {Long.new => 'v2'}})
230
+ # assert_equal ['v1', 'v2'],
231
+ # @twitter.get_columns(:StatusRelationships, key, 'user_timelines', ['1', key])
232
+ # end
233
+
234
+ def test_count_keys
235
+ @twitter.insert(:Statuses, key + "1", {'body' => '1'})
236
+ @twitter.insert(:Statuses, key + "2", {'body' => '2'})
237
+ @twitter.insert(:Statuses, key + "3", {'body' => '3'})
238
+ assert_equal 3, @twitter.count(:Statuses)
239
+ end
240
+
241
+ def test_count_columns
242
+ @twitter.insert(:Statuses, key, {'body' => 'v1', 'user' => 'v2'})
243
+ assert_equal 2, @twitter.count_columns(:Statuses, key)
244
+ end
245
+
246
+ def test_count_super_columns
247
+ @twitter.insert(:StatusRelationships, key, {
248
+ 'user_timelines' => {Long.new => 'v1'},
249
+ 'mentions_timelines' => {Long.new => 'v2'}})
250
+ assert_equal 2, @twitter.count_columns(:StatusRelationships, key)
251
+ end
252
+
253
+ def test_count_super_sub_columns
254
+ @twitter.insert(:StatusRelationships, key, {'user_timelines' => {Long.new => 'v1', Long.new => 'v2'}})
255
+ assert_equal 2, @twitter.count_columns(:StatusRelationships, key, 'user_timelines')
256
+ end
257
+
258
+ def test_multi_count_columns
259
+ @twitter.insert(:Users, key + '1', {'body' => 'v1', 'user' => 'v1'})
260
+ @twitter.insert(:Users, key + '2', {'body' => 'v2', 'user' => 'v2'})
261
+ assert_equal(
262
+ OrderedHash[key + '1', 2, key + '2', 2, 'bogus', 0],
263
+ @twitter.multi_count_columns(:Users, [key + '1', key + '2', 'bogus']))
264
+ assert_equal(
265
+ OrderedHash[key + '2', 2, 'bogus', 0, key + '1', 2],
266
+ @twitter.multi_count_columns(:Users, [key + '2', 'bogus', key + '1']))
267
+ end
268
+
269
+ def test_batch_insert
270
+ @twitter.insert(:Users, key + '1', {'body' => 'v1', 'user' => 'v1'})
271
+
272
+ @twitter.batch do
273
+ @twitter.insert(:Users, key + '2', {'body' => 'v2', 'user' => 'v2'})
274
+ @twitter.insert(:Users, key + '3', {'body' => 'bogus', 'user' => 'v3'})
275
+ @twitter.insert(:Users, key + '3', {'body' => 'v3', 'location' => 'v3'})
276
+ @twitter.insert(:Statuses, key + '3', {'body' => 'v'})
277
+
278
+ assert_equal({'body' => 'v1', 'user' => 'v1'}, @twitter.get(:Users, key + '1')) # Written
279
+ assert_equal({}, @twitter.get(:Users, key + '2')) # Not yet written
280
+ assert_equal({}, @twitter.get(:Statuses, key + '3')) # Not yet written
281
+
282
+ @twitter.remove(:Users, key + '1')
283
+ assert_equal({'body' => 'v1', 'user' => 'v1'}, @twitter.get(:Users, key + '1')) # Not yet removed
284
+
285
+ @twitter.remove(:Users, key + '4')
286
+ @twitter.insert(:Users, key + '4', {'body' => 'v4', 'user' => 'v4'})
287
+ assert_equal({}, @twitter.get(:Users, key + '4')) # Not yet written
288
+ end
289
+
290
+ assert_equal({'body' => 'v2', 'user' => 'v2'}, @twitter.get(:Users, key + '2')) # Written
291
+ assert_equal({'body' => 'v3', 'user' => 'v3', 'location' => 'v3'}, @twitter.get(:Users, key + '3')) # Written and compacted
292
+ assert_equal({'body' => 'v4', 'user' => 'v4'}, @twitter.get(:Users, key + '4')) # Written
293
+ assert_equal({'body' => 'v'}, @twitter.get(:Statuses, key + '3')) # Written
294
+ assert_equal({}, @twitter.get(:Users, key + '1')) # Removed
295
+ end
296
+
297
+ private
298
+
299
+ def key
300
+ caller.first[/`(.*?)'/, 1]
301
+ end
302
+ end