mongo 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +17 -0
- data/Rakefile +12 -1
- data/bin/validate +51 -0
- data/lib/mongo.rb +2 -0
- data/lib/mongo/binary.rb +34 -0
- data/lib/mongo/cursor.rb +1 -1
- data/lib/mongo/objectid.rb +17 -7
- data/lib/mongo/undefined.rb +31 -0
- data/lib/mongo/util/bson.rb +75 -16
- data/lib/mongo/util/ordered_hash.rb +1 -1
- data/lib/mongo/util/xml_to_ruby.rb +102 -0
- data/tests/test_bson.rb +29 -1
- data/tests/test_objectid.rb +12 -0
- data/tests/test_round_trip.rb +80 -96
- metadata +6 -2
data/README.rdoc
CHANGED
@@ -57,6 +57,23 @@ If you have the source code, you can run the tests.
|
|
57
57
|
|
58
58
|
The tests assume that the Mongo database is running on the default port.
|
59
59
|
|
60
|
+
The project mongo-qa (http://github.com/mongodb/mongo-qa) contains many more
|
61
|
+
Mongo driver tests that are language independent. To run thoses tests as part
|
62
|
+
of the "rake test" task, run
|
63
|
+
|
64
|
+
$ rake mongo_qa
|
65
|
+
$ rake test
|
66
|
+
|
67
|
+
The mongo_qa task uses the "git clone" command to make a copy of that project
|
68
|
+
in a directory named mongo-qa. If the directory already exists, then the
|
69
|
+
mongo_qa task uses "git pull" to updated the code that's there. The Ruby
|
70
|
+
driver tests will then use some of the data files from that project when it
|
71
|
+
runs BSON tests. You can delete this directory at any time if you don't want
|
72
|
+
to run those tests any more.
|
73
|
+
|
74
|
+
Additionally, the script bin/validate is used by the mongo-qa project's
|
75
|
+
validator script.
|
76
|
+
|
60
77
|
|
61
78
|
= Documentation
|
62
79
|
|
data/Rakefile
CHANGED
@@ -7,7 +7,7 @@ require 'rake/gempackagetask'
|
|
7
7
|
require 'rake/contrib/rubyforgepublisher'
|
8
8
|
|
9
9
|
GEM = "mongo"
|
10
|
-
GEM_VERSION = '0.0.
|
10
|
+
GEM_VERSION = '0.0.3'
|
11
11
|
SUMMARY = 'Simple pure-Ruby driver for the 10gen Mongo DB'
|
12
12
|
DESCRIPTION = 'This is a simple pure-Ruby driver for the 10gen Mongo DB. For more information about Mongo, see http://www.mongodb.org.'
|
13
13
|
AUTHOR = 'Jim Menard'
|
@@ -42,6 +42,17 @@ Rake::TestTask.new do |t|
|
|
42
42
|
t.test_files = FileList['tests/test*.rb']
|
43
43
|
end
|
44
44
|
|
45
|
+
desc "Clone or pull (update) the mongo-qa project used for testing"
|
46
|
+
task :mongo_qa do
|
47
|
+
if File.exist?('mongo-qa')
|
48
|
+
Dir.chdir('mongo-qa') do
|
49
|
+
system('git pull')
|
50
|
+
end
|
51
|
+
else
|
52
|
+
system('git clone git://github.com/mongodb/mongo-qa.git')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
45
56
|
desc "Generate documentation"
|
46
57
|
task :rdoc do
|
47
58
|
FileUtils.rm_rf('html')
|
data/bin/validate
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# usage: validate somefile.xson somefile.bson
|
4
|
+
#
|
5
|
+
# Reads somefile.xson file (XML that describes a Mongo-type document),
|
6
|
+
# converts it into a Ruby OrderedHash, runs that through the BSON
|
7
|
+
# serialization code, and writes the BSON bytes to somefile.bson.
|
8
|
+
#
|
9
|
+
# In addition, this script takes the generated BSON, reads it in then writes
|
10
|
+
# it back out to a temp BSON file. If they are different, we report that error
|
11
|
+
# to STDOUT.
|
12
|
+
#
|
13
|
+
# This script is used by the mongo-qa project
|
14
|
+
# (http://github.com/mongodb/mongo-qa).
|
15
|
+
|
16
|
+
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
|
17
|
+
require 'mongo'
|
18
|
+
require 'mongo/util/xml_to_ruby'
|
19
|
+
|
20
|
+
if ARGV.length < 2
|
21
|
+
$stderr.puts "usage: validate somefile.xson somefile.bson"
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
# Translate the .xson XML into a Ruby object, turn that object into BSON, and
|
26
|
+
# write the BSON to the file as requested.
|
27
|
+
obj = File.open(ARGV[0], 'rb') { |f| XMLToRuby.new.xml_to_ruby(f) }
|
28
|
+
bson = BSON.new.serialize(obj).to_a
|
29
|
+
File.open(ARGV[1], 'wb') { |f| bson.each { |b| f.putc(b) } }
|
30
|
+
|
31
|
+
# Now the additional testing. Read the generated BSON back in, deserialize it,
|
32
|
+
# and re-serialize the results. Compare that BSON with the BSON from the file
|
33
|
+
# we output.
|
34
|
+
bson = File.open(ARGV[1], 'rb') { |f| f.read }
|
35
|
+
bson = if RUBY_VERSION >= '1.9'
|
36
|
+
bson.bytes.to_a
|
37
|
+
else
|
38
|
+
bson.split(//).collect { |c| c[0] }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Turn the Ruby object into BSON bytes and compare with the BSON bytes from
|
42
|
+
# the file.
|
43
|
+
bson_from_ruby = BSON.new.serialize(obj).to_a
|
44
|
+
|
45
|
+
if bson.length != bson_from_ruby.length
|
46
|
+
$stderr.puts "error: round-trip BSON lengths differ when testing #{ARGV[0]}"
|
47
|
+
exit 1
|
48
|
+
elsif bson != bson_from_ruby
|
49
|
+
$stderr.puts "error: round-trip BSON contents differ when testing #{ARGV[0]}"
|
50
|
+
exit 1
|
51
|
+
end
|
data/lib/mongo.rb
CHANGED
data/lib/mongo/binary.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# --
|
2
|
+
# Copyright (C) 2008-2009 10gen Inc.
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it
|
5
|
+
# under the terms of the GNU Affero General Public License, version 3, as
|
6
|
+
# published by the Free Software Foundation.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
11
|
+
# for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU Affero General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
# ++
|
16
|
+
|
17
|
+
module XGen
|
18
|
+
module Mongo
|
19
|
+
module Driver
|
20
|
+
|
21
|
+
# An array of binary bytes. The only reason this exists is so that the
|
22
|
+
# BSON encoder will know to output the Mongo BINARY type.
|
23
|
+
class Binary < String; end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class String
|
30
|
+
# Convert a string into a XGen::Mongo::Driver::Binary
|
31
|
+
def to_mongo_binary
|
32
|
+
XGen::Mongo::Driver::Binary.new(self)
|
33
|
+
end
|
34
|
+
end
|
data/lib/mongo/cursor.rb
CHANGED
data/lib/mongo/objectid.rb
CHANGED
@@ -47,6 +47,14 @@ module XGen
|
|
47
47
|
MACHINE = ( val = rand(0x1000000); [val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff] )
|
48
48
|
PID = ( val = rand(0x10000); [val & 0xff, (val >> 8) & 0xff]; )
|
49
49
|
|
50
|
+
# The string representation of an OID is different than its internal
|
51
|
+
# and BSON byte representations. The BYTE_ORDER here maps
|
52
|
+
# internal/BSON byte position (the index in BYTE_ORDER) to the
|
53
|
+
# position of the two hex characters representing that byte in the
|
54
|
+
# string representation. For example, the 0th BSON byte corresponds to
|
55
|
+
# the (0-based) 7th pair of hex chars in the string.
|
56
|
+
BYTE_ORDER = [7, 6, 5, 4, 3, 2, 1, 0, 11, 10, 9, 8]
|
57
|
+
|
50
58
|
LOCK = Object.new
|
51
59
|
LOCK.extend Mutex_m
|
52
60
|
|
@@ -57,12 +65,10 @@ module XGen
|
|
57
65
|
# with that value.
|
58
66
|
def self.from_string(str)
|
59
67
|
data = []
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
65
|
-
self.new(data.reverse)
|
68
|
+
BYTE_ORDER.each_with_index { |string_position, data_index|
|
69
|
+
data[data_index] = str[string_position * 2, 2].to_i(16)
|
70
|
+
}
|
71
|
+
self.new(data)
|
66
72
|
end
|
67
73
|
|
68
74
|
# +data+ is an array of bytes. If nil, a new id will be generated.
|
@@ -81,7 +87,11 @@ module XGen
|
|
81
87
|
end
|
82
88
|
|
83
89
|
def to_s
|
84
|
-
|
90
|
+
str = ' ' * 24
|
91
|
+
BYTE_ORDER.each_with_index { |string_position, data_index|
|
92
|
+
str[string_position * 2, 2] = '%02x' % @data[data_index]
|
93
|
+
}
|
94
|
+
str
|
85
95
|
end
|
86
96
|
|
87
97
|
# (Would normally be private, but isn't so we can test it.)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# --
|
2
|
+
# Copyright (C) 2008-2009 10gen Inc.
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it
|
5
|
+
# under the terms of the GNU Affero General Public License, version 3, as
|
6
|
+
# published by the Free Software Foundation.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
11
|
+
# for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU Affero General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
# ++
|
16
|
+
|
17
|
+
module XGen
|
18
|
+
module Mongo
|
19
|
+
module Driver
|
20
|
+
|
21
|
+
# A special "undefined" type to match Mongo's storage of UNKNOWN values.
|
22
|
+
# "UNKNOWN" comes from JavaScript.
|
23
|
+
#
|
24
|
+
# NOTE: this class does not attempt to provide ANY of the semantics an
|
25
|
+
# "unknown" object might need. It isn't nil, it isn't special in any
|
26
|
+
# way, and there isn't any singleton value.
|
27
|
+
class Undefined < Object; end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/mongo/util/bson.rb
CHANGED
@@ -14,6 +14,7 @@
|
|
14
14
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
15
|
# ++
|
16
16
|
|
17
|
+
require 'base64'
|
17
18
|
require 'mongo/util/byte_buffer'
|
18
19
|
require 'mongo/util/ordered_hash'
|
19
20
|
require 'mongo/objectid'
|
@@ -46,7 +47,9 @@ class BSON
|
|
46
47
|
buf.put_array(val.to_s.unpack("C*") + [0])
|
47
48
|
end
|
48
49
|
|
49
|
-
def initialize
|
50
|
+
def initialize(db=nil)
|
51
|
+
# db is only needed during deserialization when the data contains a DBRef
|
52
|
+
@db = db
|
50
53
|
@buf = ByteBuffer.new
|
51
54
|
end
|
52
55
|
|
@@ -84,7 +87,13 @@ class BSON
|
|
84
87
|
serialize_null_element(@buf, k)
|
85
88
|
when REF
|
86
89
|
serialize_dbref_element(@buf, k, v)
|
87
|
-
when
|
90
|
+
when SYMBOL
|
91
|
+
serialize_symbol_element(@buf, k, v)
|
92
|
+
when BINARY
|
93
|
+
serialize_binary_element(@buf, k, v)
|
94
|
+
when UNDEFINED
|
95
|
+
serialize_undefined_element(@buf, k)
|
96
|
+
when CODE_W_SCOPE
|
88
97
|
# TODO
|
89
98
|
raise "unimplemented type #{type}"
|
90
99
|
else
|
@@ -96,13 +105,13 @@ class BSON
|
|
96
105
|
self
|
97
106
|
end
|
98
107
|
|
99
|
-
def deserialize(buf=nil)
|
108
|
+
def deserialize(buf=nil, parent=nil)
|
100
109
|
# If buf is nil, use @buf, assumed to contain already-serialized BSON.
|
101
110
|
# This is only true during testing.
|
102
111
|
@buf = ByteBuffer.new(buf.to_a) if buf
|
103
112
|
@buf.rewind
|
104
113
|
@buf.get_int # eat message size
|
105
|
-
doc =
|
114
|
+
doc = OrderedHash.new
|
106
115
|
while @buf.more?
|
107
116
|
type = @buf.get
|
108
117
|
case type
|
@@ -120,13 +129,13 @@ class BSON
|
|
120
129
|
doc[key] = deserialize_oid_data(@buf)
|
121
130
|
when ARRAY
|
122
131
|
key = deserialize_cstr(@buf)
|
123
|
-
doc[key] = deserialize_array_data(@buf)
|
132
|
+
doc[key] = deserialize_array_data(@buf, doc)
|
124
133
|
when REGEX
|
125
134
|
key = deserialize_cstr(@buf)
|
126
135
|
doc[key] = deserialize_regex_data(@buf)
|
127
136
|
when OBJECT
|
128
137
|
key = deserialize_cstr(@buf)
|
129
|
-
doc[key] = deserialize_object_data(@buf)
|
138
|
+
doc[key] = deserialize_object_data(@buf, doc)
|
130
139
|
when BOOLEAN
|
131
140
|
key = deserialize_cstr(@buf)
|
132
141
|
doc[key] = deserialize_boolean_data(@buf)
|
@@ -136,10 +145,19 @@ class BSON
|
|
136
145
|
when NULL
|
137
146
|
key = deserialize_cstr(@buf)
|
138
147
|
doc[key] = nil
|
148
|
+
when UNDEFINED
|
149
|
+
key = deserialize_cstr(@buf)
|
150
|
+
doc[key] = XGen::Mongo::Driver::Undefined.new
|
139
151
|
when REF
|
140
152
|
key = deserialize_cstr(@buf)
|
141
|
-
doc[key] = deserialize_dbref_data(@buf)
|
142
|
-
when
|
153
|
+
doc[key] = deserialize_dbref_data(@buf, key, parent)
|
154
|
+
when SYMBOL
|
155
|
+
key = deserialize_cstr(@buf)
|
156
|
+
doc[key] = deserialize_symbol_data(@buf)
|
157
|
+
when BINARY
|
158
|
+
key = deserialize_cstr(@buf)
|
159
|
+
doc[key] = deserialize_binary_data(@buf)
|
160
|
+
when CODE_W_SCOPE
|
143
161
|
# TODO
|
144
162
|
raise "unimplemented type #{type}"
|
145
163
|
when EOO
|
@@ -183,14 +201,14 @@ class BSON
|
|
183
201
|
buf.get_int
|
184
202
|
end
|
185
203
|
|
186
|
-
def deserialize_object_data(buf)
|
204
|
+
def deserialize_object_data(buf, parent)
|
187
205
|
size = buf.get_int
|
188
206
|
buf.position -= 4
|
189
|
-
BSON.new.deserialize(buf.get(size))
|
207
|
+
BSON.new(@db).deserialize(buf.get(size), parent)
|
190
208
|
end
|
191
209
|
|
192
|
-
def deserialize_array_data(buf)
|
193
|
-
h = deserialize_object_data(buf)
|
210
|
+
def deserialize_array_data(buf, parent)
|
211
|
+
h = deserialize_object_data(buf, parent)
|
194
212
|
a = []
|
195
213
|
h.each { |k, v| a[k.to_i] = v }
|
196
214
|
a
|
@@ -216,12 +234,22 @@ class BSON
|
|
216
234
|
XGen::Mongo::Driver::ObjectID.new(buf.get(12))
|
217
235
|
end
|
218
236
|
|
219
|
-
def deserialize_dbref_data(buf)
|
237
|
+
def deserialize_dbref_data(buf, key, parent)
|
220
238
|
ns = deserialize_cstr(buf)
|
221
239
|
oid = deserialize_oid_data(buf)
|
222
|
-
|
223
|
-
|
224
|
-
|
240
|
+
XGen::Mongo::Driver::DBRef.new(parent, key, @db, ns, oid)
|
241
|
+
end
|
242
|
+
|
243
|
+
def deserialize_symbol_data(buf)
|
244
|
+
deserialize_cstr(buf).intern
|
245
|
+
end
|
246
|
+
|
247
|
+
def deserialize_binary_data(buf)
|
248
|
+
len = buf.get_int
|
249
|
+
bytes = buf.get(len)
|
250
|
+
str = ''
|
251
|
+
bytes.each { |c| str << c.chr }
|
252
|
+
str.to_mongo_binary
|
225
253
|
end
|
226
254
|
|
227
255
|
def serialize_eoo_element(buf)
|
@@ -240,6 +268,31 @@ class BSON
|
|
240
268
|
buf.put_array(val.object_id.to_a)
|
241
269
|
end
|
242
270
|
|
271
|
+
def serialize_symbol_element(buf, key, val)
|
272
|
+
buf.put(SYMBOL)
|
273
|
+
self.class.serialize_cstr(buf, key)
|
274
|
+
self.class.serialize_cstr(buf, val)
|
275
|
+
end
|
276
|
+
|
277
|
+
def serialize_binary_element(buf, key, val)
|
278
|
+
buf.put(BINARY)
|
279
|
+
self.class.serialize_cstr(buf, key)
|
280
|
+
buf.put_int(val.length)
|
281
|
+
bytes = if RUBY_VERSION >= '1.9'
|
282
|
+
val.bytes.to_a
|
283
|
+
else
|
284
|
+
a = []
|
285
|
+
val.each_byte { |byte| a << byte }
|
286
|
+
a
|
287
|
+
end
|
288
|
+
buf.put_array(bytes)
|
289
|
+
end
|
290
|
+
|
291
|
+
def serialize_undefined_element(buf, key)
|
292
|
+
buf.put(UNDEFINED)
|
293
|
+
self.class.serialize_cstr(buf, key)
|
294
|
+
end
|
295
|
+
|
243
296
|
def serialize_boolean_element(buf, key, val)
|
244
297
|
buf.put(BOOLEAN)
|
245
298
|
self.class.serialize_cstr(buf, key)
|
@@ -337,6 +390,8 @@ class BSON
|
|
337
390
|
NUMBER_INT
|
338
391
|
when Numeric
|
339
392
|
NUMBER
|
393
|
+
when XGen::Mongo::Driver::Binary # must be before String
|
394
|
+
BINARY
|
340
395
|
when String
|
341
396
|
# magic awful stuff - the DB requires that a where clause is sent as CODE
|
342
397
|
key == "$where" ? CODE : STRING
|
@@ -354,6 +409,10 @@ class BSON
|
|
354
409
|
DATE
|
355
410
|
when Hash
|
356
411
|
OBJECT
|
412
|
+
when Symbol
|
413
|
+
SYMBOL
|
414
|
+
when XGen::Mongo::Driver::Undefined
|
415
|
+
UNDEFINED
|
357
416
|
else
|
358
417
|
raise "Unknown type of object: #{o.class.name}"
|
359
418
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# --
|
2
|
+
# Copyright (C) 2008-2009 10gen Inc.
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it
|
5
|
+
# under the terms of the GNU Affero General Public License, version 3, as
|
6
|
+
# published by the Free Software Foundation.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
11
|
+
# for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU Affero General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
# ++
|
16
|
+
|
17
|
+
require 'rexml/document'
|
18
|
+
require 'mongo'
|
19
|
+
|
20
|
+
# Converts a .xson file (an XML file that describes a Mongo-type document) to
|
21
|
+
# an OrderedHash.
|
22
|
+
class XMLToRuby
|
23
|
+
|
24
|
+
include XGen::Mongo::Driver
|
25
|
+
|
26
|
+
def xml_to_ruby(io)
|
27
|
+
doc = REXML::Document.new(io)
|
28
|
+
doc_to_ruby(doc.root.elements['doc'])
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def element_to_ruby(e)
|
34
|
+
type = e.name
|
35
|
+
child = e.elements[1]
|
36
|
+
case type
|
37
|
+
when 'oid'
|
38
|
+
ObjectID.from_string(e.text)
|
39
|
+
when 'ref'
|
40
|
+
dbref_to_ruby(e.elements)
|
41
|
+
when 'int'
|
42
|
+
e.text.to_i
|
43
|
+
when 'number'
|
44
|
+
e.text.to_f
|
45
|
+
when 'string', 'code'
|
46
|
+
e.text.to_s
|
47
|
+
when 'binary'
|
48
|
+
Base64.decode64(e.text.to_s).to_mongo_binary
|
49
|
+
when 'symbol'
|
50
|
+
e.text.to_s.intern
|
51
|
+
when 'boolean'
|
52
|
+
e.text.to_s == 'true'
|
53
|
+
when 'array'
|
54
|
+
array_to_ruby(e.elements)
|
55
|
+
when 'date'
|
56
|
+
Time.at(e.text.to_f / 1000.0)
|
57
|
+
when 'regex'
|
58
|
+
regex_to_ruby(e.elements)
|
59
|
+
when 'null'
|
60
|
+
nil
|
61
|
+
when 'undefined'
|
62
|
+
Undefined.new
|
63
|
+
when 'doc'
|
64
|
+
doc_to_ruby(e)
|
65
|
+
else
|
66
|
+
raise "Unknown type #{type} in element with name #{e.attributes['name']}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def doc_to_ruby(element)
|
71
|
+
oh = OrderedHash.new
|
72
|
+
element.elements.each { |e| oh[e.attributes['name']] = element_to_ruby(e) }
|
73
|
+
oh
|
74
|
+
end
|
75
|
+
|
76
|
+
def array_to_ruby(elements)
|
77
|
+
a = []
|
78
|
+
elements.each { |e|
|
79
|
+
index_str = e.attributes['name']
|
80
|
+
a[index_str.to_i] = element_to_ruby(e)
|
81
|
+
}
|
82
|
+
a
|
83
|
+
end
|
84
|
+
|
85
|
+
def regex_to_ruby(elements)
|
86
|
+
pattern = elements['pattern'].text
|
87
|
+
options_str = elements['options'].text || ''
|
88
|
+
|
89
|
+
options = 0
|
90
|
+
options |= Regexp::IGNORECASE if options_str.include?('i')
|
91
|
+
options |= Regexp::MULTILINE if options_str.include?('m')
|
92
|
+
options |= Regexp::EXTENDED if options_str.include?('x')
|
93
|
+
Regexp.new(pattern, options)
|
94
|
+
end
|
95
|
+
|
96
|
+
def dbref_to_ruby(elements)
|
97
|
+
ns = elements['ns'].text
|
98
|
+
oid_str = elements['oid'].text
|
99
|
+
DBRef.new(nil, nil, nil, ns, ObjectID.from_string(oid_str))
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
data/tests/test_bson.rb
CHANGED
@@ -8,6 +8,9 @@ class BSONTest < Test::Unit::TestCase
|
|
8
8
|
include XGen::Mongo::Driver
|
9
9
|
|
10
10
|
def setup
|
11
|
+
# We don't pass a DB to the constructor, even though we are about to test
|
12
|
+
# deserialization. This means that when we deserialize, any DBRefs will
|
13
|
+
# have nil @db ivars. That's fine for now.
|
11
14
|
@b = BSON.new
|
12
15
|
end
|
13
16
|
|
@@ -79,11 +82,36 @@ class BSONTest < Test::Unit::TestCase
|
|
79
82
|
|
80
83
|
def test_dbref
|
81
84
|
oid = ObjectID.new
|
82
|
-
doc = {
|
85
|
+
doc = {}
|
86
|
+
doc['dbref'] = DBRef.new(doc, 'dbref', nil, 'namespace', oid)
|
83
87
|
@b.serialize(doc)
|
84
88
|
doc2 = @b.deserialize
|
85
89
|
assert_equal 'namespace', doc2['dbref'].namespace
|
86
90
|
assert_equal oid, doc2['dbref'].object_id
|
87
91
|
end
|
88
92
|
|
93
|
+
def test_symbol
|
94
|
+
doc = {'sym' => :foo}
|
95
|
+
@b.serialize(doc)
|
96
|
+
doc2 = @b.deserialize
|
97
|
+
assert_equal :foo, doc2['sym']
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_binary
|
101
|
+
bin = 'binstring'.to_mongo_binary
|
102
|
+
assert_kind_of Binary, bin
|
103
|
+
|
104
|
+
doc = {'bin' => bin}
|
105
|
+
@b.serialize(doc)
|
106
|
+
doc2 = @b.deserialize
|
107
|
+
assert_equal 'binstring', doc2['bin']
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_undefined
|
111
|
+
doc = {'undef' => Undefined.new}
|
112
|
+
@b.serialize(doc)
|
113
|
+
doc2 = @b.deserialize
|
114
|
+
assert_kind_of Undefined, doc2['undef']
|
115
|
+
end
|
116
|
+
|
89
117
|
end
|
data/tests/test_objectid.rb
CHANGED
@@ -73,4 +73,16 @@ class ObjectIDTest < Test::Unit::TestCase
|
|
73
73
|
assert_equal @o.to_s, o2.to_s
|
74
74
|
end
|
75
75
|
|
76
|
+
def test_from_string_leading_zeroes
|
77
|
+
hex_str = '000000000000000000abcdef'
|
78
|
+
o = ObjectID.from_string(hex_str)
|
79
|
+
assert_equal hex_str, o.to_s
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_byte_order
|
83
|
+
hex_str = '000102030405060708090A0B'
|
84
|
+
o = ObjectID.from_string(hex_str)
|
85
|
+
assert_equal [0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x0b, 0x0a, 0x09, 0x08], o.to_a
|
86
|
+
end
|
87
|
+
|
76
88
|
end
|
data/tests/test_round_trip.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
HERE = File.dirname(__FILE__)
|
2
2
|
$LOAD_PATH[0,0] = File.join(HERE, '..', 'lib')
|
3
3
|
require 'mongo'
|
4
|
-
require '
|
4
|
+
require 'mongo/util/xml_to_ruby'
|
5
5
|
require 'test/unit'
|
6
6
|
|
7
7
|
# For each xml/bson file in the data subdirectory, we turn the XML into an
|
8
8
|
# OrderedHash and then test both Ruby-to-BSON and BSON-to-Ruby translations.
|
9
|
+
#
|
10
|
+
# There is a whole other project that includes similar tests
|
11
|
+
# (http://github.com/mongodb/mongo-qa). If the directory ../mongo-qa exists,
|
12
|
+
# then we find the BSON test files there and use those, too. Use the Rake task
|
13
|
+
# "mongo_qa" to obtain those tests.
|
9
14
|
class RoundTripTest < Test::Unit::TestCase
|
10
15
|
|
11
16
|
include XGen::Mongo::Driver
|
@@ -16,117 +21,96 @@ class RoundTripTest < Test::Unit::TestCase
|
|
16
21
|
unless @@ruby
|
17
22
|
names = Dir[File.join(HERE, 'data', '*.xml')].collect {|f| File.basename(f).sub(/\.xml$/, '') }
|
18
23
|
@@ruby = {}
|
19
|
-
names.each { |name|
|
24
|
+
names.each { |name|
|
25
|
+
File.open(File.join(HERE, 'data', "#{name}.xml")) { |f|
|
26
|
+
@@ruby[name] = XMLToRuby.new.xml_to_ruby(f)
|
27
|
+
}
|
28
|
+
}
|
20
29
|
end
|
21
30
|
end
|
22
31
|
|
23
|
-
def
|
24
|
-
|
25
|
-
doc = REXML::Document.new(f)
|
26
|
-
doc_to_ruby(doc.root.elements['doc'])
|
27
|
-
}
|
28
|
-
end
|
29
|
-
|
30
|
-
def element_to_ruby(e)
|
31
|
-
type = e.name
|
32
|
-
child = e.elements[1]
|
33
|
-
case type
|
34
|
-
when 'oid'
|
35
|
-
ObjectID.from_string(e.text)
|
36
|
-
when 'ref'
|
37
|
-
dbref_to_ruby(e.elements)
|
38
|
-
when 'int'
|
39
|
-
e.text.to_i
|
40
|
-
when 'number'
|
41
|
-
e.text.to_f
|
42
|
-
when 'string', 'code'
|
43
|
-
e.text.to_s
|
44
|
-
when 'boolean'
|
45
|
-
e.text.to_s == 'true'
|
46
|
-
when 'array'
|
47
|
-
array_to_ruby(e.elements)
|
48
|
-
when 'date'
|
49
|
-
Time.at(e.text.to_f / 1000.0)
|
50
|
-
when 'regex'
|
51
|
-
regex_to_ruby(e.elements)
|
52
|
-
when 'null'
|
53
|
-
nil
|
54
|
-
when 'doc'
|
55
|
-
doc_to_ruby(e)
|
56
|
-
else
|
57
|
-
raise "Unknown type #{type} in element with name #{e.attributes['name']}"
|
58
|
-
end
|
32
|
+
def test_dummy
|
33
|
+
assert true
|
59
34
|
end
|
60
35
|
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
|
36
|
+
def self.create_test_for_round_trip_files_in_dir(dir)
|
37
|
+
names = Dir[File.join(dir, '*.xson')].collect {|f| File.basename(f).sub(/\.xson$/, '') }
|
38
|
+
names.each { |name|
|
39
|
+
eval <<EOS
|
40
|
+
def test_#{name}_#{dir.gsub(/[^a-zA-Z0-9_]/, '_')}
|
41
|
+
one_round_trip("#{dir}", "#{name}")
|
42
|
+
end
|
43
|
+
EOS
|
44
|
+
}
|
65
45
|
end
|
66
46
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
47
|
+
# Dynamically generate one test for each test file. This way, if one test
|
48
|
+
# fails the others will still run.
|
49
|
+
create_test_for_round_trip_files_in_dir(File.join(HERE, 'data'))
|
50
|
+
mongo_qa_dir = File.join(HERE, '..', 'mongo-qa/modules/bson_tests')
|
51
|
+
if File.exist?(mongo_qa_dir)
|
52
|
+
%w(basic_types complex single_types).each { |subdir_name|
|
53
|
+
create_test_for_round_trip_files_in_dir(File.join(mongo_qa_dir, subdir_name))
|
72
54
|
}
|
73
|
-
a
|
74
55
|
end
|
75
56
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
57
|
+
# Round-trip comparisons of Ruby-to-BSON and back.
|
58
|
+
# * Take the objects that were read from XML
|
59
|
+
# * Turn them into BSON bytes
|
60
|
+
# * Compare that with the BSON files we have
|
61
|
+
# * Turn those BSON bytes back in to Ruby objects
|
62
|
+
# * Turn them back into BSON bytes
|
63
|
+
# * Compare that with the BSON files we have (or the bytes that were already
|
64
|
+
# generated)
|
65
|
+
def one_round_trip(dir, name)
|
66
|
+
obj = File.open(File.join(dir, "#{name}.xson")) { |f|
|
67
|
+
XMLToRuby.new.xml_to_ruby(f)
|
68
|
+
}
|
86
69
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
70
|
+
File.open(File.join(dir, "#{name}.bson"), 'r') { |f|
|
71
|
+
# Read the BSON from the file
|
72
|
+
bson = f.read
|
73
|
+
bson = if RUBY_VERSION >= '1.9'
|
74
|
+
bson.bytes.to_a
|
75
|
+
else
|
76
|
+
bson.split(//).collect { |c| c[0] }
|
77
|
+
end
|
92
78
|
|
93
|
-
#
|
94
|
-
#
|
95
|
-
|
96
|
-
# $stderr.puts "@@ruby['smorgasbord'] = #{@@ruby['smorgasbord'].inspect.gsub(/, /, ",\n")}" # DEBUG
|
97
|
-
# end
|
79
|
+
# Turn the Ruby object into BSON bytes and compare with the BSON bytes
|
80
|
+
# from the file.
|
81
|
+
bson_from_ruby = BSON.new.serialize(obj).to_a
|
98
82
|
|
99
|
-
|
100
|
-
@@ruby.each { |name, obj|
|
101
|
-
File.open(File.join(HERE, 'data', "#{name}.bson"), 'r') { |f|
|
102
|
-
bson = f.read
|
103
|
-
bson = if RUBY_VERSION >= '1.9'
|
104
|
-
bson.bytes
|
105
|
-
else
|
106
|
-
bson.split(//).collect { |c| c[0] }
|
107
|
-
end
|
108
|
-
bson_from_ruby = BSON.new.serialize(obj).to_a
|
83
|
+
begin
|
109
84
|
assert_equal bson.length, bson_from_ruby.length
|
110
85
|
assert_equal bson, bson_from_ruby
|
111
|
-
|
86
|
+
rescue => ex
|
87
|
+
# File.open(File.join(dir, "#{name}_out_a.bson"), 'wb') { |f| # DEBUG
|
88
|
+
# bson_from_ruby.each { |b| f.putc(b) }
|
89
|
+
# }
|
90
|
+
raise ex
|
91
|
+
end
|
92
|
+
|
93
|
+
# Turn those BSON bytes back into a Ruby object.
|
94
|
+
#
|
95
|
+
# We're passing a nil db to the contructor here, but that's OK because
|
96
|
+
# the BSON DBFef bytes don't contain the db object in any case, and we
|
97
|
+
# don't care what the database is.
|
98
|
+
obj_from_bson = BSON.new(nil).deserialize(ByteBuffer.new(bson_from_ruby))
|
99
|
+
assert_kind_of OrderedHash, obj_from_bson
|
100
|
+
|
101
|
+
# Turn that Ruby object into BSON and compare it to the original BSON
|
102
|
+
# bytes.
|
103
|
+
bson_from_ruby = BSON.new.serialize(obj_from_bson).to_a
|
104
|
+
begin
|
105
|
+
assert_equal bson.length, bson_from_ruby.length
|
106
|
+
assert_equal bson, bson_from_ruby
|
107
|
+
rescue => ex
|
108
|
+
# File.open(File.join(dir, "#{name}_out_b.bson"), 'wb') { |f| # DEBUG
|
109
|
+
# bson_from_ruby.each { |b| f.putc(b) }
|
110
|
+
# }
|
111
|
+
raise ex
|
112
|
+
end
|
112
113
|
}
|
113
114
|
end
|
114
115
|
|
115
|
-
# def test_bson_to_ruby
|
116
|
-
# @@ruby.each { |name, obj|
|
117
|
-
# File.open(File.join(HERE, 'data', "#{name}.bson"), 'r') { |f|
|
118
|
-
# bson = f.read
|
119
|
-
# obj_from_bson = BSON.new.deserialize(ByteBuffer.new(bson.split(//)))
|
120
|
-
# assert_equal obj, obj_from_bson
|
121
|
-
# }
|
122
|
-
# }
|
123
|
-
# end
|
124
|
-
|
125
|
-
# # Technically reduntant, given the other tests
|
126
|
-
# def test_roundtrips
|
127
|
-
# @@ruby.each { |name, obj|
|
128
|
-
|
129
|
-
# }
|
130
|
-
# end
|
131
|
-
|
132
116
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jim Menard
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-01-
|
12
|
+
date: 2009-01-12 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -23,7 +23,9 @@ extra_rdoc_files: []
|
|
23
23
|
|
24
24
|
files:
|
25
25
|
- bin/mongo_console
|
26
|
+
- bin/validate
|
26
27
|
- lib/mongo/admin.rb
|
28
|
+
- lib/mongo/binary.rb
|
27
29
|
- lib/mongo/collection.rb
|
28
30
|
- lib/mongo/cursor.rb
|
29
31
|
- lib/mongo/db.rb
|
@@ -42,9 +44,11 @@ files:
|
|
42
44
|
- lib/mongo/mongo.rb
|
43
45
|
- lib/mongo/objectid.rb
|
44
46
|
- lib/mongo/query.rb
|
47
|
+
- lib/mongo/undefined.rb
|
45
48
|
- lib/mongo/util/bson.rb
|
46
49
|
- lib/mongo/util/byte_buffer.rb
|
47
50
|
- lib/mongo/util/ordered_hash.rb
|
51
|
+
- lib/mongo/util/xml_to_ruby.rb
|
48
52
|
- lib/mongo.rb
|
49
53
|
- tests/test_admin.rb
|
50
54
|
- tests/test_bson.rb
|