jamesgolick-cassandra 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,7 @@
1
+
2
+ class CassandraThrift::Cassandra::Client
3
+ def send_message(*args)
4
+ pp args
5
+ super
6
+ end
7
+ 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] ? 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 < 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
@@ -0,0 +1,322 @@
1
+ require 'nokogiri'
2
+
3
+ class SimpleUUID::UUID
4
+ def >=(other)
5
+ (self <=> other) >= 0
6
+ end
7
+
8
+ def <=(other)
9
+ (self <=> other) <= 0
10
+ end
11
+ end
12
+
13
+ class Cassandra
14
+ class Mock
15
+ include ::Cassandra::Helpers
16
+ include ::Cassandra::Columns
17
+
18
+ def initialize(keyspace, storage_xml)
19
+ @keyspace = keyspace
20
+ @column_name_class = {}
21
+ @sub_column_name_class = {}
22
+ @storage_xml = storage_xml
23
+ clear_keyspace!
24
+ end
25
+
26
+ def disconnect!
27
+ end
28
+
29
+ def clear_keyspace!
30
+ @data = {}
31
+ end
32
+
33
+ def clear_column_family!(column_family)
34
+ @data[column_family.to_sym] = OrderedHash.new
35
+ end
36
+
37
+ def insert(column_family, key, hash_or_array, options = {})
38
+ if @batch
39
+ @batch << [:insert, column_family, key, hash_or_array, options]
40
+ else
41
+ raise ArgumentError if key.nil?
42
+ if column_family_type(column_family) == 'Standard'
43
+ insert_standard(column_family, key, hash_or_array)
44
+ else
45
+ insert_super(column_family, key, hash_or_array)
46
+ end
47
+ end
48
+ end
49
+
50
+ def insert_standard(column_family, key, hash_or_array)
51
+ old = cf(column_family)[key] || OrderedHash.new
52
+ cf(column_family)[key] = merge_and_sort(old, hash_or_array)
53
+ end
54
+
55
+ def insert_super(column_family, key, hash)
56
+ raise ArgumentError unless hash.is_a?(Hash)
57
+ cf(column_family)[key] ||= OrderedHash.new
58
+
59
+ hash.keys.each do |sub_key|
60
+ old = cf(column_family)[key][sub_key] || OrderedHash.new
61
+ cf(column_family)[key][sub_key] = merge_and_sort(old, hash[sub_key])
62
+ end
63
+ end
64
+
65
+ def batch
66
+ @batch = []
67
+ yield
68
+ b = @batch
69
+ @batch = nil
70
+ b.each do |mutation|
71
+ send(*mutation)
72
+ end
73
+ ensure
74
+ @batch = nil
75
+ end
76
+
77
+ def get(column_family, key, *columns_and_options)
78
+ column_family, column, sub_column, options =
79
+ extract_and_validate_params_for_real(column_family, [key], columns_and_options, READ_DEFAULTS)
80
+ if column_family_type(column_family) == 'Standard'
81
+ get_standard(column_family, key, column, options)
82
+ else
83
+ get_super(column_family, key, column, sub_column, options)
84
+ end
85
+ end
86
+
87
+ def get_standard(column_family, key, column, options)
88
+ row = cf(column_family)[key] || OrderedHash.new
89
+ if column
90
+ row[column]
91
+ else
92
+ apply_count(row, options[:count], options[:reversed])
93
+ end
94
+ end
95
+
96
+ def get_super(column_family, key, column, sub_column, options)
97
+ if column
98
+ if sub_column
99
+ if cf(column_family)[key] &&
100
+ cf(column_family)[key][column] &&
101
+ cf(column_family)[key][column][sub_column]
102
+ cf(column_family)[key][column][sub_column]
103
+ else
104
+ nil
105
+ end
106
+ else
107
+ row = cf(column_family)[key] && cf(column_family)[key][column] ?
108
+ cf(column_family)[key][column] :
109
+ OrderedHash.new
110
+ if options[:start] || options[:finish]
111
+ start = to_compare_with_type(options[:start], column_family, false)
112
+ finish = to_compare_with_type(options[:finish], column_family, false)
113
+ ret = OrderedHash.new
114
+ row.keys.each do |key|
115
+ if (start.nil? || key >= start) && (finish.nil? || key <= finish)
116
+ ret[key] = row[key]
117
+ end
118
+ end
119
+ row = ret
120
+ end
121
+ apply_count(row, options[:count], options[:reversed])
122
+ end
123
+ elsif cf(column_family)[key]
124
+ cf(column_family)[key]
125
+ else
126
+ OrderedHash.new
127
+ end
128
+ end
129
+
130
+ def exists?(column_family, key, column=nil)
131
+ !!get(column_family, key, column)
132
+ end
133
+
134
+ def multi_get(column_family, keys, *columns_and_options)
135
+ column_family, column, sub_column, options = extract_and_validate_params_for_real(column_family, keys, columns_and_options, READ_DEFAULTS)
136
+ keys.inject(OrderedHash.new) do |hash, key|
137
+ hash[key] = get(column_family, key)
138
+ hash
139
+ end
140
+ end
141
+
142
+ def remove(column_family, key, *columns_and_options)
143
+ column_family, column, sub_column, options = extract_and_validate_params_for_real(column_family, key, columns_and_options, WRITE_DEFAULTS)
144
+ if @batch
145
+ @batch << [:remove, column_family, key, column]
146
+ else
147
+ if column
148
+ if sub_column
149
+ cf(column_family)[key][column].delete(sub_column)
150
+ else
151
+ cf(column_family)[key].delete(column)
152
+ end
153
+ else
154
+ cf(column_family).delete(key)
155
+ end
156
+ end
157
+ end
158
+
159
+ def get_columns(column_family, key, *columns_and_options)
160
+ column_family, columns, sub_columns, options = extract_and_validate_params_for_real(column_family, key, columns_and_options, READ_DEFAULTS)
161
+ d = get(column_family, key)
162
+
163
+
164
+ if sub_columns
165
+ sub_columns.collect do |sub_column|
166
+ d[columns][sub_column]
167
+ end
168
+ else
169
+ columns.collect do |column|
170
+ d[column]
171
+ end
172
+ end
173
+ end
174
+
175
+ def count_columns(column_family, key, column=nil)
176
+ get(column_family, key, column).keys.length
177
+ end
178
+
179
+ def multi_get_columns(column_family, keys, columns)
180
+ keys.inject(OrderedHash.new) do |hash, key|
181
+ hash[key] = get_columns(column_family, key, columns)
182
+ hash
183
+ end
184
+ end
185
+
186
+ def multi_count_columns(column_family, keys)
187
+ keys.inject(OrderedHash.new) do |hash, key|
188
+ hash[key] = count_columns(column_family, key)
189
+ hash
190
+ end
191
+ end
192
+
193
+ def get_range(column_family, options = {})
194
+ column_family, _, _, options = extract_and_validate_params_for_real(column_family, "", [options], READ_DEFAULTS)
195
+ _get_range(column_family, options[:start], options[:finish], options[:count]).keys
196
+ end
197
+
198
+ def count_range(column_family, options={})
199
+ count = 0
200
+ l = []
201
+ start_key = ''
202
+ while (l = get_range(column_family, options.merge(:count => 1000, :start => start_key))).size > 0
203
+ count += l.size
204
+ start_key = l.last.succ
205
+ end
206
+ count
207
+ end
208
+
209
+ def schema(load=true)
210
+ if !load && !@schema
211
+ []
212
+ else
213
+ @schema ||= schema_for_keyspace(@keyspace)
214
+ end
215
+ end
216
+
217
+ private
218
+
219
+ def _get_range(column_family, start, finish, count)
220
+ ret = OrderedHash.new
221
+ start = to_compare_with_type(start, column_family)
222
+ finish = to_compare_with_type(finish, column_family)
223
+ cf(column_family).keys.sort.each do |key|
224
+ break if ret.keys.size >= count
225
+ if (start.nil? || key >= start) && (finish.nil? || key <= finish)
226
+ ret[key] = cf(column_family)[key]
227
+ end
228
+ end
229
+ ret
230
+ end
231
+
232
+ def schema_for_keyspace(keyspace)
233
+ doc = read_storage_xml
234
+ ret = {}
235
+ doc.css("Keyspaces Keyspace[@Name='#{keyspace}']").css('ColumnFamily').each do |cf|
236
+ ret[cf['Name']] = {}
237
+ if cf['CompareSubcolumnsWith']
238
+ ret[cf['Name']]['CompareSubcolumnsWith'] = 'org.apache.cassandra.db.marshal.' + cf['CompareSubcolumnsWith']
239
+ end
240
+ if cf['CompareWith']
241
+ ret[cf['Name']]['CompareWith'] = 'org.apache.cassandra.db.marshal.' + cf['CompareWith']
242
+ end
243
+ if cf['ColumnType'] == 'Super'
244
+ ret[cf['Name']]['Type'] = 'Super'
245
+ else
246
+ ret[cf['Name']]['Type'] = 'Standard'
247
+ end
248
+ end
249
+ ret
250
+ end
251
+
252
+ def read_storage_xml
253
+ @doc ||= Nokogiri::XML(open(@storage_xml))
254
+ end
255
+
256
+ def extract_and_validate_params_for_real(column_family, keys, args, options)
257
+ column_family, columns, sub_column, options = extract_and_validate_params(column_family, keys, args, options)
258
+ options[:start] = nil if options[:start] == ''
259
+ options[:finish] = nil if options[:finish] == ''
260
+ [column_family, to_compare_with_types(columns, column_family), to_compare_with_types(sub_column, column_family, false), options]
261
+ end
262
+
263
+ def to_compare_with_types(column_names, column_family, standard=true)
264
+ if column_names.is_a?(Array)
265
+ column_names.collect do |name|
266
+ to_compare_with_type(name, column_family, standard)
267
+ end
268
+ else
269
+ to_compare_with_type(column_names, column_family, standard)
270
+ end
271
+ end
272
+
273
+ def to_compare_with_type(column_name, column_family, standard=true)
274
+ return column_name if column_name.nil?
275
+ klass = if standard
276
+ schema[column_family.to_s]["CompareWith"]
277
+ else
278
+ schema[column_family.to_s]["CompareSubcolumnsWith"]
279
+ end
280
+
281
+ case klass
282
+ when "org.apache.cassandra.db.marshal.UTF8Type", "org.apache.cassandra.db.marshal.BytesType"
283
+ column_name
284
+ when "org.apache.cassandra.db.marshal.TimeUUIDType"
285
+ SimpleUUID::UUID.new(column_name)
286
+ when "org.apache.cassandra.db.marshal.LongType"
287
+ Long.new(column_name)
288
+ else
289
+ raise "Unknown column family type: #{klass.inspect}"
290
+ end
291
+ end
292
+
293
+ def column_family_type(column_family)
294
+ schema[column_family.to_s]['Type']
295
+ end
296
+
297
+ def cf(column_family)
298
+ @data[column_family.to_sym] ||= OrderedHash.new
299
+ end
300
+
301
+ def merge_and_sort(old_stuff, new_stuff)
302
+ if new_stuff.is_a?(Array)
303
+ new_stuff = new_stuff.inject({}){|h,k| h[k] = nil; h }
304
+ end
305
+ OrderedHash[old_stuff.merge(new_stuff).sort{|a,b| a[0] <=> b[0]}]
306
+ end
307
+
308
+ def apply_count(row, count, reversed=false)
309
+ if count
310
+ keys = row.keys.sort
311
+ keys = keys.reverse if reversed
312
+ keys = keys[0...count]
313
+ keys.inject(OrderedHash.new) do |memo, key|
314
+ memo[key] = row[key]
315
+ memo
316
+ end
317
+ else
318
+ row
319
+ end
320
+ end
321
+ end
322
+ end