cassandra-mavericks 0.21.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +15 -0
  2. data/CHANGELOG +150 -0
  3. data/Gemfile +7 -0
  4. data/LICENSE +202 -0
  5. data/Manifest +94 -0
  6. data/README.md +373 -0
  7. data/Rakefile +191 -0
  8. data/bin/cassandra_helper +16 -0
  9. data/cassandra.gemspec +29 -0
  10. data/conf/0.6/cassandra.in.sh +47 -0
  11. data/conf/0.6/log4j.properties +38 -0
  12. data/conf/0.6/schema.json +57 -0
  13. data/conf/0.6/storage-conf.xml +352 -0
  14. data/conf/0.7/cassandra.in.sh +46 -0
  15. data/conf/0.7/cassandra.yaml +336 -0
  16. data/conf/0.7/log4j-server.properties +41 -0
  17. data/conf/0.7/schema.json +57 -0
  18. data/conf/0.7/schema.txt +45 -0
  19. data/conf/0.8/cassandra.in.sh +41 -0
  20. data/conf/0.8/cassandra.yaml +61 -0
  21. data/conf/0.8/log4j-server.properties +40 -0
  22. data/conf/0.8/schema.json +72 -0
  23. data/conf/0.8/schema.txt +57 -0
  24. data/conf/1.0/cassandra.in.sh +41 -0
  25. data/conf/1.0/cassandra.yaml +415 -0
  26. data/conf/1.0/log4j-server.properties +40 -0
  27. data/conf/1.0/schema.json +72 -0
  28. data/conf/1.0/schema.txt +57 -0
  29. data/conf/1.1/cassandra.in.sh +41 -0
  30. data/conf/1.1/cassandra.yaml +567 -0
  31. data/conf/1.1/log4j-server.properties +44 -0
  32. data/conf/1.1/schema.json +72 -0
  33. data/conf/1.1/schema.txt +57 -0
  34. data/conf/1.2/cassandra.in.sh +41 -0
  35. data/conf/1.2/cassandra.yaml +643 -0
  36. data/conf/1.2/log4j-server.properties +44 -0
  37. data/conf/1.2/schema.json +72 -0
  38. data/conf/1.2/schema.txt +57 -0
  39. data/ext/cassandra_native.c +69 -0
  40. data/ext/extconf.rb +9 -0
  41. data/lib/cassandra.rb +47 -0
  42. data/lib/cassandra/0.6.rb +7 -0
  43. data/lib/cassandra/0.6/cassandra.rb +113 -0
  44. data/lib/cassandra/0.6/columns.rb +78 -0
  45. data/lib/cassandra/0.6/protocol.rb +91 -0
  46. data/lib/cassandra/0.7.rb +7 -0
  47. data/lib/cassandra/0.7/cassandra.rb +2 -0
  48. data/lib/cassandra/0.7/columns.rb +4 -0
  49. data/lib/cassandra/0.7/protocol.rb +5 -0
  50. data/lib/cassandra/0.8.rb +7 -0
  51. data/lib/cassandra/0.8/cassandra.rb +25 -0
  52. data/lib/cassandra/0.8/columns.rb +28 -0
  53. data/lib/cassandra/0.8/protocol.rb +10 -0
  54. data/lib/cassandra/1.0.rb +7 -0
  55. data/lib/cassandra/1.0/cassandra.rb +1 -0
  56. data/lib/cassandra/1.0/columns.rb +1 -0
  57. data/lib/cassandra/1.0/protocol.rb +1 -0
  58. data/lib/cassandra/1.1.rb +7 -0
  59. data/lib/cassandra/1.1/cassandra.rb +1 -0
  60. data/lib/cassandra/1.1/columns.rb +1 -0
  61. data/lib/cassandra/1.1/protocol.rb +1 -0
  62. data/lib/cassandra/1.2.rb +7 -0
  63. data/lib/cassandra/1.2/cassandra.rb +1 -0
  64. data/lib/cassandra/1.2/columns.rb +1 -0
  65. data/lib/cassandra/1.2/protocol.rb +1 -0
  66. data/lib/cassandra/array.rb +8 -0
  67. data/lib/cassandra/batch.rb +41 -0
  68. data/lib/cassandra/cassandra.rb +1091 -0
  69. data/lib/cassandra/column_family.rb +3 -0
  70. data/lib/cassandra/columns.rb +172 -0
  71. data/lib/cassandra/comparable.rb +28 -0
  72. data/lib/cassandra/composite.rb +137 -0
  73. data/lib/cassandra/constants.rb +11 -0
  74. data/lib/cassandra/debug.rb +9 -0
  75. data/lib/cassandra/dynamic_composite.rb +118 -0
  76. data/lib/cassandra/helpers.rb +41 -0
  77. data/lib/cassandra/keyspace.rb +3 -0
  78. data/lib/cassandra/long.rb +58 -0
  79. data/lib/cassandra/mock.rb +536 -0
  80. data/lib/cassandra/ordered_hash.rb +195 -0
  81. data/lib/cassandra/protocol.rb +137 -0
  82. data/lib/cassandra/time.rb +11 -0
  83. data/test/cassandra_client_test.rb +20 -0
  84. data/test/cassandra_mock_test.rb +128 -0
  85. data/test/cassandra_test.rb +1367 -0
  86. data/test/comparable_types_test.rb +45 -0
  87. data/test/composite_type_test.rb +86 -0
  88. data/test/eventmachine_test.rb +42 -0
  89. data/test/ordered_hash_test.rb +386 -0
  90. data/test/test_helper.rb +19 -0
  91. data/vendor/0.6/gen-rb/cassandra.rb +1481 -0
  92. data/vendor/0.6/gen-rb/cassandra_constants.rb +12 -0
  93. data/vendor/0.6/gen-rb/cassandra_types.rb +482 -0
  94. data/vendor/0.7/gen-rb/cassandra.rb +1936 -0
  95. data/vendor/0.7/gen-rb/cassandra_constants.rb +12 -0
  96. data/vendor/0.7/gen-rb/cassandra_types.rb +681 -0
  97. data/vendor/0.8/gen-rb/cassandra.rb +2215 -0
  98. data/vendor/0.8/gen-rb/cassandra_constants.rb +12 -0
  99. data/vendor/0.8/gen-rb/cassandra_types.rb +824 -0
  100. data/vendor/1.0/gen-rb/cassandra.rb +2215 -0
  101. data/vendor/1.0/gen-rb/cassandra_constants.rb +12 -0
  102. data/vendor/1.0/gen-rb/cassandra_types.rb +857 -0
  103. data/vendor/1.1/gen-rb/cassandra.rb +2571 -0
  104. data/vendor/1.1/gen-rb/cassandra_constants.rb +12 -0
  105. data/vendor/1.1/gen-rb/cassandra_types.rb +928 -0
  106. data/vendor/1.2/gen-rb/cassandra.rb +3013 -0
  107. data/vendor/1.2/gen-rb/cassandra_constants.rb +13 -0
  108. data/vendor/1.2/gen-rb/cassandra_types.rb +965 -0
  109. metadata +288 -0
@@ -0,0 +1,91 @@
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(@keyspace, mutation_map, consistency_level)
9
+ end
10
+
11
+ def _remove(key, column_path, timestamp, consistency_level)
12
+ client.remove(@keyspace, key, column_path, timestamp, consistency_level)
13
+ end
14
+
15
+ def _count_columns(column_family, key, super_column, start, stop, count, consistency)
16
+ client.get_count(@keyspace, key,
17
+ CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => super_column),
18
+ consistency
19
+ )
20
+ end
21
+
22
+ # FIXME: add support for start, stop, count functionality
23
+ def _get_columns(column_family, key, columns, sub_columns, consistency)
24
+ result = if is_super(column_family)
25
+ if sub_columns
26
+ columns_to_hash(column_family, client.get_slice(@keyspace, key,
27
+ CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => columns),
28
+ CassandraThrift::SlicePredicate.new(:column_names => sub_columns),
29
+ consistency))
30
+ else
31
+ columns_to_hash(column_family, client.get_slice(@keyspace, key,
32
+ CassandraThrift::ColumnParent.new(:column_family => column_family),
33
+ CassandraThrift::SlicePredicate.new(:column_names => columns),
34
+ consistency))
35
+ end
36
+ else
37
+ columns_to_hash(column_family, client.get_slice(@keyspace, key,
38
+ CassandraThrift::ColumnParent.new(:column_family => column_family),
39
+ CassandraThrift::SlicePredicate.new(:column_names => columns),
40
+ consistency))
41
+ end
42
+
43
+ klass = column_name_class(column_family)
44
+ (sub_columns || columns).map { |name| result[klass.new(name)] }
45
+ end
46
+
47
+ def _multiget(column_family, keys, column, sub_column, count, start, finish, reversed, consistency)
48
+ # Single values; count and range parameters have no effect
49
+ if is_super(column_family) and sub_column
50
+ column_path = CassandraThrift::ColumnPath.new(:column_family => column_family, :super_column => column, :column => sub_column)
51
+ multi_column_to_hash!(client.multiget(@keyspace, keys, column_path, consistency))
52
+ elsif !is_super(column_family) and column
53
+ column_path = CassandraThrift::ColumnPath.new(:column_family => column_family, :column => column)
54
+ multi_column_to_hash!(client.multiget(@keyspace, keys, column_path, consistency))
55
+
56
+ # Slices
57
+ else
58
+ predicate = CassandraThrift::SlicePredicate.new(:slice_range =>
59
+ CassandraThrift::SliceRange.new(
60
+ :reversed => reversed,
61
+ :count => count,
62
+ :start => start,
63
+ :finish => finish))
64
+
65
+ if is_super(column_family) and column
66
+ column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => column)
67
+ multi_sub_columns_to_hash!(column_family, client.multiget_slice(@keyspace, keys, column_parent, predicate, consistency))
68
+ else
69
+ column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family)
70
+ multi_columns_to_hash!(column_family, client.multiget_slice(@keyspace, keys, column_parent, predicate, consistency))
71
+ end
72
+ end
73
+ end
74
+
75
+ def _get_range(column_family, start_key, finish_key, key_count, columns, start, finish, count, consistency, reversed=false)
76
+ column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family)
77
+ predicate = if columns
78
+ CassandraThrift::SlicePredicate.new(:column_names => columns)
79
+ else
80
+ CassandraThrift::SlicePredicate.new(:slice_range =>
81
+ CassandraThrift::SliceRange.new(
82
+ :start => start,
83
+ :finish => finish,
84
+ :count => count,
85
+ :reversed => reversed))
86
+ end
87
+ range = CassandraThrift::KeyRange.new(:start_key => start_key, :end_key => finish_key, :count => key_count)
88
+ client.get_range_slices(@keyspace, column_parent, predicate, range, consistency)
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,7 @@
1
+ class Cassandra
2
+ def self.VERSION
3
+ "0.7"
4
+ end
5
+ end
6
+
7
+ require "#{File.expand_path(File.dirname(__FILE__))}/../cassandra"
@@ -0,0 +1,2 @@
1
+ class Cassandra
2
+ end
@@ -0,0 +1,4 @@
1
+ class Cassandra
2
+ module Columns #:nodoc:
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ class Cassandra
2
+ # Inner methods for actually doing the Thrift calls
3
+ module Protocol #:nodoc:
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class Cassandra
2
+ def self.VERSION
3
+ "0.8"
4
+ end
5
+ end
6
+
7
+ require "#{File.expand_path(File.dirname(__FILE__))}/../cassandra"
@@ -0,0 +1,25 @@
1
+ class Cassandra
2
+
3
+ ## Counters
4
+
5
+ # Add a value to the counter in cf:key:super column:column
6
+ def add(column_family, key, value, *columns_and_options)
7
+ column_family, column, sub_column, options = extract_and_validate_params(column_family, key, columns_and_options, WRITE_DEFAULTS)
8
+
9
+ mutation_map = if is_super(column_family)
10
+ {
11
+ key => {
12
+ column_family => [_super_counter_mutation(column_family, column, sub_column, value)]
13
+ }
14
+ }
15
+ else
16
+ {
17
+ key => {
18
+ column_family => [_standard_counter_mutation(column_family, column, value)]
19
+ }
20
+ }
21
+ end
22
+
23
+ @batch ? @batch << [mutation_map, options[:consistency]] : _mutate(mutation_map, options[:consistency])
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ class Cassandra
2
+ module Columns #:nodoc:
3
+ def _standard_counter_mutation(column_family, column_name, value)
4
+ CassandraThrift::Mutation.new(
5
+ :column_or_supercolumn => CassandraThrift::ColumnOrSuperColumn.new(
6
+ :counter_column => CassandraThrift::CounterColumn.new(
7
+ :name => column_name_class(column_family).new(column_name).to_s,
8
+ :value => value
9
+ )
10
+ )
11
+ )
12
+ end
13
+
14
+ def _super_counter_mutation(column_family, super_column_name, sub_column, value)
15
+ CassandraThrift::Mutation.new(:column_or_supercolumn =>
16
+ CassandraThrift::ColumnOrSuperColumn.new(
17
+ :counter_super_column => CassandraThrift::SuperColumn.new(
18
+ :name => column_name_class(column_family).new(super_column_name).to_s,
19
+ :columns => [CassandraThrift::CounterColumn.new(
20
+ :name => sub_column_name_class(column_family).new(sub_column).to_s,
21
+ :value => value
22
+ )]
23
+ )
24
+ )
25
+ )
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,10 @@
1
+ class Cassandra
2
+ # Inner methods for actually doing the Thrift calls
3
+ module Protocol #:nodoc:
4
+ private
5
+
6
+ def _remove_counter(key, column_path, consistency_level)
7
+ client.remove_counter(key, column_path, consistency_level)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ class Cassandra
2
+ def self.VERSION
3
+ "1.0"
4
+ end
5
+ end
6
+
7
+ require "#{File.expand_path(File.dirname(__FILE__))}/../cassandra"
@@ -0,0 +1 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/../0.8/cassandra"
@@ -0,0 +1 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/../0.8/columns"
@@ -0,0 +1 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/../0.8/protocol"
@@ -0,0 +1,7 @@
1
+ class Cassandra
2
+ def self.VERSION
3
+ "1.1"
4
+ end
5
+ end
6
+
7
+ require "#{File.expand_path(File.dirname(__FILE__))}/../cassandra"
@@ -0,0 +1 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/../1.0/cassandra"
@@ -0,0 +1 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/../1.0/columns"
@@ -0,0 +1 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/../1.0/protocol"
@@ -0,0 +1,7 @@
1
+ class Cassandra
2
+ def self.VERSION
3
+ "1.2"
4
+ end
5
+ end
6
+
7
+ require "#{File.expand_path(File.dirname(__FILE__))}/../cassandra"
@@ -0,0 +1 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/../1.1/cassandra"
@@ -0,0 +1 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/../1.1/columns"
@@ -0,0 +1 @@
1
+ require "#{File.expand_path(File.dirname(__FILE__))}/../1.1/protocol"
@@ -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,41 @@
1
+ class Cassandra
2
+ class Batch
3
+ include Enumerable
4
+
5
+ def initialize(cassandra, options)
6
+ @queue_size = options.delete(:queue_size) || 0
7
+ @cassandra = cassandra
8
+ @options = options
9
+ @batch_queue = []
10
+ end
11
+
12
+ ##
13
+ # Append mutation to the batch queue
14
+ # Flush the batch queue if full
15
+ #
16
+ def <<(mutation)
17
+ @batch_queue << mutation
18
+ if @queue_size > 0 and @batch_queue.length >= @queue_size
19
+ begin
20
+ @cassandra.flush_batch(@options)
21
+ ensure
22
+ @batch_queue = []
23
+ end
24
+ end
25
+ end
26
+
27
+ ##
28
+ # Implement each method (required by Enumerable)
29
+ #
30
+ def each(&block)
31
+ @batch_queue.each(&block)
32
+ end
33
+
34
+ ##
35
+ # Queue size
36
+ #
37
+ def length
38
+ @batch_queue.length
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,1091 @@
1
+ =begin rdoc
2
+ Create a new Cassandra client instance. Accepts a keyspace name, and optional host and port.
3
+
4
+ client = Cassandra.new('twitter', '127.0.0.1:9160')
5
+
6
+ If the server requires authentication, you must authenticate before make calls
7
+
8
+ client.login!('username','password')
9
+
10
+ You can then make calls to the server via the <tt>client</tt> instance.
11
+
12
+ client.insert(:UserRelationships, "5", {"user_timeline" => {SimpleUUID::UUID.new => "1"}})
13
+ client.get(:UserRelationships, "5", "user_timeline")
14
+
15
+ For read methods, valid option parameters are:
16
+
17
+ <tt>:count</tt>:: How many results to return. Defaults to 100.
18
+ <tt>:start</tt>:: Column name token at which to start iterating, inclusive. Defaults to nil, which means the first column in the collation order.
19
+ <tt>:finish</tt>:: Column name token at which to stop iterating, inclusive. Defaults to nil, which means no boundary.
20
+ <tt>:reversed</tt>:: Swap the direction of the collation order.
21
+ <tt>:consistency</tt>:: The consistency level of the request. Defaults to <tt>Cassandra::Consistency::ONE</tt> (one node must respond). Other valid options are <tt>Cassandra::Consistency::ZERO</tt>, <tt>Cassandra::Consistency::QUORUM</tt>, and <tt>Cassandra::Consistency::ALL</tt>.
22
+
23
+ Note that some read options have no relevance in some contexts.
24
+
25
+ For write methods, valid option parameters are:
26
+
27
+ <tt>:timestamp </tt>:: The transaction timestamp. Defaults to the current time in milliseconds. This is used for conflict resolution by the server; you normally never need to change it.
28
+ <tt>:consistency</tt>:: See above.
29
+
30
+ For the initial client instantiation, you may also pass in <tt>:thrift_client<tt> with a ThriftClient subclass attached. On connection, that class will be used instead of the default ThriftClient class, allowing you to add additional behavior to the connection (e.g. query logging).
31
+
32
+ =end
33
+
34
+ class Cassandra
35
+ include Columns
36
+ include Protocol
37
+ include Helpers
38
+
39
+ class AccessError < StandardError #:nodoc:
40
+ end
41
+
42
+ module Consistency
43
+ include CassandraThrift::ConsistencyLevel
44
+ end
45
+
46
+ WRITE_DEFAULTS = {
47
+ :count => 1000,
48
+ :timestamp => nil,
49
+ :consistency => Consistency::ONE,
50
+ :ttl => nil
51
+ }
52
+
53
+ READ_DEFAULTS = {
54
+ :count => 100,
55
+ :start => nil,
56
+ :finish => nil,
57
+ :reversed => false,
58
+ :consistency => Consistency::ONE
59
+ }
60
+
61
+ THRIFT_DEFAULTS = {
62
+ :transport_wrapper => Thrift::FramedTransport,
63
+ :thrift_client_class => ThriftClient
64
+ }
65
+
66
+ THRIFT_DEFAULTS[:protocol] = Thrift::BinaryProtocolAccelerated if Thrift.const_defined?(:BinaryProtocolAccelerated)
67
+
68
+ attr_reader :keyspace, :servers, :schema, :thrift_client_options, :thrift_client_class, :auth_request
69
+
70
+ def self.DEFAULT_TRANSPORT_WRAPPER
71
+ Thrift::FramedTransport
72
+ end
73
+
74
+ # Create a new Cassandra instance and open the connection.
75
+ def initialize(keyspace, servers = "127.0.0.1:9160", thrift_client_options = {})
76
+ @is_super = {}
77
+ @column_name_class = {}
78
+ @sub_column_name_class = {}
79
+ @column_name_maker = {}
80
+ @sub_column_name_maker = {}
81
+ @auto_discover_nodes = true
82
+ thrift_client_options[:transport_wrapper] ||= Cassandra.DEFAULT_TRANSPORT_WRAPPER
83
+ @thrift_client_options = THRIFT_DEFAULTS.merge(thrift_client_options)
84
+ @thrift_client_class = @thrift_client_options[:thrift_client_class]
85
+ @keyspace = keyspace
86
+ @servers = Array(servers)
87
+ end
88
+
89
+ ##
90
+ # This method will prevent us from trying to auto-discover all the
91
+ # server addresses, and only use the list of servers provided on
92
+ # initialization.
93
+
94
+ # This is primarily helpful when the cassandra cluster is communicating
95
+ # internally on a different ip address than what you are using to connect.
96
+ # A prime example of this would be when using EC2 to host a cluster.
97
+ # Typically, the cluster would be communicating over the local ip
98
+ # addresses issued by Amazon, but any clients connecting from outside EC2
99
+ # would need to use the public ip.
100
+ #
101
+ def disable_node_auto_discovery!
102
+ @auto_discover_nodes = false
103
+ end
104
+
105
+ ##
106
+ # Disconnect the current client connection.
107
+ #
108
+ def disconnect!
109
+ if @client
110
+ @client.disconnect!
111
+ @client = nil
112
+ end
113
+ end
114
+
115
+ ##
116
+ # Issues a login attempt using the username and password specified.
117
+ #
118
+ # * username
119
+ # * password
120
+ #
121
+ def login!(username, password)
122
+ request = CassandraThrift::AuthenticationRequest.new
123
+ request.credentials = {'username' => username, 'password' => password}
124
+ ret = client.login(request)
125
+
126
+ # To avoid a double login on the initial connect, we set
127
+ # @auth_request after the first successful login.
128
+ #
129
+ @auth_request = request
130
+ ret
131
+ end
132
+
133
+ def inspect
134
+ "#<Cassandra:#{object_id}, @keyspace=#{keyspace.inspect}, @schema={#{
135
+ Array(schema(false).cf_defs).map {|cfdef| ":#{cfdef.name} => #{cfdef.column_type}"}.join(', ')
136
+ }}, @servers=#{servers.inspect}>"
137
+ end
138
+
139
+ ##
140
+ # Set the keyspace to use.
141
+ #
142
+ # Please note that this only works on version 0.7.0 and higher.
143
+ def keyspace=(ks)
144
+ return false if Cassandra.VERSION.to_f < 0.7
145
+
146
+ client.set_keyspace(ks)
147
+ @schema = nil; @keyspace = ks
148
+ end
149
+
150
+ ##
151
+ # Return an array of the keyspace names available.
152
+ #
153
+ # Please note that this only works on version 0.7.0 and higher.
154
+ def keyspaces
155
+ return false if Cassandra.VERSION.to_f < 0.7
156
+
157
+ client.describe_keyspaces.to_a.collect {|ksdef| ksdef.name }
158
+ end
159
+
160
+ ##
161
+ # Return a hash of column_family definitions indexed by their
162
+ # names
163
+ def column_families
164
+ return false if Cassandra.VERSION.to_f < 0.7
165
+
166
+ schema.cf_defs.inject(Hash.new){|memo, cf_def| memo[cf_def.name] = cf_def; memo;}
167
+ end
168
+
169
+ ##
170
+ # Return a Cassandra::Keyspace object loaded with the current
171
+ # keyspaces schema.
172
+ #
173
+ # Please note that this only works on version 0.7.0 and higher.
174
+ def schema(load=true)
175
+ return false if Cassandra.VERSION.to_f < 0.7
176
+
177
+ if !load && !@schema
178
+ Cassandra::Keyspace.new
179
+ else
180
+ @schema ||= client.describe_keyspace(@keyspace)
181
+ end
182
+ end
183
+
184
+ ##
185
+ # This returns true if all servers are in agreement on the schema.
186
+ #
187
+ # Please note that this only works on version 0.7.0 and higher.
188
+ def schema_agreement?
189
+ return false if Cassandra.VERSION.to_f < 0.7
190
+
191
+ client.describe_schema_versions().length == 1
192
+ end
193
+
194
+ ##
195
+ # Lists the current cassandra.thrift version.
196
+ #
197
+ # Please note that this only works on version 0.7.0 and higher.
198
+ def version
199
+ return false if Cassandra.VERSION.to_f < 0.7
200
+
201
+ client.describe_version()
202
+ end
203
+
204
+ ##
205
+ # Returns the string name specified for the cluster.
206
+ #
207
+ # Please note that this only works on version 0.7.0 and higher.
208
+ def cluster_name
209
+ return false if Cassandra.VERSION.to_f < 0.7
210
+
211
+ @cluster_name ||= client.describe_cluster_name()
212
+ end
213
+
214
+ ##
215
+ # Returns an array of CassandraThrift::TokenRange objects indicating
216
+ # which servers make up the current ring. What their start and end
217
+ # tokens are, and their list of endpoints.
218
+ #
219
+ # Please note that this only works on version 0.7.0 and higher.
220
+ def ring
221
+ return false if Cassandra.VERSION.to_f < 0.7
222
+
223
+ client.describe_ring(@keyspace)
224
+ end
225
+
226
+ ##
227
+ # Returns a string identifying which partitioner is in use by the
228
+ # current cluster. Typically, this will be RandomPartitioner, but it
229
+ # could be OrderPreservingPartioner as well.
230
+ #
231
+ # Please note that this only works on version 0.7.0 and higher.
232
+ def partitioner
233
+ return false if Cassandra.VERSION.to_f < 0.7
234
+
235
+ client.describe_partitioner()
236
+ end
237
+
238
+ ##
239
+ # Remove all rows in the column family you request.
240
+ #
241
+ # * column_family
242
+ # * options
243
+ # * consitency
244
+ # * timestamp
245
+ #
246
+ def truncate!(column_family)
247
+ client.truncate(column_family.to_s)
248
+ end
249
+ alias clear_column_family! truncate!
250
+
251
+ ##
252
+ # Remove all column families in the keyspace.
253
+ #
254
+ # This method calls Cassandra#truncate! for each column family in the
255
+ # keyspace.
256
+ #
257
+ # Please note that this only works on version 0.7.0 and higher.
258
+ #
259
+ def clear_keyspace!
260
+ return false if Cassandra.VERSION.to_f < 0.7
261
+
262
+ schema.cf_defs.each { |cfdef| truncate!(cfdef.name) }
263
+ end
264
+
265
+ ##
266
+ # Creates a new column family from the passed in
267
+ # Cassandra::ColumnFamily instance, and returns the schema id.
268
+ #
269
+ def add_column_family(cf_def)
270
+ return false if Cassandra.VERSION.to_f < 0.7
271
+
272
+ begin
273
+ res = client.system_add_column_family(cf_def)
274
+ rescue CassandraThrift::TimedOutException => te
275
+ puts "Timed out: #{te.inspect}"
276
+ end
277
+ @schema = nil
278
+ res
279
+ end
280
+
281
+ ##
282
+ # Delete the specified column family. Return the new schema id.
283
+ #
284
+ # * column_family - The column_family name to drop.
285
+ #
286
+ def drop_column_family(column_family)
287
+ return false if Cassandra.VERSION.to_f < 0.7
288
+
289
+ begin
290
+ res = client.system_drop_column_family(column_family)
291
+ rescue CassandraThrift::TimedOutException => te
292
+ puts "Timed out: #{te.inspect}"
293
+ end
294
+ @schema = nil
295
+ res
296
+ end
297
+
298
+ ##
299
+ # Rename a column family. Returns the new schema id.
300
+ #
301
+ # * old_name - The current column_family name.
302
+ # * new_name - The desired column_family name.
303
+ #
304
+ def rename_column_family(old_name, new_name)
305
+ return false if Cassandra.VERSION.to_f != 0.7
306
+
307
+ begin
308
+ res = client.system_rename_column_family(old_name, new_name)
309
+ rescue CassandraThrift::TimedOutException => te
310
+ puts "Timed out: #{te.inspect}"
311
+ end
312
+ @schema = nil
313
+ res
314
+ end
315
+
316
+ ##
317
+ # Update the column family based on the passed in definition.
318
+ #
319
+ def update_column_family(cf_def)
320
+ return false if Cassandra.VERSION.to_f < 0.7
321
+
322
+ begin
323
+ res = client.system_update_column_family(cf_def)
324
+ rescue CassandraThrift::TimedOutException => te
325
+ puts "Timed out: #{te.inspect}"
326
+ end
327
+ @schema = nil
328
+ res
329
+ end
330
+
331
+ ##
332
+ # Add keyspace using the passed in keyspace definition.
333
+ #
334
+ # Returns the new schema id.
335
+ #
336
+ def add_keyspace(ks_def)
337
+ return false if Cassandra.VERSION.to_f < 0.7
338
+
339
+ begin
340
+ res = client.system_add_keyspace(ks_def)
341
+ rescue CassandraThrift::TimedOutException => toe
342
+ puts "Timed out: #{toe.inspect}"
343
+ rescue Thrift::TransportException => te
344
+ puts "Timed out: #{te.inspect}"
345
+ end
346
+ @keyspaces = nil
347
+ res
348
+ end
349
+
350
+ ##
351
+ # Deletes keyspace using the passed in keyspace name.
352
+ #
353
+ # Returns the new schema id.
354
+ #
355
+ def drop_keyspace(keyspace=@keyspace)
356
+ return false if Cassandra.VERSION.to_f < 0.7
357
+
358
+ begin
359
+ res = client.system_drop_keyspace(keyspace)
360
+ rescue CassandraThrift::TimedOutException => toe
361
+ puts "Timed out: #{toe.inspect}"
362
+ rescue Thrift::TransportException => te
363
+ puts "Timed out: #{te.inspect}"
364
+ end
365
+ keyspace = "system" if keyspace.eql?(@keyspace)
366
+ @keyspaces = nil
367
+ res
368
+ end
369
+
370
+ ##
371
+ # Renames keyspace.
372
+ #
373
+ # * old_name - Current keyspace name.
374
+ # * new_name - Desired keyspace name.
375
+ #
376
+ # Returns the new schema id
377
+ def rename_keyspace(old_name, new_name)
378
+ return false if Cassandra.VERSION.to_f < 0.7
379
+
380
+ begin
381
+ res = client.system_rename_keyspace(old_name, new_name)
382
+ rescue CassandraThrift::TimedOutException => toe
383
+ puts "Timed out: #{toe.inspect}"
384
+ rescue Thrift::TransportException => te
385
+ puts "Timed out: #{te.inspect}"
386
+ end
387
+ keyspace = new_name if old_name.eql?(@keyspace)
388
+ @keyspaces = nil
389
+ res
390
+ end
391
+
392
+ ##
393
+ # Update the keyspace using the passed in keyspace definition.
394
+ #
395
+ def update_keyspace(ks_def)
396
+ return false if Cassandra.VERSION.to_f < 0.7
397
+
398
+ begin
399
+ res = client.system_update_keyspace(ks_def)
400
+ rescue CassandraThrift::TimedOutException => toe
401
+ puts "Timed out: #{toe.inspect}"
402
+ rescue Thrift::TransportException => te
403
+ puts "Timed out: #{te.inspect}"
404
+ end
405
+ @keyspaces = nil
406
+ res
407
+ end
408
+ ##
409
+ # The initial default consistency is set to ONE, but you can use this method
410
+ # to override the normal default with your specified value. Use this if you
411
+ # do not want to specify a write consistency for each insert statement.
412
+ #
413
+ def default_write_consistency=(value)
414
+ WRITE_DEFAULTS[:consistency] = value
415
+ end
416
+
417
+ ##
418
+ # The initial default consistency is set to ONE, but you can use this method
419
+ # to override the normal default with your specified value. Use this if you
420
+ # do not want to specify a read consistency for each query.
421
+ #
422
+ def default_read_consistency=(value)
423
+ READ_DEFAULTS[:consistency] = value
424
+ end
425
+
426
+ ##
427
+ # This is the main method used to insert rows into cassandra. If the
428
+ # column\_family that you are inserting into is a SuperColumnFamily then
429
+ # the hash passed in should be a nested hash, otherwise it should be a
430
+ # flat hash.
431
+ #
432
+ # This method can also be called while in batch mode. If in batch mode
433
+ # then we queue up the mutations (an insert in this case) and pass them to
434
+ # cassandra in a single batch at the end of the block.
435
+ #
436
+ # * column\_family - The column\_family that you are inserting into.
437
+ # * key - The row key to insert.
438
+ # * hash - The columns or super columns to insert.
439
+ # * options - Valid options are:
440
+ # * :timestamp - Uses the current time if none specified.
441
+ # * :consistency - Uses the default write consistency if none specified.
442
+ # * :ttl - If specified this is the number of seconds after the insert that this value will be available.
443
+ #
444
+ def insert(column_family, key, hash, options = {})
445
+ column_family, _, _, options = extract_and_validate_params(column_family, key, [options], WRITE_DEFAULTS)
446
+
447
+ timestamp = options[:timestamp] || Time.stamp
448
+ mutation_map = if is_super(column_family)
449
+ {
450
+ key => {
451
+ column_family => hash.collect{|k,v| _super_insert_mutation(column_family, k, v, timestamp, options[:ttl]) }
452
+ }
453
+ }
454
+ else
455
+ {
456
+ key => {
457
+ column_family => hash.collect{|k,v| _standard_insert_mutation(column_family, k, v, timestamp, options[:ttl])}
458
+ }
459
+ }
460
+ end
461
+
462
+ @batch ? @batch << [mutation_map, options[:consistency]] : _mutate(mutation_map, options[:consistency])
463
+ end
464
+
465
+
466
+ ##
467
+ # This method is used to delete (actually marking them as deleted with a
468
+ # tombstone) rows, columns, or super columns depending on the parameters
469
+ # passed. If only a key is passed the entire row will be marked as deleted.
470
+ # If a column name is passed in that column will be deleted.
471
+ #
472
+ # This method can also be used in batch mode. If in batch mode then we
473
+ # queue up the mutations (a deletion in this case)
474
+ #
475
+ # * column\_family - The column\_family that you are inserting into.
476
+ # * key - The row key to insert.
477
+ # * columns - Either a single super_column or a list of columns.
478
+ # * sub_columns - The list of sub\_columns to select.
479
+ # * options - Valid options are:
480
+ # * :timestamp - Uses the current time if none specified.
481
+ # * :consistency - Uses the default write consistency if none specified.
482
+ #
483
+ def remove(column_family, key, *columns_and_options)
484
+ column_family, columns, sub_column, options = extract_and_validate_params(column_family, key, columns_and_options, WRITE_DEFAULTS)
485
+
486
+ if columns.is_a? Array
487
+ if sub_column
488
+ raise ArgumentError, 'remove does not support sub_columns with array of columns'
489
+ end
490
+ else
491
+ columns = [columns]
492
+ end
493
+
494
+ timestamp = options[:timestamp]|| Time.stamp
495
+
496
+ mutation_map =
497
+ {
498
+ key => {
499
+ column_family => columns.map {|column|
500
+ _delete_mutation(column_family, column, sub_column, timestamp)
501
+ }
502
+ }
503
+ }
504
+
505
+ mutation = [mutation_map, options[:consistency]]
506
+
507
+ @batch ? @batch << mutation : _mutate(*mutation)
508
+ end
509
+
510
+ ##
511
+ # Count the columns for the provided parameters.
512
+ #
513
+ # * column_family - The column_family that you are inserting into.
514
+ # * key - The row key to insert.
515
+ # * columns - Either a single super_column or a list of columns.
516
+ # * sub_columns - The list of sub_columns to select.
517
+ # * options - Valid options are:
518
+ # * :start - The column name to start from.
519
+ # * :stop - The column name to stop at.
520
+ # * :count - The maximum count of columns to return. (By default cassandra will count up to 100 columns)
521
+ # * :consistency - Uses the default read consistency if none specified.
522
+ #
523
+ def count_columns(column_family, key, *columns_and_options)
524
+ column_family, super_column, _, options =
525
+ extract_and_validate_params(column_family, key, columns_and_options, READ_DEFAULTS)
526
+ _count_columns(column_family, key, super_column, options[:start], options[:stop], options[:count], options[:consistency])
527
+ end
528
+
529
+ ##
530
+ # Multi-key version of Cassandra#count_columns. Please note that this
531
+ # queries the server for each key passed in.
532
+ #
533
+ # Supports same parameters as Cassandra#count_columns.
534
+ #
535
+ # * column_family - The column_family that you are inserting into.
536
+ # * key - The row key to insert.
537
+ # * columns - Either a single super_column or a list of columns.
538
+ # * sub_columns - The list of sub_columns to select.
539
+ # * options - Valid options are:
540
+ # * :consistency - Uses the default read consistency if none specified.
541
+ #
542
+ # FIXME: Not real multi; needs server support
543
+ def multi_count_columns(column_family, keys, *options)
544
+ OrderedHash[*keys.map { |key| [key, count_columns(column_family, key, *options)] }._flatten_once]
545
+ end
546
+
547
+ ##
548
+ # Return a hash of column value pairs for the path you request.
549
+ #
550
+ # * column_family - The column_family that you are inserting into.
551
+ # * key - The row key to insert.
552
+ # * columns - Either a single super_column or a list of columns.
553
+ # * sub_columns - The list of sub_columns to select.
554
+ # * options - Valid options are:
555
+ # * :consistency - Uses the default read consistency if none specified.
556
+ #
557
+ def get_columns(column_family, key, *columns_and_options)
558
+ column_family, columns, sub_columns, options =
559
+ extract_and_validate_params(column_family, key, columns_and_options, READ_DEFAULTS)
560
+ _get_columns(column_family, key, columns, sub_columns, options[:consistency])
561
+ end
562
+
563
+ ##
564
+ # Multi-key version of Cassandra#get_columns. Please note that this
565
+ # queries the server for each key passed in.
566
+ #
567
+ # Supports same parameters as Cassandra#get_columns
568
+ #
569
+ # * column_family - The column_family that you are inserting into.
570
+ # * key - The row key to insert.
571
+ # * columns - Either a single super_column or a list of columns.
572
+ # * sub_columns - The list of sub_columns to select.
573
+ # * options - Valid options are:
574
+ # * :consistency - Uses the default read consistency if none specified.
575
+ #
576
+ def multi_get_columns(column_family, keys, *columns_and_options)
577
+ column_family, columns, sub_columns, options =
578
+ extract_and_validate_params(column_family, keys, columns_and_options, READ_DEFAULTS)
579
+ _multi_get_columns(column_family, keys, columns, sub_columns, options[:consistency])
580
+ end
581
+
582
+ ##
583
+ # Return a hash (actually, a Cassandra::OrderedHash) or a single value
584
+ # representing the element at the column_family:key:[column]:[sub_column]
585
+ # path you request.
586
+ #
587
+ # * column_family - The column_family that you are inserting into.
588
+ # * key - The row key to insert.
589
+ # * column - Either a single super_column or single column.
590
+ # * sub_column - A single sub_column to select.
591
+ # * options - Valid options are:
592
+ # * :count - The number of columns requested to be returned.
593
+ # * :start - The starting value for selecting a range of columns.
594
+ # * :finish - The final value for selecting a range of columns.
595
+ # * :reversed - If set to true the results will be returned in
596
+ # reverse order.
597
+ # * :consistency - Uses the default read consistency if none specified.
598
+ #
599
+ def get(column_family, key, *columns_and_options)
600
+ multi_get(column_family, [key], *columns_and_options)[key]
601
+ end
602
+
603
+ ##
604
+ # Multi-key version of Cassandra#get.
605
+ #
606
+ # This method allows you to select multiple rows with a single query.
607
+ # If a key that is passed in doesn't exist an empty hash will be
608
+ # returned.
609
+ #
610
+ # Supports the same parameters as Cassandra#get.
611
+ #
612
+ # * column_family - The column_family that you are inserting into.
613
+ # * keys - An array of keys to select.
614
+ # * column - Either a single super_column or a single column.
615
+ # * sub_column - A single ub_columns to select.
616
+ # * options - Valid options are:
617
+ # * :count - The number of columns requested to be returned.
618
+ # * :start - The starting value for selecting a range of columns.
619
+ # * :finish - The final value for selecting a range of columns.
620
+ # * :reversed - If set to true the results will be returned in reverse order.
621
+ # * :consistency - Uses the default read consistency if none specified.
622
+ #
623
+ def multi_get(column_family, keys, *columns_and_options)
624
+ column_family, column, sub_column, options =
625
+ extract_and_validate_params(column_family, keys, columns_and_options, READ_DEFAULTS)
626
+
627
+ hash = _multiget(column_family, keys, column, sub_column, options[:count], options[:start], options[:finish], options[:reversed], options[:consistency])
628
+
629
+ # Restore order
630
+ ordered_hash = OrderedHash.new
631
+ keys.each { |key| ordered_hash[key] = hash[key] || (OrderedHash.new if is_super(column_family) and !sub_column) }
632
+ ordered_hash
633
+ end
634
+
635
+ ##
636
+ # Return true if the column_family:key:[column]:[sub_column] path you
637
+ # request exists.
638
+ #
639
+ # If passed in only a row key it will query for any columns (limiting
640
+ # to 1) for that row key. If a column is passed in it will query for
641
+ # that specific column/super column.
642
+ #
643
+ # This method will return true or false.
644
+ #
645
+ # * column_family - The column_family that you are inserting into.
646
+ # * key - The row key to insert.
647
+ # * columns - Either a single super_column or a list of columns.
648
+ # * sub_columns - The list of sub_columns to select.
649
+ # * options - Valid options are:
650
+ # * :consistency - Uses the default read consistency if none specified.
651
+ #
652
+ def exists?(column_family, key, *columns_and_options)
653
+ column_family, column, sub_column, options =
654
+ extract_and_validate_params(column_family, key, columns_and_options, READ_DEFAULTS)
655
+ result = if column
656
+ _multiget(column_family, [key], column, sub_column, 1, '', '', false, options[:consistency])[key]
657
+ else
658
+ _multiget(column_family, [key], nil, nil, 1, '', '', false, options[:consistency])[key]
659
+ end
660
+
661
+ ![{}, nil].include?(result)
662
+ end
663
+
664
+ ##
665
+ # Return an Cassandra::OrderedHash containing the columns specified for the given
666
+ # range of keys in the column_family you request.
667
+ #
668
+ # This method is just a convenience wrapper around Cassandra#get_range_single
669
+ # and Cassandra#get_range_batch. If :key_size, :batch_size, or a block
670
+ # is passed in Cassandra#get_range_batch will be called. Otherwise
671
+ # Cassandra#get_range_single will be used.
672
+ #
673
+ # The start_key and finish_key parameters are only useful for iterating of all records
674
+ # as is done in the Cassandra#each and Cassandra#each_key methods if you are using the
675
+ # RandomPartitioner.
676
+ #
677
+ # If the table is partitioned with OrderPreservingPartitioner you may
678
+ # use the start_key and finish_key params to select all records with
679
+ # the same prefix value.
680
+ #
681
+ # If a block is passed in we will yield the row key and columns for
682
+ # each record returned.
683
+ #
684
+ # Please note that Cassandra returns a row for each row that has existed in the
685
+ # system since gc_grace_seconds. This is because deleted row keys are marked as
686
+ # deleted, but left in the system until the cluster has had resonable time to replicate the deletion.
687
+ # This function attempts to suppress deleted rows (actually any row returned without
688
+ # columns is suppressed).
689
+ #
690
+ # Please note that when enabling the :reversed option, :start and :finish should be swapped (e.g.
691
+ # reversal happens before selecting the range).
692
+ #
693
+ # * column_family - The column_family that you are inserting into.
694
+ # * options - Valid options are:
695
+ # * :start_key - The starting value for selecting a range of keys (only useful with OPP).
696
+ # * :finish_key - The final value for selecting a range of keys (only useful with OPP).
697
+ # * :key_count - The total number of keys to return from the query. (see note regarding deleted records)
698
+ # * :batch_size - The maximum number of keys to return per query. If specified will loop until :key_count is obtained or all records have been returned.
699
+ # * :columns - A list of columns to return.
700
+ # * :count - The number of columns requested to be returned.
701
+ # * :start - The starting value for selecting a range of columns.
702
+ # * :finish - The final value for selecting a range of columns.
703
+ # * :reversed - If set to true the results will be returned in reverse order.
704
+ # * :consistency - Uses the default read consistency if none specified.
705
+ #
706
+ def get_range(column_family, options = {}, &blk)
707
+ if block_given? || options[:key_count] || options[:batch_size]
708
+ get_range_batch(column_family, options, &blk)
709
+ else
710
+ get_range_single(column_family, options, &blk)
711
+ end
712
+ end
713
+
714
+ ##
715
+ # Return an Cassandra::OrderedHash containing the columns specified for the given
716
+ # range of keys in the column_family you request.
717
+ #
718
+ # See Cassandra#get_range for more details.
719
+ #
720
+ def get_range_single(column_family, options = {})
721
+ return_empty_rows = options.delete(:return_empty_rows) || false
722
+
723
+ column_family, _, _, options =
724
+ extract_and_validate_params(column_family, "", [options],
725
+ READ_DEFAULTS.merge(:start_key => '',
726
+ :finish_key => '',
727
+ :key_count => 100,
728
+ :columns => nil,
729
+ :reversed => false
730
+ )
731
+ )
732
+
733
+ results = _get_range( column_family,
734
+ options[:start_key].to_s,
735
+ options[:finish_key].to_s,
736
+ options[:key_count],
737
+ options[:columns],
738
+ options[:start].to_s,
739
+ options[:finish].to_s,
740
+ options[:count],
741
+ options[:consistency],
742
+ options[:reversed] )
743
+
744
+ multi_key_slices_to_hash(column_family, results, return_empty_rows)
745
+ end
746
+
747
+ ##
748
+ # Return an Cassandra::OrderedHash containing the columns specified for the given
749
+ # range of keys in the column_family you request.
750
+ #
751
+ # If a block is passed in we will yield the row key and columns for
752
+ # each record returned and return a nil value instead of a Cassandra::OrderedHash.
753
+ #
754
+ # See Cassandra#get_range for more details.
755
+ #
756
+ def get_range_batch(column_family, options = {})
757
+ batch_size = options.delete(:batch_size) || 100
758
+ count = options.delete(:key_count)
759
+ result = (!block_given? && {}) || nil
760
+ num_results = 0
761
+
762
+ options[:start_key] ||= ''
763
+ last_key = nil
764
+
765
+ while count.nil? || count > num_results
766
+ res = get_range_single(column_family, options.merge!(:start_key => last_key || options[:start_key],
767
+ :key_count => batch_size,
768
+ :return_empty_rows => true
769
+ ))
770
+ break if res.keys.last == last_key
771
+
772
+ res.each do |key, columns|
773
+ next if last_key == key
774
+ next if num_results == count
775
+
776
+ unless columns == {}
777
+ if block_given?
778
+ yield key, columns
779
+ else
780
+ result[key] = columns
781
+ end
782
+ num_results += 1
783
+ end
784
+
785
+ last_key = key
786
+ end
787
+ end
788
+
789
+ result
790
+ end
791
+
792
+ ##
793
+ # Count all rows in the column_family you request.
794
+ #
795
+ # This method just calls Cassandra#get_range_keys and returns the
796
+ # number of records returned.
797
+ #
798
+ # See Cassandra#get_range for options.
799
+ #
800
+ def count_range(column_family, options = {})
801
+ get_range_keys(column_family, options).length
802
+ end
803
+
804
+ ##
805
+ # Return an Array containing all of the keys within a given range.
806
+ #
807
+ # This method just calls Cassandra#get_range and returns the
808
+ # row keys for the records returned.
809
+ #
810
+ # See Cassandra#get_range for options.
811
+ #
812
+ def get_range_keys(column_family, options = {})
813
+ get_range(column_family,options.merge!(:count => 1)).keys
814
+ end
815
+
816
+ ##
817
+ # Iterate through each key within the given parameters. This function can be
818
+ # used to iterate over each key in the given column family.
819
+ #
820
+ # This method just calls Cassandra#get_range and yields each row key.
821
+ #
822
+ # See Cassandra#get_range for options.
823
+ #
824
+ def each_key(column_family, options = {})
825
+ get_range_batch(column_family, options) do |key, columns|
826
+ yield key
827
+ end
828
+ end
829
+
830
+ ##
831
+ # Iterate through each row in the given column family
832
+ #
833
+ # This method just calls Cassandra#get_range and yields the key and
834
+ # columns.
835
+ #
836
+ # See Cassandra#get_range for options.
837
+ #
838
+ def each(column_family, options = {})
839
+ get_range_batch(column_family, options) do |key, columns|
840
+ yield key, columns
841
+ end
842
+ end
843
+
844
+ ##
845
+ # Open a batch operation and yield self. Inserts and deletes will be queued
846
+ # until the block closes or the queue is full(if option :queue_size is set),
847
+ # and then sent atomically to the server.
848
+ #
849
+ # Supports the :consistency option, which overrides the consistency set in
850
+ # the individual commands.
851
+ #
852
+ def batch(options = {})
853
+ @batch = Cassandra::Batch.new(self, options)
854
+
855
+ _, _, _, options =
856
+ extract_and_validate_params(schema.cf_defs.first.name, "", [options], WRITE_DEFAULTS)
857
+
858
+ yield(self)
859
+ flush_batch(options)
860
+ ensure
861
+ @batch = nil
862
+ end
863
+
864
+ ##
865
+ # Send the batch queue to the server
866
+ #
867
+ def flush_batch(options={})
868
+ compacted_map,seen_clevels = compact_mutations!
869
+
870
+ clevel = if options[:consistency] != nil # Override any clevel from individual mutations if
871
+ options[:consistency]
872
+ elsif seen_clevels.length > 1 # Cannot choose which CLevel to use if there are several ones
873
+ raise "Multiple consistency levels used in the batch, and no override...cannot pick one"
874
+ else # if no consistency override has been provided but all the clevels in the batch are the same: use that one
875
+ seen_clevels.first
876
+ end
877
+
878
+ _mutate(compacted_map,clevel)
879
+ end
880
+
881
+ ##
882
+ # Create secondary index.
883
+ #
884
+ # * keyspace
885
+ # * column_family
886
+ # * column_name
887
+ # * validation_class
888
+ #
889
+ def create_index(keyspace, column_family, column_name, validation_class)
890
+ return false if Cassandra.VERSION.to_f < 0.7
891
+
892
+ cf_def = client.describe_keyspace(keyspace).cf_defs.find{|x| x.name == column_family}
893
+ if !cf_def.nil? and !cf_def.column_metadata.find{|x| x.name == column_name}
894
+ c_def = CassandraThrift::ColumnDef.new do |cd|
895
+ cd.name = column_name
896
+ cd.validation_class = "org.apache.cassandra.db.marshal."+validation_class
897
+ cd.index_type = CassandraThrift::IndexType::KEYS
898
+ end
899
+ cf_def.column_metadata.push(c_def)
900
+ update_column_family(cf_def)
901
+ end
902
+ end
903
+
904
+ ##
905
+ # Delete secondary index.
906
+ #
907
+ # * keyspace
908
+ # * column_family
909
+ # * column_name
910
+ #
911
+ def drop_index(keyspace, column_family, column_name)
912
+ return false if Cassandra.VERSION.to_f < 0.7
913
+
914
+ cf_def = client.describe_keyspace(keyspace).cf_defs.find{|x| x.name == column_family}
915
+ if !cf_def.nil? and cf_def.column_metadata.find{|x| x.name == column_name}
916
+ cf_def.column_metadata.delete_if{|x| x.name == column_name}
917
+ update_column_family(cf_def)
918
+ end
919
+ end
920
+
921
+ ##
922
+ # This method is mostly used internally by get_index_slices to create
923
+ # a CassandraThrift::IndexExpression for the given options.
924
+ #
925
+ # * column_name - Column to be compared
926
+ # * value - Value to compare against
927
+ # * comparison - Type of comparison to do.
928
+ #
929
+ def create_index_expression(column_name, value, comparison)
930
+ return false if Cassandra.VERSION.to_f < 0.7
931
+
932
+ CassandraThrift::IndexExpression.new(
933
+ :column_name => column_name,
934
+ :value => value,
935
+ :op => (case comparison
936
+ when nil, "EQ", "eq", "=="
937
+ CassandraThrift::IndexOperator::EQ
938
+ when "GTE", "gte", ">="
939
+ CassandraThrift::IndexOperator::GTE
940
+ when "GT", "gt", ">"
941
+ CassandraThrift::IndexOperator::GT
942
+ when "LTE", "lte", "<="
943
+ CassandraThrift::IndexOperator::LTE
944
+ when "LT", "lt", "<"
945
+ CassandraThrift::IndexOperator::LT
946
+ end ))
947
+ end
948
+ alias :create_idx_expr :create_index_expression
949
+
950
+ ##
951
+ # This method takes an array if CassandraThrift::IndexExpression
952
+ # objects and creates a CassandraThrift::IndexClause for use in the
953
+ # Cassandra#get_index_slices
954
+ #
955
+ # * index_expressions - Array of CassandraThrift::IndexExpressions.
956
+ # * start - The starting row key.
957
+ # * count - The count of items to be returned
958
+ #
959
+ def create_index_clause(index_expressions, start = "", count = 100)
960
+ return false if Cassandra.VERSION.to_f < 0.7
961
+
962
+ CassandraThrift::IndexClause.new(
963
+ :start_key => start,
964
+ :expressions => index_expressions,
965
+ :count => count)
966
+ end
967
+ alias :create_idx_clause :create_index_clause
968
+
969
+ ##
970
+ # This method is used to query a secondary index with a set of
971
+ # provided search parameters.
972
+ #
973
+ # Please note that you can either specify a
974
+ # CassandraThrift::IndexClause or an array of hashes with the
975
+ # format as below.
976
+ #
977
+ # * column_family - The Column Family this operation will be run on.
978
+ # * index_clause - This can either be a CassandraThrift::IndexClause or an array of hashes with the following keys:
979
+ # * :column_name - Column to be compared
980
+ # * :value - Value to compare against
981
+ # * :comparison - Type of comparison to do.
982
+ # * options
983
+ # * :key_count - Set maximum number of rows to return. (Only works if CassandraThrift::IndexClause is not passed in.)
984
+ # * :start_key - Set starting row key for search. (Only works if CassandraThrift::IndexClause is not passed in.)
985
+ # * :consistency
986
+ #
987
+ # TODO: Supercolumn support.
988
+ def get_indexed_slices(column_family, index_clause, *columns_and_options)
989
+ return false if Cassandra.VERSION.to_f < 0.7
990
+
991
+ column_family, columns, _, options =
992
+ extract_and_validate_params(column_family, [], columns_and_options,
993
+ READ_DEFAULTS.merge(:key_count => 100, :start_key => nil, :key_start => nil))
994
+
995
+ start_key = options[:start_key] || options[:key_start] || ""
996
+
997
+ if index_clause.class != CassandraThrift::IndexClause
998
+ index_expressions = index_clause.collect do |expression|
999
+ create_index_expression(expression[:column_name], expression[:value], expression[:comparison])
1000
+ end
1001
+
1002
+ index_clause = create_index_clause(index_expressions, start_key, options[:key_count])
1003
+ end
1004
+
1005
+ key_slices = _get_indexed_slices(column_family, index_clause, columns, options[:count], options[:start],
1006
+ options[:finish], options[:reversed], options[:consistency])
1007
+
1008
+ key_slices.inject(OrderedHash.new) {|h, key_slice| h[key_slice.key] = key_slice.columns; h }
1009
+ end
1010
+
1011
+ protected
1012
+
1013
+ def calling_method
1014
+ "#{self.class}##{caller[0].split('`').last[0..-3]}"
1015
+ end
1016
+
1017
+ ##
1018
+ # Roll up queued mutations, to improve atomicity (and performance).
1019
+ #
1020
+ def compact_mutations!
1021
+ used_clevels = {} # hash that lists the consistency levels seen in the batch array. key is the clevel, value is true
1022
+ by_key = Hash.new{|h,k | h[k] = {}}
1023
+ # @batch is an array of mutation_ops.
1024
+ # A mutation op is a 2-item array containing [mutationmap, consistency_number]
1025
+ # a mutation map is a hash, by key (string) that has a hash by CF name, containing a list of column_mutations)
1026
+ @batch.each do |mutation_op|
1027
+ # A single mutation op looks like:
1028
+ # For an insert/update
1029
+ #[ { key1 =>
1030
+ # { CF1 => [several of CassThrift:Mutation(colname,value,TS,ttl)]
1031
+ # CF2 => [several mutations]
1032
+ # },
1033
+ # key2 => {...} # Not sure if they can come batched like this...so there might only be a single key (and CF)
1034
+ # }, # [0]
1035
+ # consistency # [1]
1036
+ #]
1037
+ mmap = mutation_op[0] # :remove OR a hash like {"key"=> {"CF"=>[mutationclass1,...] } }
1038
+ used_clevels[mutation_op[1]] = true #save the clevel required for this operation
1039
+
1040
+ mmap.keys.each do |k|
1041
+ mmap[k].keys.each do |cf| # For each CF in that key
1042
+ by_key[k][cf] ||= []
1043
+ by_key[k][cf].concat(mmap[k][cf]) # Append the list of mutations for that key and CF
1044
+ end
1045
+ end
1046
+ end
1047
+ # Returns the batch mutations map, and an array with the consistency levels 'seen' in the batch
1048
+ [by_key, used_clevels.keys]
1049
+ end
1050
+
1051
+ ##
1052
+ # Creates a new client as specified by Cassandra.thrift_client_options[:thrift_client_class]
1053
+ #
1054
+ def new_client
1055
+ thrift_client_class.new(CassandraThrift::Cassandra::Client, @servers, @thrift_client_options)
1056
+ end
1057
+
1058
+ def client
1059
+ if @client.nil? || @client.current_server.nil?
1060
+ reconnect!
1061
+ end
1062
+ @client
1063
+ end
1064
+
1065
+ def reconnect!
1066
+ @servers = all_nodes
1067
+ @client = new_client
1068
+ @client.add_callback :post_connect do |cli|
1069
+ # Set the active keyspace after connecting
1070
+ cli.set_keyspace(@keyspace)
1071
+
1072
+ # If using an authenticated keyspace, ensure we relogin
1073
+ cli.login(@auth_request) if @auth_request
1074
+ end
1075
+ end
1076
+
1077
+ def all_nodes
1078
+ if @auto_discover_nodes && !@keyspace.eql?("system")
1079
+ temp_client = new_client
1080
+ begin
1081
+ ips = (temp_client.describe_ring(@keyspace).map {|range| range.endpoints}).flatten.uniq
1082
+ port = @servers.first.split(':').last
1083
+ ips.map{|ip| "#{ip}:#{port}" }
1084
+ ensure
1085
+ temp_client.disconnect!
1086
+ end
1087
+ else
1088
+ @servers
1089
+ end
1090
+ end
1091
+ end