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.
- data/LICENSE.txt +190 -0
- data/bin/b2json +65 -0
- data/bin/j2bson +72 -0
- data/ext/java/jar/bson-2.2.jar +0 -0
- data/ext/java/jar/jbson.jar +0 -0
- data/ext/java/jar/mongo-2.4.jar +0 -0
- data/lib/bson.rb +108 -0
- data/lib/bson/bson_c.rb +39 -0
- data/lib/bson/bson_java.rb +28 -0
- data/lib/bson/bson_ruby.rb +612 -0
- data/lib/bson/byte_buffer.rb +276 -0
- data/lib/bson/exceptions.rb +44 -0
- data/lib/bson/ordered_hash.rb +159 -0
- data/lib/bson/types/binary.rb +56 -0
- data/lib/bson/types/code.rb +59 -0
- data/lib/bson/types/dbref.rb +46 -0
- data/lib/bson/types/min_max_keys.rb +60 -0
- data/lib/bson/types/object_id.rb +194 -0
- data/test/bson/binary_test.rb +15 -0
- data/test/bson/bson_test.rb +565 -0
- data/test/bson/byte_buffer_test.rb +190 -0
- data/test/bson/hash_with_indifferent_access_test.rb +38 -0
- data/test/bson/json_test.rb +17 -0
- data/test/bson/object_id_test.rb +141 -0
- data/test/bson/ordered_hash_test.rb +197 -0
- metadata +100 -0
@@ -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
|