mongo 0.18.2 → 0.18.3

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 (51) hide show
  1. data/README.rdoc +37 -28
  2. data/Rakefile +19 -0
  3. data/bin/objectid_benchmark.rb +23 -0
  4. data/examples/admin.rb +7 -6
  5. data/examples/capped.rb +3 -4
  6. data/examples/cursor.rb +4 -3
  7. data/examples/gridfs.rb +3 -2
  8. data/examples/index_test.rb +10 -9
  9. data/examples/info.rb +3 -2
  10. data/examples/queries.rb +3 -2
  11. data/examples/simple.rb +3 -2
  12. data/examples/strict.rb +2 -1
  13. data/examples/types.rb +4 -3
  14. data/lib/mongo.rb +29 -12
  15. data/lib/mongo/admin.rb +17 -2
  16. data/lib/mongo/collection.rb +230 -169
  17. data/lib/mongo/connection.rb +136 -91
  18. data/lib/mongo/cursor.rb +68 -40
  19. data/lib/mongo/db.rb +247 -123
  20. data/lib/mongo/{errors.rb → exceptions.rb} +6 -5
  21. data/lib/mongo/gridfs.rb +6 -0
  22. data/lib/mongo/gridfs/grid_store.rb +142 -94
  23. data/lib/mongo/types/binary.rb +11 -1
  24. data/lib/mongo/types/code.rb +6 -1
  25. data/lib/mongo/types/dbref.rb +7 -2
  26. data/lib/mongo/types/min_max_keys.rb +58 -0
  27. data/lib/mongo/types/objectid.rb +76 -20
  28. data/lib/mongo/types/regexp_of_holding.rb +5 -0
  29. data/lib/mongo/util/bson_ruby.rb +36 -2
  30. data/lib/mongo/util/byte_buffer.rb +18 -2
  31. data/lib/mongo/util/conversions.rb +6 -5
  32. data/lib/mongo/util/ordered_hash.rb +3 -1
  33. data/lib/mongo/util/support.rb +3 -0
  34. data/lib/mongo/util/xml_to_ruby.rb +7 -0
  35. data/test/test_bson.rb +48 -0
  36. data/test/test_collection.rb +13 -0
  37. data/test/test_connection.rb +35 -0
  38. data/test/test_conversions.rb +1 -1
  39. data/test/test_cursor.rb +37 -5
  40. data/test/test_db.rb +51 -2
  41. data/test/test_db_api.rb +4 -7
  42. data/test/test_grid_store.rb +10 -0
  43. data/test/test_objectid.rb +16 -2
  44. data/test/test_ordered_hash.rb +14 -0
  45. data/test/threading/test_threading_large_pool.rb +4 -4
  46. data/test/unit/db_test.rb +43 -0
  47. metadata +5 -7
  48. data/examples/benchmarks.rb +0 -42
  49. data/examples/blog.rb +0 -76
  50. data/lib/mongo/constants.rb +0 -15
  51. data/test/mongo-qa/_common.rb +0 -8
@@ -14,13 +14,13 @@
14
14
  # limitations under the License.
15
15
  # ++
16
16
 
17
- require 'mutex_m'
17
+ require 'thread'
18
18
  require 'socket'
19
19
  require 'digest/md5'
20
20
 
21
21
  module Mongo
22
22
 
23
- # Representation of an ObjectId for Mongo.
23
+ # ObjectID class for documents in MongoDB.
24
24
  class ObjectID
25
25
  # This is the legacy byte ordering for Babble. Versions of the Ruby
26
26
  # driver prior to 0.14 used this byte ordering when converting ObjectID
@@ -30,11 +30,20 @@ module Mongo
30
30
  # with ObjectID#legacy_string_convert
31
31
  BYTE_ORDER = [7, 6, 5, 4, 3, 2, 1, 0, 11, 10, 9, 8]
32
32
 
33
- LOCK = Object.new
34
- LOCK.extend Mutex_m
35
-
33
+ @@lock = Mutex.new
36
34
  @@index = 0
37
35
 
36
+ # Create a new object id. If no parameter is given, an id corresponding
37
+ # to the ObjectID BSON data type will be created. This is a 12-byte value
38
+ # consisting of a 4-byte timestamp, a 3-byte machine id, a 2-byte process id,
39
+ # and a 3-byte counter.
40
+ #
41
+ # @param [Array] data should be an array of bytes. If you want
42
+ # to generate a standard MongoDB object id, leave this argument blank.
43
+ def initialize(data=nil)
44
+ @data = data || generate
45
+ end
46
+
38
47
  def self.legal?(str)
39
48
  len = BYTE_ORDER.length * 2
40
49
  str =~ /([0-9a-f]+)/i
@@ -42,33 +51,61 @@ module Mongo
42
51
  str && str.length == len && match == str
43
52
  end
44
53
 
54
+ # Create an object id from the given time. This is useful for doing range
55
+ # queries; it works because MongoDB's object ids begin
56
+ # with a timestamp.
57
+ #
58
+ # @param [Time] time a utc time to encode as an object id.
59
+ #
60
+ # @return [Mongo::ObjectID]
61
+ #
62
+ # @example Return all document created before Jan 1, 2010.
63
+ # time = Time.utc(2010, 1, 1)
64
+ # time_id = ObjectID.from_time(time)
65
+ # collection.find({'_id' => {'$lt' => time_id}})
66
+ def self.from_time(time)
67
+ self.new([time.to_i,0,0].pack("NNN").unpack("C12"))
68
+ end
69
+
45
70
  # Adds a primary key to the given document if needed.
71
+ #
72
+ # @param [Hash] doc a document requiring an _id.
73
+ #
74
+ # @return [Mongo::ObjectID, Object] returns a newly-created or
75
+ # current _id for the given document.
46
76
  def self.create_pk(doc)
47
77
  doc.has_key?(:_id) || doc.has_key?('_id') ? doc : doc.merge!(:_id => self.new)
48
78
  end
49
79
 
50
- # +data+ is an array of bytes. If nil, a new id will be generated.
51
- def initialize(data=nil)
52
- @data = data || generate
53
- end
54
-
55
- def eql?(other)
56
- @data == other.instance_variable_get("@data")
80
+ # Check equality of this object id with another.
81
+ #
82
+ # @param [Mongo::ObjectID] object_id
83
+ def eql?(object_id)
84
+ @data == object_id.instance_variable_get("@data")
57
85
  end
58
86
  alias_method :==, :eql?
59
87
 
60
- # Returns a unique hashcode for the object.
88
+ # Get a unique hashcode for this object.
61
89
  # This is required since we've defined an #eql? method.
90
+ #
91
+ # @return [Integer]
62
92
  def hash
63
93
  @data.hash
64
94
  end
65
95
 
96
+ # Get an array representation of the object id.
97
+ #
98
+ # @return [Array]
66
99
  def to_a
67
100
  @data.dup
68
101
  end
69
102
 
70
103
  # Given a string representation of an ObjectID, return a new ObjectID
71
104
  # with that value.
105
+ #
106
+ # @param [String] str
107
+ #
108
+ # @return [Mongo::ObjectID]
72
109
  def self.from_string(str)
73
110
  raise InvalidObjectID, "illegal ObjectID format" unless legal?(str)
74
111
  data = []
@@ -78,11 +115,13 @@ module Mongo
78
115
  self.new(data)
79
116
  end
80
117
 
118
+ # @deprecated
81
119
  # Create a new ObjectID given a string representation of an ObjectID
82
120
  # using the legacy byte ordering. This method may eventually be
83
121
  # removed. If you are not sure that you need this method you should be
84
122
  # using the regular from_string.
85
123
  def self.from_string_legacy(str)
124
+ warn "Support for legacy object ids has been DEPRECATED."
86
125
  raise InvalidObjectID, "illegal ObjectID format" unless legal?(str)
87
126
  data = []
88
127
  BYTE_ORDER.each_with_index { |string_position, data_index|
@@ -91,6 +130,9 @@ module Mongo
91
130
  self.new(data)
92
131
  end
93
132
 
133
+ # Get a string representation of this object id.
134
+ #
135
+ # @return [String]
94
136
  def to_s
95
137
  str = ' ' * 24
96
138
  12.times do |i|
@@ -98,13 +140,22 @@ module Mongo
98
140
  end
99
141
  str
100
142
  end
143
+ alias_method :inspect, :to_s
144
+
145
+ # Convert to MongoDB extended JSON format. Since JSON includes type information,
146
+ # but lacks an ObjectID type, this JSON format encodes the type using an $id key.
147
+ #
148
+ # @return [String] the object id represented as MongoDB extended JSON.
149
+ def to_json(escaped=false)
150
+ "{\"$oid\": \"#{to_s}\"}"
151
+ end
101
152
 
102
- def inspect; to_s; end
103
-
153
+ # @deprecated
104
154
  # Get a string representation of this ObjectID using the legacy byte
105
155
  # ordering. This method may eventually be removed. If you are not sure
106
156
  # that you need this method you should be using the regular to_s.
107
157
  def to_s_legacy
158
+ warn "Support for legacy object ids has been DEPRECATED."
108
159
  str = ' ' * 24
109
160
  BYTE_ORDER.each_with_index { |string_position, data_index|
110
161
  str[string_position * 2, 2] = '%02x' % @data[data_index]
@@ -112,11 +163,13 @@ module Mongo
112
163
  str
113
164
  end
114
165
 
166
+ # @deprecated
115
167
  # Convert a string representation of an ObjectID using the legacy byte
116
168
  # ordering to the proper byte ordering. This method may eventually be
117
169
  # removed. If you are not sure that you need this method it is probably
118
170
  # unnecessary.
119
171
  def self.legacy_string_convert(str)
172
+ warn "Support for legacy object ids has been DEPRECATED."
120
173
  legacy = ' ' * 24
121
174
  BYTE_ORDER.each_with_index do |legacy_pos, pos|
122
175
  legacy[legacy_pos * 2, 2] = str[pos * 2, 2]
@@ -124,10 +177,13 @@ module Mongo
124
177
  legacy
125
178
  end
126
179
 
127
- # Returns the utc time at which this ObjectID was generated. This may
128
- # be used in lieu of a created_at timestamp.
180
+ # Return the UTC time at which this ObjectID was generated. This may
181
+ # be used in lieu of a created_at timestamp since this information
182
+ # is always encoded in the object id.
183
+ #
184
+ # @return [Time] the time at which this object was created.
129
185
  def generation_time
130
- Time.at(@data.pack("C4").unpack("N")[0])
186
+ Time.at(@data.pack("C4").unpack("N")[0]).utc
131
187
  end
132
188
 
133
189
  private
@@ -155,9 +211,9 @@ module Mongo
155
211
  end
156
212
 
157
213
  def get_inc
158
- LOCK.mu_synchronize {
214
+ @@lock.synchronize do
159
215
  @@index = (@@index + 1) % 0xFFFFFF
160
- }
216
+ end
161
217
  end
162
218
  end
163
219
  end
@@ -24,14 +24,19 @@ module Mongo
24
24
  # Note that you do not have to use this class at all if you wish to
25
25
  # store regular expressions in Mongo. The Mongo and Ruby regex option
26
26
  # flags are the same. Storing regexes is discouraged, in any case.
27
+ #
28
+ # @deprecated
27
29
  class RegexpOfHolding < Regexp
28
30
 
29
31
  attr_accessor :extra_options_str
30
32
 
33
+ # @deprecated we're no longer supporting this.
31
34
  # +str+ and +options+ are the same as Regexp. +extra_options_str+
32
35
  # contains all the other flags that were in Mongo but we do not use or
33
36
  # understand.
34
37
  def initialize(str, options, extra_options_str)
38
+ warn "RegexpOfHolding is deprecated; the modifiers i, m, and x will be stored automatically as BSON." +
39
+ "If you're only storing the options i, m, and x, you can safely ignore this message."
35
40
  super(str, options)
36
41
  @extra_options_str = extra_options_str
37
42
  end
@@ -163,6 +163,10 @@ class BSON_RUBY
163
163
  serialize_null_element(@buf, k)
164
164
  when CODE_W_SCOPE
165
165
  serialize_code_w_scope(@buf, k, v)
166
+ when MAXKEY
167
+ serialize_max_key_element(@buf, k)
168
+ when MINKEY
169
+ serialize_min_key_element(@buf, k)
166
170
  else
167
171
  raise "unhandled type #{type}"
168
172
  end
@@ -234,6 +238,12 @@ class BSON_RUBY
234
238
  key = deserialize_cstr(@buf)
235
239
  doc[key] = [deserialize_number_int_data(@buf),
236
240
  deserialize_number_int_data(@buf)]
241
+ when MAXKEY
242
+ key = deserialize_cstr(@buf)
243
+ doc[key] = MaxKey.new
244
+ when MINKEY, 255 # This is currently easier than unpack the type byte as an unsigned char.
245
+ key = deserialize_cstr(@buf)
246
+ doc[key] = MinKey.new
237
247
  when EOO
238
248
  break
239
249
  else
@@ -466,6 +476,16 @@ class BSON_RUBY
466
476
  self.class.serialize_cstr(buf, options_str.split(//).sort.uniq.join)
467
477
  end
468
478
 
479
+ def serialize_max_key_element(buf, key)
480
+ buf.put(MAXKEY)
481
+ self.class.serialize_key(buf, key)
482
+ end
483
+
484
+ def serialize_min_key_element(buf, key)
485
+ buf.put(MINKEY)
486
+ self.class.serialize_key(buf, key)
487
+ end
488
+
469
489
  def serialize_oid_element(buf, key, val)
470
490
  buf.put(OID)
471
491
  self.class.serialize_key(buf, key)
@@ -529,7 +549,7 @@ class BSON_RUBY
529
549
  NULL
530
550
  when Integer
531
551
  NUMBER_INT
532
- when Numeric
552
+ when Float
533
553
  NUMBER
534
554
  when ByteBuffer
535
555
  BINARY
@@ -553,8 +573,22 @@ class BSON_RUBY
553
573
  OBJECT
554
574
  when Symbol
555
575
  SYMBOL
576
+ when MaxKey
577
+ MAXKEY
578
+ when MinKey
579
+ MINKEY
580
+ when Numeric
581
+ raise InvalidDocument, "Cannot serialize the Numeric type #{o.class} as BSON; only Fixum, Bignum, and Float are supported."
582
+ when Date, DateTime
583
+ raise InvalidDocument, "#{o.class} is not currently supported; " +
584
+ "use a UTC Time instance instead."
556
585
  else
557
- raise InvalidDocument, "Unknown type of object: #{o.class.name}"
586
+ if defined?(ActiveSupport::TimeWithZone) && o.is_a?(ActiveSupport::TimeWithZone)
587
+ raise InvalidDocument, "ActiveSupport::TimeWithZone is not currently supported; " +
588
+ "use a UTC Time instance instead."
589
+ else
590
+ raise InvalidDocument, "Cannot serialize #{o.class} as a BSON type; it either isn't supported or won't translate to BSON."
591
+ end
558
592
  end
559
593
  end
560
594
 
@@ -17,6 +17,20 @@
17
17
  # A byte buffer.
18
18
  class ByteBuffer
19
19
 
20
+ # Commonly-used integers.
21
+ INT_LOOKUP = {
22
+ 0 => [0, 0, 0, 0],
23
+ 1 => [1, 0, 0, 0],
24
+ 2 => [2, 0, 0, 0],
25
+ 3 => [3, 0, 0, 0],
26
+ 4 => [4, 0, 0, 0],
27
+ 2001 => [209, 7, 0, 0],
28
+ 2002 => [210, 7, 0, 0],
29
+ 2004 => [212, 7, 0, 0],
30
+ 2005 => [213, 7, 0, 0],
31
+ 2006 => [214, 7, 0, 0]
32
+ }
33
+
20
34
  attr_reader :order
21
35
 
22
36
  def initialize(initial_data=[])
@@ -100,8 +114,10 @@ class ByteBuffer
100
114
  end
101
115
 
102
116
  def put_int(i, offset=nil)
103
- a = []
104
- [i].pack(@int_pack_order).each_byte { |b| a << b }
117
+ unless a = INT_LOOKUP[i]
118
+ a = []
119
+ [i].pack(@int_pack_order).each_byte { |b| a << b }
120
+ end
105
121
  put_array(a, offset)
106
122
  end
107
123
 
@@ -19,8 +19,8 @@ module Mongo #:nodoc:
19
19
  # objects to mongo-friendly parameters.
20
20
  module Conversions
21
21
 
22
- ASCENDING = ["ascending", "asc", "1"]
23
- DESCENDING = ["descending", "desc", "-1"]
22
+ ASCENDING_CONVERSION = ["ascending", "asc", "1"]
23
+ DESCENDING_CONVERSION = ["descending", "desc", "-1"]
24
24
 
25
25
  # Converts the supplied +Array+ to a +Hash+ to pass to mongo as
26
26
  # sorting parameters. The returned +Hash+ will vary depending
@@ -76,11 +76,12 @@ module Mongo #:nodoc:
76
76
  # If the value is invalid then an error will be raised.
77
77
  def sort_value(value)
78
78
  val = value.to_s.downcase
79
- return 1 if ASCENDING.include?(val)
80
- return -1 if DESCENDING.include?(val)
79
+ return 1 if ASCENDING_CONVERSION.include?(val)
80
+ return -1 if DESCENDING_CONVERSION.include?(val)
81
81
  raise InvalidSortValueError.new(
82
82
  "#{self} was supplied as a sort direction when acceptable values are: " +
83
- "Mongo::ASCENDING, 'ascending', 'asc', :ascending, :asc, 1, Mongo::DESCENDING, 'descending', 'desc', :descending, :desc, -1.")
83
+ "Mongo::ASCENDING, 'ascending', 'asc', :ascending, :asc, 1, Mongo::DESCENDING, " +
84
+ "'descending', 'desc', :descending, :desc, -1.")
84
85
  end
85
86
  end
86
87
  end
@@ -88,6 +88,8 @@ class OrderedHash < Hash
88
88
  super(other)
89
89
  end
90
90
 
91
+ alias :update :merge!
92
+
91
93
  def inspect
92
94
  str = '{'
93
95
  str << (@ordered_keys || []).collect { |k| "\"#{k}\"=>#{self.[](k).inspect}" }.join(", ")
@@ -118,7 +120,7 @@ class OrderedHash < Hash
118
120
  code = 37 * code + key.hash
119
121
  code = 37 * code + value.hash
120
122
  end
121
- code
123
+ code & 0x7fffffff
122
124
  end
123
125
 
124
126
  def eql?(o)
@@ -13,8 +13,11 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  # ++
16
+
17
+ #:nodoc:
16
18
  class Object
17
19
 
20
+ #:nodoc:
18
21
  def returning(value)
19
22
  yield value
20
23
  value
@@ -17,6 +17,7 @@
17
17
  require 'rexml/document'
18
18
  require 'mongo'
19
19
 
20
+ # @deprecated
20
21
  # Converts a .xson file (an XML file that describes a Mongo-type document) to
21
22
  # an OrderedHash.
22
23
  class XMLToRuby
@@ -24,6 +25,7 @@ class XMLToRuby
24
25
  include Mongo
25
26
 
26
27
  def xml_to_ruby(io)
28
+ warn "XMLToRuby is deprecated. The .xson format is not longer in use."
27
29
  doc = REXML::Document.new(io)
28
30
  doc_to_ruby(doc.root.elements['doc'])
29
31
  end
@@ -31,6 +33,7 @@ class XMLToRuby
31
33
  protected
32
34
 
33
35
  def element_to_ruby(e)
36
+ warn "XMLToRuby is deprecated. The .xson format is not longer in use."
34
37
  type = e.name
35
38
  child = e.elements[1]
36
39
  case type
@@ -71,12 +74,14 @@ class XMLToRuby
71
74
  end
72
75
 
73
76
  def doc_to_ruby(element)
77
+ warn "XMLToRuby is deprecated. The .xson format is not longer in use."
74
78
  oh = OrderedHash.new
75
79
  element.elements.each { |e| oh[e.attributes['name']] = element_to_ruby(e) }
76
80
  oh
77
81
  end
78
82
 
79
83
  def array_to_ruby(elements)
84
+ warn "XMLToRuby is deprecated. The .xson format is not longer in use."
80
85
  a = []
81
86
  elements.each { |e|
82
87
  index_str = e.attributes['name']
@@ -86,6 +91,7 @@ class XMLToRuby
86
91
  end
87
92
 
88
93
  def regex_to_ruby(elements)
94
+ warn "XMLToRuby is deprecated. The .xson format is not longer in use."
89
95
  pattern = elements['pattern'].text
90
96
  options_str = elements['options'].text || ''
91
97
 
@@ -97,6 +103,7 @@ class XMLToRuby
97
103
  end
98
104
 
99
105
  def dbref_to_ruby(elements)
106
+ warn "XMLToRuby is deprecated. The .xson format is not longer in use."
100
107
  ns = elements['ns'].text
101
108
  oid_str = elements['oid'].text
102
109
  DBRef.new(ns, ObjectID.from_string(oid_str))
@@ -1,5 +1,15 @@
1
1
  # encoding:utf-8
2
2
  require 'test/test_helper'
3
+ require 'complex'
4
+ require 'bigdecimal'
5
+ require 'rational'
6
+
7
+ # Need to simulating this class
8
+ # without actually loading it.
9
+ module ActiveSupport
10
+ class TimeWithZone
11
+ end
12
+ end
3
13
 
4
14
  class BSONTest < Test::Unit::TestCase
5
15
 
@@ -181,6 +191,19 @@ class BSONTest < Test::Unit::TestCase
181
191
  end
182
192
  end
183
193
 
194
+ def test_exeption_on_using_unsupported_date_class
195
+ [DateTime.now, Date.today, ActiveSupport::TimeWithZone.new].each do |invalid_date|
196
+ doc = {:date => invalid_date}
197
+ begin
198
+ bson = BSON.serialize(doc)
199
+ rescue => e
200
+ ensure
201
+ assert_equal InvalidDocument, e.class
202
+ assert_match /UTC Time/, e.message
203
+ end
204
+ end
205
+ end
206
+
184
207
  def test_dbref
185
208
  oid = ObjectID.new
186
209
  doc = {}
@@ -293,6 +316,19 @@ class BSONTest < Test::Unit::TestCase
293
316
  end
294
317
  end
295
318
 
319
+ def test_invalid_numeric_types
320
+ [BigDecimal.new("1.0"), Complex(0, 1), Rational(2, 3)].each do |type|
321
+ doc = {"x" => type}
322
+ begin
323
+ BSON.serialize(doc)
324
+ rescue => e
325
+ ensure
326
+ assert_equal InvalidDocument, e.class
327
+ assert_match /Cannot serialize/, e.message
328
+ end
329
+ end
330
+ end
331
+
296
332
  def test_do_not_change_original_object
297
333
  val = OrderedHash.new
298
334
  val['not_id'] = 1
@@ -335,6 +371,18 @@ class BSONTest < Test::Unit::TestCase
335
371
  end
336
372
  end
337
373
 
374
+ def test_max_key
375
+ doc = {"a" => MaxKey.new}
376
+
377
+ assert_equal doc, BSON.deserialize(BSON.serialize(doc).to_a)
378
+ end
379
+
380
+ def test_min_key
381
+ doc = {"a" => MinKey.new}
382
+
383
+ assert_equal doc, BSON.deserialize(BSON.serialize(doc).to_a)
384
+ end
385
+
338
386
  def test_invalid_object
339
387
  o = Object.new
340
388
  assert_raise InvalidDocument do