cassandra 0.2.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+
2
+ class Array
3
+ def _flatten_once
4
+ result = []
5
+ each { |el| result.concat(Array(el)) }
6
+ result
7
+ end
8
+ end
@@ -0,0 +1,315 @@
1
+
2
+ class Cassandra
3
+ include Helper
4
+ class AccessError < StandardError; end
5
+
6
+ MAX_INT = 2**31 - 1
7
+
8
+ module Consistency
9
+ include CassandraThrift::ConsistencyLevel
10
+ NONE = ZERO
11
+ WEAK = ONE
12
+ STRONG = QUORUM
13
+ PERFECT = ALL
14
+ end
15
+
16
+ attr_reader :keyspace, :host, :port, :serializer, :transport, :client, :schema
17
+
18
+ # Instantiate a new Cassandra and open the connection.
19
+ def initialize(keyspace, host = '127.0.0.1', port = 9160, serializer = Cassandra::Serialization::JSON)
20
+ @is_super = {}
21
+ @column_name_class = {}
22
+ @sub_column_name_class = {}
23
+
24
+ @keyspace = keyspace
25
+ @host = host
26
+ @port = port
27
+ @serializer = serializer
28
+
29
+ @transport = Thrift::BufferedTransport.new(Thrift::Socket.new(@host, @port))
30
+ @transport.open
31
+ @client = CassandraThrift::Cassandra::SafeClient.new(
32
+ CassandraThrift::Cassandra::Client.new(Thrift::BinaryProtocol.new(@transport)),
33
+ @transport)
34
+
35
+ keyspaces = @client.get_string_list_property("tables")
36
+ unless keyspaces.include?(@keyspace)
37
+ raise AccessError, "Keyspace #{@keyspace.inspect} not found. Available: #{keyspaces.inspect}"
38
+ end
39
+
40
+ @schema = @client.describe_table(@keyspace)
41
+ end
42
+
43
+ def inspect
44
+ "#<Cassandra:#{object_id}, @keyspace=#{keyspace.inspect}, @schema={#{
45
+ schema.map {|name, hash| ":#{name} => #{hash['type'].inspect}"}.join(', ')
46
+ }}, @host=#{host.inspect}, @port=#{port}, @serializer=#{serializer.name}>"
47
+ end
48
+
49
+ ## Write
50
+
51
+ # Insert a row for a key. Pass a flat hash for a regular column family, and
52
+ # a nested hash for a super column family.
53
+ def insert(column_family, key, hash, consistency = Consistency::WEAK, timestamp = Time.stamp)
54
+ column_family = column_family.to_s
55
+ mutation = if is_super(column_family)
56
+ CassandraThrift::BatchMutationSuper.new(:key => key, :cfmap => {column_family.to_s => hash_to_super_columns(column_family, hash, timestamp)})
57
+ else
58
+ CassandraThrift::BatchMutation.new(:key => key, :cfmap => {column_family.to_s => hash_to_columns(column_family, hash, timestamp)})
59
+ end
60
+ # FIXME Batched operations discard the consistency argument
61
+ @batch ? @batch << mutation : _insert(mutation, consistency)
62
+ end
63
+
64
+ private
65
+
66
+ def _insert(mutation, consistency = Consistency::WEAK)
67
+ case mutation
68
+ when CassandraThrift::BatchMutationSuper then @client.batch_insert_super_column(@keyspace, mutation, consistency)
69
+ when CassandraThrift::BatchMutation then @client.batch_insert(@keyspace, mutation, consistency)
70
+ end
71
+ end
72
+
73
+ public
74
+
75
+ ## Delete
76
+
77
+ # Remove the element at the column_family:key:[column]:[sub_column]
78
+ # path you request.
79
+ def remove(column_family, key, column = nil, sub_column = nil, consistency = Consistency::WEAK, timestamp = Time.stamp)
80
+ column_family = column_family.to_s
81
+ assert_column_name_classes(column_family, column, sub_column)
82
+
83
+ column = column.to_s if column
84
+ sub_column = sub_column.to_s if sub_column
85
+ args = [column_family, key, column, sub_column, consistency, timestamp]
86
+ @batch ? @batch << args : _remove(*args)
87
+ end
88
+
89
+ private
90
+
91
+ def _remove(column_family, key, column, sub_column, consistency, timestamp)
92
+ column_path_or_parent = if is_super(column_family)
93
+ CassandraThrift::ColumnPathOrParent.new(:column_family => column_family, :super_column => column, :column => sub_column)
94
+ else
95
+ CassandraThrift::ColumnPathOrParent.new(:column_family => column_family, :column => column)
96
+ end
97
+ @client.remove(@keyspace, key, column_path_or_parent, timestamp, consistency)
98
+ end
99
+
100
+ public
101
+
102
+ # Remove all rows in the column family you request.
103
+ def clear_column_family!(column_family)
104
+ # Does not support consistency argument
105
+ get_key_range(column_family).each do |key|
106
+ remove(column_family, key)
107
+ end
108
+ end
109
+
110
+ # Remove all rows in the keyspace
111
+ def clear_keyspace!
112
+ # Does not support consistency argument
113
+ @schema.keys.each do |column_family|
114
+ clear_column_family!(column_family)
115
+ end
116
+ end
117
+
118
+ ## Read
119
+
120
+ # Count the elements at the column_family:key:[super_column] path you
121
+ # request.
122
+ def count_columns(column_family, key, super_column = nil, consistency = Consistency::WEAK)
123
+ column_family = column_family.to_s
124
+ assert_column_name_classes(column_family, super_column)
125
+
126
+ super_column = super_column.to_s if super_column
127
+ @client.get_column_count(@keyspace, key,
128
+ CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => super_column),
129
+ consistency
130
+ )
131
+ end
132
+
133
+ # Multi-key version of Cassandra#count_columns.
134
+ def multi_count_columns(column_family, keys, super_column = nil, consistency = Consistency::WEAK)
135
+ OrderedHash[*keys.map do |key|
136
+ [key, count_columns(column_family, key, super_column)]
137
+ end._flatten_once]
138
+ end
139
+
140
+ # Return a list of single values for the elements at the
141
+ # column_family:key:column[s]:[sub_columns] path you request.
142
+ def get_columns(column_family, key, columns, sub_columns = nil, consistency = Consistency::WEAK)
143
+ column_family = column_family.to_s
144
+ assert_column_name_classes(column_family, columns, sub_columns)
145
+
146
+ result = if is_super(column_family)
147
+ if sub_columns
148
+ columns_to_hash(column_family, @client.get_slice_by_names(@keyspace, key,
149
+ CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => columns),
150
+ sub_columns, consistency))
151
+ else
152
+ columns_to_hash(column_family, @client.get_slice_super_by_names(@keyspace, key, column_family, columns, consistency))
153
+ end
154
+ else
155
+ columns_to_hash(column_family, @client.get_slice_by_names(@keyspace, key,
156
+ CassandraThrift::ColumnParent.new(:column_family => column_family), columns, consistency))
157
+ end
158
+ sub_columns || columns.map { |name| result[name] }
159
+ end
160
+
161
+ # Multi-key version of Cassandra#get_columns.
162
+ def multi_get_columns(column_family, keys, columns, sub_columns = nil, consistency = Consistency::WEAK)
163
+ OrderedHash[*keys.map do |key|
164
+ [key, get_columns(column_family, key, columns, sub_columns, consistency)]
165
+ end._flatten_once]
166
+ end
167
+
168
+ # Return a hash (actually, a Cassandra::OrderedHash) or a single value
169
+ # representing the element at the column_family:key:[column]:[sub_column]
170
+ # path you request.
171
+ def get(column_family, key, column = nil, sub_column = nil, limit = 100, column_range = ''..'', reversed = false, consistency = Consistency::WEAK)
172
+ column_family = column_family.to_s
173
+ assert_column_name_classes(column_family, column, sub_column)
174
+ _get(column_family, key, column, sub_column, limit = 100, column_range, reversed, consistency)
175
+ rescue CassandraThrift::NotFoundException
176
+ is_super(column_family) && !sub_column ? OrderedHash.new : nil
177
+ end
178
+
179
+ # Multi-key version of Cassandra#get.
180
+ def multi_get(column_family, keys, column = nil, sub_column = nil, limit = 100, column_range = ''..'', reversed = false, consistency = Consistency::WEAK)
181
+ OrderedHash[*keys.map do |key|
182
+ [key, get(column_family, key, column, sub_column, limit, column_range, reversed, consistency)]
183
+ end._flatten_once]
184
+ end
185
+
186
+ # Return true if the column_family:key:[column]:[sub_column] path you
187
+ # request exists.
188
+ def exists?(column_family, key, column = nil, sub_column = nil, consistency = Consistency::WEAK)
189
+ column_family = column_family.to_s
190
+ assert_column_name_classes(column_family, column, sub_column)
191
+ _get(column_family, key, column, sub_column, 1, ''..'', false, consistency)
192
+ true
193
+ rescue CassandraThrift::NotFoundException
194
+ end
195
+
196
+ private
197
+
198
+ def _get(column_family, key, column = nil, sub_column = nil, limit = 100, column_range = ''..'', reversed = false, consistency = Consistency::WEAK)
199
+ column = column.to_s if column
200
+ sub_column = sub_column.to_s if sub_column
201
+ # FIXME Comparable types in a are not checked
202
+ column_range = (column_range.begin.to_s)..(column_range.end.to_s)
203
+
204
+ # You have got to be kidding
205
+ if is_super(column_family)
206
+ if sub_column
207
+ # Limit and column_range parameters have no effect
208
+ load(@client.get_column(@keyspace, key,
209
+ CassandraThrift::ColumnPath.new(:column_family => column_family, :super_column => column, :column => sub_column),
210
+ consistency).value)
211
+ elsif column
212
+ sub_columns_to_hash(column_family,
213
+ @client.get_slice(@keyspace, key,
214
+ CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => column),
215
+ column_range.begin, column_range.end, !reversed, limit, consistency))
216
+ else
217
+ columns_to_hash(column_family,
218
+ @client.get_slice_super(@keyspace, key, column_family, column_range.begin, column_range.end, !reversed, limit, consistency))
219
+ end
220
+ else
221
+ if column
222
+ # Limit and column_range parameters have no effect
223
+ load(@client.get_column(@keyspace, key,
224
+ CassandraThrift::ColumnPath.new(:column_family => column_family, :column => column),
225
+ consistency).value)
226
+ else
227
+ columns_to_hash(column_family,
228
+ @client.get_slice(@keyspace, key,
229
+ CassandraThrift::ColumnParent.new(:column_family => column_family),
230
+ column_range.begin, column_range.end, !reversed, limit, consistency))
231
+ end
232
+ end
233
+ end
234
+
235
+ public
236
+
237
+ # Return a list of keys in the column_family you request. Requires the
238
+ # table to be partitioned with OrderPreservingHash.
239
+ def get_key_range(column_family, key_range = ''..'', limit = 100, consistency = Consistency::WEAK)
240
+ column_family = column_family.to_s
241
+ @client.get_key_range(@keyspace, column_family, key_range.begin, key_range.end, limit)
242
+ end
243
+
244
+ # Count all rows in the column_family you request. Requires the table
245
+ # to be partitioned with OrderPreservingHash.
246
+ def count(column_family, key_range = ''..'', limit = MAX_INT, consistency = Consistency::WEAK)
247
+ get_key_range(column_family, key_range, limit, consistency).size
248
+ end
249
+
250
+ def batch
251
+ @batch = []
252
+ yield
253
+ compact_mutations!
254
+ dispatch_mutations!
255
+ @batch = nil
256
+ end
257
+
258
+ private
259
+
260
+ def compact_mutations!
261
+ compact_batch = []
262
+ mutations = {}
263
+
264
+ @batch << nil # Close it
265
+ @batch.each do |m|
266
+ case m
267
+ when Array, nil
268
+ # Flush compacted mutations
269
+ compact_batch.concat(mutations.values.map {|x| x.values}.flatten)
270
+ mutations = {}
271
+ # Insert delete operation
272
+ compact_batch << m
273
+ else # BatchMutation, BatchMutationSuper
274
+ # Do a nested hash merge
275
+ if mutation_class = mutations[m.class]
276
+ if mutation = mutation_class[m.key]
277
+ if columns = mutation.cfmap[m.cfmap.keys.first]
278
+ columns.concat(m.cfmap.values.first)
279
+ else
280
+ mutation.cfmap.merge!(m.cfmap)
281
+ end
282
+ else
283
+ mutation_class[m.key] = m
284
+ end
285
+ else
286
+ mutations[m.class] = {m.key => m}
287
+ end
288
+ end
289
+ end
290
+
291
+ @batch = compact_batch
292
+ end
293
+
294
+ def dispatch_mutations!
295
+ @batch.each do |args|
296
+ case args
297
+ when Array
298
+ _remove(*args)
299
+ when CassandraThrift::BatchMutationSuper, CassandraThrift::BatchMutation
300
+ _insert(*args)
301
+ end
302
+ end
303
+ end
304
+
305
+ def dump(object)
306
+ # Special-case nil as the empty byte array
307
+ return "" if object == nil
308
+ @serializer.dump(object)
309
+ end
310
+
311
+ def load(object)
312
+ return nil if object == ""
313
+ @serializer.load(object)
314
+ end
315
+ end
@@ -0,0 +1,28 @@
1
+
2
+ class Cassandra
3
+ # Abstract base class for comparable numeric column name types
4
+ class Comparable
5
+ class TypeError < ::TypeError
6
+ end
7
+
8
+ def <=>(other)
9
+ self.to_i <=> other.to_i
10
+ end
11
+
12
+ def hash
13
+ @bytes.hash
14
+ end
15
+
16
+ def eql?(other)
17
+ other.is_a?(Comparable) and @bytes == other.to_s
18
+ end
19
+
20
+ def ==(other)
21
+ self.to_i == other.to_i
22
+ end
23
+
24
+ def to_s
25
+ @bytes
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,8 @@
1
+
2
+ class Cassandra
3
+ module Constants
4
+ UUID = Cassandra::UUID
5
+ Long = Cassandra::Long
6
+ OrderedHash = Cassandra::OrderedHash
7
+ end
8
+ end
@@ -0,0 +1,85 @@
1
+ class Cassandra
2
+
3
+ # A bunch of crap, mostly related to introspecting on column types
4
+ module Helper
5
+
6
+ private
7
+
8
+ def is_super(column_family)
9
+ @is_super[column_family] ||= column_family_property(column_family, 'Type') == "Super"
10
+ end
11
+
12
+ def column_name_class(column_family)
13
+ @column_name_class[column_family] ||= column_name_class_for_key(column_family, "CompareWith")
14
+ end
15
+
16
+ def sub_column_name_class(column_family)
17
+ @sub_column_name_class[column_family] ||= column_name_class_for_key(column_family, "CompareSubcolumnsWith")
18
+ end
19
+
20
+ def column_name_class_for_key(column_family, comparator_key)
21
+ property = column_family_property(column_family, comparator_key)
22
+ property =~ /.*\.(.*?)Type/
23
+ self.class.const_get($1) # Long, UUID
24
+ rescue NameError
25
+ String # UTF8, Ascii, Bytes, anything else
26
+ rescue TypeError
27
+ nil # Called sub_column_name_class on a standard column family
28
+ end
29
+
30
+ def column_family_property(column_family, key)
31
+ @schema[column_family][key]
32
+ rescue NoMethodError
33
+ raise AccessError, "Invalid column family \"#{column_family}\""
34
+ end
35
+
36
+ def assert_column_name_classes(column_family, columns, sub_columns = nil)
37
+ {Array(columns) => column_name_class(column_family),
38
+ Array(sub_columns) => sub_column_name_class(column_family)}.each do |columns, klass|
39
+ columns.each do |column|
40
+ raise Comparable::TypeError, "Expected #{column.inspect} to be a #{klass}" if !column.is_a?(klass)
41
+ end
42
+ end
43
+ end
44
+
45
+ def columns_to_hash(column_family, columns)
46
+ columns_to_hash_for_classes(columns, column_name_class(column_family), sub_column_name_class(column_family))
47
+ end
48
+
49
+ def sub_columns_to_hash(column_family, columns)
50
+ columns_to_hash_for_classes(columns, sub_column_name_class(column_family))
51
+ end
52
+
53
+ def columns_to_hash_for_classes(columns, column_name_class, sub_column_name_class = nil)
54
+ hash = OrderedHash.new
55
+ Array(columns).each do |c|
56
+ hash[column_name_class.new(c.name)] = if c.is_a?(CassandraThrift::SuperColumn)
57
+ # Pop the class stack, and recurse
58
+ columns_to_hash_for_classes(c.columns, sub_column_name_class)
59
+ else
60
+ load(c.value)
61
+ end
62
+ end
63
+ hash
64
+ end
65
+
66
+ def hash_to_columns(column_family, hash, timestamp)
67
+ assert_column_name_classes(column_family, hash.keys)
68
+ hash_to_columns_without_assertion(column_family, hash, timestamp)
69
+ end
70
+
71
+ def hash_to_columns_without_assertion(column_family, hash, timestamp)
72
+ hash.map do |column, value|
73
+ CassandraThrift::Column.new(:name => column.to_s, :value => dump(value), :timestamp => timestamp)
74
+ end
75
+ end
76
+
77
+ def hash_to_super_columns(column_family, hash, timestamp)
78
+ assert_column_name_classes(column_family, hash.keys)
79
+ hash.map do |column, sub_hash|
80
+ assert_column_name_classes(column_family, nil, sub_hash.keys)
81
+ CassandraThrift::SuperColumn.new(:name => column.to_s, :columns => hash_to_columns_without_assertion(column_family, sub_hash, timestamp))
82
+ end
83
+ end
84
+ end
85
+ end