pahagon-mongo-abd 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/README.rdoc +353 -0
  2. data/Rakefile +62 -0
  3. data/bin/bson_benchmark.rb +59 -0
  4. data/bin/mongo_console +21 -0
  5. data/bin/run_test_script +19 -0
  6. data/bin/standard_benchmark +109 -0
  7. data/examples/admin.rb +41 -0
  8. data/examples/benchmarks.rb +42 -0
  9. data/examples/blog.rb +76 -0
  10. data/examples/capped.rb +23 -0
  11. data/examples/cursor.rb +47 -0
  12. data/examples/gridfs.rb +87 -0
  13. data/examples/index_test.rb +125 -0
  14. data/examples/info.rb +30 -0
  15. data/examples/queries.rb +69 -0
  16. data/examples/simple.rb +23 -0
  17. data/examples/strict.rb +34 -0
  18. data/examples/types.rb +35 -0
  19. data/lib/mongo.rb +19 -0
  20. data/lib/mongo/admin.rb +83 -0
  21. data/lib/mongo/collection.rb +415 -0
  22. data/lib/mongo/connection.rb +151 -0
  23. data/lib/mongo/cursor.rb +279 -0
  24. data/lib/mongo/db.rb +560 -0
  25. data/lib/mongo/errors.rb +26 -0
  26. data/lib/mongo/gridfs.rb +16 -0
  27. data/lib/mongo/gridfs/chunk.rb +92 -0
  28. data/lib/mongo/gridfs/grid_store.rb +464 -0
  29. data/lib/mongo/message.rb +20 -0
  30. data/lib/mongo/message/get_more_message.rb +32 -0
  31. data/lib/mongo/message/insert_message.rb +37 -0
  32. data/lib/mongo/message/kill_cursors_message.rb +31 -0
  33. data/lib/mongo/message/message.rb +80 -0
  34. data/lib/mongo/message/message_header.rb +45 -0
  35. data/lib/mongo/message/msg_message.rb +29 -0
  36. data/lib/mongo/message/opcodes.rb +27 -0
  37. data/lib/mongo/message/query_message.rb +78 -0
  38. data/lib/mongo/message/remove_message.rb +37 -0
  39. data/lib/mongo/message/update_message.rb +38 -0
  40. data/lib/mongo/query.rb +118 -0
  41. data/lib/mongo/types/binary.rb +38 -0
  42. data/lib/mongo/types/code.rb +30 -0
  43. data/lib/mongo/types/dbref.rb +33 -0
  44. data/lib/mongo/types/objectid.rb +143 -0
  45. data/lib/mongo/types/regexp_of_holding.rb +40 -0
  46. data/lib/mongo/util/bson.rb +546 -0
  47. data/lib/mongo/util/byte_buffer.rb +167 -0
  48. data/lib/mongo/util/ordered_hash.rb +113 -0
  49. data/lib/mongo/util/xml_to_ruby.rb +105 -0
  50. data/mongo-ruby-driver.gemspec +103 -0
  51. data/test/mongo-qa/_common.rb +8 -0
  52. data/test/mongo-qa/admin +26 -0
  53. data/test/mongo-qa/capped +22 -0
  54. data/test/mongo-qa/count1 +18 -0
  55. data/test/mongo-qa/dbs +22 -0
  56. data/test/mongo-qa/find +10 -0
  57. data/test/mongo-qa/find1 +15 -0
  58. data/test/mongo-qa/gridfs_in +16 -0
  59. data/test/mongo-qa/gridfs_out +17 -0
  60. data/test/mongo-qa/indices +49 -0
  61. data/test/mongo-qa/remove +25 -0
  62. data/test/mongo-qa/stress1 +35 -0
  63. data/test/mongo-qa/test1 +11 -0
  64. data/test/mongo-qa/update +18 -0
  65. data/test/test_admin.rb +69 -0
  66. data/test/test_bson.rb +268 -0
  67. data/test/test_byte_buffer.rb +69 -0
  68. data/test/test_chunk.rb +84 -0
  69. data/test/test_collection.rb +249 -0
  70. data/test/test_connection.rb +101 -0
  71. data/test/test_cursor.rb +331 -0
  72. data/test/test_db.rb +185 -0
  73. data/test/test_db_api.rb +798 -0
  74. data/test/test_db_connection.rb +18 -0
  75. data/test/test_grid_store.rb +284 -0
  76. data/test/test_message.rb +35 -0
  77. data/test/test_objectid.rb +105 -0
  78. data/test/test_ordered_hash.rb +138 -0
  79. data/test/test_round_trip.rb +120 -0
  80. data/test/test_threading.rb +37 -0
  81. metadata +135 -0
@@ -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