jamesgolick-cassandra 0.8.2

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.
@@ -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