cassandra-mavericks 0.21.1

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