Class: BSON

Inherits:
Object
  • Object
show all
Includes:
XGen::Mongo::Driver
Defined in:
lib/mongo/util/bson.rb

Overview

A BSON seralizer/deserializer.

Constant Summary collapse

MINKEY =
-1
EOO =
0
NUMBER =
1
STRING =
2
OBJECT =
3
ARRAY =
4
BINARY =
5
UNDEFINED =
6
OID =
7
BOOLEAN =
8
DATE =
9
NULL =
10
REGEX =
11
REF =
12
CODE =
13
SYMBOL =
14
CODE_W_SCOPE =
15
NUMBER_INT =
16
MAXKEY =
127

Constants included from XGen::Mongo::Driver

XGen::Mongo::Driver::OP_DELETE, XGen::Mongo::Driver::OP_GET_MORE, XGen::Mongo::Driver::OP_INSERT, XGen::Mongo::Driver::OP_KILL_CURSORS, XGen::Mongo::Driver::OP_MSG, XGen::Mongo::Driver::OP_QUERY, XGen::Mongo::Driver::OP_REPLY, XGen::Mongo::Driver::OP_UPDATE

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(db = nil) ⇒ BSON

Returns a new instance of BSON.



65
66
67
68
69
# File 'lib/mongo/util/bson.rb', line 65

def initialize(db=nil)
  # db is only needed during deserialization when the data contains a DBRef
  @db = db
  @buf = ByteBuffer.new
end

Class Method Details

.serialize_cstr(buf, val) ⇒ Object



61
62
63
# File 'lib/mongo/util/bson.rb', line 61

def self.serialize_cstr(buf, val)
  buf.put_array(to_utf8(val.to_s).unpack("C*") + [0])
end

.to_utf8(str) ⇒ Object



52
53
54
# File 'lib/mongo/util/bson.rb', line 52

def self.to_utf8(str)
  str.encode("utf-8")
end

Instance Method Details

#bson_type(o, key) ⇒ Object



407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'lib/mongo/util/bson.rb', line 407

def bson_type(o, key)
  case o
  when nil
    NULL
  when Integer
    NUMBER_INT
  when Numeric
    NUMBER
  when ByteBuffer
    BINARY
  when String
    # magic awful stuff - the DB requires that a where clause is sent as CODE
    key == "$where" ? CODE : STRING
  when Array
    ARRAY
  when Regexp
    REGEX
  when ObjectID
    OID
  when DBRef
    REF
  when true, false
    BOOLEAN
  when Time
    DATE
  when Hash
    OBJECT
  when Symbol
    SYMBOL
  when Undefined
    UNDEFINED
  else
    raise "Unknown type of object: #{o.class.name}"
  end
end

#deserialize(buf = nil, parent = nil) ⇒ Object



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/mongo/util/bson.rb', line 127

def deserialize(buf=nil, parent=nil)
  # If buf is nil, use @buf, assumed to contain already-serialized BSON.
  # This is only true during testing.
  @buf = ByteBuffer.new(buf.to_a) if buf
  @buf.rewind
  @buf.get_int                # eat message size
  doc = OrderedHash.new
  while @buf.more?
    type = @buf.get
    case type
    when STRING, CODE
      key = deserialize_cstr(@buf)
      doc[key] = deserialize_string_data(@buf)
    when SYMBOL
      key = deserialize_cstr(@buf)
      doc[key] = deserialize_string_data(@buf).intern
    when NUMBER
      key = deserialize_cstr(@buf)
      doc[key] = deserialize_number_data(@buf)
    when NUMBER_INT
      key = deserialize_cstr(@buf)
      doc[key] = deserialize_number_int_data(@buf)
    when OID
      key = deserialize_cstr(@buf)
      doc[key] = deserialize_oid_data(@buf)
    when ARRAY
      key = deserialize_cstr(@buf)
      doc[key] = deserialize_array_data(@buf, doc)
    when REGEX
      key = deserialize_cstr(@buf)
      doc[key] = deserialize_regex_data(@buf)
    when OBJECT
      key = deserialize_cstr(@buf)
      doc[key] = deserialize_object_data(@buf, doc)
    when BOOLEAN
      key = deserialize_cstr(@buf)
      doc[key] = deserialize_boolean_data(@buf)
    when DATE
      key = deserialize_cstr(@buf)
      doc[key] = deserialize_date_data(@buf)
    when NULL
      key = deserialize_cstr(@buf)
      doc[key] = nil
    when UNDEFINED
      key = deserialize_cstr(@buf)
      doc[key] = Undefined.new
    when REF
      key = deserialize_cstr(@buf)
      doc[key] = deserialize_dbref_data(@buf, key, parent)
    when BINARY
      key = deserialize_cstr(@buf)
      doc[key] = deserialize_binary_data(@buf)
    when CODE_W_SCOPE
      # TODO CODE_W_SCOPE unimplemented; may be removed
      raise "unimplemented type #{type}"
    when EOO
      break
    else
      raise "Unknown type #{type}, key = #{key}"
    end
  end
  @buf.rewind
  doc
end

#deserialize_array_data(buf, parent) ⇒ Object



230
231
232
233
234
235
# File 'lib/mongo/util/bson.rb', line 230

def deserialize_array_data(buf, parent)
  h = deserialize_object_data(buf, parent)
  a = []
  h.each { |k, v| a[k.to_i] = v }
  a
end

#deserialize_binary_data(buf) ⇒ Object



268
269
270
271
272
273
# File 'lib/mongo/util/bson.rb', line 268

def deserialize_binary_data(buf)
  len = buf.get_int
  type = buf.get
  len = buf.get_int if type == Binary::SUBTYPE_BYTES
  Binary.new(buf.get(len), type)
end

#deserialize_boolean_data(buf) ⇒ Object



212
213
214
# File 'lib/mongo/util/bson.rb', line 212

def deserialize_boolean_data(buf)
  buf.get == 1
end

#deserialize_cstr(buf) ⇒ Object



394
395
396
397
398
399
400
401
402
403
404
405
# File 'lib/mongo/util/bson.rb', line 394

def deserialize_cstr(buf)
  chars = ""
  while true
    b = buf.get
    break if b == 0
    chars << b.chr
  end
  if RUBY_VERSION >= '1.9'
    chars.force_encoding("utf-8") # Mongo stores UTF-8
  end
  chars
end

#deserialize_date_data(buf) ⇒ Object



207
208
209
210
# File 'lib/mongo/util/bson.rb', line 207

def deserialize_date_data(buf)
  millisecs = buf.get_long()
  Time.at(millisecs.to_f / 1000.0) # at() takes fractional seconds
end

#deserialize_dbref_data(buf, key, parent) ⇒ Object



262
263
264
265
266
# File 'lib/mongo/util/bson.rb', line 262

def deserialize_dbref_data(buf, key, parent)
  ns = deserialize_string_data(buf)
  oid = deserialize_oid_data(buf)
  DBRef.new(parent, key, @db, ns, oid)
end

#deserialize_number_data(buf) ⇒ Object



216
217
218
# File 'lib/mongo/util/bson.rb', line 216

def deserialize_number_data(buf)
  buf.get_double
end

#deserialize_number_int_data(buf) ⇒ Object



220
221
222
# File 'lib/mongo/util/bson.rb', line 220

def deserialize_number_int_data(buf)
  buf.get_int
end

#deserialize_object_data(buf, parent) ⇒ Object



224
225
226
227
228
# File 'lib/mongo/util/bson.rb', line 224

def deserialize_object_data(buf, parent)
  size = buf.get_int
  buf.position -= 4
  BSON.new(@db).deserialize(buf.get(size), parent)
end

#deserialize_oid_data(buf) ⇒ Object



258
259
260
# File 'lib/mongo/util/bson.rb', line 258

def deserialize_oid_data(buf)
  ObjectID.new(buf.get(12))
end

#deserialize_regex_data(buf) ⇒ Object



237
238
239
240
241
242
243
244
245
246
# File 'lib/mongo/util/bson.rb', line 237

def deserialize_regex_data(buf)
  str = deserialize_cstr(buf)
  options_str = deserialize_cstr(buf)
  options = 0
  options |= Regexp::IGNORECASE if options_str.include?('i')
  options |= Regexp::MULTILINE if options_str.include?('m')
  options |= Regexp::EXTENDED if options_str.include?('x')
  options_str.gsub!(/[imx]/, '') # Now remove the three we understand
  RegexpOfHolding.new(str, options, options_str)
end

#deserialize_string_data(buf) ⇒ Object



248
249
250
251
252
253
254
255
256
# File 'lib/mongo/util/bson.rb', line 248

def deserialize_string_data(buf)
  len = buf.get_int
  bytes = buf.get(len)
  str = bytes[0..-2].pack("C*")
  if RUBY_VERSION >= '1.9'
    str.force_encoding("utf-8")
  end
  str
end

#hex_dumpObject

For debugging.



193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/mongo/util/bson.rb', line 193

def hex_dump
  str = ''
  @buf.to_a.each_with_index { |b,i|
    if (i % 8) == 0
      str << "\n" if i > 0
      str << '%4d:  ' % i
    else
      str << ' '
    end
    str << '%02X' % b
  }
  str
end

#serialize(obj) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/mongo/util/bson.rb', line 75

def serialize(obj)
  raise "Document is null" unless obj

  @buf.rewind
  # put in a placeholder for the total size
  @buf.put_int(0)

  # Write key/value pairs. Always write _id first if it exists.
  oid = obj['_id'] || obj[:_id]
  serialize_key_value('_id', oid) if oid
  obj.each {|k, v| serialize_key_value(k, v) unless k == '_id' || k == :_id }

  serialize_eoo_element(@buf)
  @buf.put_int(@buf.size, 0)
  self
end

#serialize_array_element(buf, key, val) ⇒ Object



342
343
344
345
346
347
348
# File 'lib/mongo/util/bson.rb', line 342

def serialize_array_element(buf, key, val)
  # Turn array into hash with integer indices as keys
  h = OrderedHash.new
  i = 0
  val.each { |v| h[i] = v; i += 1 }
  serialize_object_element(buf, key, h, ARRAY)
end

#serialize_binary_element(buf, key, val) ⇒ Object



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/mongo/util/bson.rb', line 289

def serialize_binary_element(buf, key, val)
  buf.put(BINARY)
  self.class.serialize_cstr(buf, key)

  bytes = val.to_a
  num_bytes = bytes.length
  subtype = val.respond_to?(:subtype) ? val.subtype : Binary::SUBTYPE_BYTES
  if subtype == Binary::SUBTYPE_BYTES
    buf.put_int(num_bytes + 4)
    buf.put(subtype)
    buf.put_int(num_bytes)
    buf.put_array(bytes)
  else
    buf.put_int(num_bytes)
    buf.put(subtype)
    buf.put_array(bytes)
  end
end

#serialize_boolean_element(buf, key, val) ⇒ Object



313
314
315
316
317
# File 'lib/mongo/util/bson.rb', line 313

def serialize_boolean_element(buf, key, val)
  buf.put(BOOLEAN)
  self.class.serialize_cstr(buf, key)
  buf.put(val ? 1 : 0)
end

#serialize_date_element(buf, key, val) ⇒ Object



319
320
321
322
323
324
# File 'lib/mongo/util/bson.rb', line 319

def serialize_date_element(buf, key, val)
  buf.put(DATE)
  self.class.serialize_cstr(buf, key)
  millisecs = (val.to_f * 1000).to_i
  buf.put_long(millisecs)
end

#serialize_dbref_element(buf, key, val) ⇒ Object



284
285
286
287
# File 'lib/mongo/util/bson.rb', line 284

def serialize_dbref_element(buf, key, val)
  serialize_string_element(buf, key, val.namespace, REF)
  buf.put_array(val.object_id.to_a)
end

#serialize_eoo_element(buf) ⇒ Object



275
276
277
# File 'lib/mongo/util/bson.rb', line 275

def serialize_eoo_element(buf)
  buf.put(EOO)
end

#serialize_key_value(k, v) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/mongo/util/bson.rb', line 92

def serialize_key_value(k, v)
    type = bson_type(v, k)
    case type
    when STRING, CODE, SYMBOL
      serialize_string_element(@buf, k, v, type)
    when NUMBER, NUMBER_INT
      serialize_number_element(@buf, k, v, type)
    when OBJECT
      serialize_object_element(@buf, k, v)
    when OID
      serialize_oid_element(@buf, k, v)
    when ARRAY
      serialize_array_element(@buf, k, v)
    when REGEX
      serialize_regex_element(@buf, k, v)
    when BOOLEAN
      serialize_boolean_element(@buf, k, v)
    when DATE
      serialize_date_element(@buf, k, v)
    when NULL
      serialize_null_element(@buf, k)
    when REF
      serialize_dbref_element(@buf, k, v)
    when BINARY
      serialize_binary_element(@buf, k, v)
    when UNDEFINED
      serialize_undefined_element(@buf, k)
    when CODE_W_SCOPE
      # TODO CODE_W_SCOPE unimplemented; may be removed
      raise "unimplemented type #{type}"
    else
      raise "unhandled type #{type}"
    end
end

#serialize_null_element(buf, key) ⇒ Object



279
280
281
282
# File 'lib/mongo/util/bson.rb', line 279

def serialize_null_element(buf, key)
  buf.put(NULL)
  self.class.serialize_cstr(buf, key)
end

#serialize_number_element(buf, key, val, type) ⇒ Object



326
327
328
329
330
331
332
333
334
# File 'lib/mongo/util/bson.rb', line 326

def serialize_number_element(buf, key, val, type)
  buf.put(type)
  self.class.serialize_cstr(buf, key)
  if type == NUMBER
    buf.put_double(val)
  else
    buf.put_int(val)
  end
end

#serialize_object_element(buf, key, val, opcode = OBJECT) ⇒ Object



336
337
338
339
340
# File 'lib/mongo/util/bson.rb', line 336

def serialize_object_element(buf, key, val, opcode=OBJECT)
  buf.put(opcode)
  self.class.serialize_cstr(buf, key)
  buf.put_array(BSON.new.serialize(val).to_a)
end

#serialize_oid_element(buf, key, val) ⇒ Object



367
368
369
370
371
372
# File 'lib/mongo/util/bson.rb', line 367

def serialize_oid_element(buf, key, val)
  buf.put(OID)
  self.class.serialize_cstr(buf, key)

  buf.put_array(val.to_a)
end

#serialize_regex_element(buf, key, val) ⇒ Object



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/mongo/util/bson.rb', line 350

def serialize_regex_element(buf, key, val)
  buf.put(REGEX)
  self.class.serialize_cstr(buf, key)

  str = val.to_s.sub(/.*?:/, '')[0..-2] # Turn "(?xxx:yyy)" into "yyy"
  self.class.serialize_cstr(buf, str)

  options = val.options
  options_str = ''
  options_str << 'i' if ((options & Regexp::IGNORECASE) != 0)
  options_str << 'm' if ((options & Regexp::MULTILINE) != 0)
  options_str << 'x' if ((options & Regexp::EXTENDED) != 0)
  options_str << val.extra_options_str if val.respond_to?(:extra_options_str)
  # Must store option chars in alphabetical order
  self.class.serialize_cstr(buf, options_str.split(//).sort.uniq.join)
end

#serialize_string_element(buf, key, val, type) ⇒ Object



374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/mongo/util/bson.rb', line 374

def serialize_string_element(buf, key, val, type)
  buf.put(type)
  self.class.serialize_cstr(buf, key)

  # Make a hole for the length
  len_pos = buf.position
  buf.put_int(0)

  # Save the string
  start_pos = buf.position
  self.class.serialize_cstr(buf, val)
  end_pos = buf.position

  # Put the string size in front
  buf.put_int(end_pos - start_pos, len_pos)

  # Go back to where we were
  buf.position = end_pos
end

#serialize_undefined_element(buf, key) ⇒ Object



308
309
310
311
# File 'lib/mongo/util/bson.rb', line 308

def serialize_undefined_element(buf, key)
  buf.put(UNDEFINED)
  self.class.serialize_cstr(buf, key)
end

#to_aObject



71
72
73
# File 'lib/mongo/util/bson.rb', line 71

def to_a
  @buf.to_a
end