mongo 0.1.0 → 0.15

Sign up to get free protection for your applications and to get access to all the features.
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