sessionm-cassandra 1.0.0

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