cassandra 0.11.0 → 0.11.1

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 (67) hide show
  1. data/CHANGELOG +10 -0
  2. data/LICENSE +0 -0
  3. data/Manifest +12 -13
  4. data/README.md +344 -0
  5. data/Rakefile +52 -8
  6. data/cassandra.gemspec +5 -5
  7. data/conf/0.6/cassandra.in.sh +0 -0
  8. data/conf/0.6/log4j.properties +0 -0
  9. data/conf/0.6/schema.json +9 -0
  10. data/conf/0.6/storage-conf.xml +10 -0
  11. data/conf/0.7/cassandra.in.sh +0 -0
  12. data/conf/0.7/cassandra.yaml +0 -0
  13. data/conf/0.7/log4j-server.properties +0 -0
  14. data/conf/0.7/schema.json +9 -0
  15. data/conf/0.7/schema.txt +5 -16
  16. data/conf/0.8/cassandra.in.sh +0 -0
  17. data/conf/0.8/cassandra.yaml +1 -1
  18. data/conf/0.8/log4j-server.properties +0 -0
  19. data/conf/0.8/schema.json +19 -1
  20. data/conf/0.8/schema.txt +12 -17
  21. data/lib/cassandra.rb +3 -2
  22. data/lib/cassandra/0.6.rb +0 -0
  23. data/lib/cassandra/0.6/cassandra.rb +57 -6
  24. data/lib/cassandra/0.6/columns.rb +19 -0
  25. data/lib/cassandra/0.6/protocol.rb +2 -1
  26. data/lib/cassandra/0.7.rb +0 -0
  27. data/lib/cassandra/0.7/cassandra.rb +0 -270
  28. data/lib/cassandra/0.7/columns.rb +1 -81
  29. data/lib/cassandra/0.7/protocol.rb +0 -112
  30. data/lib/cassandra/0.8.rb +0 -0
  31. data/lib/cassandra/0.8/cassandra.rb +5 -267
  32. data/lib/cassandra/0.8/columns.rb +1 -81
  33. data/lib/cassandra/0.8/protocol.rb +9 -103
  34. data/lib/cassandra/array.rb +0 -0
  35. data/lib/cassandra/cassandra.rb +715 -92
  36. data/lib/cassandra/{0.7/column_family.rb → column_family.rb} +0 -0
  37. data/lib/cassandra/columns.rb +63 -6
  38. data/lib/cassandra/comparable.rb +0 -0
  39. data/lib/cassandra/constants.rb +0 -0
  40. data/lib/cassandra/debug.rb +0 -0
  41. data/lib/cassandra/helpers.rb +0 -0
  42. data/lib/cassandra/{0.7/keyspace.rb → keyspace.rb} +0 -0
  43. data/lib/cassandra/long.rb +0 -0
  44. data/lib/cassandra/mock.rb +45 -8
  45. data/lib/cassandra/ordered_hash.rb +0 -0
  46. data/lib/cassandra/protocol.rb +119 -0
  47. data/lib/cassandra/time.rb +0 -0
  48. data/test/cassandra_client_test.rb +0 -0
  49. data/test/cassandra_mock_test.rb +3 -0
  50. data/test/cassandra_test.rb +202 -20
  51. data/test/comparable_types_test.rb +0 -0
  52. data/test/eventmachine_test.rb +0 -0
  53. data/test/ordered_hash_test.rb +0 -0
  54. data/test/test_helper.rb +1 -1
  55. data/vendor/0.6/gen-rb/cassandra.rb +0 -0
  56. data/vendor/0.6/gen-rb/cassandra_constants.rb +0 -0
  57. data/vendor/0.6/gen-rb/cassandra_types.rb +0 -0
  58. data/vendor/0.7/gen-rb/cassandra.rb +0 -0
  59. data/vendor/0.7/gen-rb/cassandra_constants.rb +0 -0
  60. data/vendor/0.7/gen-rb/cassandra_types.rb +0 -0
  61. data/vendor/0.8/gen-rb/cassandra.rb +0 -0
  62. data/vendor/0.8/gen-rb/cassandra_constants.rb +0 -0
  63. data/vendor/0.8/gen-rb/cassandra_types.rb +0 -0
  64. metadata +27 -29
  65. data/README.rdoc +0 -99
  66. data/lib/cassandra/0.8/column_family.rb +0 -3
  67. data/lib/cassandra/0.8/keyspace.rb +0 -3
@@ -5,15 +5,15 @@ class Cassandra
5
5
  private
6
6
 
7
7
  def is_super(column_family)
8
- @is_super[column_family] ||= column_family_property(column_family, 'Type') == "Super"
8
+ @is_super[column_family] ||= column_family_property(column_family, 'column_type') == "Super"
9
9
  end
10
10
 
11
11
  def column_name_class(column_family)
12
- @column_name_class[column_family] ||= column_name_class_for_key(column_family, "CompareWith")
12
+ @column_name_class[column_family] ||= column_name_class_for_key(column_family, "comparator_type")
13
13
  end
14
14
 
15
15
  def sub_column_name_class(column_family)
16
- @sub_column_name_class[column_family] ||= column_name_class_for_key(column_family, "CompareSubcolumnsWith")
16
+ @sub_column_name_class[column_family] ||= column_name_class_for_key(column_family, "subcomparator_type")
17
17
  end
18
18
 
19
19
  def column_name_class_for_key(column_family, comparator_key)
@@ -28,10 +28,11 @@ class Cassandra
28
28
  end
29
29
 
30
30
  def column_family_property(column_family, key)
31
- unless schema[column_family]
31
+ cfdef = schema.cf_defs.find {|cfdef| cfdef.name == column_family }
32
+ unless cfdef
32
33
  raise AccessError, "Invalid column family \"#{column_family}\""
33
34
  end
34
- schema[column_family][key]
35
+ cfdef.send(key)
35
36
  end
36
37
 
37
38
  def multi_key_slices_to_hash(column_family, array, return_empty_rows = false)
@@ -72,16 +73,72 @@ class Cassandra
72
73
  def columns_to_hash_for_classes(columns, column_name_class, sub_column_name_class = nil)
73
74
  hash = OrderedHash.new
74
75
  Array(columns).each do |c|
75
- c = c.super_column || c.column if c.is_a?(CassandraThrift::ColumnOrSuperColumn)
76
+ c = c.super_column || c.column || c.counter_column if c.is_a?(CassandraThrift::ColumnOrSuperColumn)
76
77
  case c
77
78
  when CassandraThrift::SuperColumn
78
79
  hash.[]=(column_name_class.new(c.name), columns_to_hash_for_classes(c.columns, sub_column_name_class)) # Pop the class stack, and recurse
79
80
  when CassandraThrift::Column
80
81
  hash.[]=(column_name_class.new(c.name), c.value, c.timestamp)
82
+ when CassandraThrift::CounterColumn
83
+ hash.[]=(column_name_class.new(c.name), c.value, 0)
81
84
  end
82
85
  end
83
86
  hash
84
87
  end
85
88
 
89
+ def _standard_insert_mutation(column_family, column_name, value, timestamp, ttl = nil)
90
+ CassandraThrift::Mutation.new(
91
+ :column_or_supercolumn => CassandraThrift::ColumnOrSuperColumn.new(
92
+ :column => CassandraThrift::Column.new(
93
+ :name => column_name_class(column_family).new(column_name).to_s,
94
+ :value => value,
95
+ :timestamp => timestamp,
96
+ :ttl => ttl
97
+ )
98
+ )
99
+ )
100
+ end
101
+
102
+ def _super_insert_mutation(column_family, super_column_name, sub_columns, timestamp, ttl = nil)
103
+ CassandraThrift::Mutation.new(:column_or_supercolumn =>
104
+ CassandraThrift::ColumnOrSuperColumn.new(
105
+ :super_column => CassandraThrift::SuperColumn.new(
106
+ :name => column_name_class(column_family).new(super_column_name).to_s,
107
+ :columns => sub_columns.collect { |sub_column_name, sub_column_value|
108
+ CassandraThrift::Column.new(
109
+ :name => sub_column_name_class(column_family).new(sub_column_name).to_s,
110
+ :value => sub_column_value.to_s,
111
+ :timestamp => timestamp,
112
+ :ttl => ttl
113
+ )
114
+ }
115
+ )
116
+ )
117
+ )
118
+ end
119
+
120
+ # General info about a deletion object within a mutation
121
+ # timestamp - required. If this is the only param, it will cause deletion of the whole key at that TS
122
+ # supercolumn - opt. If passed, the deletes will only occur within that supercolumn (only subcolumns
123
+ # will be deleted). Otherwise the normal columns will be deleted.
124
+ # predicate - opt. Defines how to match the columns to delete. if supercolumn passed, the slice will
125
+ # be scoped to subcolumns of that supercolumn.
126
+
127
+ # Deletes a single column from the containing key/CF (and possibly supercolumn), at a given timestamp.
128
+ # Although mutations (as opposed to 'remove' calls) support deleting slices and lists of columns in one shot, this is not implemented here.
129
+ # The main reason being that the batch function takes removes, but removes don't have that capability...so we'd need to change the remove
130
+ # methods to use delete mutation calls...although that might have performance implications. We'll leave that refactoring for later.
131
+ def _delete_mutation(cf, column, subcolumn, timestamp, options={})
132
+ deletion_hash = {:timestamp => timestamp}
133
+ if is_super(cf)
134
+ deletion_hash[:super_column] = column if column
135
+ deletion_hash[:predicate] = CassandraThrift::SlicePredicate.new(:column_names => [subcolumn]) if subcolumn
136
+ else
137
+ deletion_hash[:predicate] = CassandraThrift::SlicePredicate.new(:column_names => [column]) if column
138
+ end
139
+ CassandraThrift::Mutation.new(
140
+ :deletion => CassandraThrift::Deletion.new(deletion_hash)
141
+ )
142
+ end
86
143
  end
87
144
  end
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -34,6 +34,14 @@ class Cassandra
34
34
  @data[column_family.to_sym] = OrderedHash.new
35
35
  end
36
36
 
37
+ def default_write_consistency=(value)
38
+ WRITE_DEFAULTS[:consistency] = value
39
+ end
40
+
41
+ def default_read_consistency=(value)
42
+ READ_DEFAULTS[:consistency] = value
43
+ end
44
+
37
45
  def insert(column_family, key, hash_or_array, options = {})
38
46
  if @batch
39
47
  @batch << [:insert, column_family, key, hash_or_array, options]
@@ -118,8 +126,11 @@ class Cassandra
118
126
  end
119
127
  end
120
128
 
121
- def exists?(column_family, key, column=nil)
122
- !!get(column_family, key, column)
129
+ def exists?(column_family, key, *columns_and_options)
130
+ column_family, column, sub_column, options = extract_and_validate_params_for_real(column_family, [key], columns_and_options, READ_DEFAULTS)
131
+ results = get(column_family, key, column, sub_column)
132
+
133
+ ![{}, nil].include?(results)
123
134
  end
124
135
 
125
136
  def multi_get(column_family, keys, *columns_and_options)
@@ -258,21 +269,28 @@ class Cassandra
258
269
  end
259
270
  end
260
271
 
261
- def create_idx_expr(c_name, value, op)
272
+ def create_index_expression(c_name, value, op)
262
273
  {:column_name => c_name, :value => value, :comparison => op}
263
274
  end
275
+ alias :create_idx_expr :create_index_expression
264
276
 
265
- def create_idx_clause(idx_expressions, start = "")
266
- {:start => start, :index_expressions => idx_expressions}
277
+ def create_index_clause(idx_expressions, start = "", count = 100)
278
+ {:start => start, :index_expressions => idx_expressions, :count => count, :type => :index_clause}
267
279
  end
280
+ alias :create_idx_clause :create_index_clause
268
281
 
269
282
  def get_indexed_slices(column_family, idx_clause, *columns_and_options)
270
283
  column_family, columns, _, options =
271
- extract_and_validate_params_for_real(column_family, [], columns_and_options, READ_DEFAULTS)
284
+ extract_and_validate_params_for_real(column_family, [], columns_and_options, READ_DEFAULTS.merge(:key_count => 100, :key_start => ""))
285
+
286
+ unless [Hash, OrderedHash].include?(idx_clause.class) && idx_clause[:type] == :index_clause
287
+ idx_clause = create_index_clause(idx_clause, options[:key_start], options[:key_count])
288
+ end
272
289
 
273
290
  ret = {}
274
291
  cf(column_family).each do |key, row|
275
292
  next if idx_clause[:start] != '' && key < idx_clause[:start]
293
+ next if ret.length == idx_clause[:count]
276
294
 
277
295
  matches = []
278
296
  idx_clause[:index_expressions].each do |expr|
@@ -288,6 +306,23 @@ class Cassandra
288
306
  ret
289
307
  end
290
308
 
309
+ def add(column_family, key, value, *columns_and_options)
310
+ column_family, column, sub_column, options = extract_and_validate_params_for_real(column_family, key, columns_and_options, WRITE_DEFAULTS)
311
+
312
+ if is_super(column_family)
313
+ cf(column_family)[key] ||= OrderedHash.new
314
+ cf(column_family)[key][column] ||= OrderedHash.new
315
+ cf(column_family)[key][column][sub_column] ||= 0
316
+ cf(column_family)[key][column][sub_column] += value
317
+ else
318
+ cf(column_family)[key] ||= OrderedHash.new
319
+ cf(column_family)[key][column] ||= 0
320
+ cf(column_family)[key][column] += value
321
+ end
322
+
323
+ nil
324
+ end
325
+
291
326
  def schema(load=true)
292
327
  @schema
293
328
  end
@@ -310,9 +345,11 @@ class Cassandra
310
345
  break if ret.keys.size >= key_count
311
346
  if (start_key.nil? || key >= start_key) && (finish_key.nil? || key <= finish_key)
312
347
  if columns
313
- ret[key] = columns.inject(OrderedHash.new){|hash, column_name| hash[column_name] = cf(column_family)[key][column_name]; hash;}
348
+ #ret[key] = columns.inject(OrderedHash.new){|hash, column_name| hash[column_name] = cf(column_family)[key][column_name]; hash;}
349
+ ret[key] = columns_to_hash(column_family, cf(column_family)[key].select{|k,v| columns.include?(k)})
314
350
  else
315
- ret[key] = apply_range(cf(column_family)[key], column_family, start, finish, !is_super(column_family))
351
+ #ret[key] = apply_range(cf(column_family)[key], column_family, start, finish, !is_super(column_family))
352
+ ret[key] = apply_range(columns_to_hash(column_family, cf(column_family)[key]), column_family, start, finish)
316
353
  end
317
354
  end
318
355
  end
File without changes
@@ -0,0 +1,119 @@
1
+ class Cassandra
2
+ # Inner methods for actually doing the Thrift calls
3
+ module Protocol #:nodoc:
4
+ private
5
+
6
+ def _mutate(mutation_map, consistency_level)
7
+ client.batch_mutate(mutation_map, consistency_level)
8
+ end
9
+
10
+ def _remove(key, column_path, timestamp, consistency_level)
11
+ client.remove(key, column_path, timestamp, consistency_level)
12
+ end
13
+
14
+ # FIXME: Add support for start, stop, count
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
+ # FIXME: Add support for start, stop, count
28
+ def _get_columns(column_family, key, columns, sub_columns, consistency)
29
+ result = if is_super(column_family)
30
+ if sub_columns
31
+ columns_to_hash(column_family, client.get_slice(key,
32
+ CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => columns),
33
+ CassandraThrift::SlicePredicate.new(:column_names => sub_columns),
34
+ consistency))
35
+ else
36
+ columns_to_hash(column_family, client.get_slice(key,
37
+ CassandraThrift::ColumnParent.new(:column_family => column_family),
38
+ CassandraThrift::SlicePredicate.new(:column_names => columns),
39
+ consistency))
40
+ end
41
+ else
42
+ columns_to_hash(column_family, client.get_slice(key,
43
+ CassandraThrift::ColumnParent.new(:column_family => column_family),
44
+ CassandraThrift::SlicePredicate.new(:column_names => columns),
45
+ consistency))
46
+ end
47
+
48
+ klass = column_name_class(column_family)
49
+ (sub_columns || columns).map { |name| result[klass.new(name)] }
50
+ end
51
+
52
+ def _multiget(column_family, keys, column, sub_column, count, start, finish, reversed, consistency)
53
+ # Single values; count and range parameters have no effect
54
+ if is_super(column_family) and sub_column
55
+ predicate = CassandraThrift::SlicePredicate.new(:column_names => [sub_column])
56
+ column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => column)
57
+ column_hash = multi_sub_columns_to_hash!(column_family, client.multiget_slice(keys, column_parent, predicate, consistency))
58
+
59
+ klass = sub_column_name_class(column_family)
60
+ keys.inject({}){|hash, key| hash[key] = column_hash[key][klass.new(sub_column)]; hash}
61
+ elsif !is_super(column_family) and column
62
+ predicate = CassandraThrift::SlicePredicate.new(:column_names => [column])
63
+ column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family)
64
+ column_hash = multi_columns_to_hash!(column_family, client.multiget_slice(keys, column_parent, predicate, consistency))
65
+
66
+ klass = column_name_class(column_family)
67
+ keys.inject({}){|hash, key| hash[key] = column_hash[key][klass.new(column)]; hash}
68
+
69
+ # Slices
70
+ else
71
+ predicate = CassandraThrift::SlicePredicate.new(:slice_range =>
72
+ CassandraThrift::SliceRange.new(
73
+ :reversed => reversed,
74
+ :count => count,
75
+ :start => start,
76
+ :finish => finish))
77
+
78
+ if is_super(column_family) and column
79
+ column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => column)
80
+ multi_sub_columns_to_hash!(column_family, client.multiget_slice(keys, column_parent, predicate, consistency))
81
+ else
82
+ column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family)
83
+ multi_columns_to_hash!(column_family, client.multiget_slice(keys, column_parent, predicate, consistency))
84
+ end
85
+ end
86
+ end
87
+
88
+ def _get_range(column_family, start_key, finish_key, key_count, columns, start, finish, count, consistency)
89
+ column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family)
90
+ predicate = if columns
91
+ CassandraThrift::SlicePredicate.new(:column_names => columns)
92
+ else
93
+ CassandraThrift::SlicePredicate.new(:slice_range =>
94
+ CassandraThrift::SliceRange.new(
95
+ :start => start,
96
+ :finish => finish,
97
+ :count => count))
98
+ end
99
+ range = CassandraThrift::KeyRange.new(:start_key => start_key, :end_key => finish_key, :count => key_count)
100
+ client.get_range_slices(column_parent, predicate, range, consistency)
101
+ end
102
+
103
+ # TODO: Supercolumn support
104
+ def _get_indexed_slices(column_family, index_clause, column, count, start, finish, reversed, consistency)
105
+ column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family)
106
+ if column
107
+ predicate = CassandraThrift::SlicePredicate.new(:column_names => [column])
108
+ else
109
+ predicate = CassandraThrift::SlicePredicate.new(:slice_range =>
110
+ CassandraThrift::SliceRange.new(
111
+ :reversed => reversed,
112
+ :count => count,
113
+ :start => start,
114
+ :finish => finish))
115
+ end
116
+ client.get_indexed_slices(column_parent, index_clause, predicate, consistency)
117
+ end
118
+ end
119
+ end
File without changes
File without changes
@@ -17,6 +17,9 @@ class CassandraMockTest < CassandraTest
17
17
  @blogs_long = Cassandra::Mock.new('MultiblogLong', @test_schema)
18
18
  @blogs_long.clear_keyspace!
19
19
 
20
+ @type_conversions = Cassandra::Mock.new('TypeConversions', @test_schema)
21
+ @type_conversions.clear_keyspace!
22
+
20
23
  @uuids = (0..6).map {|i| SimpleUUID::UUID.new(Time.at(2**(24+i))) }
21
24
  @longs = (0..6).map {|i| Long.new(Time.at(2**(24+i))) }
22
25
  end
@@ -4,15 +4,21 @@ class CassandraTest < Test::Unit::TestCase
4
4
  include Cassandra::Constants
5
5
 
6
6
  def setup
7
- @twitter = Cassandra.new('Twitter', "127.0.0.1:9160", :retries => 2, :exception_classes => [])
7
+ @twitter = Cassandra.new('Twitter', "127.0.0.1:9160", :retries => 2, :connect_timeout => 1, :exception_classes => [])
8
8
  @twitter.clear_keyspace!
9
9
 
10
- @blogs = Cassandra.new('Multiblog', "127.0.0.1:9160",:retries => 2, :exception_classes => [])
10
+ @blogs = Cassandra.new('Multiblog', "127.0.0.1:9160", :retries => 2, :connect_timeout => 1, :exception_classes => [])
11
11
  @blogs.clear_keyspace!
12
12
 
13
- @blogs_long = Cassandra.new('MultiblogLong', "127.0.0.1:9160",:retries => 2, :exception_classes => [])
13
+ @blogs_long = Cassandra.new('MultiblogLong', "127.0.0.1:9160", :retries => 2, :connect_timeout => 1, :exception_classes => [])
14
14
  @blogs_long.clear_keyspace!
15
15
 
16
+ @type_conversions = Cassandra.new('TypeConversions', "127.0.0.1:9160", :retries => 2, :connect_timeout => 1, :exception_classes => [])
17
+ @type_conversions.clear_keyspace!
18
+
19
+ Cassandra::WRITE_DEFAULTS[:consistency] = Cassandra::Consistency::ONE
20
+ Cassandra::READ_DEFAULTS[:consistency] = Cassandra::Consistency::ONE
21
+
16
22
  @uuids = (0..6).map {|i| SimpleUUID::UUID.new(Time.at(2**(24+i))) }
17
23
  @longs = (0..6).map {|i| Long.new(Time.at(2**(24+i))) }
18
24
  end
@@ -24,13 +30,37 @@ class CassandraTest < Test::Unit::TestCase
24
30
  end
25
31
  end
26
32
 
33
+ def test_setting_default_consistency
34
+ assert_nothing_raised do
35
+ @twitter.default_read_consistency = Cassandra::Consistency::ALL
36
+ end
37
+ assert_equal(Cassandra::READ_DEFAULTS[:consistency], Cassandra::Consistency::ALL)
38
+
39
+ assert_nothing_raised do
40
+ @twitter.default_write_consistency = Cassandra::Consistency::ALL
41
+ end
42
+ assert_equal(Cassandra::WRITE_DEFAULTS[:consistency], Cassandra::Consistency::ALL)
43
+ end
44
+
27
45
  def test_get_key
46
+
28
47
  @twitter.insert(:Users, key, {'body' => 'v', 'user' => 'v'})
29
48
  assert_equal({'body' => 'v', 'user' => 'v'}, @twitter.get(:Users, key))
30
49
  assert_equal(['body', 'user'].sort, @twitter.get(:Users, key).timestamps.keys.sort)
31
50
  assert_equal({}, @twitter.get(:Users, 'bogus'))
32
51
  end
33
52
 
53
+ def test_get_single_column_returns_single_value
54
+ @twitter.insert(:Users, key, {'body' => 'body_text', 'user' => 'user_name'})
55
+ assert_equal('body_text', @twitter.get(:Users, key, 'body'))
56
+ assert_equal('user_name', @twitter.get(:Users, key, 'user'))
57
+
58
+ @blogs.insert(:Blogs, key,
59
+ {@uuids[0] => 'I like this cat', @uuids[1] => 'Buttons is cuter', @uuids[2] => 'I disagree'})
60
+
61
+ assert_equal('I like this cat', @blogs.get(:Blogs, key, @uuids[0]))
62
+ end
63
+
34
64
  def test_get_key_preserving_order
35
65
  # In-order hash is preserved
36
66
  hash = OrderedHash['a', '', 'b', '', 'c', '', 'd', '',]
@@ -116,9 +146,25 @@ class CassandraTest < Test::Unit::TestCase
116
146
  assert_equal 5, @twitter.get(:Statuses, k, :start => "body-0", :finish => "body-4", :count => 7).length
117
147
  end
118
148
 
119
- def test_exists_with_only_key
149
+ def test_exists
120
150
  @twitter.insert(:Statuses, key, {'body' => 'v'})
121
- assert @twitter.exists?(:Statuses, key)
151
+ assert_equal true, @twitter.exists?(:Statuses, key)
152
+ assert_equal false, @twitter.exists?(:Statuses, 'bogus')
153
+
154
+ columns = {@uuids[1] => 'v1', @uuids[2] => 'v2'}
155
+ @twitter.insert(:StatusRelationships, key, {'user_timelines' => columns})
156
+
157
+ # verify return value when searching by key
158
+ assert_equal true, @twitter.exists?(:StatusRelationships, key)
159
+ assert_equal false, @twitter.exists?(:StatusRelationships, 'bogus')
160
+
161
+ # verify return value when searching by key and column
162
+ assert_equal true, @twitter.exists?(:StatusRelationships, key, 'user_timelines')
163
+ assert_equal false, @twitter.exists?(:StatusRelationships, key, 'bogus')
164
+
165
+ # verify return value when searching by key and column and subcolumn
166
+ assert_equal true, @twitter.exists?(:StatusRelationships, key, 'user_timelines', @uuids[1])
167
+ assert_equal false, @twitter.exists?(:StatusRelationships, key, 'user_timelines', @uuids[3])
122
168
  end
123
169
 
124
170
  def test_get_super_key
@@ -180,7 +226,7 @@ class CassandraTest < Test::Unit::TestCase
180
226
  assert_equal(columns.keys.sort, @twitter.get(:StatusRelationships, key, 'user_timelines').timestamps.keys.sort)
181
227
  assert_equal({}, @twitter.get(:StatusRelationships, 'bogus', 'user_timelines'))
182
228
  # FIXME Not sure if this is valid
183
- # assert_nil @twitter.exists?(:StatusRelationships, 'bogus', 'user_timelines')
229
+ assert_equal false, @twitter.exists?(:StatusRelationships, 'bogus', 'user_timelines')
184
230
  end
185
231
 
186
232
  def test_get_super_value
@@ -190,16 +236,16 @@ class CassandraTest < Test::Unit::TestCase
190
236
  assert_nil @twitter.get(:StatusRelationships, 'bogus', 'user_timelines', columns.keys.first)
191
237
  end
192
238
 
193
- def test_get_range_with_key_range
194
- skip('This test requires the use of OrderPreservingPartitioner on the cluster to work properly.')
195
- k = key
196
- @twitter.insert(:Statuses, k + '2', {'body' => '1'})
197
- @twitter.insert(:Statuses, k + '3', {'body' => '1'})
198
- @twitter.insert(:Statuses, k + '4', {'body' => '1'})
199
- @twitter.insert(:Statuses, k + '5', {'body' => '1'})
200
- @twitter.insert(:Statuses, k + '6', {'body' => '1'})
201
- assert_equal([k + '3', k + '4', k + '5'], @twitter.get_range(:Statuses, :start_key => k + '3', :finish_key => k + '5').keys)
202
- end
239
+ # def test_get_range_with_key_range
240
+ # skip('This test requires the use of OrderPreservingPartitioner on the cluster to work properly.')
241
+ # k = key
242
+ # @twitter.insert(:Statuses, k + '2', {'body' => '1'})
243
+ # @twitter.insert(:Statuses, k + '3', {'body' => '1'})
244
+ # @twitter.insert(:Statuses, k + '4', {'body' => '1'})
245
+ # @twitter.insert(:Statuses, k + '5', {'body' => '1'})
246
+ # @twitter.insert(:Statuses, k + '6', {'body' => '1'})
247
+ # assert_equal([k + '3', k + '4', k + '5'], @twitter.get_range(:Statuses, :start_key => k + '3', :finish_key => k + '5').keys)
248
+ # end
203
249
 
204
250
  def test_get_range
205
251
  # make sure that deleted rows are not included in the iteration
@@ -524,6 +570,73 @@ class CassandraTest < Test::Unit::TestCase
524
570
  assert_equal({'user' => 'user'}, @twitter.get(:Users, k))
525
571
  end
526
572
 
573
+ def test_each_key
574
+ num_users = rand(60)
575
+ num_users.times do |twit_counter|
576
+ @twitter.insert(:Users, "Twitter : #{twit_counter}", {'body' => 'v1', 'user' => 'v1'})
577
+ end
578
+ counter = 0
579
+ @twitter.each_key(:Users) do |_, _|
580
+ counter += 1
581
+ end
582
+ assert_equal num_users, counter
583
+ end
584
+
585
+ def test_each_with_column_predicate
586
+ num_users = rand(60)
587
+ num_users.times do |twit_counter|
588
+ @twitter.insert(:Users, "Twitter : #{twit_counter}", {'body' => 'v1', 'user' => 'v1'})
589
+ end
590
+ counter = 0
591
+ @twitter.each(:Users, :batch_size => 10, :start => 'body', :finish => 'body') do |key, columns|
592
+ assert_equal 1, columns.length
593
+ counter += 1
594
+ end
595
+ assert_equal num_users, counter
596
+ end
597
+
598
+ def test_each_with_super_column
599
+ num_users = rand(50)
600
+ block_name = key
601
+ num_users.times do |twit_counter|
602
+ @twitter.insert(:StatusRelationships, block_name + twit_counter.to_s, {
603
+ 'user_timelines' => {@uuids[1] => 'v1', @uuids[2] => 'v2'},
604
+ 'mentions_timelines' => {@uuids[3] => 'v3'}})
605
+ end
606
+
607
+ counter = 0
608
+ # Restrict to one super column ::
609
+ @twitter.each(:StatusRelationships, :batch_size => 10, :start => 'user_timelines', :finish => 'user_timelines') do |key, columns|
610
+ columns.each do |_, column_value|
611
+ assert_equal 2, column_value.length
612
+ end
613
+ counter += 1
614
+ end
615
+
616
+ #Both super columns
617
+ @twitter.each(:StatusRelationships, :batch_size => 10, :start => 'mentions_timelines', :finish => 'user_timelines') do |key,columns|
618
+ assert_equal 2, columns.length
619
+ counter += 1
620
+ end
621
+
622
+ assert_equal num_users*2, counter
623
+
624
+ end
625
+
626
+ def test_each_column_types
627
+ num_users = rand(60)
628
+ num_users.times do |twit_counter|
629
+ @type_conversions.insert(:UUIDColumnConversion, twit_counter.to_s, {@uuids[1] => 'v1'})
630
+ end
631
+ counter = 0
632
+ @type_conversions.each(:UUIDColumnConversion) do |_, columns|
633
+ counter += 1
634
+ columns.keys.each {|column_name| assert_equal SimpleUUID::UUID, column_name.class}
635
+ end
636
+ assert_equal num_users, counter
637
+ end
638
+
639
+
527
640
  if CASSANDRA_VERSION.to_f >= 0.7
528
641
  def test_creating_and_dropping_new_index
529
642
  @twitter.create_index('Twitter', 'Statuses', 'column_name', 'LongType')
@@ -541,14 +654,83 @@ class CassandraTest < Test::Unit::TestCase
541
654
  @twitter.create_index('Twitter', 'Statuses', 'x', 'LongType')
542
655
 
543
656
  @twitter.insert(:Statuses, 'row1', { 'x' => [0,10].pack("NN") })
544
- @twitter.insert(:Statuses, 'row2', { 'x' => [0,20].pack("NN") })
657
+
658
+ (2..10).to_a.each do |i|
659
+ @twitter.insert(:Statuses, 'row' + i.to_s, { 'x' => [0,20].pack("NN"), 'non_indexed' => [i].pack('N*') })
660
+ end
661
+
662
+ @twitter.insert(:Statuses, 'row11', { 'x' => [0,30].pack("NN") })
663
+
664
+ expressions = [{:column_name => 'x', :value => [0,20].pack("NN"), :comparison => "=="}]
665
+
666
+ # verify multiples will be returned
667
+ assert_equal 9, @twitter.get_indexed_slices(:Statuses, expressions).length
668
+
669
+ # verify that GT and LT queries perform properly
670
+ expressions = [
671
+ {:column_name => 'x', :value => [0,20].pack("NN"), :comparison => "=="},
672
+ {:column_name => 'non_indexed', :value => [5].pack("N*"), :comparison => ">"}
673
+ ]
674
+ assert_equal(5, @twitter.get_indexed_slices(:Statuses, expressions).length)
675
+ end
676
+
677
+ def test_old_get_indexed_slices
678
+ @twitter.create_index('Twitter', 'Statuses', 'x', 'LongType')
679
+
680
+ @twitter.insert(:Statuses, 'row1', { 'x' => [0,10].pack("NN") })
681
+
682
+ (2..10).to_a.each do |i|
683
+ @twitter.insert(:Statuses, 'row' + i.to_s, { 'x' => [0,20].pack("NN"), 'non_indexed' => [i].pack('N*') })
684
+ end
685
+
686
+ @twitter.insert(:Statuses, 'row11', { 'x' => [0,30].pack("NN") })
545
687
 
546
688
  idx_expr = @twitter.create_idx_expr('x', [0,20].pack("NN"), "==")
689
+
690
+ # verify count is observed
691
+ idx_clause = @twitter.create_idx_clause([idx_expr], "", 1)
692
+ assert_equal 1, @twitter.get_indexed_slices(:Statuses, idx_clause).length
693
+
694
+ # verify multiples will be returned
547
695
  idx_clause = @twitter.create_idx_clause([idx_expr])
696
+ assert_equal 9, @twitter.get_indexed_slices(:Statuses, idx_clause).length
697
+
698
+ # verify that GT and LT queries perform properly
699
+ idx_expr = [
700
+ @twitter.create_idx_expr('x', [0,20].pack("NN"), "=="),
701
+ @twitter.create_idx_expr('non_indexed', [5].pack("N*"), ">")
702
+ ]
703
+ idx_clause = @twitter.create_idx_clause(idx_expr)
704
+ assert_equal(5, @twitter.get_indexed_slices(:Statuses, idx_clause).length)
705
+ end
706
+ end
707
+
708
+ if CASSANDRA_VERSION.to_f >= 0.8
709
+ def test_adding_getting_value_in_counter
710
+ assert_nil @twitter.add(:UserCounters, 'bob', 5, 'tweet_count')
711
+ assert_equal(5, @twitter.get(:UserCounters, 'bob', 'tweet_count'))
712
+ assert_nil @twitter.get(:UserCounters, 'bogus', 'tweet_count')
713
+ end
714
+
715
+ def test_get_counter_slice
716
+ assert_nil @twitter.add(:UserCounters, 'bob', 5, 'tweet_count')
717
+ assert_equal({'tweet_count' => 5}, @twitter.get(:UserCounters, 'bob', :start => "tweet_count", :finish => "tweet_count"))
718
+ end
719
+
720
+ def test_adding_getting_value_in_multiple_counters
721
+ assert_nil @twitter.add(:UserCounters, 'bob', 5, 'tweet_count')
722
+ assert_nil @twitter.add(:UserCounters, 'bob', 7, 'follower_count')
723
+ assert_equal(5, @twitter.get(:UserCounters, 'bob', 'tweet_count'))
724
+ assert_nil @twitter.get(:UserCounters, 'bogus', 'tweet_count')
725
+ assert_equal([5, 7], @twitter.get_columns(:UserCounters, 'bob', ['tweet_count', 'follower_count']))
726
+ assert_equal([5, 7, nil], @twitter.get_columns(:UserCounters, 'bob', ['tweet_count', 'follower_count', 'bogus']))
727
+ end
548
728
 
549
- indexed_row = @twitter.get_indexed_slices(:Statuses, idx_clause)
550
- assert_equal(1, indexed_row.length)
551
- assert_equal('row2', indexed_row.keys.first)
729
+ def test_adding_getting_value_in_multiple_counters_with_super_columns
730
+ assert_nil @twitter.add(:UserCounterAggregates, 'bob', 1, 'DAU', 'today')
731
+ assert_nil @twitter.add(:UserCounterAggregates, 'bob', 2, 'DAU', 'tomorrow')
732
+ assert_equal(1, @twitter.get(:UserCounterAggregates, 'bob', 'DAU', 'today'))
733
+ assert_equal(2, @twitter.get(:UserCounterAggregates, 'bob', 'DAU', 'tomorrow'))
552
734
  end
553
735
  end
554
736