cassandra 0.9.1 → 0.10.0

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.
Files changed (45) hide show
  1. data/CHANGELOG +12 -0
  2. data/Manifest +31 -12
  3. data/README.rdoc +3 -2
  4. data/Rakefile +53 -23
  5. data/cassandra.gemspec +6 -8
  6. data/conf/{cassandra.in.sh → 0.6/cassandra.in.sh} +0 -0
  7. data/conf/{log4j.properties → 0.6/log4j.properties} +0 -0
  8. data/conf/0.6/schema.json +48 -0
  9. data/conf/{storage-conf.xml → 0.6/storage-conf.xml} +5 -5
  10. data/conf/0.7/cassandra.in.sh +46 -0
  11. data/conf/0.7/cassandra.yaml +336 -0
  12. data/conf/0.7/log4j-server.properties +41 -0
  13. data/conf/0.7/schema.json +48 -0
  14. data/conf/0.7/schema.txt +56 -0
  15. data/conf/0.8/cassandra.in.sh +41 -0
  16. data/conf/0.8/cassandra.yaml +61 -0
  17. data/conf/0.8/log4j-server.properties +40 -0
  18. data/conf/0.8/schema.json +48 -0
  19. data/conf/0.8/schema.txt +56 -0
  20. data/lib/cassandra.rb +1 -1
  21. data/lib/cassandra/0.6/cassandra.rb +1 -0
  22. data/lib/cassandra/0.6/columns.rb +5 -7
  23. data/lib/cassandra/0.6/protocol.rb +1 -1
  24. data/lib/cassandra/0.7/cassandra.rb +5 -5
  25. data/lib/cassandra/0.7/columns.rb +5 -6
  26. data/lib/cassandra/0.7/protocol.rb +12 -3
  27. data/lib/cassandra/0.8.rb +7 -0
  28. data/lib/cassandra/0.8/cassandra.rb +272 -0
  29. data/lib/cassandra/0.8/column_family.rb +3 -0
  30. data/lib/cassandra/0.8/columns.rb +84 -0
  31. data/lib/cassandra/0.8/keyspace.rb +3 -0
  32. data/lib/cassandra/0.8/protocol.rb +120 -0
  33. data/lib/cassandra/cassandra.rb +6 -11
  34. data/lib/cassandra/helpers.rb +1 -0
  35. data/lib/cassandra/mock.rb +107 -64
  36. data/lib/cassandra/ordered_hash.rb +1 -6
  37. data/test/cassandra_mock_test.rb +7 -27
  38. data/test/cassandra_test.rb +41 -15
  39. data/test/eventmachine_test.rb +30 -30
  40. data/test/test_helper.rb +2 -1
  41. data/vendor/0.8/gen-rb/cassandra.rb +2215 -0
  42. data/vendor/0.8/gen-rb/cassandra_constants.rb +12 -0
  43. data/vendor/0.8/gen-rb/cassandra_types.rb +814 -0
  44. metadata +50 -27
  45. data/conf/cassandra.yaml +0 -113
@@ -0,0 +1,3 @@
1
+ class Cassandra
2
+ class Keyspace < CassandraThrift::KsDef ; end
3
+ end
@@ -0,0 +1,120 @@
1
+
2
+ class Cassandra
3
+ # Inner methods for actually doing the Thrift calls
4
+ module Protocol #:nodoc:
5
+ private
6
+
7
+ def _mutate(mutation_map, consistency_level)
8
+ client.batch_mutate(mutation_map, consistency_level)
9
+ end
10
+
11
+ def _remove(key, column_path, timestamp, consistency_level)
12
+ client.remove(key, column_path, timestamp, consistency_level)
13
+ end
14
+
15
+ def _count_columns(column_family, key, super_column, consistency)
16
+ client.get_count(key,
17
+ CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => super_column),
18
+ CassandraThrift::SlicePredicate.new(:slice_range =>
19
+ CassandraThrift::SliceRange.new(
20
+ :start => '',
21
+ :finish => ''
22
+ )),
23
+ consistency
24
+ )
25
+ end
26
+
27
+ def _get_columns(column_family, key, columns, sub_columns, consistency)
28
+ result = if is_super(column_family)
29
+ if sub_columns
30
+ columns_to_hash(column_family, client.get_slice(key,
31
+ CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => columns),
32
+ CassandraThrift::SlicePredicate.new(:column_names => sub_columns),
33
+ consistency))
34
+ else
35
+ columns_to_hash(column_family, client.get_slice(key,
36
+ CassandraThrift::ColumnParent.new(:column_family => column_family),
37
+ CassandraThrift::SlicePredicate.new(:column_names => columns),
38
+ consistency))
39
+ end
40
+ else
41
+ columns_to_hash(column_family, client.get_slice(key,
42
+ CassandraThrift::ColumnParent.new(:column_family => column_family),
43
+ CassandraThrift::SlicePredicate.new(:column_names => columns),
44
+ consistency))
45
+ end
46
+
47
+ klass = column_name_class(column_family)
48
+ (sub_columns || columns).map { |name| result[klass.new(name)] }
49
+ end
50
+
51
+ def _multiget(column_family, keys, column, sub_column, count, start, finish, reversed, consistency)
52
+ # Single values; count and range parameters have no effect
53
+ if is_super(column_family) and sub_column
54
+ predicate = CassandraThrift::SlicePredicate.new(:column_names => [sub_column])
55
+ column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => column)
56
+ column_hash = multi_sub_columns_to_hash!(column_family, client.multiget_slice(keys, column_parent, predicate, consistency))
57
+
58
+ klass = sub_column_name_class(column_family)
59
+ keys.inject({}){|hash, key| hash[key] = column_hash[key][klass.new(sub_column)]; hash}
60
+ elsif !is_super(column_family) and column
61
+ predicate = CassandraThrift::SlicePredicate.new(:column_names => [column])
62
+ column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family)
63
+ column_hash = multi_columns_to_hash!(column_family, client.multiget_slice(keys, column_parent, predicate, consistency))
64
+
65
+ keys.inject({}){|hash, key| hash[key] = column_hash[key][column]; hash}
66
+
67
+ # Slices
68
+ else
69
+ predicate = CassandraThrift::SlicePredicate.new(:slice_range =>
70
+ CassandraThrift::SliceRange.new(
71
+ :reversed => reversed,
72
+ :count => count,
73
+ :start => start,
74
+ :finish => finish))
75
+
76
+ if is_super(column_family) and column
77
+ column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => column)
78
+ multi_sub_columns_to_hash!(column_family, client.multiget_slice(keys, column_parent, predicate, consistency))
79
+ else
80
+ column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family)
81
+ multi_columns_to_hash!(column_family, client.multiget_slice(keys, column_parent, predicate, consistency))
82
+ end
83
+ end
84
+ end
85
+
86
+ def _get_range(column_family, start, finish, count, consistency)
87
+ column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family)
88
+ predicate = CassandraThrift::SlicePredicate.new(:slice_range => CassandraThrift::SliceRange.new(:start => '', :finish => ''))
89
+ range = CassandraThrift::KeyRange.new(:start_key => start, :end_key => finish, :count => count)
90
+ client.get_range_slices(column_parent, predicate, range, 1)
91
+ end
92
+
93
+ def _get_range_keys(column_family, start, finish, count, consistency)
94
+ _get_range(column_family, start, finish, count, consistency).collect{|i| i.key }
95
+ end
96
+
97
+ # TODO: Supercolumn support
98
+ def _get_indexed_slices(column_family, idx_clause, column, count, start, finish, reversed, consistency)
99
+ column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family)
100
+ if column
101
+ predicate = CassandraThrift::SlicePredicate.new(:column_names => [column])
102
+ else
103
+ predicate = CassandraThrift::SlicePredicate.new(:slice_range =>
104
+ CassandraThrift::SliceRange.new(
105
+ :reversed => reversed,
106
+ :count => count,
107
+ :start => start,
108
+ :finish => finish))
109
+ end
110
+ client.get_indexed_slices(column_parent, idx_clause, predicate, consistency)
111
+ end
112
+
113
+ def each_key(column_family)
114
+ column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family.to_s)
115
+ predicate = CassandraThrift::SlicePredicate.new(:column_names => [])
116
+ range = CassandraThrift::KeyRange.new(:start_key => '', :end_key => '')
117
+ client.get_range_slices(column_parent, predicate, range, 1).each{|i| yield i.key }
118
+ end
119
+ end
120
+ end
@@ -146,9 +146,9 @@ class Cassandra
146
146
  mutation_map =
147
147
  {
148
148
  key => {
149
- column_family => [ _delete_mutation(column_family , is_super(column_family)? column : nil , sub_column , options[:timestamp]|| Time.stamp) ]
149
+ column_family => [ _delete_mutation(column_family, column, sub_column, options[:timestamp]|| Time.stamp) ]
150
150
  }
151
- }
151
+ }
152
152
  @batch << [mutation_map, options[:consistency]]
153
153
  else
154
154
  # Let's continue using the 'remove' thrift method...not sure about the implications/performance of using the mutate instead
@@ -276,7 +276,7 @@ class Cassandra
276
276
  # Roll up queued mutations, to improve atomicity (and performance).
277
277
  def compact_mutations!
278
278
  used_clevels = {} # hash that lists the consistency levels seen in the batch array. key is the clevel, value is true
279
- by_key = {}
279
+ by_key = Hash.new{|h,k | h[k] = {}}
280
280
  # @batch is an array of mutation_ops.
281
281
  # A mutation op is a 2-item array containing [mutationmap, consistency_number]
282
282
  # a mutation map is a hash, by key (string) that has a hash by CF name, containing a list of column_mutations)
@@ -291,18 +291,13 @@ class Cassandra
291
291
  # }, # [0]
292
292
  # consistency # [1]
293
293
  #]
294
- # For a remove:
295
- # [ :remove, # [0]
296
- # [key, CassThrift:ColPath, timestamp, consistency ] # [1]
297
- # ]
298
294
  mmap = mutation_op[0] # :remove OR a hash like {"key"=> {"CF"=>[mutationclass1,...] } }
299
- used_clevels[mutation_op[1]]=true #save the clevel required for this operation
295
+ used_clevels[mutation_op[1]] = true #save the clevel required for this operation
300
296
 
301
297
  mmap.keys.each do |k|
302
- by_key[k] = {} unless by_key.has_key? k #make sure the key exists
303
298
  mmap[k].keys.each do |cf| # For each CF in that key
304
- by_key[k][cf] = [] unless by_key[k][cf] != nil
305
- by_key[k][cf].concat mmap[k][cf] # Append the list of mutations for that key and CF
299
+ by_key[k][cf] ||= []
300
+ by_key[k][cf].concat(mmap[k][cf]) # Append the list of mutations for that key and CF
306
301
  end
307
302
  end
308
303
  end
@@ -17,6 +17,7 @@ class Cassandra
17
17
 
18
18
  # Ranges
19
19
  column, sub_column = args[0], args[1]
20
+ raise ArgumentError, "Invalid arguments: subcolumns specified for a non-supercolumn family" if sub_column && !is_super(column_family)
20
21
  klass, sub_klass = column_name_class(column_family), sub_column_name_class(column_family)
21
22
  range_class = column ? sub_klass : klass
22
23
 
@@ -1,5 +1,3 @@
1
- require 'nokogiri'
2
-
3
1
  class SimpleUUID::UUID
4
2
  def >=(other)
5
3
  (self <=> other) >= 0
@@ -15,11 +13,13 @@ class Cassandra
15
13
  include ::Cassandra::Helpers
16
14
  include ::Cassandra::Columns
17
15
 
18
- def initialize(keyspace, storage_xml)
16
+ def initialize(keyspace, schema)
17
+ @is_super = {}
19
18
  @keyspace = keyspace
20
19
  @column_name_class = {}
21
20
  @sub_column_name_class = {}
22
- @storage_xml = storage_xml
21
+ @indexes = {}
22
+ @schema = schema[keyspace]
23
23
  clear_keyspace!
24
24
  end
25
25
 
@@ -39,7 +39,7 @@ class Cassandra
39
39
  @batch << [:insert, column_family, key, hash_or_array, options]
40
40
  else
41
41
  raise ArgumentError if key.nil?
42
- if column_family_type(column_family) == 'Standard'
42
+ if !is_super(column_family)
43
43
  insert_standard(column_family, key, hash_or_array)
44
44
  else
45
45
  insert_super(column_family, key, hash_or_array)
@@ -77,7 +77,7 @@ class Cassandra
77
77
  def get(column_family, key, *columns_and_options)
78
78
  column_family, column, sub_column, options =
79
79
  extract_and_validate_params_for_real(column_family, [key], columns_and_options, READ_DEFAULTS)
80
- if column_family_type(column_family) == 'Standard'
80
+ if !is_super(column_family)
81
81
  get_standard(column_family, key, column, options)
82
82
  else
83
83
  get_super(column_family, key, column, sub_column, options)
@@ -85,7 +85,9 @@ class Cassandra
85
85
  end
86
86
 
87
87
  def get_standard(column_family, key, column, options)
88
- row = cf(column_family)[key] || OrderedHash.new
88
+ columns = cf(column_family)[key] || OrderedHash.new
89
+ row = columns_to_hash(column_family, columns)
90
+
89
91
  if column
90
92
  row[column]
91
93
  else
@@ -95,26 +97,24 @@ class Cassandra
95
97
  end
96
98
 
97
99
  def get_super(column_family, key, column, sub_column, options)
100
+ columns = cf(column_family)[key] || OrderedHash.new
101
+ row = columns_to_hash(column_family, columns)
102
+
98
103
  if column
99
104
  if sub_column
100
- if cf(column_family)[key] &&
101
- cf(column_family)[key][column] &&
102
- cf(column_family)[key][column][sub_column]
103
- cf(column_family)[key][column][sub_column]
105
+ if row[column] &&
106
+ row[column][sub_column]
107
+ row[column][sub_column]
104
108
  else
105
109
  nil
106
110
  end
107
111
  else
108
- row = cf(column_family)[key] && cf(column_family)[key][column] ?
109
- cf(column_family)[key][column] :
110
- OrderedHash.new
112
+ row = row[column] || OrderedHash.new
111
113
  row = apply_range(row, column_family, options[:start], options[:finish], false)
112
114
  apply_count(row, options[:count], options[:reversed])
113
115
  end
114
- elsif cf(column_family)[key]
115
- cf(column_family)[key]
116
116
  else
117
- OrderedHash.new
117
+ row
118
118
  end
119
119
  end
120
120
 
@@ -137,9 +137,9 @@ class Cassandra
137
137
  else
138
138
  if column
139
139
  if sub_column
140
- cf(column_family)[key][column].delete(sub_column)
140
+ cf(column_family)[key][column].delete(sub_column.to_s)
141
141
  else
142
- cf(column_family)[key].delete(column)
142
+ cf(column_family)[key].delete(column.to_s)
143
143
  end
144
144
  else
145
145
  cf(column_family).delete(key)
@@ -151,7 +151,6 @@ class Cassandra
151
151
  column_family, columns, sub_columns, options = extract_and_validate_params_for_real(column_family, key, columns_and_options, READ_DEFAULTS)
152
152
  d = get(column_family, key)
153
153
 
154
-
155
154
  if sub_columns
156
155
  sub_columns.collect do |sub_column|
157
156
  d[columns][sub_column]
@@ -197,16 +196,74 @@ class Cassandra
197
196
  count
198
197
  end
199
198
 
200
- def schema(load=true)
201
- if !load && !@schema
202
- []
199
+ def create_index(ks_name, cf_name, c_name, v_class)
200
+ if @indexes[ks_name] &&
201
+ @indexes[ks_name][cf_name] &&
202
+ @indexes[ks_name][cf_name][c_name]
203
+ nil
204
+
205
+ else
206
+ @indexes[ks_name] ||= {}
207
+ @indexes[ks_name][cf_name] ||= {}
208
+ @indexes[ks_name][cf_name][c_name] = true
209
+ end
210
+ end
211
+
212
+ def drop_index(ks_name, cf_name, c_name)
213
+ if @indexes[ks_name] &&
214
+ @indexes[ks_name][cf_name] &&
215
+ @indexes[ks_name][cf_name][c_name]
216
+
217
+ @indexes[ks_name][cf_name].delete(c_name)
203
218
  else
204
- @schema ||= schema_for_keyspace(@keyspace)
219
+ nil
205
220
  end
206
221
  end
207
222
 
223
+ def create_idx_expr(c_name, value, op)
224
+ {:column_name => c_name, :value => value, :comparison => op}
225
+ end
226
+
227
+ def create_idx_clause(idx_expressions, start = "")
228
+ {:start => start, :index_expressions => idx_expressions}
229
+ end
230
+
231
+ def get_indexed_slices(column_family, idx_clause, *columns_and_options)
232
+ column_family, columns, _, options =
233
+ extract_and_validate_params_for_real(column_family, [], columns_and_options, READ_DEFAULTS)
234
+
235
+ ret = {}
236
+ cf(column_family).each do |key, row|
237
+ next if idx_clause[:start] != '' && key < idx_clause[:start]
238
+
239
+ matches = []
240
+ idx_clause[:index_expressions].each do |expr|
241
+ next if row[expr[:column_name]].nil?
242
+ next unless row[expr[:column_name]].send(expr[:comparison].to_sym, expr[:value])
243
+
244
+ matches << expr
245
+ end
246
+
247
+ ret[key] = row if matches.length == idx_clause[:index_expressions].length
248
+ end
249
+
250
+ ret
251
+ end
252
+
253
+ def schema(load=true)
254
+ @schema
255
+ end
256
+
257
+ def column_family_property(column_family, key)
258
+ schema[column_family.to_s][key]
259
+ end
260
+
208
261
  private
209
262
 
263
+ def schema_for_keyspace(keyspace)
264
+ @schema
265
+ end
266
+
210
267
  def _get_range(column_family, start, finish, count)
211
268
  ret = OrderedHash.new
212
269
  start = to_compare_with_type(start, column_family)
@@ -220,30 +277,6 @@ class Cassandra
220
277
  ret
221
278
  end
222
279
 
223
- def schema_for_keyspace(keyspace)
224
- doc = read_storage_xml
225
- ret = {}
226
- doc.css("Keyspaces Keyspace[@Name='#{keyspace}']").css('ColumnFamily').each do |cf|
227
- ret[cf['Name']] = {}
228
- if cf['CompareSubcolumnsWith']
229
- ret[cf['Name']]['CompareSubcolumnsWith'] = 'org.apache.cassandra.db.marshal.' + cf['CompareSubcolumnsWith']
230
- end
231
- if cf['CompareWith']
232
- ret[cf['Name']]['CompareWith'] = 'org.apache.cassandra.db.marshal.' + cf['CompareWith']
233
- end
234
- if cf['ColumnType'] == 'Super'
235
- ret[cf['Name']]['Type'] = 'Super'
236
- else
237
- ret[cf['Name']]['Type'] = 'Standard'
238
- end
239
- end
240
- ret
241
- end
242
-
243
- def read_storage_xml
244
- @doc ||= Nokogiri::XML(open(@storage_xml))
245
- end
246
-
247
280
  def extract_and_validate_params_for_real(column_family, keys, args, options)
248
281
  column_family, columns, sub_column, options = extract_and_validate_params(column_family, keys, args, options)
249
282
  options[:start] = nil if options[:start] == ''
@@ -264,25 +297,12 @@ class Cassandra
264
297
  def to_compare_with_type(column_name, column_family, standard=true)
265
298
  return column_name if column_name.nil?
266
299
  klass = if standard
267
- schema[column_family.to_s]["CompareWith"]
300
+ column_name_class(column_family)
268
301
  else
269
- schema[column_family.to_s]["CompareSubcolumnsWith"]
302
+ sub_column_name_class(column_family)
270
303
  end
271
304
 
272
- case klass
273
- when "org.apache.cassandra.db.marshal.UTF8Type", "org.apache.cassandra.db.marshal.BytesType"
274
- column_name
275
- when "org.apache.cassandra.db.marshal.TimeUUIDType"
276
- SimpleUUID::UUID.new(column_name)
277
- when "org.apache.cassandra.db.marshal.LongType"
278
- Long.new(column_name)
279
- else
280
- raise "Unknown column family type: #{klass.inspect}"
281
- end
282
- end
283
-
284
- def column_family_type(column_family)
285
- schema[column_family.to_s]['Type']
305
+ klass.new(column_name)
286
306
  end
287
307
 
288
308
  def cf(column_family)
@@ -293,9 +313,32 @@ class Cassandra
293
313
  if new_stuff.is_a?(Array)
294
314
  new_stuff = new_stuff.inject({}){|h,k| h[k] = nil; h }
295
315
  end
316
+
317
+ new_stuff = new_stuff.to_a.inject({}){|h,k| h[k[0].to_s] = k[1]; h }
318
+
296
319
  OrderedHash[old_stuff.merge(new_stuff).sort{|a,b| a[0] <=> b[0]}]
297
320
  end
298
321
 
322
+ def columns_to_hash(column_family, columns)
323
+ column_class, sub_column_class = column_name_class(column_family), sub_column_name_class(column_family)
324
+ output = OrderedHash.new
325
+
326
+ columns.each do |column_name, value|
327
+ column = column_class.new(column_name)
328
+
329
+ if [Hash, OrderedHash].include?(value.class)
330
+ output[column] ||= OrderedHash.new
331
+ value.each do |sub_column, sub_column_value|
332
+ output[column][sub_column_class.new(sub_column)] = sub_column_value
333
+ end
334
+ else
335
+ output[column_class.new(column_name)] = value
336
+ end
337
+ end
338
+
339
+ output
340
+ end
341
+
299
342
  def apply_count(row, count, reversed=false)
300
343
  if count
301
344
  keys = row.keys.sort