mongo 0.18.3 → 0.19

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