pahagon-mongo-abd 0.14.1
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 +353 -0
- data/Rakefile +62 -0
- data/bin/bson_benchmark.rb +59 -0
- data/bin/mongo_console +21 -0
- 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 +19 -0
- data/lib/mongo/admin.rb +83 -0
- data/lib/mongo/collection.rb +415 -0
- data/lib/mongo/connection.rb +151 -0
- data/lib/mongo/cursor.rb +279 -0
- data/lib/mongo/db.rb +560 -0
- 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 +20 -0
- data/lib/mongo/message/get_more_message.rb +32 -0
- data/lib/mongo/message/insert_message.rb +37 -0
- data/lib/mongo/message/kill_cursors_message.rb +31 -0
- data/lib/mongo/message/message.rb +80 -0
- data/lib/mongo/message/message_header.rb +45 -0
- data/lib/mongo/message/msg_message.rb +29 -0
- data/lib/mongo/message/opcodes.rb +27 -0
- data/lib/mongo/message/query_message.rb +78 -0
- data/lib/mongo/message/remove_message.rb +37 -0
- data/lib/mongo/message/update_message.rb +38 -0
- data/lib/mongo/query.rb +118 -0
- data/lib/mongo/types/binary.rb +38 -0
- data/lib/mongo/types/code.rb +30 -0
- data/lib/mongo/types/dbref.rb +33 -0
- data/lib/mongo/types/objectid.rb +143 -0
- data/lib/mongo/types/regexp_of_holding.rb +40 -0
- data/lib/mongo/util/bson.rb +546 -0
- data/lib/mongo/util/byte_buffer.rb +167 -0
- data/lib/mongo/util/ordered_hash.rb +113 -0
- data/lib/mongo/util/xml_to_ruby.rb +105 -0
- 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/test/test_admin.rb +69 -0
- data/test/test_bson.rb +268 -0
- data/test/test_byte_buffer.rb +69 -0
- data/test/test_chunk.rb +84 -0
- data/test/test_collection.rb +249 -0
- data/test/test_connection.rb +101 -0
- data/test/test_cursor.rb +331 -0
- data/test/test_db.rb +185 -0
- data/test/test_db_api.rb +798 -0
- data/test/test_db_connection.rb +18 -0
- data/test/test_grid_store.rb +284 -0
- data/test/test_message.rb +35 -0
- data/test/test_objectid.rb +105 -0
- data/test/test_ordered_hash.rb +138 -0
- data/test/test_round_trip.rb +120 -0
- data/test/test_threading.rb +37 -0
- metadata +135 -0
data/lib/mongo/query.rb
ADDED
@@ -0,0 +1,118 @@
|
|
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
|
+
require 'mongo/collection'
|
18
|
+
require 'mongo/message'
|
19
|
+
require 'mongo/types/code'
|
20
|
+
|
21
|
+
module Mongo
|
22
|
+
|
23
|
+
# A query against a collection. A query's selector is a hash. See the
|
24
|
+
# Mongo documentation for query details.
|
25
|
+
class Query
|
26
|
+
|
27
|
+
attr_accessor :number_to_skip, :number_to_return, :order_by, :snapshot
|
28
|
+
# If true, $explain will be set in QueryMessage that uses this query.
|
29
|
+
attr_accessor :explain
|
30
|
+
# Either +nil+ or a hash (preferably an OrderedHash).
|
31
|
+
attr_accessor :hint
|
32
|
+
attr_reader :selector # writer defined below
|
33
|
+
|
34
|
+
# sel :: A hash describing the query. See the Mongo docs for details.
|
35
|
+
#
|
36
|
+
# return_fields :: If not +nil+, a single field name or an array of
|
37
|
+
# field names. Only those fields will be returned.
|
38
|
+
# (Called :fields in calls to Collection#find.)
|
39
|
+
#
|
40
|
+
# number_to_skip :: Number of records to skip before returning
|
41
|
+
# records. Default is 0.
|
42
|
+
#
|
43
|
+
# number_to_return :: Max number of records to return. (Called :limit
|
44
|
+
# in calls to Collection#find.) Default is 0 (all
|
45
|
+
# records).
|
46
|
+
#
|
47
|
+
# order_by :: If not +nil+, specifies record sort order. May be a
|
48
|
+
# String, Hash, OrderedHash, or Array. If a string, the
|
49
|
+
# results will be ordered by that field in ascending
|
50
|
+
# order. If an array, it should be an array of field names
|
51
|
+
# which will all be sorted in ascending order. If a hash,
|
52
|
+
# it may be either a regular Hash or an OrderedHash. The
|
53
|
+
# keys should be field names, and the values should be 1
|
54
|
+
# (ascending) or -1 (descending). Note that if it is a
|
55
|
+
# regular Hash then sorting by more than one field
|
56
|
+
# probably will not be what you intend because key order
|
57
|
+
# is not preserved. (order_by is called :sort in calls to
|
58
|
+
# Collection#find.)
|
59
|
+
#
|
60
|
+
# hint :: If not +nil+, specifies query hint fields. Must be either
|
61
|
+
# +nil+ or a hash (preferably an OrderedHash). See
|
62
|
+
# Collection#hint.
|
63
|
+
def initialize(sel={}, return_fields=nil, number_to_skip=0, number_to_return=0, order_by=nil, hint=nil, snapshot=nil)
|
64
|
+
@number_to_skip, @number_to_return, @order_by, @hint, @snapshot =
|
65
|
+
number_to_skip, number_to_return, order_by, hint, snapshot
|
66
|
+
@explain = nil
|
67
|
+
self.selector = sel
|
68
|
+
self.fields = return_fields
|
69
|
+
end
|
70
|
+
|
71
|
+
# Set query selector hash. If sel is Code/string, it will be used as a
|
72
|
+
# $where clause. (See Mongo docs for details.)
|
73
|
+
def selector=(sel)
|
74
|
+
@selector = case sel
|
75
|
+
when nil
|
76
|
+
{}
|
77
|
+
when Code
|
78
|
+
{"$where" => sel}
|
79
|
+
when String
|
80
|
+
{"$where" => Code.new(sel)}
|
81
|
+
when Hash
|
82
|
+
sel
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Set fields to return. If +val+ is +nil+ or empty, all fields will be
|
87
|
+
# returned.
|
88
|
+
def fields=(val)
|
89
|
+
@fields = val
|
90
|
+
@fields = nil if @fields && @fields.empty?
|
91
|
+
end
|
92
|
+
|
93
|
+
def fields
|
94
|
+
case @fields
|
95
|
+
when String
|
96
|
+
{@fields => 1}
|
97
|
+
when Array
|
98
|
+
if @fields.length == 0
|
99
|
+
nil
|
100
|
+
else
|
101
|
+
h = {}
|
102
|
+
@fields.each { |field| h[field] = 1 }
|
103
|
+
h
|
104
|
+
end
|
105
|
+
else # nil, anything else
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def contains_special_fields
|
111
|
+
(@order_by != nil && @order_by.length > 0) || @explain || @hint || @snapshot
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_s
|
115
|
+
"find(#{@selector.inspect})" + (@order_by ? ".sort(#{@order_by.inspect})" : "")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,38 @@
|
|
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
|
+
require 'mongo/util/byte_buffer'
|
18
|
+
|
19
|
+
module Mongo
|
20
|
+
|
21
|
+
# An array of binary bytes with a Mongo subtype value.
|
22
|
+
class Binary < ByteBuffer
|
23
|
+
|
24
|
+
SUBTYPE_BYTES = 0x02
|
25
|
+
SUBTYPE_UUID = 0x03
|
26
|
+
SUBTYPE_MD5 = 0x05
|
27
|
+
SUBTYPE_USER_DEFINED = 0x80
|
28
|
+
|
29
|
+
# One of the SUBTYPE_* constants. Default is SUBTYPE_BYTES.
|
30
|
+
attr_accessor :subtype
|
31
|
+
|
32
|
+
def initialize(initial_data=[], subtype=SUBTYPE_BYTES)
|
33
|
+
super(initial_data)
|
34
|
+
@subtype = subtype
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -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
|
@@ -0,0 +1,33 @@
|
|
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
|
+
class DBRef
|
20
|
+
|
21
|
+
attr_reader :namespace, :object_id
|
22
|
+
|
23
|
+
def initialize(namespace, object_id)
|
24
|
+
@namespace, @object_id =
|
25
|
+
namespace, object_id
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
"ns: #{namespace}, id: #{object_id}"
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,143 @@
|
|
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
|
+
require 'mutex_m'
|
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
|
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)
|
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
|
+
}
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,40 @@
|
|
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
|
+
# 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
|
28
|
+
|
29
|
+
attr_accessor :extra_options_str
|
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
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,546 @@
|
|
1
|
+
# --
|
2
|
+
# Copyright (C) 2008-2009 10gen Inc.
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it
|
5
|
+
# under the terms of the GNU Affero General Public License, version 3, as
|
6
|
+
# published by the Free Software Foundation.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
11
|
+
# for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU Affero General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
# ++
|
16
|
+
|
17
|
+
require 'base64'
|
18
|
+
require 'mongo/util/byte_buffer'
|
19
|
+
require 'mongo/util/ordered_hash'
|
20
|
+
require 'mongo/types/binary'
|
21
|
+
require 'mongo/types/dbref'
|
22
|
+
require 'mongo/types/objectid'
|
23
|
+
require 'mongo/types/regexp_of_holding'
|
24
|
+
|
25
|
+
# A BSON seralizer/deserializer.
|
26
|
+
class BSON
|
27
|
+
|
28
|
+
include Mongo
|
29
|
+
|
30
|
+
MINKEY = -1
|
31
|
+
EOO = 0
|
32
|
+
NUMBER = 1
|
33
|
+
STRING = 2
|
34
|
+
OBJECT = 3
|
35
|
+
ARRAY = 4
|
36
|
+
BINARY = 5
|
37
|
+
UNDEFINED = 6
|
38
|
+
OID = 7
|
39
|
+
BOOLEAN = 8
|
40
|
+
DATE = 9
|
41
|
+
NULL = 10
|
42
|
+
REGEX = 11
|
43
|
+
REF = 12
|
44
|
+
CODE = 13
|
45
|
+
SYMBOL = 14
|
46
|
+
CODE_W_SCOPE = 15
|
47
|
+
NUMBER_INT = 16
|
48
|
+
TIMESTAMP = 17
|
49
|
+
NUMBER_LONG = 18
|
50
|
+
MAXKEY = 127
|
51
|
+
|
52
|
+
if RUBY_VERSION >= '1.9'
|
53
|
+
def self.to_utf8(str)
|
54
|
+
str.encode("utf-8")
|
55
|
+
end
|
56
|
+
else
|
57
|
+
def self.to_utf8(str)
|
58
|
+
str # TODO Ruby 1.8 punt for now
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.serialize_cstr(buf, val)
|
63
|
+
buf.put_array(to_utf8(val.to_s).unpack("C*") + [0])
|
64
|
+
end
|
65
|
+
|
66
|
+
def initialize()
|
67
|
+
@buf = ByteBuffer.new
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_a
|
71
|
+
@buf.to_a
|
72
|
+
end
|
73
|
+
|
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
|
150
|
+
else
|
151
|
+
@buf = ByteBuffer.new(buf.to_a) if buf
|
152
|
+
end
|
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
|
162
|
+
else
|
163
|
+
@buf = ByteBuffer.new(buf.to_a) if buf
|
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
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# For debugging.
|
235
|
+
def hex_dump
|
236
|
+
str = ''
|
237
|
+
@buf.to_a.each_with_index { |b,i|
|
238
|
+
if (i % 8) == 0
|
239
|
+
str << "\n" if i > 0
|
240
|
+
str << '%4d: ' % i
|
241
|
+
else
|
242
|
+
str << ' '
|
243
|
+
end
|
244
|
+
str << '%02X' % b
|
245
|
+
}
|
246
|
+
str
|
247
|
+
end
|
248
|
+
|
249
|
+
def deserialize_date_data(buf)
|
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
|
254
|
+
end
|
255
|
+
|
256
|
+
def deserialize_boolean_data(buf)
|
257
|
+
buf.get == 1
|
258
|
+
end
|
259
|
+
|
260
|
+
def deserialize_number_data(buf)
|
261
|
+
buf.get_double
|
262
|
+
end
|
263
|
+
|
264
|
+
def deserialize_number_int_data(buf)
|
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
|
269
|
+
end
|
270
|
+
|
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)
|
278
|
+
size = buf.get_int
|
279
|
+
buf.position -= 4
|
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
|
286
|
+
end
|
287
|
+
|
288
|
+
def deserialize_array_data(buf)
|
289
|
+
h = deserialize_object_data(buf)
|
290
|
+
a = []
|
291
|
+
h.each { |k, v| a[k.to_i] = v }
|
292
|
+
a
|
293
|
+
end
|
294
|
+
|
295
|
+
def deserialize_regex_data(buf)
|
296
|
+
str = deserialize_cstr(buf)
|
297
|
+
options_str = deserialize_cstr(buf)
|
298
|
+
options = 0
|
299
|
+
options |= Regexp::IGNORECASE if options_str.include?('i')
|
300
|
+
options |= Regexp::MULTILINE if options_str.include?('m')
|
301
|
+
options |= Regexp::EXTENDED if options_str.include?('x')
|
302
|
+
options_str.gsub!(/[imx]/, '') # Now remove the three we understand
|
303
|
+
RegexpOfHolding.new(str, options, options_str)
|
304
|
+
end
|
305
|
+
|
306
|
+
def deserialize_string_data(buf)
|
307
|
+
len = buf.get_int
|
308
|
+
bytes = buf.get(len)
|
309
|
+
str = bytes[0..-2]
|
310
|
+
if str.respond_to? "pack"
|
311
|
+
str = str.pack("C*")
|
312
|
+
end
|
313
|
+
if RUBY_VERSION >= '1.9'
|
314
|
+
str.force_encoding("utf-8")
|
315
|
+
end
|
316
|
+
str
|
317
|
+
end
|
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
|
+
|
337
|
+
def deserialize_oid_data(buf)
|
338
|
+
ObjectID.new(buf.get(12))
|
339
|
+
end
|
340
|
+
|
341
|
+
def deserialize_dbref_data(buf)
|
342
|
+
ns = deserialize_string_data(buf)
|
343
|
+
oid = deserialize_oid_data(buf)
|
344
|
+
DBRef.new(ns, oid)
|
345
|
+
end
|
346
|
+
|
347
|
+
def deserialize_binary_data(buf)
|
348
|
+
len = buf.get_int
|
349
|
+
type = buf.get
|
350
|
+
len = buf.get_int if type == Binary::SUBTYPE_BYTES
|
351
|
+
Binary.new(buf.get(len), type)
|
352
|
+
end
|
353
|
+
|
354
|
+
def serialize_eoo_element(buf)
|
355
|
+
buf.put(EOO)
|
356
|
+
end
|
357
|
+
|
358
|
+
def serialize_null_element(buf, key)
|
359
|
+
buf.put(NULL)
|
360
|
+
self.class.serialize_cstr(buf, key)
|
361
|
+
end
|
362
|
+
|
363
|
+
def serialize_dbref_element(buf, key, val)
|
364
|
+
oh = OrderedHash.new
|
365
|
+
oh['$ref'] = val.namespace
|
366
|
+
oh['$id'] = val.object_id
|
367
|
+
serialize_object_element(buf, key, oh, false)
|
368
|
+
end
|
369
|
+
|
370
|
+
def serialize_binary_element(buf, key, val)
|
371
|
+
buf.put(BINARY)
|
372
|
+
self.class.serialize_cstr(buf, key)
|
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
|
387
|
+
end
|
388
|
+
|
389
|
+
def serialize_boolean_element(buf, key, val)
|
390
|
+
buf.put(BOOLEAN)
|
391
|
+
self.class.serialize_cstr(buf, key)
|
392
|
+
buf.put(val ? 1 : 0)
|
393
|
+
end
|
394
|
+
|
395
|
+
def serialize_date_element(buf, key, val)
|
396
|
+
buf.put(DATE)
|
397
|
+
self.class.serialize_cstr(buf, key)
|
398
|
+
millisecs = (val.to_f * 1000).to_i
|
399
|
+
buf.put_long(millisecs)
|
400
|
+
end
|
401
|
+
|
402
|
+
def serialize_number_element(buf, key, val, type)
|
403
|
+
if type == NUMBER
|
404
|
+
buf.put(type)
|
405
|
+
self.class.serialize_cstr(buf, key)
|
406
|
+
buf.put_double(val)
|
407
|
+
else
|
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
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
def serialize_object_element(buf, key, val, check_keys, opcode=OBJECT)
|
424
|
+
buf.put(opcode)
|
425
|
+
self.class.serialize_cstr(buf, key)
|
426
|
+
buf.put_array(BSON.new.serialize(val, check_keys).to_a)
|
427
|
+
end
|
428
|
+
|
429
|
+
def serialize_array_element(buf, key, val, check_keys)
|
430
|
+
# Turn array into hash with integer indices as keys
|
431
|
+
h = OrderedHash.new
|
432
|
+
i = 0
|
433
|
+
val.each { |v| h[i] = v; i += 1 }
|
434
|
+
serialize_object_element(buf, key, h, check_keys, ARRAY)
|
435
|
+
end
|
436
|
+
|
437
|
+
def serialize_regex_element(buf, key, val)
|
438
|
+
buf.put(REGEX)
|
439
|
+
self.class.serialize_cstr(buf, key)
|
440
|
+
|
441
|
+
str = val.to_s.sub(/.*?:/, '')[0..-2] # Turn "(?xxx:yyy)" into "yyy"
|
442
|
+
self.class.serialize_cstr(buf, str)
|
443
|
+
|
444
|
+
options = val.options
|
445
|
+
options_str = ''
|
446
|
+
options_str << 'i' if ((options & Regexp::IGNORECASE) != 0)
|
447
|
+
options_str << 'm' if ((options & Regexp::MULTILINE) != 0)
|
448
|
+
options_str << 'x' if ((options & Regexp::EXTENDED) != 0)
|
449
|
+
options_str << val.extra_options_str if val.respond_to?(:extra_options_str)
|
450
|
+
# Must store option chars in alphabetical order
|
451
|
+
self.class.serialize_cstr(buf, options_str.split(//).sort.uniq.join)
|
452
|
+
end
|
453
|
+
|
454
|
+
def serialize_oid_element(buf, key, val)
|
455
|
+
buf.put(OID)
|
456
|
+
self.class.serialize_cstr(buf, key)
|
457
|
+
|
458
|
+
buf.put_array(val.to_a)
|
459
|
+
end
|
460
|
+
|
461
|
+
def serialize_string_element(buf, key, val, type)
|
462
|
+
buf.put(type)
|
463
|
+
self.class.serialize_cstr(buf, key)
|
464
|
+
|
465
|
+
# Make a hole for the length
|
466
|
+
len_pos = buf.position
|
467
|
+
buf.put_int(0)
|
468
|
+
|
469
|
+
# Save the string
|
470
|
+
start_pos = buf.position
|
471
|
+
self.class.serialize_cstr(buf, val)
|
472
|
+
end_pos = buf.position
|
473
|
+
|
474
|
+
# Put the string size in front
|
475
|
+
buf.put_int(end_pos - start_pos, len_pos)
|
476
|
+
|
477
|
+
# Go back to where we were
|
478
|
+
buf.position = end_pos
|
479
|
+
end
|
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
|
+
|
498
|
+
def deserialize_cstr(buf)
|
499
|
+
chars = ""
|
500
|
+
while true
|
501
|
+
b = buf.get
|
502
|
+
break if b == 0
|
503
|
+
chars << b.chr
|
504
|
+
end
|
505
|
+
if RUBY_VERSION >= '1.9'
|
506
|
+
chars.force_encoding("utf-8") # Mongo stores UTF-8
|
507
|
+
end
|
508
|
+
chars
|
509
|
+
end
|
510
|
+
|
511
|
+
def bson_type(o)
|
512
|
+
case o
|
513
|
+
when nil
|
514
|
+
NULL
|
515
|
+
when Integer
|
516
|
+
NUMBER_INT
|
517
|
+
when Numeric
|
518
|
+
NUMBER
|
519
|
+
when ByteBuffer
|
520
|
+
BINARY
|
521
|
+
when Code
|
522
|
+
CODE_W_SCOPE
|
523
|
+
when String
|
524
|
+
STRING
|
525
|
+
when Array
|
526
|
+
ARRAY
|
527
|
+
when Regexp
|
528
|
+
REGEX
|
529
|
+
when ObjectID
|
530
|
+
OID
|
531
|
+
when DBRef
|
532
|
+
REF
|
533
|
+
when true, false
|
534
|
+
BOOLEAN
|
535
|
+
when Time
|
536
|
+
DATE
|
537
|
+
when Hash
|
538
|
+
OBJECT
|
539
|
+
when Symbol
|
540
|
+
SYMBOL
|
541
|
+
else
|
542
|
+
raise "Unknown type of object: #{o.class.name}"
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
end
|