mongo 0.1.0 → 0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +268 -71
- data/Rakefile +27 -62
- data/bin/bson_benchmark.rb +59 -0
- data/bin/mongo_console +3 -3
- data/bin/run_test_script +19 -0
- data/bin/standard_benchmark +109 -0
- data/examples/admin.rb +41 -0
- data/examples/benchmarks.rb +42 -0
- data/examples/blog.rb +76 -0
- data/examples/capped.rb +23 -0
- data/examples/cursor.rb +47 -0
- data/examples/gridfs.rb +87 -0
- data/examples/index_test.rb +125 -0
- data/examples/info.rb +30 -0
- data/examples/queries.rb +69 -0
- data/examples/simple.rb +23 -0
- data/examples/strict.rb +34 -0
- data/examples/types.rb +35 -0
- data/lib/mongo.rb +9 -2
- data/lib/mongo/admin.rb +65 -68
- data/lib/mongo/collection.rb +379 -117
- data/lib/mongo/connection.rb +151 -0
- data/lib/mongo/cursor.rb +271 -216
- data/lib/mongo/db.rb +500 -315
- data/lib/mongo/errors.rb +26 -0
- data/lib/mongo/gridfs.rb +16 -0
- data/lib/mongo/gridfs/chunk.rb +92 -0
- data/lib/mongo/gridfs/grid_store.rb +464 -0
- data/lib/mongo/message.rb +16 -0
- data/lib/mongo/message/get_more_message.rb +24 -13
- data/lib/mongo/message/insert_message.rb +29 -11
- data/lib/mongo/message/kill_cursors_message.rb +23 -12
- data/lib/mongo/message/message.rb +74 -62
- data/lib/mongo/message/message_header.rb +35 -24
- data/lib/mongo/message/msg_message.rb +21 -9
- data/lib/mongo/message/opcodes.rb +26 -15
- data/lib/mongo/message/query_message.rb +63 -43
- data/lib/mongo/message/remove_message.rb +29 -12
- data/lib/mongo/message/update_message.rb +30 -13
- data/lib/mongo/query.rb +97 -89
- data/lib/mongo/types/binary.rb +25 -21
- data/lib/mongo/types/code.rb +30 -0
- data/lib/mongo/types/dbref.rb +19 -23
- data/lib/mongo/types/objectid.rb +130 -116
- data/lib/mongo/types/regexp_of_holding.rb +27 -31
- data/lib/mongo/util/bson.rb +273 -160
- data/lib/mongo/util/byte_buffer.rb +32 -28
- data/lib/mongo/util/ordered_hash.rb +88 -42
- data/lib/mongo/util/xml_to_ruby.rb +18 -15
- data/mongo-ruby-driver.gemspec +103 -0
- data/test/mongo-qa/_common.rb +8 -0
- data/test/mongo-qa/admin +26 -0
- data/test/mongo-qa/capped +22 -0
- data/test/mongo-qa/count1 +18 -0
- data/test/mongo-qa/dbs +22 -0
- data/test/mongo-qa/find +10 -0
- data/test/mongo-qa/find1 +15 -0
- data/test/mongo-qa/gridfs_in +16 -0
- data/test/mongo-qa/gridfs_out +17 -0
- data/test/mongo-qa/indices +49 -0
- data/test/mongo-qa/remove +25 -0
- data/test/mongo-qa/stress1 +35 -0
- data/test/mongo-qa/test1 +11 -0
- data/test/mongo-qa/update +18 -0
- data/{tests → test}/test_admin.rb +25 -16
- data/test/test_bson.rb +268 -0
- data/{tests → test}/test_byte_buffer.rb +0 -0
- data/test/test_chunk.rb +84 -0
- data/test/test_collection.rb +282 -0
- data/test/test_connection.rb +101 -0
- data/test/test_cursor.rb +321 -0
- data/test/test_db.rb +196 -0
- data/test/test_db_api.rb +798 -0
- data/{tests → test}/test_db_connection.rb +4 -3
- data/test/test_grid_store.rb +284 -0
- data/{tests → test}/test_message.rb +1 -1
- data/test/test_objectid.rb +105 -0
- data/{tests → test}/test_ordered_hash.rb +55 -0
- data/{tests → test}/test_round_trip.rb +13 -9
- data/test/test_threading.rb +37 -0
- metadata +74 -32
- data/bin/validate +0 -51
- data/lib/mongo/mongo.rb +0 -74
- data/lib/mongo/types/undefined.rb +0 -31
- data/tests/test_bson.rb +0 -135
- data/tests/test_cursor.rb +0 -66
- data/tests/test_db.rb +0 -51
- data/tests/test_db_api.rb +0 -349
- data/tests/test_objectid.rb +0 -88
@@ -0,0 +1,30 @@
|
|
1
|
+
# --
|
2
|
+
# Copyright (C) 2008-2009 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 Mongo
|
18
|
+
|
19
|
+
# JavaScript code to be evaluated by MongoDB
|
20
|
+
class Code < String
|
21
|
+
# Hash mapping identifiers to their values
|
22
|
+
attr_accessor :scope
|
23
|
+
|
24
|
+
def initialize(code, scope={})
|
25
|
+
super(code)
|
26
|
+
@scope = scope
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
data/lib/mongo/types/dbref.rb
CHANGED
@@ -1,37 +1,33 @@
|
|
1
1
|
# --
|
2
2
|
# Copyright (C) 2008-2009 10gen Inc.
|
3
3
|
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
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
7
|
#
|
8
|
-
#
|
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.
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
9
|
#
|
13
|
-
#
|
14
|
-
#
|
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
15
|
# ++
|
16
16
|
|
17
|
-
module
|
18
|
-
module Mongo
|
19
|
-
module Driver
|
17
|
+
module Mongo
|
20
18
|
|
21
|
-
|
19
|
+
class DBRef
|
22
20
|
|
23
|
-
|
21
|
+
attr_reader :namespace, :object_id
|
24
22
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
def to_s
|
31
|
-
"ns: #{namespace}, id: #{object_id}"
|
32
|
-
end
|
23
|
+
def initialize(namespace, object_id)
|
24
|
+
@namespace, @object_id =
|
25
|
+
namespace, object_id
|
26
|
+
end
|
33
27
|
|
34
|
-
|
28
|
+
def to_s
|
29
|
+
"ns: #{namespace}, id: #{object_id}"
|
35
30
|
end
|
31
|
+
|
36
32
|
end
|
37
33
|
end
|
data/lib/mongo/types/objectid.rb
CHANGED
@@ -1,129 +1,143 @@
|
|
1
1
|
# --
|
2
2
|
# Copyright (C) 2008-2009 10gen Inc.
|
3
3
|
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
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
7
|
#
|
8
|
-
#
|
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.
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
9
|
#
|
13
|
-
#
|
14
|
-
#
|
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
15
|
# ++
|
16
16
|
|
17
17
|
require 'mutex_m'
|
18
|
-
require '
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
LOCK = Object.new
|
59
|
-
LOCK.extend Mutex_m
|
60
|
-
|
61
|
-
@@index_time = Time.new.to_i
|
62
|
-
@@index = 0
|
63
|
-
|
64
|
-
# Given a string representation of an ObjectID, return a new ObjectID
|
65
|
-
# with that value.
|
66
|
-
def self.from_string(str)
|
67
|
-
data = []
|
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)
|
72
|
-
end
|
73
|
-
|
74
|
-
# +data+ is an array of bytes. If nil, a new id will be generated.
|
75
|
-
# The time +t+ is only used for testing; leave it nil.
|
76
|
-
def initialize(data=nil, t=nil)
|
77
|
-
@data = data || generate_id(t)
|
78
|
-
end
|
79
|
-
|
80
|
-
def eql?(other)
|
81
|
-
@data == other.to_a
|
82
|
-
end
|
83
|
-
alias_method :==, :eql?
|
84
|
-
|
85
|
-
def to_a
|
86
|
-
@data.dup
|
87
|
-
end
|
88
|
-
|
89
|
-
def to_s
|
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
|
95
|
-
end
|
96
|
-
|
97
|
-
# (Would normally be private, but isn't so we can test it.)
|
98
|
-
def generate_id(t=nil)
|
99
|
-
t ||= Time.new.to_i
|
100
|
-
buf = ByteBuffer.new
|
101
|
-
buf.put_int(t & 0xffffffff)
|
102
|
-
buf.put_array(MACHINE)
|
103
|
-
buf.put_array(PID)
|
104
|
-
i = index_for_time(t)
|
105
|
-
buf.put(i & 0xff)
|
106
|
-
buf.put((i >> 8) & 0xff)
|
107
|
-
buf.put((i >> 16) & 0xff)
|
108
|
-
|
109
|
-
buf.rewind
|
110
|
-
buf.to_a.dup
|
111
|
-
end
|
112
|
-
|
113
|
-
# (Would normally be private, but isn't so we can test it.)
|
114
|
-
def index_for_time(t)
|
115
|
-
LOCK.mu_synchronize {
|
116
|
-
if t != @@index_time
|
117
|
-
@@index = 0
|
118
|
-
@@index_time = t
|
119
|
-
end
|
120
|
-
retval = @@index
|
121
|
-
@@index += 1
|
122
|
-
retval
|
123
|
-
}
|
124
|
-
end
|
18
|
+
require 'socket'
|
19
|
+
require 'digest/md5'
|
20
|
+
|
21
|
+
module Mongo
|
22
|
+
|
23
|
+
# Representation of an ObjectId for Mongo.
|
24
|
+
class ObjectID
|
25
|
+
# This is the legacy byte ordering for Babble. Versions of the Ruby
|
26
|
+
# driver prior to 0.14 used this byte ordering when converting ObjectID
|
27
|
+
# instances to and from strings. If you have string representations of
|
28
|
+
# ObjectIDs using the legacy byte ordering make sure to use the
|
29
|
+
# to_s_legacy and from_string_legacy methods, or convert your strings
|
30
|
+
# with ObjectID#legacy_string_convert
|
31
|
+
BYTE_ORDER = [7, 6, 5, 4, 3, 2, 1, 0, 11, 10, 9, 8]
|
32
|
+
|
33
|
+
LOCK = Object.new
|
34
|
+
LOCK.extend Mutex_m
|
35
|
+
|
36
|
+
@@index = 0
|
37
|
+
|
38
|
+
def self.legal?(str)
|
39
|
+
len = BYTE_ORDER.length * 2
|
40
|
+
str =~ /([0-9a-f]+)/i
|
41
|
+
match = $1
|
42
|
+
str && str.length == len && match == str
|
43
|
+
end
|
44
|
+
|
45
|
+
# +data+ is an array of bytes. If nil, a new id will be generated.
|
46
|
+
def initialize(data=nil)
|
47
|
+
@data = data || generate
|
48
|
+
end
|
49
|
+
|
50
|
+
def eql?(other)
|
51
|
+
@data == other.instance_variable_get("@data")
|
52
|
+
end
|
53
|
+
alias_method :==, :eql?
|
54
|
+
|
55
|
+
def to_a
|
56
|
+
@data.dup
|
57
|
+
end
|
125
58
|
|
59
|
+
# Given a string representation of an ObjectID, return a new ObjectID
|
60
|
+
# with that value.
|
61
|
+
def self.from_string(str)
|
62
|
+
raise "illegal ObjectID format" unless legal?(str)
|
63
|
+
data = []
|
64
|
+
12.times do |i|
|
65
|
+
data[i] = str[i * 2, 2].to_i(16)
|
126
66
|
end
|
67
|
+
self.new(data)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Create a new ObjectID given a string representation of an ObjectID
|
71
|
+
# using the legacy byte ordering. This method may eventually be
|
72
|
+
# removed. If you are not sure that you need this method you should be
|
73
|
+
# using the regular from_string.
|
74
|
+
def self.from_string_legacy(str)
|
75
|
+
raise "illegal ObjectID format" unless legal?(str)
|
76
|
+
data = []
|
77
|
+
BYTE_ORDER.each_with_index { |string_position, data_index|
|
78
|
+
data[data_index] = str[string_position * 2, 2].to_i(16)
|
79
|
+
}
|
80
|
+
self.new(data)
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
str = ' ' * 24
|
85
|
+
12.times do |i|
|
86
|
+
str[i * 2, 2] = '%02x' % @data[i]
|
87
|
+
end
|
88
|
+
str
|
89
|
+
end
|
90
|
+
|
91
|
+
def inspect; to_s; end
|
92
|
+
|
93
|
+
# Get a string representation of this ObjectID using the legacy byte
|
94
|
+
# ordering. This method may eventually be removed. If you are not sure
|
95
|
+
# that you need this method you should be using the regular to_s.
|
96
|
+
def to_s_legacy
|
97
|
+
str = ' ' * 24
|
98
|
+
BYTE_ORDER.each_with_index { |string_position, data_index|
|
99
|
+
str[string_position * 2, 2] = '%02x' % @data[data_index]
|
100
|
+
}
|
101
|
+
str
|
102
|
+
end
|
103
|
+
|
104
|
+
# Convert a string representation of an ObjectID using the legacy byte
|
105
|
+
# ordering to the proper byte ordering. This method may eventually be
|
106
|
+
# removed. If you are not sure that you need this method it is probably
|
107
|
+
# unnecessary.
|
108
|
+
def self.legacy_string_convert(str)
|
109
|
+
legacy = ' ' * 24
|
110
|
+
BYTE_ORDER.each_with_index do |legacy_pos, pos|
|
111
|
+
legacy[legacy_pos * 2, 2] = str[pos * 2, 2]
|
112
|
+
end
|
113
|
+
legacy
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def generate
|
119
|
+
oid = ''
|
120
|
+
|
121
|
+
# 4 bytes current time
|
122
|
+
time = Time.new.to_i
|
123
|
+
oid += [time].pack("N")
|
124
|
+
|
125
|
+
# 3 bytes machine
|
126
|
+
oid += Digest::MD5.digest(Socket.gethostname)[0, 3]
|
127
|
+
|
128
|
+
# 2 bytes pid
|
129
|
+
oid += [Process.pid % 0xFFFF].pack("n")
|
130
|
+
|
131
|
+
# 3 bytes inc
|
132
|
+
oid += [get_inc].pack("N")[1, 3]
|
133
|
+
|
134
|
+
oid.unpack("C12")
|
135
|
+
end
|
136
|
+
|
137
|
+
def get_inc
|
138
|
+
LOCK.mu_synchronize {
|
139
|
+
@@index = (@@index + 1) % 0xFFFFFF
|
140
|
+
}
|
127
141
|
end
|
128
142
|
end
|
129
143
|
end
|
@@ -1,44 +1,40 @@
|
|
1
1
|
# --
|
2
2
|
# Copyright (C) 2008-2009 10gen Inc.
|
3
3
|
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
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
7
|
#
|
8
|
-
#
|
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.
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
9
|
#
|
13
|
-
#
|
14
|
-
#
|
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
15
|
# ++
|
16
16
|
|
17
|
-
module
|
18
|
-
module Mongo
|
19
|
-
module Driver
|
17
|
+
module Mongo
|
20
18
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
19
|
+
# A Regexp that can hold on to extra options and ignore them. Mongo
|
20
|
+
# regexes may contain option characters beyond 'i', 'm', and 'x'. (Note
|
21
|
+
# that Mongo only uses those three, but that regexes coming from other
|
22
|
+
# languages may store different option characters.)
|
23
|
+
#
|
24
|
+
# Note that you do not have to use this class at all if you wish to
|
25
|
+
# store regular expressions in Mongo. The Mongo and Ruby regex option
|
26
|
+
# flags are the same. Storing regexes is discouraged, in any case.
|
27
|
+
class RegexpOfHolding < Regexp
|
30
28
|
|
31
|
-
|
32
|
-
|
33
|
-
# +str+ and +options+ are the same as Regexp. +extra_options_str+
|
34
|
-
# contains all the other flags that were in Mongo but we do not use or
|
35
|
-
# understand.
|
36
|
-
def initialize(str, options, extra_options_str)
|
37
|
-
super(str, options)
|
38
|
-
@extra_options_str = extra_options_str
|
39
|
-
end
|
40
|
-
end
|
29
|
+
attr_accessor :extra_options_str
|
41
30
|
|
31
|
+
# +str+ and +options+ are the same as Regexp. +extra_options_str+
|
32
|
+
# contains all the other flags that were in Mongo but we do not use or
|
33
|
+
# understand.
|
34
|
+
def initialize(str, options, extra_options_str)
|
35
|
+
super(str, options)
|
36
|
+
@extra_options_str = extra_options_str
|
42
37
|
end
|
43
38
|
end
|
39
|
+
|
44
40
|
end
|
data/lib/mongo/util/bson.rb
CHANGED
@@ -21,11 +21,12 @@ require 'mongo/types/binary'
|
|
21
21
|
require 'mongo/types/dbref'
|
22
22
|
require 'mongo/types/objectid'
|
23
23
|
require 'mongo/types/regexp_of_holding'
|
24
|
-
require 'mongo/types/undefined'
|
25
24
|
|
26
25
|
# A BSON seralizer/deserializer.
|
27
26
|
class BSON
|
28
27
|
|
28
|
+
include Mongo
|
29
|
+
|
29
30
|
MINKEY = -1
|
30
31
|
EOO = 0
|
31
32
|
NUMBER = 1
|
@@ -44,6 +45,8 @@ class BSON
|
|
44
45
|
SYMBOL = 14
|
45
46
|
CODE_W_SCOPE = 15
|
46
47
|
NUMBER_INT = 16
|
48
|
+
TIMESTAMP = 17
|
49
|
+
NUMBER_LONG = 18
|
47
50
|
MAXKEY = 127
|
48
51
|
|
49
52
|
if RUBY_VERSION >= '1.9'
|
@@ -52,7 +55,7 @@ class BSON
|
|
52
55
|
end
|
53
56
|
else
|
54
57
|
def self.to_utf8(str)
|
55
|
-
str # TODO punt for now
|
58
|
+
str # TODO Ruby 1.8 punt for now
|
56
59
|
end
|
57
60
|
end
|
58
61
|
|
@@ -60,9 +63,7 @@ class BSON
|
|
60
63
|
buf.put_array(to_utf8(val.to_s).unpack("C*") + [0])
|
61
64
|
end
|
62
65
|
|
63
|
-
def initialize(
|
64
|
-
# db is only needed during deserialization when the data contains a DBRef
|
65
|
-
@db = db
|
66
|
+
def initialize()
|
66
67
|
@buf = ByteBuffer.new
|
67
68
|
end
|
68
69
|
|
@@ -70,115 +71,164 @@ class BSON
|
|
70
71
|
@buf.to_a
|
71
72
|
end
|
72
73
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
#
|
109
|
-
|
74
|
+
begin
|
75
|
+
require 'mongo_ext/cbson'
|
76
|
+
def serialize(obj, check_keys=false)
|
77
|
+
@buf = ByteBuffer.new(CBson.serialize(obj, check_keys))
|
78
|
+
end
|
79
|
+
rescue LoadError
|
80
|
+
def serialize(obj, check_keys=false)
|
81
|
+
raise "Document is null" unless obj
|
82
|
+
|
83
|
+
@buf.rewind
|
84
|
+
# put in a placeholder for the total size
|
85
|
+
@buf.put_int(0)
|
86
|
+
|
87
|
+
# Write key/value pairs. Always write _id first if it exists.
|
88
|
+
if obj.has_key? '_id'
|
89
|
+
serialize_key_value('_id', obj['_id'], check_keys)
|
90
|
+
elsif obj.has_key? :_id
|
91
|
+
serialize_key_value('_id', obj[:_id], check_keys)
|
92
|
+
end
|
93
|
+
|
94
|
+
obj.each {|k, v| serialize_key_value(k, v, check_keys) unless k == '_id' || k == :_id }
|
95
|
+
|
96
|
+
serialize_eoo_element(@buf)
|
97
|
+
@buf.put_int(@buf.size, 0)
|
98
|
+
self
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def serialize_key_value(k, v, check_keys)
|
103
|
+
k = k.to_s
|
104
|
+
if check_keys
|
105
|
+
if k[0] == ?$
|
106
|
+
raise InvalidName.new("key #{k} must not start with '$'")
|
107
|
+
end
|
108
|
+
if k.include? ?.
|
109
|
+
raise InvalidName.new("key #{k} must not contain '.'")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
type = bson_type(v)
|
113
|
+
case type
|
114
|
+
when STRING, SYMBOL
|
115
|
+
serialize_string_element(@buf, k, v, type)
|
116
|
+
when NUMBER, NUMBER_INT
|
117
|
+
serialize_number_element(@buf, k, v, type)
|
118
|
+
when OBJECT
|
119
|
+
serialize_object_element(@buf, k, v, check_keys)
|
120
|
+
when OID
|
121
|
+
serialize_oid_element(@buf, k, v)
|
122
|
+
when ARRAY
|
123
|
+
serialize_array_element(@buf, k, v, check_keys)
|
124
|
+
when REGEX
|
125
|
+
serialize_regex_element(@buf, k, v)
|
126
|
+
when BOOLEAN
|
127
|
+
serialize_boolean_element(@buf, k, v)
|
128
|
+
when DATE
|
129
|
+
serialize_date_element(@buf, k, v)
|
130
|
+
when NULL
|
131
|
+
serialize_null_element(@buf, k)
|
132
|
+
when REF
|
133
|
+
serialize_dbref_element(@buf, k, v)
|
134
|
+
when BINARY
|
135
|
+
serialize_binary_element(@buf, k, v)
|
136
|
+
when UNDEFINED
|
137
|
+
serialize_null_element(@buf, k)
|
138
|
+
when CODE_W_SCOPE
|
139
|
+
serialize_code_w_scope(@buf, k, v)
|
140
|
+
else
|
141
|
+
raise "unhandled type #{type}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
begin
|
146
|
+
require 'mongo_ext/cbson'
|
147
|
+
def deserialize(buf=nil)
|
148
|
+
if buf.is_a? String
|
149
|
+
@buf = ByteBuffer.new(buf) if buf
|
110
150
|
else
|
111
|
-
|
151
|
+
@buf = ByteBuffer.new(buf.to_a) if buf
|
112
152
|
end
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
@buf = ByteBuffer.new(buf.to_a) if buf
|
123
|
-
@buf.rewind
|
124
|
-
@buf.get_int # eat message size
|
125
|
-
doc = OrderedHash.new
|
126
|
-
while @buf.more?
|
127
|
-
type = @buf.get
|
128
|
-
case type
|
129
|
-
when STRING, CODE
|
130
|
-
key = deserialize_cstr(@buf)
|
131
|
-
doc[key] = deserialize_string_data(@buf)
|
132
|
-
when SYMBOL
|
133
|
-
key = deserialize_cstr(@buf)
|
134
|
-
doc[key] = deserialize_string_data(@buf).intern
|
135
|
-
when NUMBER
|
136
|
-
key = deserialize_cstr(@buf)
|
137
|
-
doc[key] = deserialize_number_data(@buf)
|
138
|
-
when NUMBER_INT
|
139
|
-
key = deserialize_cstr(@buf)
|
140
|
-
doc[key] = deserialize_number_int_data(@buf)
|
141
|
-
when OID
|
142
|
-
key = deserialize_cstr(@buf)
|
143
|
-
doc[key] = deserialize_oid_data(@buf)
|
144
|
-
when ARRAY
|
145
|
-
key = deserialize_cstr(@buf)
|
146
|
-
doc[key] = deserialize_array_data(@buf, doc)
|
147
|
-
when REGEX
|
148
|
-
key = deserialize_cstr(@buf)
|
149
|
-
doc[key] = deserialize_regex_data(@buf)
|
150
|
-
when OBJECT
|
151
|
-
key = deserialize_cstr(@buf)
|
152
|
-
doc[key] = deserialize_object_data(@buf, doc)
|
153
|
-
when BOOLEAN
|
154
|
-
key = deserialize_cstr(@buf)
|
155
|
-
doc[key] = deserialize_boolean_data(@buf)
|
156
|
-
when DATE
|
157
|
-
key = deserialize_cstr(@buf)
|
158
|
-
doc[key] = deserialize_date_data(@buf)
|
159
|
-
when NULL
|
160
|
-
key = deserialize_cstr(@buf)
|
161
|
-
doc[key] = nil
|
162
|
-
when UNDEFINED
|
163
|
-
key = deserialize_cstr(@buf)
|
164
|
-
doc[key] = XGen::Mongo::Driver::Undefined.new
|
165
|
-
when REF
|
166
|
-
key = deserialize_cstr(@buf)
|
167
|
-
doc[key] = deserialize_dbref_data(@buf, key, parent)
|
168
|
-
when BINARY
|
169
|
-
key = deserialize_cstr(@buf)
|
170
|
-
doc[key] = deserialize_binary_data(@buf)
|
171
|
-
when CODE_W_SCOPE
|
172
|
-
# TODO
|
173
|
-
raise "unimplemented type #{type}"
|
174
|
-
when EOO
|
175
|
-
break
|
153
|
+
@buf.rewind
|
154
|
+
CBson.deserialize(@buf.to_s)
|
155
|
+
end
|
156
|
+
rescue LoadError
|
157
|
+
def deserialize(buf=nil)
|
158
|
+
# If buf is nil, use @buf, assumed to contain already-serialized BSON.
|
159
|
+
# This is only true during testing.
|
160
|
+
if buf.is_a? String
|
161
|
+
@buf = ByteBuffer.new(buf) if buf
|
176
162
|
else
|
177
|
-
|
163
|
+
@buf = ByteBuffer.new(buf.to_a) if buf
|
178
164
|
end
|
165
|
+
@buf.rewind
|
166
|
+
@buf.get_int # eat message size
|
167
|
+
doc = OrderedHash.new
|
168
|
+
while @buf.more?
|
169
|
+
type = @buf.get
|
170
|
+
case type
|
171
|
+
when STRING, CODE
|
172
|
+
key = deserialize_cstr(@buf)
|
173
|
+
doc[key] = deserialize_string_data(@buf)
|
174
|
+
when SYMBOL
|
175
|
+
key = deserialize_cstr(@buf)
|
176
|
+
doc[key] = deserialize_string_data(@buf).intern
|
177
|
+
when NUMBER
|
178
|
+
key = deserialize_cstr(@buf)
|
179
|
+
doc[key] = deserialize_number_data(@buf)
|
180
|
+
when NUMBER_INT
|
181
|
+
key = deserialize_cstr(@buf)
|
182
|
+
doc[key] = deserialize_number_int_data(@buf)
|
183
|
+
when NUMBER_LONG
|
184
|
+
key = deserialize_cstr(@buf)
|
185
|
+
doc[key] = deserialize_number_long_data(@buf)
|
186
|
+
when OID
|
187
|
+
key = deserialize_cstr(@buf)
|
188
|
+
doc[key] = deserialize_oid_data(@buf)
|
189
|
+
when ARRAY
|
190
|
+
key = deserialize_cstr(@buf)
|
191
|
+
doc[key] = deserialize_array_data(@buf)
|
192
|
+
when REGEX
|
193
|
+
key = deserialize_cstr(@buf)
|
194
|
+
doc[key] = deserialize_regex_data(@buf)
|
195
|
+
when OBJECT
|
196
|
+
key = deserialize_cstr(@buf)
|
197
|
+
doc[key] = deserialize_object_data(@buf)
|
198
|
+
when BOOLEAN
|
199
|
+
key = deserialize_cstr(@buf)
|
200
|
+
doc[key] = deserialize_boolean_data(@buf)
|
201
|
+
when DATE
|
202
|
+
key = deserialize_cstr(@buf)
|
203
|
+
doc[key] = deserialize_date_data(@buf)
|
204
|
+
when NULL
|
205
|
+
key = deserialize_cstr(@buf)
|
206
|
+
doc[key] = nil
|
207
|
+
when UNDEFINED
|
208
|
+
key = deserialize_cstr(@buf)
|
209
|
+
doc[key] = nil
|
210
|
+
when REF
|
211
|
+
key = deserialize_cstr(@buf)
|
212
|
+
doc[key] = deserialize_dbref_data(@buf)
|
213
|
+
when BINARY
|
214
|
+
key = deserialize_cstr(@buf)
|
215
|
+
doc[key] = deserialize_binary_data(@buf)
|
216
|
+
when CODE_W_SCOPE
|
217
|
+
key = deserialize_cstr(@buf)
|
218
|
+
doc[key] = deserialize_code_w_scope_data(@buf)
|
219
|
+
when TIMESTAMP
|
220
|
+
key = deserialize_cstr(@buf)
|
221
|
+
doc[key] = [deserialize_number_int_data(@buf),
|
222
|
+
deserialize_number_int_data(@buf)]
|
223
|
+
when EOO
|
224
|
+
break
|
225
|
+
else
|
226
|
+
raise "Unknown type #{type}, key = #{key}"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
@buf.rewind
|
230
|
+
doc
|
179
231
|
end
|
180
|
-
@buf.rewind
|
181
|
-
doc
|
182
232
|
end
|
183
233
|
|
184
234
|
# For debugging.
|
@@ -197,8 +247,10 @@ class BSON
|
|
197
247
|
end
|
198
248
|
|
199
249
|
def deserialize_date_data(buf)
|
200
|
-
|
201
|
-
|
250
|
+
unsigned = buf.get_long()
|
251
|
+
# see note for deserialize_number_long_data below
|
252
|
+
milliseconds = unsigned >= 2 ** 64 / 2 ? unsigned - 2**64 : unsigned
|
253
|
+
Time.at(milliseconds.to_f / 1000.0).utc # at() takes fractional seconds
|
202
254
|
end
|
203
255
|
|
204
256
|
def deserialize_boolean_data(buf)
|
@@ -210,17 +262,31 @@ class BSON
|
|
210
262
|
end
|
211
263
|
|
212
264
|
def deserialize_number_int_data(buf)
|
213
|
-
|
265
|
+
# sometimes ruby makes me angry... why would the same code pack as signed
|
266
|
+
# but unpack as unsigned
|
267
|
+
unsigned = buf.get_int
|
268
|
+
unsigned >= 2**32 / 2 ? unsigned - 2**32 : unsigned
|
214
269
|
end
|
215
270
|
|
216
|
-
def
|
271
|
+
def deserialize_number_long_data(buf)
|
272
|
+
# same note as above applies here...
|
273
|
+
unsigned = buf.get_long
|
274
|
+
unsigned >= 2 ** 64 / 2 ? unsigned - 2**64 : unsigned
|
275
|
+
end
|
276
|
+
|
277
|
+
def deserialize_object_data(buf)
|
217
278
|
size = buf.get_int
|
218
279
|
buf.position -= 4
|
219
|
-
BSON.new(
|
280
|
+
object = BSON.new().deserialize(buf.get(size))
|
281
|
+
if object.has_key? "$ref"
|
282
|
+
DBRef.new(object["$ref"], object["$id"])
|
283
|
+
else
|
284
|
+
object
|
285
|
+
end
|
220
286
|
end
|
221
287
|
|
222
|
-
def deserialize_array_data(buf
|
223
|
-
h = deserialize_object_data(buf
|
288
|
+
def deserialize_array_data(buf)
|
289
|
+
h = deserialize_object_data(buf)
|
224
290
|
a = []
|
225
291
|
h.each { |k, v| a[k.to_i] = v }
|
226
292
|
a
|
@@ -234,35 +300,55 @@ class BSON
|
|
234
300
|
options |= Regexp::MULTILINE if options_str.include?('m')
|
235
301
|
options |= Regexp::EXTENDED if options_str.include?('x')
|
236
302
|
options_str.gsub!(/[imx]/, '') # Now remove the three we understand
|
237
|
-
|
303
|
+
RegexpOfHolding.new(str, options, options_str)
|
238
304
|
end
|
239
305
|
|
240
306
|
def deserialize_string_data(buf)
|
241
307
|
len = buf.get_int
|
242
308
|
bytes = buf.get(len)
|
243
|
-
str = bytes[0..-2]
|
309
|
+
str = bytes[0..-2]
|
310
|
+
if str.respond_to? "pack"
|
311
|
+
str = str.pack("C*")
|
312
|
+
end
|
244
313
|
if RUBY_VERSION >= '1.9'
|
245
314
|
str.force_encoding("utf-8")
|
246
315
|
end
|
247
316
|
str
|
248
317
|
end
|
249
318
|
|
319
|
+
def deserialize_code_w_scope_data(buf)
|
320
|
+
buf.get_int
|
321
|
+
len = buf.get_int
|
322
|
+
code = buf.get(len)[0..-2]
|
323
|
+
if code.respond_to? "pack"
|
324
|
+
code = code.pack("C*")
|
325
|
+
end
|
326
|
+
if RUBY_VERSION >= '1.9'
|
327
|
+
code.force_encoding("utf-8")
|
328
|
+
end
|
329
|
+
|
330
|
+
scope_size = buf.get_int
|
331
|
+
buf.position -= 4
|
332
|
+
scope = BSON.new().deserialize(buf.get(scope_size))
|
333
|
+
|
334
|
+
Code.new(code, scope)
|
335
|
+
end
|
336
|
+
|
250
337
|
def deserialize_oid_data(buf)
|
251
|
-
|
338
|
+
ObjectID.new(buf.get(12))
|
252
339
|
end
|
253
340
|
|
254
|
-
def deserialize_dbref_data(buf
|
255
|
-
ns =
|
341
|
+
def deserialize_dbref_data(buf)
|
342
|
+
ns = deserialize_string_data(buf)
|
256
343
|
oid = deserialize_oid_data(buf)
|
257
|
-
|
344
|
+
DBRef.new(ns, oid)
|
258
345
|
end
|
259
346
|
|
260
347
|
def deserialize_binary_data(buf)
|
261
348
|
len = buf.get_int
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
str.to_mongo_binary
|
349
|
+
type = buf.get
|
350
|
+
len = buf.get_int if type == Binary::SUBTYPE_BYTES
|
351
|
+
Binary.new(buf.get(len), type)
|
266
352
|
end
|
267
353
|
|
268
354
|
def serialize_eoo_element(buf)
|
@@ -275,29 +361,29 @@ class BSON
|
|
275
361
|
end
|
276
362
|
|
277
363
|
def serialize_dbref_element(buf, key, val)
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
buf
|
364
|
+
oh = OrderedHash.new
|
365
|
+
oh['$ref'] = val.namespace
|
366
|
+
oh['$id'] = val.object_id
|
367
|
+
serialize_object_element(buf, key, oh, false)
|
282
368
|
end
|
283
369
|
|
284
370
|
def serialize_binary_element(buf, key, val)
|
285
371
|
buf.put(BINARY)
|
286
372
|
self.class.serialize_cstr(buf, key)
|
287
|
-
|
288
|
-
bytes =
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
373
|
+
|
374
|
+
bytes = val.to_a
|
375
|
+
num_bytes = bytes.length
|
376
|
+
subtype = val.respond_to?(:subtype) ? val.subtype : Binary::SUBTYPE_BYTES
|
377
|
+
if subtype == Binary::SUBTYPE_BYTES
|
378
|
+
buf.put_int(num_bytes + 4)
|
379
|
+
buf.put(subtype)
|
380
|
+
buf.put_int(num_bytes)
|
381
|
+
buf.put_array(bytes)
|
382
|
+
else
|
383
|
+
buf.put_int(num_bytes)
|
384
|
+
buf.put(subtype)
|
385
|
+
buf.put_array(bytes)
|
386
|
+
end
|
301
387
|
end
|
302
388
|
|
303
389
|
def serialize_boolean_element(buf, key, val)
|
@@ -314,27 +400,38 @@ class BSON
|
|
314
400
|
end
|
315
401
|
|
316
402
|
def serialize_number_element(buf, key, val, type)
|
317
|
-
buf.put(type)
|
318
|
-
self.class.serialize_cstr(buf, key)
|
319
403
|
if type == NUMBER
|
404
|
+
buf.put(type)
|
405
|
+
self.class.serialize_cstr(buf, key)
|
320
406
|
buf.put_double(val)
|
321
407
|
else
|
322
|
-
|
408
|
+
if val > 2**64 / 2 - 1 or val < -2**64 / 2
|
409
|
+
raise RangeError.new("MongoDB can only handle 8-byte ints")
|
410
|
+
end
|
411
|
+
if val > 2**32 / 2 - 1 or val < -2**32 / 2
|
412
|
+
buf.put(NUMBER_LONG)
|
413
|
+
self.class.serialize_cstr(buf, key)
|
414
|
+
buf.put_long(val)
|
415
|
+
else
|
416
|
+
buf.put(type)
|
417
|
+
self.class.serialize_cstr(buf, key)
|
418
|
+
buf.put_int(val)
|
419
|
+
end
|
323
420
|
end
|
324
421
|
end
|
325
422
|
|
326
|
-
def serialize_object_element(buf, key, val, opcode=OBJECT)
|
423
|
+
def serialize_object_element(buf, key, val, check_keys, opcode=OBJECT)
|
327
424
|
buf.put(opcode)
|
328
425
|
self.class.serialize_cstr(buf, key)
|
329
|
-
buf.put_array(BSON.new.serialize(val).to_a)
|
426
|
+
buf.put_array(BSON.new.serialize(val, check_keys).to_a)
|
330
427
|
end
|
331
428
|
|
332
|
-
def serialize_array_element(buf, key, val)
|
429
|
+
def serialize_array_element(buf, key, val, check_keys)
|
333
430
|
# Turn array into hash with integer indices as keys
|
334
431
|
h = OrderedHash.new
|
335
432
|
i = 0
|
336
433
|
val.each { |v| h[i] = v; i += 1 }
|
337
|
-
serialize_object_element(buf, key, h, ARRAY)
|
434
|
+
serialize_object_element(buf, key, h, check_keys, ARRAY)
|
338
435
|
end
|
339
436
|
|
340
437
|
def serialize_regex_element(buf, key, val)
|
@@ -381,9 +478,26 @@ class BSON
|
|
381
478
|
buf.position = end_pos
|
382
479
|
end
|
383
480
|
|
481
|
+
def serialize_code_w_scope(buf, key, val)
|
482
|
+
buf.put(CODE_W_SCOPE)
|
483
|
+
self.class.serialize_cstr(buf, key)
|
484
|
+
|
485
|
+
# Make a hole for the length
|
486
|
+
len_pos = buf.position
|
487
|
+
buf.put_int(0)
|
488
|
+
|
489
|
+
buf.put_int(val.length + 1)
|
490
|
+
self.class.serialize_cstr(buf, val)
|
491
|
+
buf.put_array(BSON.new.serialize(val.scope).to_a)
|
492
|
+
|
493
|
+
end_pos = buf.position
|
494
|
+
buf.put_int(end_pos - len_pos, len_pos)
|
495
|
+
buf.position = end_pos
|
496
|
+
end
|
497
|
+
|
384
498
|
def deserialize_cstr(buf)
|
385
499
|
chars = ""
|
386
|
-
while
|
500
|
+
while true
|
387
501
|
b = buf.get
|
388
502
|
break if b == 0
|
389
503
|
chars << b.chr
|
@@ -394,7 +508,7 @@ class BSON
|
|
394
508
|
chars
|
395
509
|
end
|
396
510
|
|
397
|
-
def bson_type(o
|
511
|
+
def bson_type(o)
|
398
512
|
case o
|
399
513
|
when nil
|
400
514
|
NULL
|
@@ -402,18 +516,19 @@ class BSON
|
|
402
516
|
NUMBER_INT
|
403
517
|
when Numeric
|
404
518
|
NUMBER
|
405
|
-
when
|
519
|
+
when ByteBuffer
|
406
520
|
BINARY
|
521
|
+
when Code
|
522
|
+
CODE_W_SCOPE
|
407
523
|
when String
|
408
|
-
|
409
|
-
key == "$where" ? CODE : STRING
|
524
|
+
STRING
|
410
525
|
when Array
|
411
526
|
ARRAY
|
412
527
|
when Regexp
|
413
528
|
REGEX
|
414
|
-
when
|
529
|
+
when ObjectID
|
415
530
|
OID
|
416
|
-
when
|
531
|
+
when DBRef
|
417
532
|
REF
|
418
533
|
when true, false
|
419
534
|
BOOLEAN
|
@@ -423,8 +538,6 @@ class BSON
|
|
423
538
|
OBJECT
|
424
539
|
when Symbol
|
425
540
|
SYMBOL
|
426
|
-
when XGen::Mongo::Driver::Undefined
|
427
|
-
UNDEFINED
|
428
541
|
else
|
429
542
|
raise "Unknown type of object: #{o.class.name}"
|
430
543
|
end
|