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.
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