cassandra 0.2.3 → 0.4
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.tar.gz.sig +0 -0
- data/CHANGELOG +12 -0
- data/LICENSE +202 -0
- data/Manifest +24 -17
- data/README +65 -0
- data/Rakefile +13 -0
- data/cassandra.gemspec +22 -117
- data/conf/cassandra.in.sh +55 -0
- data/conf/log4j.properties +38 -0
- data/conf/storage-conf.xml +197 -0
- data/lib/cassandra.rb +21 -159
- data/lib/cassandra/array.rb +8 -0
- data/lib/cassandra/cassandra.rb +315 -0
- data/lib/cassandra/comparable.rb +28 -0
- data/lib/cassandra/constants.rb +8 -0
- data/lib/cassandra/helper.rb +85 -0
- data/lib/cassandra/long.rb +39 -0
- data/lib/cassandra/ordered_hash.rb +135 -0
- data/lib/cassandra/safe_client.rb +21 -0
- data/lib/cassandra/serialization.rb +57 -0
- data/lib/cassandra/time.rb +9 -0
- data/lib/cassandra/uuid.rb +44 -0
- data/quickstart.sh +12 -0
- data/test/cassandra_client_test.rb +302 -0
- data/test/comparable_types_test.rb +44 -0
- data/vendor/gen-rb/cassandra.rb +1138 -0
- data/vendor/gen-rb/cassandra_constants.rb +10 -0
- data/vendor/gen-rb/cassandra_types.rb +239 -0
- metadata +90 -37
- metadata.gz.sig +1 -0
- data/README.rdoc +0 -29
- data/lib/properties.rb +0 -119
- data/lib/tags.rb +0 -89
- data/misc/dan.rb +0 -64
- data/misc/meyer_reset.css +0 -53
- data/site/basic.cssy +0 -12
- data/site/basic.rb +0 -19
- data/site/cssy_title.jpg +0 -0
- data/site/flower.png +0 -0
- data/site/index.mab +0 -22
- data/site/ruby.cssy +0 -15
- data/test/basics.rb +0 -107
- data/test/dan.cssy +0 -160
- data/test/fiddle.css +0 -22
- data/test/fiddle.cssy +0 -34
- data/test/helper.rb +0 -8
@@ -0,0 +1,315 @@
|
|
1
|
+
|
2
|
+
class Cassandra
|
3
|
+
include Helper
|
4
|
+
class AccessError < StandardError; end
|
5
|
+
|
6
|
+
MAX_INT = 2**31 - 1
|
7
|
+
|
8
|
+
module Consistency
|
9
|
+
include CassandraThrift::ConsistencyLevel
|
10
|
+
NONE = ZERO
|
11
|
+
WEAK = ONE
|
12
|
+
STRONG = QUORUM
|
13
|
+
PERFECT = ALL
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :keyspace, :host, :port, :serializer, :transport, :client, :schema
|
17
|
+
|
18
|
+
# Instantiate a new Cassandra and open the connection.
|
19
|
+
def initialize(keyspace, host = '127.0.0.1', port = 9160, serializer = Cassandra::Serialization::JSON)
|
20
|
+
@is_super = {}
|
21
|
+
@column_name_class = {}
|
22
|
+
@sub_column_name_class = {}
|
23
|
+
|
24
|
+
@keyspace = keyspace
|
25
|
+
@host = host
|
26
|
+
@port = port
|
27
|
+
@serializer = serializer
|
28
|
+
|
29
|
+
@transport = Thrift::BufferedTransport.new(Thrift::Socket.new(@host, @port))
|
30
|
+
@transport.open
|
31
|
+
@client = CassandraThrift::Cassandra::SafeClient.new(
|
32
|
+
CassandraThrift::Cassandra::Client.new(Thrift::BinaryProtocol.new(@transport)),
|
33
|
+
@transport)
|
34
|
+
|
35
|
+
keyspaces = @client.get_string_list_property("tables")
|
36
|
+
unless keyspaces.include?(@keyspace)
|
37
|
+
raise AccessError, "Keyspace #{@keyspace.inspect} not found. Available: #{keyspaces.inspect}"
|
38
|
+
end
|
39
|
+
|
40
|
+
@schema = @client.describe_table(@keyspace)
|
41
|
+
end
|
42
|
+
|
43
|
+
def inspect
|
44
|
+
"#<Cassandra:#{object_id}, @keyspace=#{keyspace.inspect}, @schema={#{
|
45
|
+
schema.map {|name, hash| ":#{name} => #{hash['type'].inspect}"}.join(', ')
|
46
|
+
}}, @host=#{host.inspect}, @port=#{port}, @serializer=#{serializer.name}>"
|
47
|
+
end
|
48
|
+
|
49
|
+
## Write
|
50
|
+
|
51
|
+
# Insert a row for a key. Pass a flat hash for a regular column family, and
|
52
|
+
# a nested hash for a super column family.
|
53
|
+
def insert(column_family, key, hash, consistency = Consistency::WEAK, timestamp = Time.stamp)
|
54
|
+
column_family = column_family.to_s
|
55
|
+
mutation = if is_super(column_family)
|
56
|
+
CassandraThrift::BatchMutationSuper.new(:key => key, :cfmap => {column_family.to_s => hash_to_super_columns(column_family, hash, timestamp)})
|
57
|
+
else
|
58
|
+
CassandraThrift::BatchMutation.new(:key => key, :cfmap => {column_family.to_s => hash_to_columns(column_family, hash, timestamp)})
|
59
|
+
end
|
60
|
+
# FIXME Batched operations discard the consistency argument
|
61
|
+
@batch ? @batch << mutation : _insert(mutation, consistency)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def _insert(mutation, consistency = Consistency::WEAK)
|
67
|
+
case mutation
|
68
|
+
when CassandraThrift::BatchMutationSuper then @client.batch_insert_super_column(@keyspace, mutation, consistency)
|
69
|
+
when CassandraThrift::BatchMutation then @client.batch_insert(@keyspace, mutation, consistency)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
public
|
74
|
+
|
75
|
+
## Delete
|
76
|
+
|
77
|
+
# Remove the element at the column_family:key:[column]:[sub_column]
|
78
|
+
# path you request.
|
79
|
+
def remove(column_family, key, column = nil, sub_column = nil, consistency = Consistency::WEAK, timestamp = Time.stamp)
|
80
|
+
column_family = column_family.to_s
|
81
|
+
assert_column_name_classes(column_family, column, sub_column)
|
82
|
+
|
83
|
+
column = column.to_s if column
|
84
|
+
sub_column = sub_column.to_s if sub_column
|
85
|
+
args = [column_family, key, column, sub_column, consistency, timestamp]
|
86
|
+
@batch ? @batch << args : _remove(*args)
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def _remove(column_family, key, column, sub_column, consistency, timestamp)
|
92
|
+
column_path_or_parent = if is_super(column_family)
|
93
|
+
CassandraThrift::ColumnPathOrParent.new(:column_family => column_family, :super_column => column, :column => sub_column)
|
94
|
+
else
|
95
|
+
CassandraThrift::ColumnPathOrParent.new(:column_family => column_family, :column => column)
|
96
|
+
end
|
97
|
+
@client.remove(@keyspace, key, column_path_or_parent, timestamp, consistency)
|
98
|
+
end
|
99
|
+
|
100
|
+
public
|
101
|
+
|
102
|
+
# Remove all rows in the column family you request.
|
103
|
+
def clear_column_family!(column_family)
|
104
|
+
# Does not support consistency argument
|
105
|
+
get_key_range(column_family).each do |key|
|
106
|
+
remove(column_family, key)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Remove all rows in the keyspace
|
111
|
+
def clear_keyspace!
|
112
|
+
# Does not support consistency argument
|
113
|
+
@schema.keys.each do |column_family|
|
114
|
+
clear_column_family!(column_family)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
## Read
|
119
|
+
|
120
|
+
# Count the elements at the column_family:key:[super_column] path you
|
121
|
+
# request.
|
122
|
+
def count_columns(column_family, key, super_column = nil, consistency = Consistency::WEAK)
|
123
|
+
column_family = column_family.to_s
|
124
|
+
assert_column_name_classes(column_family, super_column)
|
125
|
+
|
126
|
+
super_column = super_column.to_s if super_column
|
127
|
+
@client.get_column_count(@keyspace, key,
|
128
|
+
CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => super_column),
|
129
|
+
consistency
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Multi-key version of Cassandra#count_columns.
|
134
|
+
def multi_count_columns(column_family, keys, super_column = nil, consistency = Consistency::WEAK)
|
135
|
+
OrderedHash[*keys.map do |key|
|
136
|
+
[key, count_columns(column_family, key, super_column)]
|
137
|
+
end._flatten_once]
|
138
|
+
end
|
139
|
+
|
140
|
+
# Return a list of single values for the elements at the
|
141
|
+
# column_family:key:column[s]:[sub_columns] path you request.
|
142
|
+
def get_columns(column_family, key, columns, sub_columns = nil, consistency = Consistency::WEAK)
|
143
|
+
column_family = column_family.to_s
|
144
|
+
assert_column_name_classes(column_family, columns, sub_columns)
|
145
|
+
|
146
|
+
result = if is_super(column_family)
|
147
|
+
if sub_columns
|
148
|
+
columns_to_hash(column_family, @client.get_slice_by_names(@keyspace, key,
|
149
|
+
CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => columns),
|
150
|
+
sub_columns, consistency))
|
151
|
+
else
|
152
|
+
columns_to_hash(column_family, @client.get_slice_super_by_names(@keyspace, key, column_family, columns, consistency))
|
153
|
+
end
|
154
|
+
else
|
155
|
+
columns_to_hash(column_family, @client.get_slice_by_names(@keyspace, key,
|
156
|
+
CassandraThrift::ColumnParent.new(:column_family => column_family), columns, consistency))
|
157
|
+
end
|
158
|
+
sub_columns || columns.map { |name| result[name] }
|
159
|
+
end
|
160
|
+
|
161
|
+
# Multi-key version of Cassandra#get_columns.
|
162
|
+
def multi_get_columns(column_family, keys, columns, sub_columns = nil, consistency = Consistency::WEAK)
|
163
|
+
OrderedHash[*keys.map do |key|
|
164
|
+
[key, get_columns(column_family, key, columns, sub_columns, consistency)]
|
165
|
+
end._flatten_once]
|
166
|
+
end
|
167
|
+
|
168
|
+
# Return a hash (actually, a Cassandra::OrderedHash) or a single value
|
169
|
+
# representing the element at the column_family:key:[column]:[sub_column]
|
170
|
+
# path you request.
|
171
|
+
def get(column_family, key, column = nil, sub_column = nil, limit = 100, column_range = ''..'', reversed = false, consistency = Consistency::WEAK)
|
172
|
+
column_family = column_family.to_s
|
173
|
+
assert_column_name_classes(column_family, column, sub_column)
|
174
|
+
_get(column_family, key, column, sub_column, limit = 100, column_range, reversed, consistency)
|
175
|
+
rescue CassandraThrift::NotFoundException
|
176
|
+
is_super(column_family) && !sub_column ? OrderedHash.new : nil
|
177
|
+
end
|
178
|
+
|
179
|
+
# Multi-key version of Cassandra#get.
|
180
|
+
def multi_get(column_family, keys, column = nil, sub_column = nil, limit = 100, column_range = ''..'', reversed = false, consistency = Consistency::WEAK)
|
181
|
+
OrderedHash[*keys.map do |key|
|
182
|
+
[key, get(column_family, key, column, sub_column, limit, column_range, reversed, consistency)]
|
183
|
+
end._flatten_once]
|
184
|
+
end
|
185
|
+
|
186
|
+
# Return true if the column_family:key:[column]:[sub_column] path you
|
187
|
+
# request exists.
|
188
|
+
def exists?(column_family, key, column = nil, sub_column = nil, consistency = Consistency::WEAK)
|
189
|
+
column_family = column_family.to_s
|
190
|
+
assert_column_name_classes(column_family, column, sub_column)
|
191
|
+
_get(column_family, key, column, sub_column, 1, ''..'', false, consistency)
|
192
|
+
true
|
193
|
+
rescue CassandraThrift::NotFoundException
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
def _get(column_family, key, column = nil, sub_column = nil, limit = 100, column_range = ''..'', reversed = false, consistency = Consistency::WEAK)
|
199
|
+
column = column.to_s if column
|
200
|
+
sub_column = sub_column.to_s if sub_column
|
201
|
+
# FIXME Comparable types in a are not checked
|
202
|
+
column_range = (column_range.begin.to_s)..(column_range.end.to_s)
|
203
|
+
|
204
|
+
# You have got to be kidding
|
205
|
+
if is_super(column_family)
|
206
|
+
if sub_column
|
207
|
+
# Limit and column_range parameters have no effect
|
208
|
+
load(@client.get_column(@keyspace, key,
|
209
|
+
CassandraThrift::ColumnPath.new(:column_family => column_family, :super_column => column, :column => sub_column),
|
210
|
+
consistency).value)
|
211
|
+
elsif column
|
212
|
+
sub_columns_to_hash(column_family,
|
213
|
+
@client.get_slice(@keyspace, key,
|
214
|
+
CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => column),
|
215
|
+
column_range.begin, column_range.end, !reversed, limit, consistency))
|
216
|
+
else
|
217
|
+
columns_to_hash(column_family,
|
218
|
+
@client.get_slice_super(@keyspace, key, column_family, column_range.begin, column_range.end, !reversed, limit, consistency))
|
219
|
+
end
|
220
|
+
else
|
221
|
+
if column
|
222
|
+
# Limit and column_range parameters have no effect
|
223
|
+
load(@client.get_column(@keyspace, key,
|
224
|
+
CassandraThrift::ColumnPath.new(:column_family => column_family, :column => column),
|
225
|
+
consistency).value)
|
226
|
+
else
|
227
|
+
columns_to_hash(column_family,
|
228
|
+
@client.get_slice(@keyspace, key,
|
229
|
+
CassandraThrift::ColumnParent.new(:column_family => column_family),
|
230
|
+
column_range.begin, column_range.end, !reversed, limit, consistency))
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
public
|
236
|
+
|
237
|
+
# Return a list of keys in the column_family you request. Requires the
|
238
|
+
# table to be partitioned with OrderPreservingHash.
|
239
|
+
def get_key_range(column_family, key_range = ''..'', limit = 100, consistency = Consistency::WEAK)
|
240
|
+
column_family = column_family.to_s
|
241
|
+
@client.get_key_range(@keyspace, column_family, key_range.begin, key_range.end, limit)
|
242
|
+
end
|
243
|
+
|
244
|
+
# Count all rows in the column_family you request. Requires the table
|
245
|
+
# to be partitioned with OrderPreservingHash.
|
246
|
+
def count(column_family, key_range = ''..'', limit = MAX_INT, consistency = Consistency::WEAK)
|
247
|
+
get_key_range(column_family, key_range, limit, consistency).size
|
248
|
+
end
|
249
|
+
|
250
|
+
def batch
|
251
|
+
@batch = []
|
252
|
+
yield
|
253
|
+
compact_mutations!
|
254
|
+
dispatch_mutations!
|
255
|
+
@batch = nil
|
256
|
+
end
|
257
|
+
|
258
|
+
private
|
259
|
+
|
260
|
+
def compact_mutations!
|
261
|
+
compact_batch = []
|
262
|
+
mutations = {}
|
263
|
+
|
264
|
+
@batch << nil # Close it
|
265
|
+
@batch.each do |m|
|
266
|
+
case m
|
267
|
+
when Array, nil
|
268
|
+
# Flush compacted mutations
|
269
|
+
compact_batch.concat(mutations.values.map {|x| x.values}.flatten)
|
270
|
+
mutations = {}
|
271
|
+
# Insert delete operation
|
272
|
+
compact_batch << m
|
273
|
+
else # BatchMutation, BatchMutationSuper
|
274
|
+
# Do a nested hash merge
|
275
|
+
if mutation_class = mutations[m.class]
|
276
|
+
if mutation = mutation_class[m.key]
|
277
|
+
if columns = mutation.cfmap[m.cfmap.keys.first]
|
278
|
+
columns.concat(m.cfmap.values.first)
|
279
|
+
else
|
280
|
+
mutation.cfmap.merge!(m.cfmap)
|
281
|
+
end
|
282
|
+
else
|
283
|
+
mutation_class[m.key] = m
|
284
|
+
end
|
285
|
+
else
|
286
|
+
mutations[m.class] = {m.key => m}
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
@batch = compact_batch
|
292
|
+
end
|
293
|
+
|
294
|
+
def dispatch_mutations!
|
295
|
+
@batch.each do |args|
|
296
|
+
case args
|
297
|
+
when Array
|
298
|
+
_remove(*args)
|
299
|
+
when CassandraThrift::BatchMutationSuper, CassandraThrift::BatchMutation
|
300
|
+
_insert(*args)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def dump(object)
|
306
|
+
# Special-case nil as the empty byte array
|
307
|
+
return "" if object == nil
|
308
|
+
@serializer.dump(object)
|
309
|
+
end
|
310
|
+
|
311
|
+
def load(object)
|
312
|
+
return nil if object == ""
|
313
|
+
@serializer.load(object)
|
314
|
+
end
|
315
|
+
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
|
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
|
+
self.to_i == other.to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
@bytes
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
class Cassandra
|
2
|
+
|
3
|
+
# A bunch of crap, mostly related to introspecting on column types
|
4
|
+
module Helper
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def is_super(column_family)
|
9
|
+
@is_super[column_family] ||= column_family_property(column_family, 'Type') == "Super"
|
10
|
+
end
|
11
|
+
|
12
|
+
def column_name_class(column_family)
|
13
|
+
@column_name_class[column_family] ||= column_name_class_for_key(column_family, "CompareWith")
|
14
|
+
end
|
15
|
+
|
16
|
+
def sub_column_name_class(column_family)
|
17
|
+
@sub_column_name_class[column_family] ||= column_name_class_for_key(column_family, "CompareSubcolumnsWith")
|
18
|
+
end
|
19
|
+
|
20
|
+
def column_name_class_for_key(column_family, comparator_key)
|
21
|
+
property = column_family_property(column_family, comparator_key)
|
22
|
+
property =~ /.*\.(.*?)Type/
|
23
|
+
self.class.const_get($1) # Long, UUID
|
24
|
+
rescue NameError
|
25
|
+
String # UTF8, Ascii, Bytes, anything else
|
26
|
+
rescue TypeError
|
27
|
+
nil # Called sub_column_name_class on a standard column family
|
28
|
+
end
|
29
|
+
|
30
|
+
def column_family_property(column_family, key)
|
31
|
+
@schema[column_family][key]
|
32
|
+
rescue NoMethodError
|
33
|
+
raise AccessError, "Invalid column family \"#{column_family}\""
|
34
|
+
end
|
35
|
+
|
36
|
+
def assert_column_name_classes(column_family, columns, sub_columns = nil)
|
37
|
+
{Array(columns) => column_name_class(column_family),
|
38
|
+
Array(sub_columns) => sub_column_name_class(column_family)}.each do |columns, klass|
|
39
|
+
columns.each do |column|
|
40
|
+
raise Comparable::TypeError, "Expected #{column.inspect} to be a #{klass}" if !column.is_a?(klass)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def columns_to_hash(column_family, columns)
|
46
|
+
columns_to_hash_for_classes(columns, column_name_class(column_family), sub_column_name_class(column_family))
|
47
|
+
end
|
48
|
+
|
49
|
+
def sub_columns_to_hash(column_family, columns)
|
50
|
+
columns_to_hash_for_classes(columns, sub_column_name_class(column_family))
|
51
|
+
end
|
52
|
+
|
53
|
+
def columns_to_hash_for_classes(columns, column_name_class, sub_column_name_class = nil)
|
54
|
+
hash = OrderedHash.new
|
55
|
+
Array(columns).each do |c|
|
56
|
+
hash[column_name_class.new(c.name)] = if c.is_a?(CassandraThrift::SuperColumn)
|
57
|
+
# Pop the class stack, and recurse
|
58
|
+
columns_to_hash_for_classes(c.columns, sub_column_name_class)
|
59
|
+
else
|
60
|
+
load(c.value)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
hash
|
64
|
+
end
|
65
|
+
|
66
|
+
def hash_to_columns(column_family, hash, timestamp)
|
67
|
+
assert_column_name_classes(column_family, hash.keys)
|
68
|
+
hash_to_columns_without_assertion(column_family, hash, timestamp)
|
69
|
+
end
|
70
|
+
|
71
|
+
def hash_to_columns_without_assertion(column_family, hash, timestamp)
|
72
|
+
hash.map do |column, value|
|
73
|
+
CassandraThrift::Column.new(:name => column.to_s, :value => dump(value), :timestamp => timestamp)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def hash_to_super_columns(column_family, hash, timestamp)
|
78
|
+
assert_column_name_classes(column_family, hash.keys)
|
79
|
+
hash.map do |column, sub_hash|
|
80
|
+
assert_column_name_classes(column_family, nil, sub_hash.keys)
|
81
|
+
CassandraThrift::SuperColumn.new(:name => column.to_s, :columns => hash_to_columns_without_assertion(column_family, sub_hash, timestamp))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|