mongo 0.18.3 → 0.19

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 (62) hide show
  1. data/README.rdoc +41 -50
  2. data/Rakefile +14 -4
  3. data/examples/gridfs.rb +25 -70
  4. data/lib/mongo.rb +4 -2
  5. data/lib/mongo/collection.rb +70 -89
  6. data/lib/mongo/connection.rb +203 -43
  7. data/lib/mongo/cursor.rb +7 -7
  8. data/lib/mongo/db.rb +61 -18
  9. data/lib/mongo/exceptions.rb +7 -1
  10. data/lib/mongo/gridfs.rb +8 -1
  11. data/lib/mongo/gridfs/chunk.rb +2 -1
  12. data/lib/mongo/gridfs/grid.rb +90 -0
  13. data/lib/mongo/gridfs/grid_file_system.rb +113 -0
  14. data/lib/mongo/gridfs/grid_io.rb +339 -0
  15. data/lib/mongo/gridfs/grid_store.rb +43 -18
  16. data/lib/mongo/types/binary.rb +5 -1
  17. data/lib/mongo/types/code.rb +1 -1
  18. data/lib/mongo/types/dbref.rb +3 -1
  19. data/lib/mongo/types/min_max_keys.rb +1 -1
  20. data/lib/mongo/types/objectid.rb +16 -55
  21. data/lib/mongo/types/regexp_of_holding.rb +1 -1
  22. data/lib/mongo/util/bson_c.rb +2 -2
  23. data/lib/mongo/util/bson_ruby.rb +22 -11
  24. data/lib/mongo/util/byte_buffer.rb +1 -1
  25. data/lib/mongo/util/conversions.rb +1 -1
  26. data/lib/mongo/util/ordered_hash.rb +6 -1
  27. data/lib/mongo/util/server_version.rb +1 -1
  28. data/lib/mongo/util/support.rb +1 -1
  29. data/mongo-ruby-driver.gemspec +1 -1
  30. data/test/auxillary/authentication_test.rb +68 -0
  31. data/test/auxillary/autoreconnect_test.rb +41 -0
  32. data/test/binary_test.rb +15 -0
  33. data/test/{test_bson.rb → bson_test.rb} +63 -6
  34. data/test/{test_byte_buffer.rb → byte_buffer_test.rb} +0 -0
  35. data/test/{test_chunk.rb → chunk_test.rb} +0 -0
  36. data/test/{test_collection.rb → collection_test.rb} +35 -39
  37. data/test/{test_connection.rb → connection_test.rb} +33 -3
  38. data/test/{test_conversions.rb → conversions_test.rb} +0 -0
  39. data/test/{test_cursor.rb → cursor_test.rb} +0 -7
  40. data/test/{test_db_api.rb → db_api_test.rb} +3 -6
  41. data/test/{test_db_connection.rb → db_connection_test.rb} +0 -0
  42. data/test/{test_db.rb → db_test.rb} +33 -15
  43. data/test/grid_file_system_test.rb +210 -0
  44. data/test/grid_io_test.rb +78 -0
  45. data/test/{test_grid_store.rb → grid_store_test.rb} +33 -2
  46. data/test/grid_test.rb +87 -0
  47. data/test/{test_objectid.rb → objectid_test.rb} +2 -33
  48. data/test/{test_ordered_hash.rb → ordered_hash_test.rb} +4 -0
  49. data/test/{test_slave_connection.rb → slave_connection_test.rb} +0 -0
  50. data/test/test_helper.rb +2 -2
  51. data/test/{test_threading.rb → threading_test.rb} +0 -0
  52. data/test/unit/collection_test.rb +12 -3
  53. data/test/unit/connection_test.rb +85 -24
  54. data/test/unit/cursor_test.rb +1 -2
  55. data/test/unit/db_test.rb +70 -69
  56. metadata +27 -23
  57. data/bin/objectid_benchmark.rb +0 -23
  58. data/bin/perf.rb +0 -30
  59. data/lib/mongo/admin.rb +0 -95
  60. data/lib/mongo/util/xml_to_ruby.rb +0 -112
  61. data/test/test_admin.rb +0 -67
  62. data/test/test_round_trip.rb +0 -114
@@ -1,5 +1,5 @@
1
1
  # --
2
- # Copyright (C) 2008-2009 10gen Inc.
2
+ # Copyright (C) 2008-2010 10gen Inc.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -30,7 +30,10 @@ module Mongo
30
30
  STANDARD_HEADER_SIZE = 16
31
31
  RESPONSE_HEADER_SIZE = 20
32
32
 
33
- attr_reader :logger, :size, :host, :port, :nodes, :sockets, :checked_out
33
+ MONGODB_URI_MATCHER = /(([.\w\d]+):([\w\d]+)@)?([.\w\d]+)(:([\w\d]+))?(\/([-\d\w]+))?/
34
+ MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]"
35
+
36
+ attr_reader :logger, :size, :host, :port, :nodes, :auths, :sockets, :checked_out
34
37
 
35
38
  # Counter for generating unique request ids.
36
39
  @@current_request_id = 0
@@ -74,19 +77,26 @@ module Mongo
74
77
  # @example localhost, 3000, where this node may be a slave
75
78
  # Connection.new("localhost", 3000, :slave_ok => true)
76
79
  #
77
- # @example A pair of servers. The driver will always talk to master.
78
- # # On connection errors, Mongo::ConnectionFailure will be raised.
80
+ # @example DEPRECATED. To initialize a paired connection, use Connection.paired instead.
79
81
  # Connection.new({:left => ["db1.example.com", 27017],
80
82
  # :right => ["db2.example.com", 27017]})
81
83
  #
82
- # @example A pair of servers with connection pooling enabled. Note the nil param placeholder for port.
84
+ # @example DEPRECATED. To initialize a paired connection, use Connection.paired instead.
83
85
  # Connection.new({:left => ["db1.example.com", 27017],
84
86
  # :right => ["db2.example.com", 27017]}, nil,
85
87
  # :pool_size => 20, :timeout => 5)
86
88
  #
87
89
  # @see http://www.mongodb.org/display/DOCS/Replica+Pairs+in+Ruby Replica pairs in Ruby
90
+ #
91
+ # @core connections
88
92
  def initialize(pair_or_host=nil, port=nil, options={})
89
- @nodes = format_pair(pair_or_host, port)
93
+ @auths = []
94
+
95
+ if block_given?
96
+ @nodes = yield self
97
+ else
98
+ @nodes = format_pair(pair_or_host, port)
99
+ end
90
100
 
91
101
  # Host and port of current master.
92
102
  @host = @port = nil
@@ -121,12 +131,109 @@ module Mongo
121
131
  connect_to_master if should_connect
122
132
  end
123
133
 
134
+ # Initialize a paired connection to MongoDB.
135
+ #
136
+ # @param nodes [Array] An array of arrays, each of which specified a host and port.
137
+ # @param opts Takes the same options as Connection.new
138
+ #
139
+ # @example
140
+ # Connection.new([["db1.example.com", 27017],
141
+ # ["db2.example.com", 27017]])
142
+ #
143
+ # @example
144
+ # Connection.new([["db1.example.com", 27017],
145
+ # ["db2.example.com", 27017]],
146
+ # :pool_size => 20, :timeout => 5)
147
+ #
148
+ # @return [Mongo::Connection]
149
+ def self.paired(nodes, opts={})
150
+ unless nodes.length == 2 && nodes.all? {|n| n.is_a? Array}
151
+ raise MongoArgumentError, "Connection.paired requires that exactly two nodes be specified."
152
+ end
153
+ # Block returns an array, the first element being an array of nodes and the second an array
154
+ # of authorizations for the database.
155
+ new(nil, nil, opts) do |con|
156
+ [con.pair_val_to_connection(nodes[0]), con.pair_val_to_connection(nodes[1])]
157
+ end
158
+ end
159
+
160
+ # Initialize a connection to MongoDB using the MongoDB URI spec:
161
+ #
162
+ # @param uri [String]
163
+ # A string of the format mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]
164
+ #
165
+ # @param opts Any of the options available for Connection.new
166
+ #
167
+ # @return [Mongo::Connection]
168
+ def self.from_uri(uri, opts={})
169
+ new(nil, nil, opts) do |con|
170
+ con.parse_uri(uri)
171
+ end
172
+ end
173
+
174
+ # Apply each of the saved database authentications.
175
+ #
176
+ # @return [Boolean] returns true if authentications exist and succeeed, false
177
+ # if none exists.
178
+ #
179
+ # @raise [AuthenticationError] raises an exception if any one
180
+ # authentication fails.
181
+ def apply_saved_authentication
182
+ return false if @auths.empty?
183
+ @auths.each do |auth|
184
+ self[auth['db_name']].authenticate(auth['username'], auth['password'], false)
185
+ end
186
+ true
187
+ end
188
+
189
+ # Save an authentication to this connection. When connecting,
190
+ # the connection will attempt to re-authenticate on every db
191
+ # specificed in the list of auths. This method is called automatically
192
+ # by DB#authenticate.
193
+ #
194
+ # @param [String] db_name
195
+ # @param [String] username
196
+ # @param [String] password
197
+ #
198
+ # @return [Hash] a hash representing the authentication just added.
199
+ def add_auth(db_name, username, password)
200
+ remove_auth(db_name)
201
+ auth = {}
202
+ auth['db_name'] = db_name
203
+ auth['username'] = username
204
+ auth['password'] = password
205
+ @auths << auth
206
+ auth
207
+ end
208
+
209
+ # Remove a saved authentication for this connection.
210
+ #
211
+ # @param [String] db_name
212
+ #
213
+ # @return [Boolean]
214
+ def remove_auth(db_name)
215
+ return unless @auths
216
+ if @auths.reject! { |a| a['db_name'] == db_name }
217
+ true
218
+ else
219
+ false
220
+ end
221
+ end
222
+
223
+ # Remove all authenication information stored in this connection.
224
+ #
225
+ # @return [true] this operation return true because it always succeeds.
226
+ def clear_auths
227
+ @auths = []
228
+ true
229
+ end
230
+
124
231
  # Return a hash with all database names
125
232
  # and their respective sizes on disk.
126
233
  #
127
234
  # @return [Hash]
128
235
  def database_info
129
- doc = self['admin'].command(:listDatabases => 1)
236
+ doc = self['admin'].command({:listDatabases => 1}, false, true)
130
237
  returning({}) do |info|
131
238
  doc['databases'].each { |db| info[db['name']] = db['sizeOnDisk'].to_i }
132
239
  end
@@ -145,6 +252,8 @@ module Mongo
145
252
  # @param [String] db_name a valid database name.
146
253
  #
147
254
  # @return [Mongo::DB]
255
+ #
256
+ # @core databases db-instance_method
148
257
  def db(db_name, options={})
149
258
  DB.new(db_name, self, options.merge(:logger => @logger))
150
259
  end
@@ -154,6 +263,8 @@ module Mongo
154
263
  # @param [String] db_name a valid database name.
155
264
  #
156
265
  # @return [Mongo::DB]
266
+ #
267
+ # @core databases []-instance_method
157
268
  def [](db_name)
158
269
  DB.new(db_name, self, :logger => @logger)
159
270
  end
@@ -177,7 +288,7 @@ module Mongo
177
288
  oh[:fromhost] = from_host
178
289
  oh[:fromdb] = from
179
290
  oh[:todb] = to
180
- self["admin"].command(oh)
291
+ self["admin"].command(oh, false, true)
181
292
  end
182
293
 
183
294
  # Increment and return the next available request id.
@@ -195,7 +306,7 @@ module Mongo
195
306
  #
196
307
  # @return [Hash]
197
308
  def server_info
198
- db("admin").command({:buildinfo => 1}, {:admin => true, :check_response => true})
309
+ self["admin"].command({:buildinfo => 1}, false, true)
199
310
  end
200
311
 
201
312
  # Get the build version of the current server.
@@ -310,6 +421,7 @@ module Mongo
310
421
  result = self['admin'].command({:ismaster => 1}, false, false, socket)
311
422
  if result['ok'] == 1 && ((is_master = result['ismaster'] == 1) || @slave_ok)
312
423
  @host, @port = host, port
424
+ apply_saved_authentication
313
425
  end
314
426
 
315
427
  # Note: slave_ok can be true only when connecting to a single node.
@@ -321,6 +433,7 @@ module Mongo
321
433
  break if is_master || @slave_ok
322
434
  rescue SocketError, SystemCallError, IOError => ex
323
435
  socket.close if socket
436
+ close
324
437
  false
325
438
  end
326
439
  end
@@ -343,6 +456,86 @@ module Mongo
343
456
  @checked_out.clear
344
457
  end
345
458
 
459
+ ## Configuration helper methods
460
+
461
+ # Returns an array of host-port pairs.
462
+ #
463
+ # @private
464
+ def format_pair(pair_or_host, port)
465
+ case pair_or_host
466
+ when String
467
+ [[pair_or_host, port ? port.to_i : DEFAULT_PORT]]
468
+ when Hash
469
+ warn "Initializing a paired connection with Connection.new is deprecated. Use Connection.pair instead."
470
+ connections = []
471
+ connections << pair_val_to_connection(pair_or_host[:left])
472
+ connections << pair_val_to_connection(pair_or_host[:right])
473
+ connections
474
+ when nil
475
+ [['localhost', DEFAULT_PORT]]
476
+ end
477
+ end
478
+
479
+ # Convert an argument containing a host name string and a
480
+ # port number integer into a [host, port] pair array.
481
+ #
482
+ # @private
483
+ def pair_val_to_connection(a)
484
+ case a
485
+ when nil
486
+ ['localhost', DEFAULT_PORT]
487
+ when String
488
+ [a, DEFAULT_PORT]
489
+ when Integer
490
+ ['localhost', a]
491
+ when Array
492
+ a
493
+ end
494
+ end
495
+
496
+ # Parse a MongoDB URI. This method is used by Connection.from_uri.
497
+ # Returns an array of nodes and an array of db authorizations, if applicable.
498
+ #
499
+ # @private
500
+ def parse_uri(string)
501
+ if string =~ /^mongodb:\/\//
502
+ string = string[10..-1]
503
+ else
504
+ raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
505
+ end
506
+
507
+ nodes = []
508
+ auths = []
509
+ specs = string.split(',')
510
+ specs.each do |spec|
511
+ matches = MONGODB_URI_MATCHER.match(spec)
512
+ if !matches
513
+ raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
514
+ end
515
+
516
+ uname = matches[2]
517
+ pwd = matches[3]
518
+ host = matches[4]
519
+ port = matches[6] || DEFAULT_PORT
520
+ if !(port.to_s =~ /^\d+$/)
521
+ raise MongoArgumentError, "Invalid port #{port}; port must be specified as digits."
522
+ end
523
+ port = port.to_i
524
+ db = matches[8]
525
+
526
+ if (uname || pwd || db) && !(uname && pwd && db)
527
+ raise MongoArgumentError, "MongoDB URI must include all three of username, password, " +
528
+ "and db if any one of these is specified."
529
+ else
530
+ add_auth(db, uname, pwd)
531
+ end
532
+
533
+ nodes << [host, port]
534
+ end
535
+
536
+ nodes
537
+ end
538
+
346
539
  private
347
540
 
348
541
  # Return a socket to the pool.
@@ -513,43 +706,10 @@ module Mongo
513
706
  message += chunk
514
707
  end
515
708
  rescue => ex
709
+ close
516
710
  raise ConnectionFailure, "Operation failed with the following exception: #{ex}"
517
711
  end
518
712
  message
519
713
  end
520
-
521
-
522
- ## Private helper methods
523
-
524
- # Returns an array of host-port pairs.
525
- def format_pair(pair_or_host, port)
526
- case pair_or_host
527
- when String
528
- [[pair_or_host, port ? port.to_i : DEFAULT_PORT]]
529
- when Hash
530
- connections = []
531
- connections << pair_val_to_connection(pair_or_host[:left])
532
- connections << pair_val_to_connection(pair_or_host[:right])
533
- connections
534
- when nil
535
- [['localhost', DEFAULT_PORT]]
536
- end
537
- end
538
-
539
- # Turns an array containing a host name string and a
540
- # port number integer into a [host, port] pair array.
541
- def pair_val_to_connection(a)
542
- case a
543
- when nil
544
- ['localhost', DEFAULT_PORT]
545
- when String
546
- [a, DEFAULT_PORT]
547
- when Integer
548
- ['localhost', a]
549
- when Array
550
- a
551
- end
552
- end
553
-
554
714
  end
555
715
  end
data/lib/mongo/cursor.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2008-2009 10gen Inc.
1
+ # Copyright (C) 2008-2010 10gen Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -29,6 +29,8 @@ module Mongo
29
29
  # similar methods. Application developers shouldn't have to create cursors manually.
30
30
  #
31
31
  # @return [Cursor]
32
+ #
33
+ # @core cursors constructor_details
32
34
  def initialize(collection, options={})
33
35
  @db = collection.db
34
36
  @collection = collection
@@ -76,12 +78,6 @@ module Mongo
76
78
  doc
77
79
  end
78
80
 
79
- # @deprecated use Cursor#next_document instead.
80
- def next_object
81
- warn "Cursor#next_object is deprecated; please use Cursor#next_document instead."
82
- next_document
83
- end
84
-
85
81
  # Get the size of the result set for this query.
86
82
  #
87
83
  # @return [Integer] the number of objects in the result set for this query. Does
@@ -131,6 +127,8 @@ module Mongo
131
127
  # @return [Integer] the current number_to_return if no parameter is given.
132
128
  #
133
129
  # @raise [InvalidOperation] if this cursor has already been used.
130
+ #
131
+ # @core limit limit-instance_method
134
132
  def limit(number_to_return=nil)
135
133
  return @limit unless number_to_return
136
134
  check_modifiable
@@ -200,6 +198,8 @@ module Mongo
200
198
  # Get the explain plan for this cursor.
201
199
  #
202
200
  # @return [Hash] a document containing the explain plan for this cursor.
201
+ #
202
+ # @core explain explain-instance_method
203
203
  def explain
204
204
  c = Cursor.new(@collection, query_options_hash.merge(:limit => -@limit.abs, :explain => true))
205
205
  explanation = c.next_document
data/lib/mongo/db.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # --
2
- # Copyright (C) 2008-2009 10gen Inc.
2
+ # Copyright (C) 2008-2010 10gen Inc.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -62,6 +62,8 @@ module Mongo
62
62
  # which should take a hash and return a hash which merges the original hash with any primary key
63
63
  # fields the factory wishes to inject. (NOTE: if the object already has a primary key,
64
64
  # the factory should not inject a new key).
65
+ #
66
+ # @core databases constructor_details
65
67
  def initialize(db_name, connection, options={})
66
68
  @name = validate_db_name(db_name)
67
69
  @connection = connection
@@ -74,9 +76,16 @@ module Mongo
74
76
  #
75
77
  # @param [String] username
76
78
  # @param [String] password
79
+ # @param [Boolean] save_auth
80
+ # Save this authentication to the connection object using Connection#add_auth. This
81
+ # will ensure that the authentication will be applied on database reconnect.
77
82
  #
78
83
  # @return [Boolean]
79
- def authenticate(username, password)
84
+ #
85
+ # @raise [AuthenticationError]
86
+ #
87
+ # @core authenticate authenticate-instance_method
88
+ def authenticate(username, password, save_auth=true)
80
89
  doc = command(:getnonce => 1)
81
90
  raise "error retrieving nonce: #{doc}" unless ok?(doc)
82
91
  nonce = doc['nonce']
@@ -86,18 +95,60 @@ module Mongo
86
95
  auth['user'] = username
87
96
  auth['nonce'] = nonce
88
97
  auth['key'] = Digest::MD5.hexdigest("#{nonce}#{username}#{hash_password(username, password)}")
89
- ok?(command(auth))
98
+ if ok?(command(auth))
99
+ if save_auth
100
+ @connection.add_auth(@name, username, password)
101
+ end
102
+ true
103
+ else
104
+ raise(Mongo::AuthenticationError, "Failed to authenticate user '#{username}' on db '#{self.name}'")
105
+ end
106
+ end
107
+
108
+ # Adds a user to this database for use with authentication. If the user already
109
+ # exists in the system, the password will be updated.
110
+ #
111
+ # @param [String] username
112
+ # @param [String] password
113
+ #
114
+ # @return [Hash] an object representing the user.
115
+ def add_user(username, password)
116
+ users = self[SYSTEM_USER_COLLECTION]
117
+ user = users.find_one({:user => username}) || {:user => username}
118
+ user['pwd'] = hash_password(username, password)
119
+ users.save(user)
120
+ return user
121
+ end
122
+
123
+ # Remove the given user from this database. Returns false if the user
124
+ # doesn't exist in the system.
125
+ #
126
+ # @param [String] username
127
+ #
128
+ # @return [Boolean]
129
+ def remove_user(username)
130
+ if self[SYSTEM_USER_COLLECTION].find_one({:user => username})
131
+ self[SYSTEM_USER_COLLECTION].remove({:user => username}, :safe => true)
132
+ else
133
+ return false
134
+ end
90
135
  end
91
136
 
92
- # Deauthorizes use for this database for this connection.
137
+ # Deauthorizes use for this database for this connection. Also removes
138
+ # any saved authorization in the connection class associated with this
139
+ # database.
93
140
  #
94
141
  # @raise [MongoDBError] if logging out fails.
95
142
  #
96
143
  # @return [Boolean]
97
144
  def logout
98
145
  doc = command(:logout => 1)
99
- return true if ok?(doc)
100
- raise MongoDBError, "error logging out: #{doc.inspect}"
146
+ if ok?(doc)
147
+ @connection.remove_auth(@name)
148
+ true
149
+ else
150
+ raise MongoDBError, "error logging out: #{doc.inspect}"
151
+ end
101
152
  end
102
153
 
103
154
  # Get an array of collection names in this database.
@@ -170,12 +221,6 @@ module Mongo
170
221
  raise MongoDBError, "Error creating collection: #{doc.inspect}"
171
222
  end
172
223
 
173
- # @deprecated all the admin methods are now included in the DB class.
174
- def admin
175
- warn "DB#admin has been DEPRECATED. All the admin functions are now available in the DB class itself."
176
- Admin.new(self)
177
- end
178
-
179
224
  # Get a collection by name.
180
225
  #
181
226
  # @param [String] name the collection name.
@@ -386,6 +431,8 @@ module Mongo
386
431
  # @param [Socket] sock a socket to use. This is mainly for internal use.
387
432
  #
388
433
  # @return [Hash]
434
+ #
435
+ # @core commands command_instance-method
389
436
  def command(selector, admin=false, check_response=false, sock=nil)
390
437
  raise MongoArgumentError, "command must be given a selector" unless selector.is_a?(Hash) && !selector.empty?
391
438
  if selector.class.eql?(Hash) && selector.keys.length > 1
@@ -402,12 +449,6 @@ module Mongo
402
449
  end
403
450
  end
404
451
 
405
- # @deprecated please use DB#command instead.
406
- def db_command(*args)
407
- warn "DB#db_command has been DEPRECATED. Please use DB#command instead."
408
- command(args[0], args[1])
409
- end
410
-
411
452
  # A shortcut returning db plus dot plus collection name.
412
453
  #
413
454
  # @param [String] collection_name
@@ -439,6 +480,8 @@ module Mongo
439
480
  # get the results using DB#profiling_info.
440
481
  #
441
482
  # @return [Symbol] :off, :slow_only, or :all
483
+ #
484
+ # @core profiling profiling_level-instance_method
442
485
  def profiling_level
443
486
  oh = OrderedHash.new
444
487
  oh[:profile] = -1