sessionm-cassandra 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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