bson 1.2.0-jruby

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,56 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+
19
+ require 'bson/byte_buffer'
20
+
21
+ module BSON
22
+
23
+ # An array of binary bytes with a MongoDB subtype. See the subtype
24
+ # constants for reference.
25
+ #
26
+ # Use this class when storing binary data in documents.
27
+ class Binary < ByteBuffer
28
+
29
+ SUBTYPE_SIMPLE = 0x00
30
+ SUBTYPE_BYTES = 0x02
31
+ SUBTYPE_UUID = 0x03
32
+ SUBTYPE_MD5 = 0x05
33
+ SUBTYPE_USER_DEFINED = 0x80
34
+
35
+ # One of the SUBTYPE_* constants. Default is SUBTYPE_BYTES.
36
+ attr_accessor :subtype
37
+
38
+ # Create a buffer for storing binary data in MongoDB.
39
+ #
40
+ # @param [Array, String] data to story as BSON binary. If a string is given, the on
41
+ # Ruby 1.9 it will be forced to the binary encoding.
42
+ # @param [Fixnum] one of four values specifying a BSON binary subtype. Possible values are
43
+ # SUBTYPE_BYTES, SUBTYPE_UUID, SUBTYPE_MD5, and SUBTYPE_USER_DEFINED.
44
+ #
45
+ # @see http://www.mongodb.org/display/DOCS/BSON#BSON-noteondatabinary BSON binary subtypes.
46
+ def initialize(data=[], subtype=SUBTYPE_BYTES)
47
+ super(data)
48
+ @subtype = subtype
49
+ end
50
+
51
+ def inspect
52
+ "<BSON::Binary:#{object_id}>"
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,59 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+
19
+ module BSON
20
+
21
+ # JavaScript code to be evaluated by MongoDB.
22
+ class Code
23
+
24
+ # Hash mapping identifiers to their values
25
+ attr_accessor :scope, :code
26
+
27
+ # Wrap code to be evaluated by MongoDB.
28
+ #
29
+ # @param [String] code the JavaScript code.
30
+ # @param [Hash] a document mapping identifiers to values, which
31
+ # represent the scope in which the code is to be executed.
32
+ def initialize(code, scope={})
33
+ @code = code
34
+ @scope = scope
35
+
36
+ unless @code.is_a?(String)
37
+ raise ArgumentError, "BSON::Code must be in the form of a String; #{@code.class} is not allowed."
38
+ end
39
+ end
40
+
41
+ def length
42
+ @code.length
43
+ end
44
+
45
+ def ==(other)
46
+ self.class == other.class &&
47
+ @code == other.code && @scope == other.scope
48
+ end
49
+
50
+ def inspect
51
+ "<BSON::Code:#{object_id} @data=\"#{@code}\" @scope=\"#{@scope.inspect}\">"
52
+ end
53
+
54
+ def to_bson_code
55
+ self
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+
19
+ module BSON
20
+
21
+ # A reference to another object in a MongoDB database.
22
+ class DBRef
23
+
24
+ attr_reader :namespace, :object_id
25
+
26
+ # Create a DBRef. Use this class in conjunction with DB#dereference.
27
+ #
28
+ # @param [String] a collection name
29
+ # @param [ObjectID] an object id
30
+ #
31
+ # @core dbrefs constructor_details
32
+ def initialize(namespace, object_id)
33
+ @namespace = namespace
34
+ @object_id = object_id
35
+ end
36
+
37
+ def to_s
38
+ "ns: #{namespace}, id: #{object_id}"
39
+ end
40
+
41
+ def to_hash
42
+ {"$ns" => @namespace, "$id" => @object_id }
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,60 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+
19
+ module BSON
20
+
21
+ # A class representing the BSON MaxKey type. MaxKey will always compare greater than
22
+ # all other BSON types and values.
23
+ #
24
+ # @example Sorting (assume @numbers is a collection):
25
+ #
26
+ # >> @numbers.save({"n" => Mongo::MaxKey.new})
27
+ # >> @numbers.save({"n" => 0})
28
+ # >> @numbers.save({"n" => 5_000_000})
29
+ # >> @numbers.find.sort("n").to_a
30
+ # => [{"_id"=>4b5a050c238d3bace2000004, "n"=>0},
31
+ # {"_id"=>4b5a04e6238d3bace2000002, "n"=>5_000_000},
32
+ # {"_id"=>4b5a04ea238d3bace2000003, "n"=>#<Mongo::MaxKey:0x1014ef410>},
33
+ # ]
34
+ class MaxKey
35
+
36
+ def ==(obj)
37
+ obj.class == MaxKey
38
+ end
39
+ end
40
+
41
+ # A class representing the BSON MinKey type. MinKey will always compare less than
42
+ # all other BSON types and values.
43
+ #
44
+ # @example Sorting (assume @numbers is a collection):
45
+ #
46
+ # >> @numbers.save({"n" => Mongo::MinKey.new})
47
+ # >> @numbers.save({"n" => -1_000_000})
48
+ # >> @numbers.save({"n" => 1_000_000})
49
+ # >> @numbers.find.sort("n").to_a
50
+ # => [{"_id"=>4b5a050c238d3bace2000004, "n"=>#<Mongo::MinKey:0x1014ef410>},
51
+ # {"_id"=>4b5a04e6238d3bace2000002, "n"=>-1_000_000},
52
+ # {"_id"=>4b5a04ea238d3bace2000003, "n"=>1_000_000},
53
+ # ]
54
+ class MinKey
55
+
56
+ def ==(obj)
57
+ obj.class == MinKey
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,194 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+
19
+ require 'thread'
20
+ require 'socket'
21
+ require 'digest/md5'
22
+
23
+ module BSON
24
+
25
+ def BSON::ObjectId(s)
26
+ ObjectId.from_string(s)
27
+ end
28
+
29
+ # Generates MongoDB object ids.
30
+ #
31
+ # @core objectids
32
+ class ObjectId
33
+ @@lock = Mutex.new
34
+ @@index = 0
35
+
36
+ # Create a new object id. If no parameter is given, an id corresponding
37
+ # to the ObjectId BSON data type will be created. This is a 12-byte value
38
+ # consisting of a 4-byte timestamp, a 3-byte machine id, a 2-byte process id,
39
+ # and a 3-byte counter.
40
+ #
41
+ # @param [Array] data should be an array of bytes. If you want
42
+ # to generate a standard MongoDB object id, leave this argument blank.
43
+ def initialize(data=nil)
44
+ @data = data || generate
45
+ end
46
+
47
+ attr_accessor :data
48
+
49
+ # Determine if the supplied string is legal. Legal strings will
50
+ # consist of 24 hexadecimal characters.
51
+ #
52
+ # @param [String] str
53
+ #
54
+ # @return [Boolean]
55
+ def self.legal?(str)
56
+ str =~ /^[0-9a-f]{24}$/i ? true : false
57
+ end
58
+
59
+ # Create an object id from the given time. This is useful for doing range
60
+ # queries; it works because MongoDB's object ids begin
61
+ # with a timestamp.
62
+ #
63
+ # @param [Time] time a utc time to encode as an object id.
64
+ #
65
+ # @return [Mongo::ObjectId]
66
+ #
67
+ # @example Return all document created before Jan 1, 2010.
68
+ # time = Time.utc(2010, 1, 1)
69
+ # time_id = ObjectId.from_time(time)
70
+ # collection.find({'_id' => {'$lt' => time_id}})
71
+ def self.from_time(time)
72
+ self.new([time.to_i,0,0].pack("NNN").unpack("C12"))
73
+ end
74
+
75
+ # Adds a primary key to the given document if needed.
76
+ #
77
+ # @param [Hash] doc a document requiring an _id.
78
+ #
79
+ # @return [Mongo::ObjectId, Object] returns a newly-created or
80
+ # current _id for the given document.
81
+ def self.create_pk(doc)
82
+ doc.has_key?(:_id) || doc.has_key?('_id') ? doc : doc.merge!(:_id => self.new)
83
+ end
84
+
85
+ # Check equality of this object id with another.
86
+ #
87
+ # @param [Mongo::ObjectId] object_id
88
+ def eql?(object_id)
89
+ @data == object_id.instance_variable_get("@data")
90
+ end
91
+ alias_method :==, :eql?
92
+
93
+ # Get a unique hashcode for this object.
94
+ # This is required since we've defined an #eql? method.
95
+ #
96
+ # @return [Integer]
97
+ def hash
98
+ @data.hash
99
+ end
100
+
101
+ # Get an array representation of the object id.
102
+ #
103
+ # @return [Array]
104
+ def to_a
105
+ @data.dup
106
+ end
107
+
108
+ # Get the array representation without cloning.
109
+ #
110
+ # @return [Array]
111
+ def data
112
+ @data
113
+ end
114
+
115
+ # Given a string representation of an ObjectId, return a new ObjectId
116
+ # with that value.
117
+ #
118
+ # @param [String] str
119
+ #
120
+ # @return [Mongo::ObjectId]
121
+ def self.from_string(str)
122
+ raise InvalidObjectId, "illegal ObjectId format" unless legal?(str)
123
+ data = []
124
+ 12.times do |i|
125
+ data[i] = str[i * 2, 2].to_i(16)
126
+ end
127
+ self.new(data)
128
+ end
129
+
130
+ # Get a string representation of this object id.
131
+ #
132
+ # @return [String]
133
+ def to_s
134
+ @data.map {|e| v=e.to_s(16); v.size == 1 ? "0#{v}" : v }.join
135
+ end
136
+
137
+ def inspect
138
+ "BSON::ObjectId('#{to_s}')"
139
+ end
140
+
141
+ # Convert to MongoDB extended JSON format. Since JSON includes type information,
142
+ # but lacks an ObjectId type, this JSON format encodes the type using an $oid key.
143
+ #
144
+ # @return [String] the object id represented as MongoDB extended JSON.
145
+ def to_json(*a)
146
+ "{\"$oid\": \"#{to_s}\"}"
147
+ end
148
+
149
+ # Create the JSON hash structure convert to MongoDB extended format. Rails 2.3.3
150
+ # introduced as_json to create the needed hash structure to encode objects into JSON.
151
+ #
152
+ # @return [Hash] the hash representation as MongoDB extended JSON
153
+ def as_json(options ={})
154
+ {"$oid" => to_s}
155
+ end
156
+
157
+ # Return the UTC time at which this ObjectId was generated. This may
158
+ # be used in lieu of a created_at timestamp since this information
159
+ # is always encoded in the object id.
160
+ #
161
+ # @return [Time] the time at which this object was created.
162
+ def generation_time
163
+ Time.at(@data.pack("C4").unpack("N")[0]).utc
164
+ end
165
+
166
+ private
167
+
168
+ # This gets overwritten by the C extension if it loads.
169
+ def generate
170
+ oid = ''
171
+
172
+ # 4 bytes current time
173
+ time = Time.new.to_i
174
+ oid += [time].pack("N")
175
+
176
+ # 3 bytes machine
177
+ oid += Digest::MD5.digest(Socket.gethostname)[0, 3]
178
+
179
+ # 2 bytes pid
180
+ oid += [Process.pid % 0xFFFF].pack("n")
181
+
182
+ # 3 bytes inc
183
+ oid += [get_inc].pack("N")[1, 3]
184
+
185
+ oid.unpack("C12")
186
+ end
187
+
188
+ def get_inc
189
+ @@lock.synchronize do
190
+ @@index = (@@index + 1) % 0xFFFFFF
191
+ end
192
+ end
193
+ end
194
+ 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,565 @@
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
+ Time.zone = "Pacific Time (US & Canada)"
10
+ Zone = Time.zone.now
11
+ rescue LoadError
12
+ warn 'Mocking time with zone'
13
+ module ActiveSupport
14
+ class TimeWithZone
15
+ def initialize(utc_time, zone)
16
+ end
17
+ end
18
+ end
19
+ Zone = ActiveSupport::TimeWithZone.new(Time.now.utc, 'EST')
20
+ end
21
+
22
+ class BSONTest < Test::Unit::TestCase
23
+
24
+ include BSON
25
+
26
+ def setup
27
+ @encoder = BSON::BSON_CODER
28
+ end
29
+
30
+ def assert_doc_pass(doc, options={})
31
+ bson = @encoder.serialize(doc)
32
+ if options[:debug]
33
+ puts "DEBUGGING DOC:"
34
+ p bson.to_a
35
+ puts "DESERIALIZES TO:"
36
+ end
37
+ assert_equal @encoder.serialize(doc).to_a, bson.to_a
38
+ assert_equal doc, @encoder.deserialize(bson)
39
+ end
40
+
41
+ def test_require_hash
42
+ assert_raise_error InvalidDocument, "takes a Hash" do
43
+ BSON.serialize('foo')
44
+ end
45
+
46
+ assert_raise_error InvalidDocument, "takes a Hash" do
47
+ BSON.serialize(Object.new)
48
+ end
49
+
50
+ assert_raise_error InvalidDocument, "takes a Hash" do
51
+ BSON.serialize(Set.new)
52
+ end
53
+ end
54
+
55
+ def test_string
56
+ doc = {'doc' => 'hello, world'}
57
+ assert_doc_pass(doc)
58
+ end
59
+
60
+ def test_valid_utf8_string
61
+ doc = {'doc' => 'aé'}
62
+ assert_doc_pass(doc)
63
+ end
64
+
65
+ def test_valid_utf8_key
66
+ doc = {'aé' => 'hello'}
67
+ assert_doc_pass(doc)
68
+ end
69
+
70
+ def test_limit_max_bson_size
71
+ doc = {'name' => 'a' * BSON_CODER.max_bson_size}
72
+ assert_raise InvalidDocument do
73
+ assert @encoder.serialize(doc)
74
+ end
75
+ end
76
+
77
+ def test_max_bson_size
78
+ assert BSON_CODER.max_bson_size >= BSON::DEFAULT_MAX_BSON_SIZE
79
+ end
80
+
81
+ def test_update_max_bson_size
82
+ require 'ostruct'
83
+ mock_conn = OpenStruct.new
84
+ size = 7 * 1024 * 1024
85
+ mock_conn.max_bson_size = size
86
+ assert_equal size, BSON_CODER.update_max_bson_size(mock_conn)
87
+ assert_equal size, BSON_CODER.max_bson_size
88
+ end
89
+
90
+ def test_round_trip
91
+ doc = {'doc' => 123}
92
+ @encoder.deserialize(@encoder.serialize(doc))
93
+ end
94
+
95
+ # In 1.8 we test that other string encodings raise an exception.
96
+ # In 1.9 we test that they get auto-converted.
97
+ if RUBY_VERSION < '1.9'
98
+ if ! RUBY_PLATFORM =~ /java/
99
+ require 'iconv'
100
+ def test_non_utf8_string
101
+ string = Iconv.conv('iso-8859-1', 'utf-8', 'aé')
102
+ doc = {'doc' => string}
103
+ assert_doc_pass(doc)
104
+ assert_raise InvalidStringEncoding do
105
+ @encoder.serialize(doc)
106
+ end
107
+ end
108
+
109
+ def test_non_utf8_key
110
+ key = Iconv.conv('iso-8859-1', 'utf-8', 'aé')
111
+ doc = {key => 'hello'}
112
+ assert_raise InvalidStringEncoding do
113
+ @encoder.serialize(doc)
114
+ end
115
+ end
116
+ end
117
+ else
118
+ def test_non_utf8_string
119
+ bson = BSON::BSON_CODER.serialize({'str' => 'aé'.encode('iso-8859-1')})
120
+ result = BSON::BSON_CODER.deserialize(bson)['str']
121
+ assert_equal 'aé', result
122
+ assert_equal 'UTF-8', result.encoding.name
123
+ end
124
+
125
+ def test_non_utf8_key
126
+ bson = BSON::BSON_CODER.serialize({'aé'.encode('iso-8859-1') => 'hello'})
127
+ assert_equal 'hello', BSON::BSON_CODER.deserialize(bson)['aé']
128
+ end
129
+
130
+ # Based on a test from sqlite3-ruby
131
+ def test_default_internal_is_honored
132
+ before_enc = Encoding.default_internal
133
+
134
+ str = "壁に耳あり、障子に目あり"
135
+ bson = BSON::BSON_CODER.serialize("x" => str)
136
+
137
+ Encoding.default_internal = 'EUC-JP'
138
+ out = BSON::BSON_CODER.deserialize(bson)["x"]
139
+
140
+ assert_equal Encoding.default_internal, out.encoding
141
+ assert_equal str.encode('EUC-JP'), out
142
+ assert_equal str, out.encode(str.encoding)
143
+ ensure
144
+ Encoding.default_internal = before_enc
145
+ end
146
+ end
147
+
148
+ def test_code
149
+ doc = {'$where' => Code.new('this.a.b < this.b')}
150
+ assert_doc_pass(doc)
151
+ end
152
+
153
+ def test_code_with_symbol
154
+ assert_raise_error ArgumentError, "BSON::Code must be in the form of a String" do
155
+ Code.new(:fubar)
156
+ end
157
+ end
158
+
159
+ def test_code_with_scope
160
+ doc = {'$where' => Code.new('this.a.b < this.b', {'foo' => 1})}
161
+ assert_doc_pass(doc)
162
+ end
163
+
164
+ def test_double
165
+ doc = {'doc' => 41.25}
166
+ assert_doc_pass(doc)
167
+ end
168
+
169
+ def test_int
170
+ doc = {'doc' => 42}
171
+ assert_doc_pass(doc)
172
+
173
+ doc = {"doc" => -5600}
174
+ assert_doc_pass(doc)
175
+
176
+ doc = {"doc" => 2147483647}
177
+ assert_doc_pass(doc)
178
+
179
+ doc = {"doc" => -2147483648}
180
+ assert_doc_pass(doc)
181
+ end
182
+
183
+ def test_ordered_hash
184
+ doc = BSON::OrderedHash.new
185
+ doc["b"] = 1
186
+ doc["a"] = 2
187
+ doc["c"] = 3
188
+ doc["d"] = 4
189
+ assert_doc_pass(doc)
190
+ end
191
+
192
+ def test_object
193
+ doc = {'doc' => {'age' => 42, 'name' => 'Spongebob', 'shoe_size' => 9.5}}
194
+ assert_doc_pass(doc)
195
+ end
196
+
197
+ def test_embedded_document_with_nil
198
+ doc = {'doc' => {'age' => 42, 'name' => nil, 'shoe_size' => 9.5}}
199
+ assert_doc_pass(doc)
200
+ end
201
+
202
+ def test_embedded_document_with_date
203
+ doc = {'doc' => {'age' => 42, 'date' => Time.now.utc, 'shoe_size' => 9.5}}
204
+ bson = @encoder.serialize(doc)
205
+ doc2 = @encoder.deserialize(bson)
206
+ assert doc['doc']
207
+ assert_equal 42, doc['doc']['age']
208
+ assert_equal 9.5, doc['doc']['shoe_size']
209
+ assert_in_delta Time.now, doc['doc']['date'], 1
210
+ end
211
+
212
+ def test_oid
213
+ doc = {'doc' => ObjectId.new}
214
+ assert_doc_pass(doc)
215
+ end
216
+
217
+ def test_array
218
+ doc = {'doc' => [1, 2, 'a', 'b']}
219
+ assert_doc_pass(doc)
220
+ end
221
+
222
+ def test_array_keys
223
+ doc = {'doc' => [1, 2, 'a', 'b']}
224
+ bson = @encoder.serialize(doc).to_a
225
+ assert_equal 48, bson[14]
226
+ assert_equal 49, bson[21]
227
+ assert_equal 50, bson[28]
228
+ assert_equal 51, bson[37]
229
+ end
230
+
231
+ def test_regex
232
+ doc = {'doc' => /foobar/i}
233
+ assert_doc_pass(doc)
234
+ end
235
+
236
+ def test_boolean
237
+ doc = {'doc' => true}
238
+ assert_doc_pass(doc)
239
+ end
240
+
241
+ def test_date
242
+ doc = {'date' => Time.now}
243
+ bson = @encoder.serialize(doc)
244
+ doc2 = @encoder.deserialize(bson)
245
+ # Mongo only stores up to the millisecond
246
+ assert_in_delta doc['date'], doc2['date'], 0.001
247
+ end
248
+
249
+ def test_date_returns_as_utc
250
+ doc = {'date' => Time.now}
251
+ bson = @encoder.serialize(doc)
252
+ doc2 = @encoder.deserialize(bson)
253
+ assert doc2['date'].utc?
254
+ end
255
+
256
+ def test_date_before_epoch
257
+ begin
258
+ doc = {'date' => Time.utc(1600)}
259
+ bson = @encoder.serialize(doc)
260
+ doc2 = @encoder.deserialize(bson)
261
+ # Mongo only stores up to the millisecond
262
+ assert_in_delta doc['date'], doc2['date'], 2
263
+ rescue ArgumentError
264
+ # some versions of Ruby won't let you create pre-epoch Time instances
265
+ #
266
+ # TODO figure out how that will work if somebady has saved data
267
+ # w/ early dates already and is just querying for it.
268
+ end
269
+ end
270
+
271
+ def test_exeption_on_using_unsupported_date_class
272
+ [DateTime.now, Date.today, Zone].each do |invalid_date|
273
+ doc = {:date => invalid_date}
274
+ begin
275
+ bson = BSON::BSON_CODER.serialize(doc)
276
+ rescue => e
277
+ ensure
278
+ if !invalid_date.is_a? Time
279
+ assert_equal InvalidDocument, e.class
280
+ assert_match /UTC Time/, e.message
281
+ end
282
+ end
283
+ end
284
+ end
285
+
286
+ def test_dbref
287
+ oid = ObjectId.new
288
+ doc = {}
289
+ doc['dbref'] = DBRef.new('namespace', oid)
290
+ bson = @encoder.serialize(doc)
291
+ doc2 = @encoder.deserialize(bson)
292
+
293
+ # Java doesn't deserialize to DBRefs
294
+ if RUBY_PLATFORM =~ /java/
295
+ assert_equal 'namespace', doc2['dbref']['$ns']
296
+ assert_equal oid, doc2['dbref']['$id']
297
+ else
298
+ assert_equal 'namespace', doc2['dbref'].namespace
299
+ assert_equal oid, doc2['dbref'].object_id
300
+ end
301
+ end
302
+
303
+ def test_symbol
304
+ doc = {'sym' => :foo}
305
+ bson = @encoder.serialize(doc)
306
+ doc2 = @encoder.deserialize(bson)
307
+ assert_equal :foo, doc2['sym']
308
+ end
309
+
310
+ def test_binary
311
+ bin = Binary.new
312
+ 'binstring'.each_byte { |b| bin.put(b) }
313
+
314
+ doc = {'bin' => bin}
315
+ bson = @encoder.serialize(doc)
316
+ doc2 = @encoder.deserialize(bson)
317
+ bin2 = doc2['bin']
318
+ assert_kind_of Binary, bin2
319
+ assert_equal 'binstring', bin2.to_s
320
+ assert_equal Binary::SUBTYPE_BYTES, bin2.subtype
321
+ end
322
+
323
+ def test_binary_with_string
324
+ b = Binary.new('somebinarystring')
325
+ doc = {'bin' => b}
326
+ bson = @encoder.serialize(doc)
327
+ doc2 = @encoder.deserialize(bson)
328
+ bin2 = doc2['bin']
329
+ assert_kind_of Binary, bin2
330
+ assert_equal 'somebinarystring', bin2.to_s
331
+ assert_equal Binary::SUBTYPE_BYTES, bin2.subtype
332
+ end
333
+
334
+ def test_binary_type
335
+ bin = Binary.new([1, 2, 3, 4, 5], Binary::SUBTYPE_USER_DEFINED)
336
+
337
+ doc = {'bin' => bin}
338
+ bson = @encoder.serialize(doc)
339
+ doc2 = @encoder.deserialize(bson)
340
+ bin2 = doc2['bin']
341
+ assert_kind_of Binary, bin2
342
+ assert_equal [1, 2, 3, 4, 5], bin2.to_a
343
+ assert_equal Binary::SUBTYPE_USER_DEFINED, bin2.subtype
344
+ end
345
+
346
+ # Java doesn't support binary subtype 0 yet
347
+ if !(RUBY_PLATFORM =~ /java/)
348
+ def test_binary_subtype_0
349
+ bin = Binary.new([1, 2, 3, 4, 5], Binary::SUBTYPE_SIMPLE)
350
+
351
+ doc = {'bin' => bin}
352
+ bson = @encoder.serialize(doc)
353
+ doc2 = @encoder.deserialize(bson)
354
+ bin2 = doc2['bin']
355
+ assert_kind_of Binary, bin2
356
+ assert_equal [1, 2, 3, 4, 5], bin2.to_a
357
+ assert_equal Binary::SUBTYPE_SIMPLE, bin2.subtype
358
+ end
359
+ end
360
+
361
+ def test_binary_byte_buffer
362
+ bb = Binary.new
363
+ 5.times { |i| bb.put(i + 1) }
364
+
365
+ doc = {'bin' => bb}
366
+ bson = @encoder.serialize(doc)
367
+ doc2 = @encoder.deserialize(bson)
368
+ bin2 = doc2['bin']
369
+ assert_kind_of Binary, bin2
370
+ assert_equal [1, 2, 3, 4, 5], bin2.to_a
371
+ assert_equal Binary::SUBTYPE_BYTES, bin2.subtype
372
+ end
373
+
374
+ def test_put_id_first
375
+ val = BSON::OrderedHash.new
376
+ val['not_id'] = 1
377
+ val['_id'] = 2
378
+ roundtrip = @encoder.deserialize(@encoder.serialize(val, false, true).to_s)
379
+ assert_kind_of BSON::OrderedHash, roundtrip
380
+ assert_equal '_id', roundtrip.keys.first
381
+
382
+ val = {'a' => 'foo', 'b' => 'bar', :_id => 42, 'z' => 'hello'}
383
+ roundtrip = @encoder.deserialize(@encoder.serialize(val, false, true).to_s)
384
+ assert_kind_of BSON::OrderedHash, roundtrip
385
+ assert_equal '_id', roundtrip.keys.first
386
+ end
387
+
388
+ def test_nil_id
389
+ doc = {"_id" => nil}
390
+ assert_doc_pass(doc)
391
+ end
392
+
393
+ if !(RUBY_PLATFORM =~ /java/)
394
+ def test_timestamp
395
+ val = {"test" => [4, 20]}
396
+ assert_equal val, @encoder.deserialize([0x13, 0x00, 0x00, 0x00,
397
+ 0x11, 0x74, 0x65, 0x73,
398
+ 0x74, 0x00, 0x04, 0x00,
399
+ 0x00, 0x00, 0x14, 0x00,
400
+ 0x00, 0x00, 0x00])
401
+
402
+ end
403
+ end
404
+
405
+ def test_overflow
406
+ doc = {"x" => 2**75}
407
+ assert_raise RangeError do
408
+ bson = @encoder.serialize(doc)
409
+ end
410
+
411
+ doc = {"x" => 9223372036854775}
412
+ assert_doc_pass(doc)
413
+
414
+ doc = {"x" => 9223372036854775807}
415
+ assert_doc_pass(doc)
416
+
417
+ doc["x"] = doc["x"] + 1
418
+ assert_raise RangeError do
419
+ bson = @encoder.serialize(doc)
420
+ end
421
+
422
+ doc = {"x" => -9223372036854775}
423
+ assert_doc_pass(doc)
424
+
425
+ doc = {"x" => -9223372036854775808}
426
+ assert_doc_pass(doc)
427
+
428
+ doc["x"] = doc["x"] - 1
429
+ assert_raise RangeError do
430
+ bson = BSON::BSON_CODER.serialize(doc)
431
+ end
432
+ end
433
+
434
+ def test_invalid_numeric_types
435
+ [BigDecimal.new("1.0"), Complex(0, 1), Rational(2, 3)].each do |type|
436
+ doc = {"x" => type}
437
+ begin
438
+ @encoder.serialize(doc)
439
+ rescue => e
440
+ ensure
441
+ assert_equal InvalidDocument, e.class
442
+ assert_match /Cannot serialize/, e.message
443
+ end
444
+ end
445
+ end
446
+
447
+ def test_do_not_change_original_object
448
+ val = BSON::OrderedHash.new
449
+ val['not_id'] = 1
450
+ val['_id'] = 2
451
+ assert val.keys.include?('_id')
452
+ @encoder.serialize(val)
453
+ assert val.keys.include?('_id')
454
+
455
+ val = {'a' => 'foo', 'b' => 'bar', :_id => 42, 'z' => 'hello'}
456
+ assert val.keys.include?(:_id)
457
+ @encoder.serialize(val)
458
+ assert val.keys.include?(:_id)
459
+ end
460
+
461
+ # note we only test for _id here because in the general case we will
462
+ # write duplicates for :key and "key". _id is a special case because
463
+ # we call has_key? to check for it's existence rather than just iterating
464
+ # over it like we do for the rest of the keys. thus, things like
465
+ # HashWithIndifferentAccess can cause problems for _id but not for other
466
+ # keys. rather than require rails to test with HWIA directly, we do this
467
+ # somewhat hacky test.
468
+ #
469
+ # Note that the driver only eliminates duplicate ids when move_id is true.
470
+ def test_no_duplicate_id
471
+ dup = {"_id" => "foo", :_id => "foo"}
472
+ one = {"_id" => "foo"}
473
+
474
+ assert_equal @encoder.serialize(one, false, true).to_a, @encoder.serialize(dup, false, true).to_a
475
+ end
476
+
477
+ def test_duplicate_keys
478
+ #dup = {"_foo" => "foo", :_foo => "foo"}
479
+ #one = {"_foo" => "foo"}
480
+
481
+ #assert_equal @encoder.serialize(one).to_a, @encoder.serialize(dup).to_a
482
+ warn "Pending test for duplicate keys"
483
+ end
484
+
485
+ def test_no_duplicate_id_when_moving_id
486
+ dup = {"_id" => "foo", :_id => "foo"}
487
+ one = {:_id => "foo"}
488
+
489
+ assert_equal @encoder.serialize(one, false, true).to_s, @encoder.serialize(dup, false, true).to_s
490
+ end
491
+
492
+ def test_null_character
493
+ doc = {"a" => "\x00"}
494
+
495
+ assert_doc_pass(doc)
496
+
497
+ assert_raise InvalidDocument do
498
+ @encoder.serialize({"\x00" => "a"})
499
+ end
500
+
501
+ assert_raise InvalidDocument do
502
+ @encoder.serialize({"a" => (Regexp.compile "ab\x00c")})
503
+ end
504
+ end
505
+
506
+ def test_max_key
507
+ doc = {"a" => MaxKey.new}
508
+ assert_doc_pass(doc)
509
+ end
510
+
511
+ def test_min_key
512
+ doc = {"a" => MinKey.new}
513
+ assert_doc_pass(doc)
514
+ end
515
+
516
+ def test_invalid_object
517
+ o = Object.new
518
+ assert_raise InvalidDocument do
519
+ @encoder.serialize({:foo => o})
520
+ end
521
+
522
+ assert_raise InvalidDocument do
523
+ @encoder.serialize({:foo => Date.today})
524
+ end
525
+ end
526
+
527
+ def test_move_id
528
+ a = BSON::OrderedHash.new
529
+ a['text'] = 'abc'
530
+ a['key'] = 'abc'
531
+ a['_id'] = 1
532
+
533
+
534
+ assert_equal ")\000\000\000\020_id\000\001\000\000\000\002text" +
535
+ "\000\004\000\000\000abc\000\002key\000\004\000\000\000abc\000\000",
536
+ @encoder.serialize(a, false, true).to_s
537
+
538
+ assert_equal ")\000\000\000\002text\000\004\000\000\000abc\000\002key" +
539
+ "\000\004\000\000\000abc\000\020_id\000\001\000\000\000\000",
540
+ @encoder.serialize(a, false, false).to_s
541
+ end
542
+
543
+ def test_move_id_with_nested_doc
544
+ b = BSON::OrderedHash.new
545
+ b['text'] = 'abc'
546
+ b['_id'] = 2
547
+ c = BSON::OrderedHash.new
548
+ c['text'] = 'abc'
549
+ c['hash'] = b
550
+ c['_id'] = 3
551
+ assert_equal ">\000\000\000\020_id\000\003\000\000\000\002text" +
552
+ "\000\004\000\000\000abc\000\003hash\000\034\000\000" +
553
+ "\000\002text\000\004\000\000\000abc\000\020_id\000\002\000\000\000\000\000",
554
+ @encoder.serialize(c, false, true).to_s
555
+
556
+ # Java doesn't support this. Isn't actually necessary.
557
+ if !(RUBY_PLATFORM =~ /java/)
558
+ assert_equal ">\000\000\000\002text\000\004\000\000\000abc\000\003hash" +
559
+ "\000\034\000\000\000\002text\000\004\000\000\000abc\000\020_id" +
560
+ "\000\002\000\000\000\000\020_id\000\003\000\000\000\000",
561
+ @encoder.serialize(c, false, false).to_s
562
+ end
563
+ end
564
+
565
+ end