bson 0.20

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bson might be problematic. Click here for more details.

@@ -0,0 +1,36 @@
1
+ # --
2
+ # Copyright (C) 2008-2010 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ module BSON
18
+
19
+ # JavaScript code to be evaluated by MongoDB.
20
+ class Code < String
21
+
22
+ # Hash mapping identifiers to their values
23
+ attr_accessor :scope
24
+
25
+ # Wrap code to be evaluated by MongoDB.
26
+ #
27
+ # @param [String] code the JavaScript code.
28
+ # @param [Hash] a document mapping identifiers to values, which
29
+ # represent the scope in which the code is to be executed.
30
+ def initialize(code, scope={})
31
+ super(code)
32
+ @scope = scope
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,40 @@
1
+ # --
2
+ # Copyright (C) 2008-2010 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ module BSON
18
+
19
+ # A reference to another object in a MongoDB database.
20
+ class DBRef
21
+
22
+ attr_reader :namespace, :object_id
23
+
24
+ # Create a DBRef. Use this class in conjunction with DB#dereference.
25
+ #
26
+ # @param [String] a collection name
27
+ # @param [ObjectID] an object id
28
+ #
29
+ # @core dbrefs constructor_details
30
+ def initialize(namespace, object_id)
31
+ @namespace = namespace
32
+ @object_id = object_id
33
+ end
34
+
35
+ def to_s
36
+ "ns: #{namespace}, id: #{object_id}"
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,58 @@
1
+ # --
2
+ # Copyright (C) 2008-2010 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ module BSON
18
+
19
+ # A class representing the BSON MaxKey type. MaxKey will always compare greater than
20
+ # all other BSON types and values.
21
+ #
22
+ # @example Sorting (assume @numbers is a collection):
23
+ #
24
+ # >> @numbers.save({"n" => Mongo::MaxKey.new})
25
+ # >> @numbers.save({"n" => 0})
26
+ # >> @numbers.save({"n" => 5_000_000})
27
+ # >> @numbers.find.sort("n").to_a
28
+ # => [{"_id"=>4b5a050c238d3bace2000004, "n"=>0},
29
+ # {"_id"=>4b5a04e6238d3bace2000002, "n"=>5_000_000},
30
+ # {"_id"=>4b5a04ea238d3bace2000003, "n"=>#<Mongo::MaxKey:0x1014ef410>},
31
+ # ]
32
+ class MaxKey
33
+
34
+ def ==(obj)
35
+ obj.class == MaxKey
36
+ end
37
+ end
38
+
39
+ # A class representing the BSON MinKey type. MinKey will always compare less than
40
+ # all other BSON types and values.
41
+ #
42
+ # @example Sorting (assume @numbers is a collection):
43
+ #
44
+ # >> @numbers.save({"n" => Mongo::MinKey.new})
45
+ # >> @numbers.save({"n" => -1_000_000})
46
+ # >> @numbers.save({"n" => 1_000_000})
47
+ # >> @numbers.find.sort("n").to_a
48
+ # => [{"_id"=>4b5a050c238d3bace2000004, "n"=>#<Mongo::MinKey:0x1014ef410>},
49
+ # {"_id"=>4b5a04e6238d3bace2000002, "n"=>-1_000_000},
50
+ # {"_id"=>4b5a04ea238d3bace2000003, "n"=>1_000_000},
51
+ # ]
52
+ class MinKey
53
+
54
+ def ==(obj)
55
+ obj.class == MinKey
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,180 @@
1
+ # --
2
+ # Copyright (C) 2008-2010 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ require 'thread'
18
+ require 'socket'
19
+ require 'digest/md5'
20
+
21
+ module BSON
22
+
23
+ # Generates MongoDB object ids.
24
+ #
25
+ # @core objectids
26
+ class ObjectID
27
+ @@lock = Mutex.new
28
+ @@index = 0
29
+
30
+ # Create a new object id. If no parameter is given, an id corresponding
31
+ # to the ObjectID BSON data type will be created. This is a 12-byte value
32
+ # consisting of a 4-byte timestamp, a 3-byte machine id, a 2-byte process id,
33
+ # and a 3-byte counter.
34
+ #
35
+ # @param [Array] data should be an array of bytes. If you want
36
+ # to generate a standard MongoDB object id, leave this argument blank.
37
+ def initialize(data=nil)
38
+ @data = data || generate
39
+ end
40
+
41
+ # Determine if the supplied string is legal. Legal strings will
42
+ # consist of 24 hexadecimal characters.
43
+ #
44
+ # @param [String] str
45
+ #
46
+ # @return [Boolean]
47
+ def self.legal?(str)
48
+ len = 24
49
+ str =~ /([0-9a-f]+)/i
50
+ match = $1
51
+ str && str.length == len && match == str
52
+ end
53
+
54
+ # Create an object id from the given time. This is useful for doing range
55
+ # queries; it works because MongoDB's object ids begin
56
+ # with a timestamp.
57
+ #
58
+ # @param [Time] time a utc time to encode as an object id.
59
+ #
60
+ # @return [Mongo::ObjectID]
61
+ #
62
+ # @example Return all document created before Jan 1, 2010.
63
+ # time = Time.utc(2010, 1, 1)
64
+ # time_id = ObjectID.from_time(time)
65
+ # collection.find({'_id' => {'$lt' => time_id}})
66
+ def self.from_time(time)
67
+ self.new([time.to_i,0,0].pack("NNN").unpack("C12"))
68
+ end
69
+
70
+ # Adds a primary key to the given document if needed.
71
+ #
72
+ # @param [Hash] doc a document requiring an _id.
73
+ #
74
+ # @return [Mongo::ObjectID, Object] returns a newly-created or
75
+ # current _id for the given document.
76
+ def self.create_pk(doc)
77
+ doc.has_key?(:_id) || doc.has_key?('_id') ? doc : doc.merge!(:_id => self.new)
78
+ end
79
+
80
+ # Check equality of this object id with another.
81
+ #
82
+ # @param [Mongo::ObjectID] object_id
83
+ def eql?(object_id)
84
+ @data == object_id.instance_variable_get("@data")
85
+ end
86
+ alias_method :==, :eql?
87
+
88
+ # Get a unique hashcode for this object.
89
+ # This is required since we've defined an #eql? method.
90
+ #
91
+ # @return [Integer]
92
+ def hash
93
+ @data.hash
94
+ end
95
+
96
+ # Get an array representation of the object id.
97
+ #
98
+ # @return [Array]
99
+ def to_a
100
+ @data.dup
101
+ end
102
+
103
+ # Given a string representation of an ObjectID, return a new ObjectID
104
+ # with that value.
105
+ #
106
+ # @param [String] str
107
+ #
108
+ # @return [Mongo::ObjectID]
109
+ def self.from_string(str)
110
+ raise InvalidObjectID, "illegal ObjectID format" unless legal?(str)
111
+ data = []
112
+ 12.times do |i|
113
+ data[i] = str[i * 2, 2].to_i(16)
114
+ end
115
+ self.new(data)
116
+ end
117
+
118
+ # Get a string representation of this object id.
119
+ #
120
+ # @return [String]
121
+ def to_s
122
+ str = ' ' * 24
123
+ 12.times do |i|
124
+ str[i * 2, 2] = '%02x' % @data[i]
125
+ end
126
+ str
127
+ end
128
+
129
+ def inspect
130
+ "ObjectID('#{to_s}')"
131
+ end
132
+
133
+ # Convert to MongoDB extended JSON format. Since JSON includes type information,
134
+ # but lacks an ObjectID type, this JSON format encodes the type using an $id key.
135
+ #
136
+ # @return [String] the object id represented as MongoDB extended JSON.
137
+ def to_json(escaped=false)
138
+ "{\"$oid\": \"#{to_s}\"}"
139
+ end
140
+
141
+ # Return the UTC time at which this ObjectID was generated. This may
142
+ # be used in lieu of a created_at timestamp since this information
143
+ # is always encoded in the object id.
144
+ #
145
+ # @return [Time] the time at which this object was created.
146
+ def generation_time
147
+ Time.at(@data.pack("C4").unpack("N")[0]).utc
148
+ end
149
+
150
+ private
151
+
152
+ # We need to define this method only if CBson isn't loaded.
153
+ unless defined? CBson
154
+ def generate
155
+ oid = ''
156
+
157
+ # 4 bytes current time
158
+ time = Time.new.to_i
159
+ oid += [time].pack("N")
160
+
161
+ # 3 bytes machine
162
+ oid += Digest::MD5.digest(Socket.gethostname)[0, 3]
163
+
164
+ # 2 bytes pid
165
+ oid += [Process.pid % 0xFFFF].pack("n")
166
+
167
+ # 3 bytes inc
168
+ oid += [get_inc].pack("N")[1, 3]
169
+
170
+ oid.unpack("C12")
171
+ end
172
+ end
173
+
174
+ def get_inc
175
+ @@lock.synchronize do
176
+ @@index = (@@index + 1) % 0xFFFFFF
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,15 @@
1
+ # encoding:utf-8
2
+ require 'test/test_helper'
3
+
4
+ class BinaryTest < Test::Unit::TestCase
5
+ context "Inspecting" do
6
+ setup do
7
+ @data = ("THIS IS BINARY " * 50).unpack("c*")
8
+ end
9
+
10
+ should "not display actual data" do
11
+ binary = BSON::Binary.new(@data)
12
+ assert_equal "<BSON::Binary:#{binary.object_id}>", binary.inspect
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,471 @@
1
+ # encoding:utf-8
2
+ require 'test/test_helper'
3
+ require 'complex'
4
+ require 'bigdecimal'
5
+ require 'rational'
6
+
7
+ begin
8
+ require 'active_support/core_ext'
9
+ require 'active_support/hash_with_indifferent_access'
10
+ Time.zone = "Pacific Time (US & Canada)"
11
+ Zone = Time.zone.now
12
+ rescue LoadError
13
+ warn 'Could not test BSON with HashWithIndifferentAccess.'
14
+ module ActiveSupport
15
+ class TimeWithZone
16
+ end
17
+ end
18
+ Zone = ActiveSupport::TimeWithZone.new
19
+ end
20
+
21
+ class BSONTest < Test::Unit::TestCase
22
+
23
+ include BSON
24
+
25
+ def test_deprecated_bson_module
26
+ doc = {'doc' => 'hello, world'}
27
+ bson = BSON.serialize(doc)
28
+ assert_equal doc, BSON.deserialize(bson)
29
+ end
30
+
31
+ def test_string
32
+ doc = {'doc' => 'hello, world'}
33
+ bson = bson = BSON::BSON_CODER.serialize(doc)
34
+ assert_equal doc, BSON::BSON_CODER.deserialize(bson)
35
+ end
36
+
37
+ def test_valid_utf8_string
38
+ doc = {'doc' => 'aé'}
39
+ bson = bson = BSON::BSON_CODER.serialize(doc)
40
+ assert_equal doc, BSON::BSON_CODER.deserialize(bson)
41
+ end
42
+
43
+ def test_valid_utf8_key
44
+ doc = {'aé' => 'hello'}
45
+ bson = bson = BSON::BSON_CODER.serialize(doc)
46
+ assert_equal doc, BSON::BSON_CODER.deserialize(bson)
47
+ end
48
+
49
+ def test_document_length
50
+ doc = {'name' => 'a' * 5 * 1024 * 1024}
51
+ assert_raise InvalidDocument do
52
+ assert BSON::BSON_CODER.serialize(doc)
53
+ end
54
+ end
55
+
56
+ # In 1.8 we test that other string encodings raise an exception.
57
+ # In 1.9 we test that they get auto-converted.
58
+ if RUBY_VERSION < '1.9'
59
+ require 'iconv'
60
+ def test_invalid_string
61
+ string = Iconv.conv('iso-8859-1', 'utf-8', 'aé')
62
+ doc = {'doc' => string}
63
+ assert_raise InvalidStringEncoding do
64
+ BSON::BSON_CODER.serialize(doc)
65
+ end
66
+ end
67
+
68
+ def test_invalid_key
69
+ key = Iconv.conv('iso-8859-1', 'utf-8', 'aé')
70
+ doc = {key => 'hello'}
71
+ assert_raise InvalidStringEncoding do
72
+ BSON::BSON_CODER.serialize(doc)
73
+ end
74
+ end
75
+ else
76
+ def test_non_utf8_string
77
+ bson = BSON::BSON_CODER.serialize({'str' => 'aé'.encode('iso-8859-1')})
78
+ result = BSON::BSON_CODER.deserialize(bson)['str']
79
+ assert_equal 'aé', result
80
+ assert_equal 'UTF-8', result.encoding.name
81
+ end
82
+
83
+ def test_non_utf8_key
84
+ bson = BSON::BSON_CODER.serialize({'aé'.encode('iso-8859-1') => 'hello'})
85
+ assert_equal 'hello', BSON::BSON_CODER.deserialize(bson)['aé']
86
+ end
87
+ end
88
+
89
+ def test_code
90
+ doc = {'$where' => Code.new('this.a.b < this.b')}
91
+ bson = BSON::BSON_CODER.serialize(doc)
92
+ assert_equal doc, BSON::BSON_CODER.deserialize(bson)
93
+ end
94
+
95
+ def test_number
96
+ doc = {'doc' => 41.99}
97
+ bson = BSON::BSON_CODER.serialize(doc)
98
+ assert_equal doc, BSON::BSON_CODER.deserialize(bson)
99
+ end
100
+
101
+ def test_int
102
+ doc = {'doc' => 42}
103
+ bson = BSON::BSON_CODER.serialize(doc)
104
+ assert_equal doc, BSON::BSON_CODER.deserialize(bson)
105
+
106
+ doc = {"doc" => -5600}
107
+ bson = BSON::BSON_CODER.serialize(doc)
108
+ assert_equal doc, BSON::BSON_CODER.deserialize(bson)
109
+
110
+ doc = {"doc" => 2147483647}
111
+ bson = BSON::BSON_CODER.serialize(doc)
112
+ assert_equal doc, BSON::BSON_CODER.deserialize(bson)
113
+
114
+ doc = {"doc" => -2147483648}
115
+ bson = BSON::BSON_CODER.serialize(doc)
116
+ assert_equal doc, BSON::BSON_CODER.deserialize(bson)
117
+ end
118
+
119
+ def test_ordered_hash
120
+ doc = OrderedHash.new
121
+ doc["b"] = 1
122
+ doc["a"] = 2
123
+ doc["c"] = 3
124
+ doc["d"] = 4
125
+ bson = BSON::BSON_CODER.serialize(doc)
126
+ assert_equal doc, BSON::BSON_CODER.deserialize(bson)
127
+ end
128
+
129
+ def test_object
130
+ doc = {'doc' => {'age' => 42, 'name' => 'Spongebob', 'shoe_size' => 9.5}}
131
+ bson = BSON::BSON_CODER.serialize(doc)
132
+ assert_equal doc, BSON::BSON_CODER.deserialize(bson)
133
+ end
134
+
135
+ def test_oid
136
+ doc = {'doc' => ObjectID.new}
137
+ bson = BSON::BSON_CODER.serialize(doc)
138
+ assert_equal doc, BSON::BSON_CODER.deserialize(bson)
139
+ end
140
+
141
+ def test_array
142
+ doc = {'doc' => [1, 2, 'a', 'b']}
143
+ bson = BSON::BSON_CODER.serialize(doc)
144
+ assert_equal doc, BSON::BSON_CODER.deserialize(bson)
145
+ end
146
+
147
+ def test_regex
148
+ doc = {'doc' => /foobar/i}
149
+ bson = BSON::BSON_CODER.serialize(doc)
150
+ doc2 = BSON::BSON_CODER.deserialize(bson)
151
+ assert_equal doc, doc2
152
+
153
+ r = doc2['doc']
154
+ assert_kind_of Regexp, r
155
+
156
+ doc = {'doc' => r}
157
+ bson_doc = BSON::BSON_CODER.serialize(doc)
158
+ doc2 = nil
159
+ doc2 = BSON::BSON_CODER.deserialize(bson_doc)
160
+ assert_equal doc, doc2
161
+ end
162
+
163
+ def test_boolean
164
+ doc = {'doc' => true}
165
+ bson = BSON::BSON_CODER.serialize(doc)
166
+ assert_equal doc, BSON::BSON_CODER.deserialize(bson)
167
+ end
168
+
169
+ def test_date
170
+ doc = {'date' => Time.now}
171
+ bson = BSON::BSON_CODER.serialize(doc)
172
+ doc2 = BSON::BSON_CODER.deserialize(bson)
173
+ # Mongo only stores up to the millisecond
174
+ assert_in_delta doc['date'], doc2['date'], 0.001
175
+ end
176
+
177
+ def test_date_returns_as_utc
178
+ doc = {'date' => Time.now}
179
+ bson = BSON::BSON_CODER.serialize(doc)
180
+ doc2 = BSON::BSON_CODER.deserialize(bson)
181
+ assert doc2['date'].utc?
182
+ end
183
+
184
+ def test_date_before_epoch
185
+ begin
186
+ doc = {'date' => Time.utc(1600)}
187
+ bson = BSON::BSON_CODER.serialize(doc)
188
+ doc2 = BSON::BSON_CODER.deserialize(bson)
189
+ # Mongo only stores up to the millisecond
190
+ assert_in_delta doc['date'], doc2['date'], 0.001
191
+ rescue ArgumentError
192
+ # some versions of Ruby won't let you create pre-epoch Time instances
193
+ #
194
+ # TODO figure out how that will work if somebady has saved data
195
+ # w/ early dates already and is just querying for it.
196
+ end
197
+ end
198
+
199
+ def test_exeption_on_using_unsupported_date_class
200
+ [DateTime.now, Date.today, Zone].each do |invalid_date|
201
+ doc = {:date => invalid_date}
202
+ begin
203
+ bson = BSON::BSON_CODER.serialize(doc)
204
+ rescue => e
205
+ ensure
206
+ if !invalid_date.is_a? Time
207
+ assert_equal InvalidDocument, e.class
208
+ assert_match /UTC Time/, e.message
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ def test_dbref
215
+ oid = ObjectID.new
216
+ doc = {}
217
+ doc['dbref'] = DBRef.new('namespace', oid)
218
+ bson = BSON::BSON_CODER.serialize(doc)
219
+ doc2 = BSON::BSON_CODER.deserialize(bson)
220
+ assert_equal 'namespace', doc2['dbref'].namespace
221
+ assert_equal oid, doc2['dbref'].object_id
222
+ end
223
+
224
+ def test_symbol
225
+ doc = {'sym' => :foo}
226
+ bson = BSON::BSON_CODER.serialize(doc)
227
+ doc2 = BSON::BSON_CODER.deserialize(bson)
228
+ assert_equal :foo, doc2['sym']
229
+ end
230
+
231
+ def test_binary
232
+ bin = Binary.new
233
+ 'binstring'.each_byte { |b| bin.put(b) }
234
+
235
+ doc = {'bin' => bin}
236
+ bson = BSON::BSON_CODER.serialize(doc)
237
+ doc2 = BSON::BSON_CODER.deserialize(bson)
238
+ bin2 = doc2['bin']
239
+ assert_kind_of Binary, bin2
240
+ assert_equal 'binstring', bin2.to_s
241
+ assert_equal Binary::SUBTYPE_BYTES, bin2.subtype
242
+ end
243
+
244
+ def test_binary_with_string
245
+ b = Binary.new('somebinarystring')
246
+ doc = {'bin' => b}
247
+ bson = BSON::BSON_CODER.serialize(doc)
248
+ doc2 = BSON::BSON_CODER.deserialize(bson)
249
+ bin2 = doc2['bin']
250
+ assert_kind_of Binary, bin2
251
+ assert_equal 'somebinarystring', bin2.to_s
252
+ assert_equal Binary::SUBTYPE_BYTES, bin2.subtype
253
+ end
254
+
255
+ def test_binary_type
256
+ bin = Binary.new([1, 2, 3, 4, 5], Binary::SUBTYPE_USER_DEFINED)
257
+
258
+ doc = {'bin' => bin}
259
+ bson = BSON::BSON_CODER.serialize(doc)
260
+ doc2 = BSON::BSON_CODER.deserialize(bson)
261
+ bin2 = doc2['bin']
262
+ assert_kind_of Binary, bin2
263
+ assert_equal [1, 2, 3, 4, 5], bin2.to_a
264
+ assert_equal Binary::SUBTYPE_USER_DEFINED, bin2.subtype
265
+ end
266
+
267
+ def test_binary_byte_buffer
268
+ bb = Binary.new
269
+ 5.times { |i| bb.put(i + 1) }
270
+
271
+ doc = {'bin' => bb}
272
+ bson = BSON::BSON_CODER.serialize(doc)
273
+ doc2 = BSON::BSON_CODER.deserialize(bson)
274
+ bin2 = doc2['bin']
275
+ assert_kind_of Binary, bin2
276
+ assert_equal [1, 2, 3, 4, 5], bin2.to_a
277
+ assert_equal Binary::SUBTYPE_BYTES, bin2.subtype
278
+ end
279
+
280
+ def test_put_id_first
281
+ val = OrderedHash.new
282
+ val['not_id'] = 1
283
+ val['_id'] = 2
284
+ roundtrip = BSON::BSON_CODER.deserialize(BSON::BSON_CODER.serialize(val, false, true).to_a)
285
+ assert_kind_of OrderedHash, roundtrip
286
+ assert_equal '_id', roundtrip.keys.first
287
+
288
+ val = {'a' => 'foo', 'b' => 'bar', :_id => 42, 'z' => 'hello'}
289
+ roundtrip = BSON::BSON_CODER.deserialize(BSON::BSON_CODER.serialize(val, false, true).to_a)
290
+ assert_kind_of OrderedHash, roundtrip
291
+ assert_equal '_id', roundtrip.keys.first
292
+ end
293
+
294
+ def test_nil_id
295
+ doc = {"_id" => nil}
296
+ assert_equal doc, BSON::BSON_CODER.deserialize(bson = BSON::BSON_CODER.serialize(doc, false, true).to_a)
297
+ end
298
+
299
+ def test_timestamp
300
+ val = {"test" => [4, 20]}
301
+ assert_equal val, BSON::BSON_CODER.deserialize([0x13, 0x00, 0x00, 0x00,
302
+ 0x11, 0x74, 0x65, 0x73,
303
+ 0x74, 0x00, 0x04, 0x00,
304
+ 0x00, 0x00, 0x14, 0x00,
305
+ 0x00, 0x00, 0x00])
306
+ end
307
+
308
+ def test_overflow
309
+ doc = {"x" => 2**75}
310
+ assert_raise RangeError do
311
+ bson = BSON::BSON_CODER.serialize(doc)
312
+ end
313
+
314
+ doc = {"x" => 9223372036854775}
315
+ assert_equal doc, BSON::BSON_CODER.deserialize(BSON::BSON_CODER.serialize(doc).to_a)
316
+
317
+ doc = {"x" => 9223372036854775807}
318
+ assert_equal doc, BSON::BSON_CODER.deserialize(BSON::BSON_CODER.serialize(doc).to_a)
319
+
320
+ doc["x"] = doc["x"] + 1
321
+ assert_raise RangeError do
322
+ bson = BSON::BSON_CODER.serialize(doc)
323
+ end
324
+
325
+ doc = {"x" => -9223372036854775}
326
+ assert_equal doc, BSON::BSON_CODER.deserialize(BSON::BSON_CODER.serialize(doc).to_a)
327
+
328
+ doc = {"x" => -9223372036854775808}
329
+ assert_equal doc, BSON::BSON_CODER.deserialize(BSON::BSON_CODER.serialize(doc).to_a)
330
+
331
+ doc["x"] = doc["x"] - 1
332
+ assert_raise RangeError do
333
+ bson = BSON::BSON_CODER.serialize(doc)
334
+ end
335
+ end
336
+
337
+ def test_invalid_numeric_types
338
+ [BigDecimal.new("1.0"), Complex(0, 1), Rational(2, 3)].each do |type|
339
+ doc = {"x" => type}
340
+ begin
341
+ BSON::BSON_CODER.serialize(doc)
342
+ rescue => e
343
+ ensure
344
+ assert_equal InvalidDocument, e.class
345
+ assert_match /Cannot serialize/, e.message
346
+ end
347
+ end
348
+ end
349
+
350
+ def test_do_not_change_original_object
351
+ val = OrderedHash.new
352
+ val['not_id'] = 1
353
+ val['_id'] = 2
354
+ assert val.keys.include?('_id')
355
+ BSON::BSON_CODER.serialize(val)
356
+ assert val.keys.include?('_id')
357
+
358
+ val = {'a' => 'foo', 'b' => 'bar', :_id => 42, 'z' => 'hello'}
359
+ assert val.keys.include?(:_id)
360
+ BSON::BSON_CODER.serialize(val)
361
+ assert val.keys.include?(:_id)
362
+ end
363
+
364
+ # note we only test for _id here because in the general case we will
365
+ # write duplicates for :key and "key". _id is a special case because
366
+ # we call has_key? to check for it's existance rather than just iterating
367
+ # over it like we do for the rest of the keys. thus, things like
368
+ # HashWithIndifferentAccess can cause problems for _id but not for other
369
+ # keys. rather than require rails to test with HWIA directly, we do this
370
+ # somewhat hacky test.
371
+ def test_no_duplicate_id
372
+ dup = {"_id" => "foo", :_id => "foo"}
373
+ one = {"_id" => "foo"}
374
+
375
+ assert_equal BSON::BSON_CODER.serialize(one).to_a, BSON::BSON_CODER.serialize(dup).to_a
376
+ end
377
+
378
+ def test_no_duplicate_id_when_moving_id
379
+ dup = {"_id" => "foo", :_id => "foo"}
380
+ one = {:_id => "foo"}
381
+
382
+ assert_equal BSON::BSON_CODER.serialize(one, false, true).to_s, BSON::BSON_CODER.serialize(dup, false, true).to_s
383
+ end
384
+
385
+ def test_null_character
386
+ doc = {"a" => "\x00"}
387
+
388
+ assert_equal doc, BSON::BSON_CODER.deserialize(BSON::BSON_CODER.serialize(doc).to_a)
389
+
390
+ assert_raise InvalidDocument do
391
+ BSON::BSON_CODER.serialize({"\x00" => "a"})
392
+ end
393
+
394
+ assert_raise InvalidDocument do
395
+ BSON::BSON_CODER.serialize({"a" => (Regexp.compile "ab\x00c")})
396
+ end
397
+ end
398
+
399
+ def test_max_key
400
+ doc = {"a" => MaxKey.new}
401
+
402
+ assert_equal doc, BSON::BSON_CODER.deserialize(BSON::BSON_CODER.serialize(doc).to_a)
403
+ end
404
+
405
+ def test_min_key
406
+ doc = {"a" => MinKey.new}
407
+
408
+ assert_equal doc, BSON::BSON_CODER.deserialize(BSON::BSON_CODER.serialize(doc).to_a)
409
+ end
410
+
411
+ def test_invalid_object
412
+ o = Object.new
413
+ assert_raise InvalidDocument do
414
+ BSON::BSON_CODER.serialize({:foo => o})
415
+ end
416
+
417
+ assert_raise InvalidDocument do
418
+ BSON::BSON_CODER.serialize({:foo => Date.today})
419
+ end
420
+ end
421
+
422
+ def test_move_id
423
+ a = OrderedHash.new
424
+ a['text'] = 'abc'
425
+ a['key'] = 'abc'
426
+ a['_id'] = 1
427
+
428
+
429
+ assert_equal ")\000\000\000\020_id\000\001\000\000\000\002text" +
430
+ "\000\004\000\000\000abc\000\002key\000\004\000\000\000abc\000\000",
431
+ BSON::BSON_CODER.serialize(a, false, true).to_s
432
+ assert_equal ")\000\000\000\002text\000\004\000\000\000abc\000\002key" +
433
+ "\000\004\000\000\000abc\000\020_id\000\001\000\000\000\000",
434
+ BSON::BSON_CODER.serialize(a, false, false).to_s
435
+ end
436
+
437
+ def test_move_id_with_nested_doc
438
+ b = OrderedHash.new
439
+ b['text'] = 'abc'
440
+ b['_id'] = 2
441
+ c = OrderedHash.new
442
+ c['text'] = 'abc'
443
+ c['hash'] = b
444
+ c['_id'] = 3
445
+ assert_equal ">\000\000\000\020_id\000\003\000\000\000\002text" +
446
+ "\000\004\000\000\000abc\000\003hash\000\034\000\000" +
447
+ "\000\002text\000\004\000\000\000abc\000\020_id\000\002\000\000\000\000\000",
448
+ BSON::BSON_CODER.serialize(c, false, true).to_s
449
+ assert_equal ">\000\000\000\002text\000\004\000\000\000abc\000\003hash" +
450
+ "\000\034\000\000\000\002text\000\004\000\000\000abc\000\020_id" +
451
+ "\000\002\000\000\000\000\020_id\000\003\000\000\000\000",
452
+ BSON::BSON_CODER.serialize(c, false, false).to_s
453
+ end
454
+
455
+ if defined?(HashWithIndifferentAccess)
456
+ def test_keep_id_with_hash_with_indifferent_access
457
+ doc = HashWithIndifferentAccess.new
458
+ embedded = HashWithIndifferentAccess.new
459
+ embedded['_id'] = ObjectID.new
460
+ doc['_id'] = ObjectID.new
461
+ doc['embedded'] = [embedded]
462
+ BSON::BSON_CODER.serialize(doc, false, true).to_a
463
+ assert doc.has_key?("_id")
464
+ assert doc['embedded'][0].has_key?("_id")
465
+
466
+ doc['_id'] = ObjectID.new
467
+ BSON::BSON_CODER.serialize(doc, false, true).to_a
468
+ assert doc.has_key?("_id")
469
+ end
470
+ end
471
+ end