cassilds 0.9.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.
- 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
|