cassilds 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +53 -0
- data/LICENSE +202 -0
- data/Manifest +45 -0
- data/README.rdoc +83 -0
- data/Rakefile +9 -0
- data/bin/cassandra_helper +16 -0
- data/cassandra.gemspec +46 -0
- data/conf/cassandra.in.sh +47 -0
- data/conf/cassandra.yaml +113 -0
- data/conf/log4j.properties +38 -0
- data/conf/storage-conf.xml +342 -0
- data/lib/cassandra/0.6/cassandra.rb +68 -0
- data/lib/cassandra/0.6/columns.rb +35 -0
- data/lib/cassandra/0.6/protocol.rb +92 -0
- data/lib/cassandra/0.6.rb +7 -0
- data/lib/cassandra/0.7/cassandra.rb +272 -0
- data/lib/cassandra/0.7/column_family.rb +3 -0
- data/lib/cassandra/0.7/columns.rb +67 -0
- data/lib/cassandra/0.7/keyspace.rb +3 -0
- data/lib/cassandra/0.7/protocol.rb +139 -0
- data/lib/cassandra/0.7.rb +7 -0
- data/lib/cassandra/array.rb +8 -0
- data/lib/cassandra/cassandra.rb +302 -0
- data/lib/cassandra/columns.rb +79 -0
- data/lib/cassandra/comparable.rb +28 -0
- data/lib/cassandra/constants.rb +11 -0
- data/lib/cassandra/debug.rb +9 -0
- data/lib/cassandra/helpers.rb +40 -0
- data/lib/cassandra/long.rb +58 -0
- data/lib/cassandra/mock.rb +326 -0
- data/lib/cassandra/ordered_hash.rb +200 -0
- data/lib/cassandra/time.rb +11 -0
- data/lib/cassandra.rb +39 -0
- data/test/cassandra_client_test.rb +20 -0
- data/test/cassandra_mock_test.rb +73 -0
- data/test/cassandra_test.rb +412 -0
- data/test/comparable_types_test.rb +45 -0
- data/test/eventmachine_test.rb +42 -0
- data/test/ordered_hash_test.rb +380 -0
- data/test/test_helper.rb +14 -0
- data/vendor/0.6/gen-rb/cassandra.rb +1481 -0
- data/vendor/0.6/gen-rb/cassandra_constants.rb +12 -0
- data/vendor/0.6/gen-rb/cassandra_types.rb +482 -0
- data/vendor/0.7/gen-rb/cassandra.rb +1937 -0
- data/vendor/0.7/gen-rb/cassandra_constants.rb +12 -0
- data/vendor/0.7/gen-rb/cassandra_types.rb +679 -0
- metadata +176 -0
@@ -0,0 +1,302 @@
|
|
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 rdoc
|
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
|
+
}.freeze
|
53
|
+
|
54
|
+
READ_DEFAULTS = {
|
55
|
+
:count => 100,
|
56
|
+
:start => nil,
|
57
|
+
:finish => nil,
|
58
|
+
:reversed => false,
|
59
|
+
:consistency => Consistency::ONE
|
60
|
+
}.freeze
|
61
|
+
|
62
|
+
THRIFT_DEFAULTS = {
|
63
|
+
:transport_wrapper => Thrift::BufferedTransport,
|
64
|
+
:thrift_client_class => ThriftClient
|
65
|
+
}.freeze
|
66
|
+
|
67
|
+
attr_reader :keyspace, :servers, :schema, :thrift_client_options, :thrift_client_class, :auth_request
|
68
|
+
|
69
|
+
# Create a new Cassandra instance and open the connection.
|
70
|
+
def initialize(keyspace, servers = "127.0.0.1:9160", thrift_client_options = {})
|
71
|
+
@is_super = {}
|
72
|
+
@column_name_class = {}
|
73
|
+
@sub_column_name_class = {}
|
74
|
+
@auto_discover_nodes = true
|
75
|
+
thrift_client_options[:transport_wrapper] ||= Cassandra.DEFAULT_TRANSPORT_WRAPPER
|
76
|
+
@thrift_client_options = THRIFT_DEFAULTS.merge(thrift_client_options)
|
77
|
+
@thrift_client_class = @thrift_client_options[:thrift_client_class]
|
78
|
+
@keyspace = keyspace
|
79
|
+
@servers = Array(servers)
|
80
|
+
end
|
81
|
+
|
82
|
+
def disable_node_auto_discovery!
|
83
|
+
@auto_discover_nodes = false
|
84
|
+
end
|
85
|
+
|
86
|
+
def disconnect!
|
87
|
+
if @client
|
88
|
+
@client.disconnect!
|
89
|
+
@client = nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def keyspaces
|
94
|
+
@keyspaces ||= client.describe_keyspaces()
|
95
|
+
end
|
96
|
+
|
97
|
+
def login!(username, password)
|
98
|
+
@auth_request = CassandraThrift::AuthenticationRequest.new
|
99
|
+
@auth_request.credentials = {'username' => username, 'password' => password}
|
100
|
+
client.login(@keyspace, @auth_request)
|
101
|
+
end
|
102
|
+
|
103
|
+
def inspect
|
104
|
+
"#<Cassandra:#{object_id}, @keyspace=#{keyspace.inspect}, @schema={#{
|
105
|
+
schema(false).map {|name, hash| ":#{name} => #{hash['type'].inspect}"}.join(', ')
|
106
|
+
}}, @servers=#{servers.inspect}>"
|
107
|
+
end
|
108
|
+
|
109
|
+
### Write
|
110
|
+
|
111
|
+
# Insert a row for a key. Pass a flat hash for a regular column family, and
|
112
|
+
# a nested hash for a super column family. Supports the <tt>:consistency</tt>,
|
113
|
+
# <tt>:timestamp</tt> and <tt>:ttl</tt> options.
|
114
|
+
def insert(column_family, key, hash, options = {})
|
115
|
+
column_family, _, _, options = extract_and_validate_params(column_family, key, [options], WRITE_DEFAULTS)
|
116
|
+
|
117
|
+
timestamp = options[:timestamp] || Time.stamp
|
118
|
+
mutation_map = if is_super(column_family)
|
119
|
+
{
|
120
|
+
key => {
|
121
|
+
column_family => hash.collect{|k,v| _super_insert_mutation(column_family, k, v, timestamp, options[:ttl]) }
|
122
|
+
}
|
123
|
+
}
|
124
|
+
else
|
125
|
+
{
|
126
|
+
key => {
|
127
|
+
column_family => hash.collect{|k,v| _standard_insert_mutation(column_family, k, v, timestamp, options[:ttl])}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
@batch ? @batch << [mutation_map, options[:consistency]] : _mutate(mutation_map, options[:consistency])
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
## Delete
|
137
|
+
|
138
|
+
# _mutate the element at the column_family:key:[column]:[sub_column]
|
139
|
+
# path you request. Supports the <tt>:consistency</tt> and <tt>:timestamp</tt>
|
140
|
+
# options.
|
141
|
+
def remove(column_family, key, *columns_and_options)
|
142
|
+
column_family, column, sub_column, options = extract_and_validate_params(column_family, key, columns_and_options, WRITE_DEFAULTS)
|
143
|
+
|
144
|
+
args = {:column_family => column_family}
|
145
|
+
columns = is_super(column_family) ? {:super_column => column, :column => sub_column} : {:column => column}
|
146
|
+
column_path = CassandraThrift::ColumnPath.new(args.merge(columns))
|
147
|
+
|
148
|
+
mutation = [:remove, [key, column_path, options[:timestamp] || Time.stamp, options[:consistency]]]
|
149
|
+
|
150
|
+
@batch ? @batch << mutation : _remove(*mutation[1])
|
151
|
+
end
|
152
|
+
|
153
|
+
### Read
|
154
|
+
|
155
|
+
# Count the elements at the column_family:key:[super_column] path you
|
156
|
+
# request. Supports the <tt>:consistency</tt> option.
|
157
|
+
def count_columns(column_family, key, *columns_and_options)
|
158
|
+
column_family, super_column, _, options =
|
159
|
+
extract_and_validate_params(column_family, key, columns_and_options, READ_DEFAULTS)
|
160
|
+
_count_columns(column_family, key, super_column, options[:consistency])
|
161
|
+
end
|
162
|
+
|
163
|
+
# Multi-key version of Cassandra#count_columns. Supports options <tt>:count</tt>,
|
164
|
+
# <tt>:start</tt>, <tt>:finish</tt>, <tt>:reversed</tt>, and <tt>:consistency</tt>.
|
165
|
+
# FIXME Not real multi; needs server support
|
166
|
+
def multi_count_columns(column_family, keys, *options)
|
167
|
+
OrderedHash[*keys.map { |key| [key, count_columns(column_family, key, *options)] }._flatten_once]
|
168
|
+
end
|
169
|
+
|
170
|
+
# Return a list of single values for the elements at the
|
171
|
+
# column_family:key:column[s]:[sub_columns] path you request. Supports the
|
172
|
+
# <tt>:consistency</tt> option.
|
173
|
+
def get_columns(column_family, key, *columns_and_options)
|
174
|
+
column_family, columns, sub_columns, options =
|
175
|
+
extract_and_validate_params(column_family, key, columns_and_options, READ_DEFAULTS)
|
176
|
+
_get_columns(column_family, key, columns, sub_columns, options[:consistency])
|
177
|
+
end
|
178
|
+
|
179
|
+
# Multi-key version of Cassandra#get_columns. Supports the <tt>:consistency</tt>
|
180
|
+
# option.
|
181
|
+
# FIXME Not real multi; needs to use a Column predicate
|
182
|
+
def multi_get_columns(column_family, keys, *options)
|
183
|
+
OrderedHash[*keys.map { |key| [key, get_columns(column_family, key, *options)] }._flatten_once]
|
184
|
+
end
|
185
|
+
|
186
|
+
# Return a hash (actually, a Cassandra::OrderedHash) or a single value
|
187
|
+
# representing the element at the column_family:key:[column]:[sub_column]
|
188
|
+
# path you request. Supports options <tt>:count</tt>, <tt>:start</tt>,
|
189
|
+
# <tt>:finish</tt>, <tt>:reversed</tt>, and <tt>:consistency</tt>.
|
190
|
+
def get(column_family, key, *columns_and_options)
|
191
|
+
multi_get(column_family, [key], *columns_and_options)[key]
|
192
|
+
end
|
193
|
+
|
194
|
+
# Multi-key version of Cassandra#get. Supports options <tt>:count</tt>,
|
195
|
+
# <tt>:start</tt>, <tt>:finish</tt>, <tt>:reversed</tt>, and <tt>:consistency</tt>.
|
196
|
+
def multi_get(column_family, keys, *columns_and_options)
|
197
|
+
column_family, column, sub_column, options =
|
198
|
+
extract_and_validate_params(column_family, keys, columns_and_options, READ_DEFAULTS)
|
199
|
+
|
200
|
+
hash = _multiget(column_family, keys, column, sub_column,
|
201
|
+
options[:start], options[:finish], options[:count], options[:reversed],
|
202
|
+
options[:consistency])
|
203
|
+
# Restore order
|
204
|
+
ordered_hash = OrderedHash.new
|
205
|
+
keys.each { |key| ordered_hash[key] = hash[key] || (OrderedHash.new if is_super(column_family) and !sub_column) }
|
206
|
+
ordered_hash
|
207
|
+
end
|
208
|
+
|
209
|
+
# Return true if the column_family:key:[column]:[sub_column] path you
|
210
|
+
# request exists. Supports the <tt>:consistency</tt> option.
|
211
|
+
def exists?(column_family, key, *columns_and_options)
|
212
|
+
column_family, column, sub_column, options =
|
213
|
+
extract_and_validate_params(column_family, key, columns_and_options, READ_DEFAULTS)
|
214
|
+
ret = nil
|
215
|
+
if column
|
216
|
+
ret = _multiget(column_family, [key], column, sub_column, '', '', 1, false, options[:consistency])[key]
|
217
|
+
else
|
218
|
+
ret = _multiget(column_family, [key], nil, nil, '', '', 1, false, options[:consistency])[key]
|
219
|
+
end
|
220
|
+
return (!ret.nil? and ret.send(:length) != 0)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Return a list of keys in the column_family you request. Requires the
|
224
|
+
# table to be partitioned with OrderPreservingHash. Supports the
|
225
|
+
# <tt>:count</tt>, <tt>:start</tt>, <tt>:finish</tt>, and <tt>:consistency</tt>
|
226
|
+
# options.
|
227
|
+
def get_range(column_family, options = {})
|
228
|
+
column_family, _, _, options =
|
229
|
+
extract_and_validate_params(column_family, "", [options], READ_DEFAULTS)
|
230
|
+
_get_range(column_family, options[:start].to_s, options[:finish].to_s,
|
231
|
+
options[:count], options[:consistency])
|
232
|
+
end
|
233
|
+
|
234
|
+
# Return a list of keys in the column_family you request. Requires the
|
235
|
+
# table to be partitioned with OrderPreservingHash. Supports the
|
236
|
+
# <tt>:count</tt>, <tt>:start</tt>, <tt>:finish</tt>, and <tt>:consistency</tt>
|
237
|
+
# options.
|
238
|
+
def get_range_hash(column_family, options = {})
|
239
|
+
column_family, _, _, options =
|
240
|
+
extract_and_validate_params(column_family, "", [options], READ_DEFAULTS)
|
241
|
+
_get_range_hash(column_family, options[:start].to_s, options[:finish].to_s,
|
242
|
+
options[:count], options[:consistency])
|
243
|
+
end
|
244
|
+
|
245
|
+
# Return a list of keys in the column_family you request. Requires the
|
246
|
+
# table to be partitioned with OrderPreservingHash. Supports the
|
247
|
+
# <tt>:count</tt>, <tt>:start</tt>, <tt>:finish</tt>, and <tt>:consistency</tt>
|
248
|
+
# options.
|
249
|
+
def get_range_columns(column_family, *columns_and_options)
|
250
|
+
column_family, columns, sub_columns, options =
|
251
|
+
extract_and_validate_params(column_family, "", columns_and_options, READ_DEFAULTS)
|
252
|
+
_get_range_columns(column_family, columns, sub_columns, options[:start].to_s,
|
253
|
+
options[:finish].to_s, options[:count], options[:consistency])
|
254
|
+
end
|
255
|
+
|
256
|
+
# Count all rows in the column_family you request. Requires the table
|
257
|
+
# to be partitioned with OrderPreservingHash. Supports the <tt>:start</tt>,
|
258
|
+
# <tt>:finish</tt>, and <tt>:consistency</tt> options.
|
259
|
+
def count_range(column_family, options = {})
|
260
|
+
get_range(column_family, options).select{|r| r.columns.length > 0}.compact.length
|
261
|
+
end
|
262
|
+
|
263
|
+
# Open a batch operation and yield self. Inserts and deletes will be queued
|
264
|
+
# until the block closes, and then sent atomically to the server. Supports
|
265
|
+
# the <tt>:consistency</tt> option, which overrides the consistency set in
|
266
|
+
# the individual commands.
|
267
|
+
def batch(options = {})
|
268
|
+
_, _, _, options =
|
269
|
+
extract_and_validate_params(schema.keys.first, "", [options], WRITE_DEFAULTS)
|
270
|
+
|
271
|
+
@batch = []
|
272
|
+
yield(self)
|
273
|
+
compact_mutations!
|
274
|
+
|
275
|
+
@batch.each do |mutation|
|
276
|
+
case mutation.first
|
277
|
+
when :remove
|
278
|
+
_remove(*mutation[1])
|
279
|
+
else
|
280
|
+
_mutate(*mutation)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
ensure
|
284
|
+
@batch = nil
|
285
|
+
end
|
286
|
+
|
287
|
+
protected
|
288
|
+
|
289
|
+
def calling_method
|
290
|
+
"#{self.class}##{caller[0].split('`').last[0..-3]}"
|
291
|
+
end
|
292
|
+
|
293
|
+
# Roll up queued mutations, to improve atomicity.
|
294
|
+
def compact_mutations!
|
295
|
+
#TODO re-do this rollup
|
296
|
+
end
|
297
|
+
|
298
|
+
def new_client
|
299
|
+
thrift_client_class.new(CassandraThrift::Cassandra::Client, @servers, @thrift_client_options)
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
|
2
|
+
class Cassandra
|
3
|
+
# A bunch of crap, mostly related to introspecting on column types
|
4
|
+
module Columns #:nodoc:
|
5
|
+
|
6
|
+
def is_super(column_family)
|
7
|
+
@is_super[column_family] ||= column_family_property(column_family, 'Type') == "Super"
|
8
|
+
end
|
9
|
+
|
10
|
+
def column_name_class(column_family)
|
11
|
+
@column_name_class[column_family] ||= column_name_class_for_key(column_family, "CompareWith")
|
12
|
+
end
|
13
|
+
|
14
|
+
def sub_column_name_class(column_family)
|
15
|
+
@sub_column_name_class[column_family] ||= column_name_class_for_key(column_family, "CompareSubcolumnsWith")
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def column_name_class_for_key(column_family, comparator_key)
|
21
|
+
property = column_family_property(column_family, comparator_key)
|
22
|
+
property =~ /.*\.(.*?)$/
|
23
|
+
case $1
|
24
|
+
when "LongType" then Long
|
25
|
+
when "LexicalUUIDType", "TimeUUIDType" then SimpleUUID::UUID
|
26
|
+
else
|
27
|
+
String # UTF8, Ascii, Bytes, anything else
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def column_family_property(column_family, key)
|
32
|
+
unless schema[column_family]
|
33
|
+
raise AccessError, "Invalid column family \"#{column_family}\""
|
34
|
+
end
|
35
|
+
schema[column_family][key]
|
36
|
+
end
|
37
|
+
|
38
|
+
def multi_column_to_hash!(hash)
|
39
|
+
hash.each do |key, column_or_supercolumn|
|
40
|
+
hash[key] = (column_or_supercolumn.column.value if column_or_supercolumn.column)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def multi_columns_to_hash!(column_family, hash)
|
45
|
+
hash.each do |key, columns|
|
46
|
+
hash[key] = columns_to_hash(column_family, columns)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def multi_sub_columns_to_hash!(column_family, hash)
|
51
|
+
hash.each do |key, sub_columns|
|
52
|
+
hash[key] = sub_columns_to_hash(column_family, sub_columns)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def columns_to_hash(column_family, columns)
|
57
|
+
columns_to_hash_for_classes(columns, column_name_class(column_family), sub_column_name_class(column_family))
|
58
|
+
end
|
59
|
+
|
60
|
+
def sub_columns_to_hash(column_family, columns)
|
61
|
+
columns_to_hash_for_classes(columns, sub_column_name_class(column_family))
|
62
|
+
end
|
63
|
+
|
64
|
+
def columns_to_hash_for_classes(columns, column_name_class, sub_column_name_class = nil)
|
65
|
+
hash = OrderedHash.new
|
66
|
+
Array(columns).each do |c|
|
67
|
+
c = c.super_column || c.column if c.is_a?(CassandraThrift::ColumnOrSuperColumn)
|
68
|
+
case c
|
69
|
+
when CassandraThrift::SuperColumn
|
70
|
+
hash.[]=(column_name_class.new(c.name), columns_to_hash_for_classes(c.columns, sub_column_name_class)) # Pop the class stack, and recurse
|
71
|
+
when CassandraThrift::Column
|
72
|
+
hash.[]=(column_name_class.new(c.name), c.value, c.timestamp)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
hash
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
class Cassandra
|
3
|
+
# Abstract base class for comparable numeric column name types
|
4
|
+
class Comparable
|
5
|
+
class TypeError < ::TypeError #:nodoc:
|
6
|
+
end
|
7
|
+
|
8
|
+
def <=>(other)
|
9
|
+
self.to_i <=> other.to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
def hash
|
13
|
+
@bytes.hash
|
14
|
+
end
|
15
|
+
|
16
|
+
def eql?(other)
|
17
|
+
other.is_a?(Comparable) and @bytes == other.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def ==(other)
|
21
|
+
other.respond_to?(:to_i) && self.to_i == other.to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
@bytes
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
|
2
|
+
class Cassandra
|
3
|
+
# A helper module you can include in your own class. Makes it easier
|
4
|
+
# to work with Cassandra subclasses.
|
5
|
+
module Constants
|
6
|
+
include Cassandra::Consistency
|
7
|
+
|
8
|
+
Long = Cassandra::Long
|
9
|
+
OrderedHash = Cassandra::OrderedHash
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Cassandra
|
2
|
+
module Helpers
|
3
|
+
def extract_and_validate_params(column_family, keys, args, options)
|
4
|
+
options = options.dup
|
5
|
+
column_family = column_family.to_s
|
6
|
+
# Keys
|
7
|
+
[keys].flatten.each do |key|
|
8
|
+
raise ArgumentError, "Key #{key.inspect} must be a String for #{caller[2].inspect}." unless key.is_a?(String)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Options
|
12
|
+
if args.last.is_a?(Hash)
|
13
|
+
extras = args.last.keys - options.keys
|
14
|
+
raise ArgumentError, "Invalid options #{extras.inspect[1..-2]} for #{caller[1]}" if extras.any?
|
15
|
+
options.merge!(args.pop)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Ranges
|
19
|
+
column, sub_column = args[0], args[1]
|
20
|
+
klass, sub_klass = column_name_class(column_family), sub_column_name_class(column_family)
|
21
|
+
range_class = column ? sub_klass : klass
|
22
|
+
|
23
|
+
[:start, :finish].each do |opt|
|
24
|
+
options[opt] = options[opt] ? (options[opt] == '' ? '' : range_class.new(options[opt]).to_s) : ''
|
25
|
+
end
|
26
|
+
|
27
|
+
[column_family, s_map(column, klass), s_map(sub_column, sub_klass), options]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Convert stuff to strings.
|
31
|
+
def s_map(el, klass)
|
32
|
+
case el
|
33
|
+
when Array then el.map { |i| s_map(i, klass) }
|
34
|
+
when NilClass then nil
|
35
|
+
else
|
36
|
+
klass.new(el).to_s
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
|
2
|
+
class Cassandra
|
3
|
+
# A temporally-ordered Long class for use in Cassandra column names
|
4
|
+
class Long < Cassandra::Comparable
|
5
|
+
|
6
|
+
# FIXME Should unify with or subclass Cassandra::UUID
|
7
|
+
def initialize(bytes = nil)
|
8
|
+
case bytes
|
9
|
+
when self.class # Long
|
10
|
+
@bytes = bytes.to_s
|
11
|
+
when String
|
12
|
+
case bytes.size
|
13
|
+
when 8 # Raw byte array
|
14
|
+
@bytes = bytes
|
15
|
+
when 18 # Human-readable UUID-like representation; inverse of #to_guid
|
16
|
+
elements = bytes.split("-")
|
17
|
+
raise TypeError, "Expected #{bytes.inspect} to cast to a #{self.class} (malformed UUID-like representation)" if elements.size != 3
|
18
|
+
@bytes = [elements.join].pack('H32')
|
19
|
+
else
|
20
|
+
raise TypeError, "Expected #{bytes.inspect} to cast to a #{self.class} (invalid bytecount)"
|
21
|
+
end
|
22
|
+
when Integer
|
23
|
+
raise TypeError, "Expected #{bytes.inspect} to cast to a #{self.class} (integer out of range)" if bytes < 0 or bytes > 2**64
|
24
|
+
@bytes = [bytes >> 32, bytes % 2**32].pack("NN")
|
25
|
+
when NilClass, Time
|
26
|
+
# Time.stamp is 52 bytes, so we have 12 bytes of entropy left over
|
27
|
+
int = ((bytes || Time).stamp << 12) + rand(2**12)
|
28
|
+
@bytes = [int >> 32, int % 2**32].pack("NN")
|
29
|
+
else
|
30
|
+
raise TypeError, "Expected #{bytes.inspect} to cast to a #{self.class} (unknown source class)"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_i
|
35
|
+
@to_i ||= begin
|
36
|
+
ints = @bytes.unpack("NN")
|
37
|
+
(ints[0] << 32) +
|
38
|
+
ints[1]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_guid
|
43
|
+
"%08x-%04x-%04x" % @bytes.unpack("Nnn")
|
44
|
+
end
|
45
|
+
|
46
|
+
def inspect
|
47
|
+
"<Cassandra::Long##{object_id} time: #{
|
48
|
+
Time.at((to_i >> 12) / 1_000_000).utc.inspect
|
49
|
+
}, usecs: #{
|
50
|
+
(to_i >> 12) % 1_000_000
|
51
|
+
}, jitter: #{
|
52
|
+
to_i % 2**12
|
53
|
+
}, guid: #{
|
54
|
+
to_guid
|
55
|
+
}>"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|