cassandra 0.11.0 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
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