mongo 0.18.2 → 0.18.3

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