voldemort-rb 0.1.2 → 0.1.3

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