mongo 0.1.0 → 0.15

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