voldemort-rb 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,6 +10,10 @@ class Connection
10
10
  attr_accessor :connected_node # The VoldemortNode we are connected to.
11
11
  attr_accessor :request_count # Used to track the number of request a node receives.
12
12
  attr_accessor :request_limit_per_node # Limit the number of request per node.
13
+ attr_accessor :key_serializer_schemas
14
+ attr_accessor :value_serializer_schemas
15
+ attr_accessor :key_serializer_type
16
+ attr_accessor :value_serializer_type
13
17
 
14
18
  STATUS_OK = "ok"
15
19
  PROTOCOL = "pb0"
@@ -37,11 +41,16 @@ class Connection
37
41
  stores_response = self.get_from("metadata", "stores.xml", false)
38
42
  stores_xml = stores_response[1][0][1]
39
43
 
44
+ self.key_serializer_type = parse_schema_type(stores_xml, 'key-serializer')
45
+ self.value_serializer_type = parse_schema_type(stores_xml, 'value-serializer')
46
+ self.key_serializer_schemas = parse_schema_from(stores_xml, 'key-serializer')
47
+ self.value_serializer_schemas = parse_schema_from(stores_xml, 'value-serializer')
48
+
40
49
  self.connect_to_random_node
41
50
  rescue StandardError => e
42
- raise("There was an error trying to bootstrap from the specified servers: #{e}")
51
+ raise("There was an error trying to bootstrap from the specified servers: #{e}")
43
52
  end
44
-
53
+
45
54
  def connect_to_random_node
46
55
  nodes = self.nodes.sort_by { rand }
47
56
  for node in nodes do
@@ -52,6 +61,28 @@ class Connection
52
61
  end
53
62
  end
54
63
  end
64
+
65
+ def parse_schema_type(xml, serializer = 'value-serializer')
66
+ doc = REXML::Document.new(xml)
67
+ type_doc = XPath.first(doc, "//stores/store[name = \"#{self.db_name}\"]/#{serializer}/type")
68
+
69
+ if(type_doc != nil)
70
+ return type_doc.text
71
+ else
72
+ return nil
73
+ end
74
+ end
75
+
76
+ def parse_schema_from(xml, serializer = 'value-serializer')
77
+ parsed_schemas = {}
78
+ doc = REXML::Document.new(xml)
79
+
80
+ XPath.each(doc, "//stores/store[name = \"#{self.db_name}\"]/#{serializer}/schema-info") do |value_serializer|
81
+ parsed_schemas[value_serializer.attributes['version']] = value_serializer.text
82
+ end
83
+
84
+ return parsed_schemas
85
+ end
55
86
 
56
87
  def parse_nodes_from(xml)
57
88
  nodes = []
data/lib/voldemort-rb.rb CHANGED
@@ -1,42 +1,65 @@
1
1
  require File.join(File.dirname(__FILE__), "connection", "voldemort_node")
2
2
  require File.join(File.dirname(__FILE__), "connection", "connection")
3
3
  require File.join(File.dirname(__FILE__), "connection", "tcp_connection")
4
+ require File.join(File.dirname(__FILE__), "voldemort-serializer")
4
5
 
5
6
  class VoldemortClient
6
7
  attr_accessor :connection
7
8
  attr_accessor :conflict_resolver
9
+ attr_accessor :key_serializer
10
+ attr_accessor :value_serializer
8
11
 
9
12
  def initialize(db_name, *hosts, &block)
10
13
  self.conflict_resolver = block unless !block
11
14
  self.connection = TCPConnection.new(db_name, hosts) # implement and modifiy if you don't want to use TCP protobuf.
12
15
  self.connection.bootstrap
16
+
17
+ case(self.connection.key_serializer_type)
18
+ when 'json'
19
+ self.key_serializer = VoldemortJsonBinarySerializer.new(self.connection.key_serializer_schemas)
20
+ else
21
+ self.key_serializer = VoldemortPassThroughSerializer.new({})
22
+ end
23
+
24
+ case(self.connection.value_serializer_type)
25
+ when 'json'
26
+ self.value_serializer = VoldemortJsonBinarySerializer.new(self.connection.value_serializer_schemas)
27
+ else
28
+ self.value_serializer = VoldemortPassThroughSerializer.new({})
29
+ end
13
30
  end
14
31
 
15
32
  def get(key)
16
- versions = self.connection.get(key)
33
+ versions = self.connection.get(key_serializer.to_bytes(key))
17
34
  version = self.resolve_conflicts(versions.versioned)
18
35
  if version
19
- version.value
36
+ value_serializer.to_object(version.value)
20
37
  else
21
38
  nil
22
39
  end
23
40
  end
24
41
 
25
42
  def get_all(keys)
43
+ serialized_keys = []
44
+
45
+ keys.each do |key|
46
+ serialized_keys << key_serializer.to_bytes(key)
47
+ end
48
+
26
49
  all_version = self.connection.get_all(keys)
27
50
  values = {}
28
51
  all_version.values.collect do |v|
29
- values[v.key] = self.resolve_conflicts(v.versions).value
52
+ values[v.key] = value_serializer.to_object(self.resolve_conflicts(v.versions).value)
30
53
  end
31
54
  values
32
55
  end
33
56
 
34
57
  def put(key, value, version = nil)
35
- self.connection.put(key, value)
58
+ self.connection.put(key_serializer.to_bytes(key), value_serializer.to_bytes(value))
36
59
  end
37
60
 
38
61
  def delete(key)
39
- self.connection.delete(key)
62
+ self.connection.delete(key_serializer.to_bytes(key))
40
63
  end
41
64
 
42
65
  def resolve_conflicts(versions)
@@ -0,0 +1,485 @@
1
+ require 'json'
2
+ require 'voldemort-rb'
3
+
4
+ class VoldemortJsonBinarySerializer
5
+ attr_accessor :has_version
6
+ attr_accessor :type_def_versions
7
+
8
+ BYTE_MIN_VAL = -128
9
+ SHORT_MIN_VAL = -32768
10
+ SHORT_MAX_VAL = 2 ** 15 - 1
11
+ INT_MIN_VAL = -2147483648
12
+ LONG_MIN_VAL = -9223372036854775808
13
+ FLOAT_MIN_VAL = 2 ** -149
14
+ DOUBLE_MIN_VAL = 2 ** -1074
15
+
16
+ def initialize(type_def_versions)
17
+ @has_version = true
18
+ @type_def_versions = {}
19
+
20
+ # convert versioned json strings to ruby objects
21
+ type_def_versions.each_pair do |version, json_type_def_version|
22
+ @type_def_versions[version.to_i] = get_type_def(json_type_def_version)
23
+ end
24
+ end
25
+
26
+ def to_signed(unsigned, bits)
27
+ max_unsigned = 2 ** bits
28
+ max_signed = 2 ** (bits - 1)
29
+ to_signed = proc { |n| (n >= max_signed) ? n - max_unsigned : n }
30
+ return to_signed[unsigned]
31
+ end
32
+
33
+ def get_type_def(json_type_def_version)
34
+ # replace all single quotes with " since the JSON parser wants it this way
35
+ json_type_def_version = json_type_def_version.gsub(/\'/, '"')
36
+
37
+ if((json_type_def_version =~ /[\{\[]/) == 0)
38
+ # check if the json is a list or string, since these are
39
+ # the only ones that JSON.parse() will work with
40
+ return JSON.parse(json_type_def_version)
41
+ else
42
+ # otherwise it's a primitive, so just strip the quotes
43
+ return json_type_def_version.gsub(/\"/, '')
44
+ end
45
+ end
46
+
47
+ def read_slice(length, bytes)
48
+ substr = bytes[0, length]
49
+ bytes.slice!(0..length - 1)
50
+ return substr
51
+ end
52
+
53
+ # handle serialization
54
+
55
+ def to_bytes(object)
56
+ bytes = ''
57
+ newest_version = 0 # TODO get highest number from map
58
+ type_def = @type_def_versions[newest_version]
59
+
60
+ if(@has_version)
61
+ bytes << newest_version.chr
62
+ end
63
+
64
+ bytes << write(object, type_def)
65
+
66
+ return bytes
67
+ end
68
+
69
+ def write(object, type)
70
+ bytes = ''
71
+
72
+ if(type.kind_of? Hash)
73
+ if(object != nil && !object.kind_of?(Hash))
74
+ # TODO throw exception
75
+ else
76
+ bytes << write_map(object, type)
77
+ end
78
+ elsif(type.kind_of? Array)
79
+ if(object != nil && !object.kind_of?(Array))
80
+ # TODO throw exception
81
+ else
82
+ bytes << write_list(object, type)
83
+ end
84
+ else
85
+ case(type)
86
+ when 'string': bytes << write_string(object)
87
+ when 'int8': bytes << write_int8(object)
88
+ when 'int16': bytes << write_int16(object)
89
+ when 'int32': bytes << write_int32(object)
90
+ when 'int64': bytes << write_int64(object)
91
+ when 'float32': bytes << write_float32(object)
92
+ when 'float64': bytes << write_float64(object)
93
+ when 'date': bytes << write_date(object)
94
+ when 'bytes': bytes << write_bytes(object)
95
+ when 'boolean': bytes << write_boolean(object)
96
+ else
97
+ # TODO throw unsupported type exception
98
+ end
99
+ end
100
+
101
+ if(bytes == '')
102
+ return nil
103
+ end
104
+
105
+ return bytes
106
+ end
107
+
108
+ def write_boolean(object)
109
+ bytes = ''
110
+
111
+ if(object == nil)
112
+ bytes << [BYTE_MIN_VAL].pack('c')
113
+ elsif(object)
114
+ bytes << [0x1].pack('c')
115
+ else
116
+ bytes << [0x0].pack('c')
117
+ end
118
+
119
+ return bytes
120
+ end
121
+
122
+ def write_string(object)
123
+ return write_bytes(object)
124
+ end
125
+
126
+ def write_int8(object)
127
+ bytes = ''
128
+
129
+ if(object == BYTE_MIN_VAL)
130
+ # TODO throw underflow exception
131
+ else
132
+ if(object == nil)
133
+ object = BYTE_MIN_VAL
134
+ end
135
+
136
+ bytes << [object].pack('c')
137
+ end
138
+
139
+ return bytes
140
+ end
141
+
142
+ def write_int16(object)
143
+ bytes = ''
144
+
145
+ if(object == SHORT_MIN_VAL)
146
+ # TODO throw underflow exception
147
+ else
148
+ if(object == nil)
149
+ object = SHORT_MIN_VAL
150
+ end
151
+
152
+ bytes << [object].pack('n')
153
+ end
154
+
155
+ return bytes
156
+ end
157
+
158
+ def write_int32(object)
159
+ bytes = ''
160
+
161
+ if(object == INT_MIN_VAL)
162
+ # TODO throw underflow exception
163
+ else
164
+ if(object == nil)
165
+ object = INT_MIN_VAL
166
+ end
167
+
168
+ # reverse here to switch little endian to big endian
169
+ # this is because pack('N') is choking on 'bigint', wtf?
170
+ bytes << [object].pack('i').reverse
171
+ end
172
+
173
+ return bytes
174
+ end
175
+
176
+ def write_int64(object)
177
+ bytes = ''
178
+
179
+ if(object == LONG_MIN_VAL)
180
+ # TODO throw underflow exception
181
+ else
182
+ if(object == nil)
183
+ object = LONG_MIN_VAL
184
+ end
185
+
186
+ # reverse here to switch little endian to big endian
187
+ # this is because pack('N') is choking on 'bigint', wtf?
188
+ bytes << [object].pack('q').reverse
189
+ end
190
+
191
+ return bytes
192
+ end
193
+
194
+ def write_float32(object)
195
+ bytes = ''
196
+
197
+ if(object == FLOAT_MIN_VAL)
198
+ # TODO throw underflow exception
199
+ else
200
+ if(object == nil)
201
+ object = FLOAT_MIN_VAL
202
+ end
203
+
204
+ bytes << [object].pack('g')
205
+ end
206
+
207
+ return bytes
208
+ end
209
+
210
+ def write_float64(object)
211
+ bytes = ''
212
+
213
+ if(object == DOUBLE_MIN_VAL)
214
+ # TODO throw underflow exception
215
+ else
216
+ if(object == nil)
217
+ object = DOUBLE_MIN_VAL
218
+ end
219
+
220
+ bytes << [object].pack('G')
221
+ end
222
+
223
+ return bytes
224
+ end
225
+
226
+ def write_date(object)
227
+ bytes = ''
228
+
229
+ if(object == LONG_MIN_VAL)
230
+ # TODO throw underflow exception
231
+ else
232
+ if(object == nil)
233
+ bytes << write_int64(nil)
234
+ else
235
+ bytes << write_int64((object.to_f * 1000).to_i)
236
+ end
237
+ end
238
+
239
+ return bytes
240
+ end
241
+
242
+ def write_bytes(object)
243
+ bytes = ''
244
+
245
+ if(object == nil)
246
+ bytes << write_int16(-1)
247
+ elsif(object.length < SHORT_MAX_VAL)
248
+ bytes << write_int16(object.length)
249
+ bytes << object
250
+ else
251
+ # TODO throw "length too long to serialize" exception
252
+ end
253
+
254
+ return bytes
255
+ end
256
+
257
+ def write_map(object, type)
258
+ bytes = ''
259
+
260
+ if(object == nil)
261
+ bytes << [-1].pack('c')
262
+ else
263
+ bytes << [1].pack('c')
264
+
265
+ if(object.length != type.length)
266
+ # TODO throw exception here.. invalid map serialization, expected: but got
267
+ else
268
+ type.sort.each do |type_pair|
269
+ key = type_pair.first
270
+ subtype = type_pair.last
271
+
272
+ if(!object.has_key? key)
273
+ # TODO throw "missing property exception"
274
+ else
275
+ bytes << write(object[key], subtype)
276
+ end
277
+ end
278
+ end
279
+ end
280
+
281
+ return bytes
282
+ end
283
+
284
+ def write_list(object, type)
285
+ bytes = ''
286
+
287
+ if(type.length != 1)
288
+ # TODO throw new exception (expected single type in list)
289
+ else
290
+ entry_type = type.first
291
+
292
+ if(object == nil)
293
+ bytes << write_int16(-1)
294
+ elsif(object.length < SHORT_MAX_VAL)
295
+ bytes << write_int16(object.length)
296
+ object.each do |o|
297
+ bytes << write(o, entry_type)
298
+ end
299
+ else
300
+ # TODO throw serialization exception
301
+ end
302
+ end
303
+
304
+ return bytes
305
+ end
306
+
307
+ # handle deserialization
308
+
309
+ def to_object(bytes)
310
+ version = 0
311
+
312
+ if(@has_version)
313
+ version = read_slice(1, bytes).to_i
314
+ end
315
+
316
+ type = @type_def_versions[version]
317
+
318
+ if(type == nil)
319
+ # TODO throw exception here
320
+ end
321
+
322
+ return read(bytes, type)
323
+ end
324
+
325
+ def read(bytes, type)
326
+ if(type.kind_of? Hash)
327
+ return read_map(bytes, type)
328
+ elsif(type.kind_of? Array)
329
+ return read_list(bytes, type)
330
+ else
331
+ case(type)
332
+ when 'string': return read_bytes(bytes)
333
+ when 'int8': return read_int8(bytes)
334
+ when 'int16': return read_int16(bytes)
335
+ when 'int32': return read_int32(bytes)
336
+ when 'int64': return read_int64(bytes)
337
+ when 'float32': return read_float32(bytes)
338
+ when 'float64': return read_float64(bytes)
339
+ when 'date': return read_date(bytes)
340
+ when 'bytes': return read_bytes(bytes)
341
+ when 'boolean': return read_boolean(bytes)
342
+ # TODO default throw unknown type exception
343
+ end
344
+ end
345
+ end
346
+
347
+ def read_map(bytes, type)
348
+ # convert to char to string, and string to int
349
+ if(read_slice(1, bytes).unpack('c').to_s.to_i == -1)
350
+ return nil
351
+ else
352
+ object = {}
353
+
354
+ type.sort.each do |type_pair|
355
+ name = type_pair.first
356
+ sub_type = type_pair.last
357
+ object[name] = read(bytes, sub_type)
358
+ end
359
+
360
+ return object
361
+ end
362
+ end
363
+
364
+ def read_list(bytes, type)
365
+ size = read_int16(bytes)
366
+ if(size < 0)
367
+ return nil
368
+ else
369
+ object = []
370
+
371
+ size.times { object << read(bytes, type.first) }
372
+
373
+ return object
374
+ end
375
+ end
376
+
377
+ def read_boolean(bytes)
378
+ b = read_slice(1, bytes).unpack('c').first
379
+
380
+ if(b < 0)
381
+ return nil
382
+ elsif(b == 0)
383
+ return false
384
+ else
385
+ return true
386
+ end
387
+ end
388
+
389
+ def read_int8(bytes)
390
+ b = read_slice(1, bytes).unpack("c").first.to_i
391
+
392
+ if(b == BYTE_MIN_VAL)
393
+ return nil
394
+ end
395
+
396
+ return b
397
+ end
398
+
399
+ def read_int16(bytes)
400
+ s = to_signed(read_slice(2, bytes).unpack("n").first, 16)
401
+
402
+ if(s == SHORT_MIN_VAL)
403
+ return nil
404
+ end
405
+
406
+ return s
407
+ end
408
+
409
+ def read_int32(bytes)
410
+ # reverse here to switch little endian to big endian
411
+ # this is because pack('N') is choking on 'bigint', wtf?
412
+ i = read_slice(4, bytes).reverse.unpack("i").first.to_i
413
+
414
+ if(i == INT_MIN_VAL)
415
+ return nil
416
+ end
417
+
418
+ return i
419
+ end
420
+
421
+ def read_int64(bytes)
422
+ # reverse here to switch little endian to big endian
423
+ # this is because pack('N') is choking on 'bigint', wtf?
424
+ l = read_slice(8, bytes).reverse.unpack("q").first.to_i
425
+
426
+ if(l == LONG_MIN_VAL)
427
+ return nil
428
+ end
429
+
430
+ return l
431
+ end
432
+
433
+ def read_float32(bytes)
434
+ f = read_slice(4, bytes).unpack("g").first.to_f
435
+
436
+ if(f == FLOAT_MIN_VAL)
437
+ return nil
438
+ end
439
+
440
+ return f
441
+ end
442
+
443
+ def read_float64(bytes)
444
+ d = read_slice(8, bytes).unpack("G").first.to_f
445
+
446
+ if(d == DOUBLE_MIN_VAL)
447
+ return nil
448
+ end
449
+
450
+ return d
451
+ end
452
+
453
+ def read_date(bytes)
454
+ d = read_int64(bytes)
455
+
456
+ if(d != nil)
457
+ d = Time.at((d / 1000).to_i, d % 1000)
458
+ end
459
+
460
+ return d
461
+ end
462
+
463
+ def read_bytes(bytes)
464
+ size = read_int16(bytes)
465
+
466
+ if(size < 0)
467
+ return nil
468
+ else
469
+ return read_slice(size, bytes)
470
+ end
471
+ end
472
+ end
473
+
474
+ class VoldemortPassThroughSerializer
475
+ def initialize(map)
476
+ end
477
+
478
+ def to_bytes(bytes)
479
+ bytes
480
+ end
481
+
482
+ def to_object(object)
483
+ object
484
+ end
485
+ end
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: voldemort-rb
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 29
4
5
  prerelease: false
5
6
  segments:
6
7
  - 0
7
8
  - 1
8
- - 2
9
- version: 0.1.2
9
+ - 3
10
+ version: 0.1.3
10
11
  platform: ruby
11
12
  authors:
12
13
  - Alejandro Crosa
@@ -14,7 +15,7 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-07-13 00:00:00 -07:00
18
+ date: 2010-09-13 00:00:00 -07:00
18
19
  default_executable:
19
20
  dependencies: []
20
21
 
@@ -33,6 +34,7 @@ files:
33
34
  - README.md
34
35
  - Rakefile
35
36
  - lib/voldemort-rb.rb
37
+ - lib/voldemort-serializer.rb
36
38
  - lib/connection/connection.rb
37
39
  - lib/connection/tcp_connection.rb
38
40
  - lib/connection/voldemort_node.rb
@@ -53,23 +55,27 @@ rdoc_options: []
53
55
  require_paths:
54
56
  - lib
55
57
  required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
56
59
  requirements:
57
60
  - - ">="
58
61
  - !ruby/object:Gem::Version
62
+ hash: 3
59
63
  segments:
60
64
  - 0
61
65
  version: "0"
62
66
  required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
63
68
  requirements:
64
69
  - - ">="
65
70
  - !ruby/object:Gem::Version
71
+ hash: 3
66
72
  segments:
67
73
  - 0
68
74
  version: "0"
69
75
  requirements: []
70
76
 
71
77
  rubyforge_project:
72
- rubygems_version: 1.3.6
78
+ rubygems_version: 1.3.7
73
79
  signing_key:
74
80
  specification_version: 3
75
81
  summary: A Ruby client for the Voldemort distributed key value store