mongo-lyon 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. data/LICENSE.txt +190 -0
  2. data/README.md +344 -0
  3. data/Rakefile +202 -0
  4. data/bin/mongo_console +34 -0
  5. data/docs/1.0_UPGRADE.md +21 -0
  6. data/docs/CREDITS.md +123 -0
  7. data/docs/FAQ.md +116 -0
  8. data/docs/GridFS.md +158 -0
  9. data/docs/HISTORY.md +225 -0
  10. data/docs/REPLICA_SETS.md +72 -0
  11. data/docs/TUTORIAL.md +247 -0
  12. data/docs/WRITE_CONCERN.md +28 -0
  13. data/lib/mongo.rb +77 -0
  14. data/lib/mongo/collection.rb +872 -0
  15. data/lib/mongo/connection.rb +875 -0
  16. data/lib/mongo/cursor.rb +449 -0
  17. data/lib/mongo/db.rb +607 -0
  18. data/lib/mongo/exceptions.rb +68 -0
  19. data/lib/mongo/gridfs/grid.rb +106 -0
  20. data/lib/mongo/gridfs/grid_ext.rb +57 -0
  21. data/lib/mongo/gridfs/grid_file_system.rb +145 -0
  22. data/lib/mongo/gridfs/grid_io.rb +394 -0
  23. data/lib/mongo/gridfs/grid_io_fix.rb +38 -0
  24. data/lib/mongo/repl_set_connection.rb +342 -0
  25. data/lib/mongo/util/conversions.rb +89 -0
  26. data/lib/mongo/util/core_ext.rb +60 -0
  27. data/lib/mongo/util/pool.rb +185 -0
  28. data/lib/mongo/util/server_version.rb +71 -0
  29. data/lib/mongo/util/support.rb +82 -0
  30. data/lib/mongo/util/uri_parser.rb +181 -0
  31. data/lib/mongo/version.rb +3 -0
  32. data/mongo.gemspec +34 -0
  33. data/test/auxillary/1.4_features.rb +166 -0
  34. data/test/auxillary/authentication_test.rb +68 -0
  35. data/test/auxillary/autoreconnect_test.rb +41 -0
  36. data/test/auxillary/repl_set_auth_test.rb +58 -0
  37. data/test/auxillary/slave_connection_test.rb +36 -0
  38. data/test/auxillary/threaded_authentication_test.rb +101 -0
  39. data/test/bson/binary_test.rb +15 -0
  40. data/test/bson/bson_test.rb +614 -0
  41. data/test/bson/byte_buffer_test.rb +190 -0
  42. data/test/bson/hash_with_indifferent_access_test.rb +38 -0
  43. data/test/bson/json_test.rb +17 -0
  44. data/test/bson/object_id_test.rb +154 -0
  45. data/test/bson/ordered_hash_test.rb +197 -0
  46. data/test/collection_test.rb +893 -0
  47. data/test/connection_test.rb +303 -0
  48. data/test/conversions_test.rb +120 -0
  49. data/test/cursor_fail_test.rb +75 -0
  50. data/test/cursor_message_test.rb +43 -0
  51. data/test/cursor_test.rb +457 -0
  52. data/test/db_api_test.rb +715 -0
  53. data/test/db_connection_test.rb +15 -0
  54. data/test/db_test.rb +287 -0
  55. data/test/grid_file_system_test.rb +244 -0
  56. data/test/grid_io_test.rb +120 -0
  57. data/test/grid_test.rb +200 -0
  58. data/test/load/thin/load.rb +24 -0
  59. data/test/load/unicorn/load.rb +23 -0
  60. data/test/replica_sets/connect_test.rb +86 -0
  61. data/test/replica_sets/connection_string_test.rb +32 -0
  62. data/test/replica_sets/count_test.rb +35 -0
  63. data/test/replica_sets/insert_test.rb +53 -0
  64. data/test/replica_sets/pooled_insert_test.rb +55 -0
  65. data/test/replica_sets/query_secondaries.rb +96 -0
  66. data/test/replica_sets/query_test.rb +51 -0
  67. data/test/replica_sets/replication_ack_test.rb +66 -0
  68. data/test/replica_sets/rs_test_helper.rb +27 -0
  69. data/test/safe_test.rb +68 -0
  70. data/test/support/hash_with_indifferent_access.rb +199 -0
  71. data/test/support/keys.rb +45 -0
  72. data/test/support_test.rb +19 -0
  73. data/test/test_helper.rb +83 -0
  74. data/test/threading/threading_with_large_pool_test.rb +90 -0
  75. data/test/threading_test.rb +87 -0
  76. data/test/tools/auth_repl_set_manager.rb +14 -0
  77. data/test/tools/repl_set_manager.rb +266 -0
  78. data/test/unit/collection_test.rb +130 -0
  79. data/test/unit/connection_test.rb +98 -0
  80. data/test/unit/cursor_test.rb +99 -0
  81. data/test/unit/db_test.rb +96 -0
  82. data/test/unit/grid_test.rb +49 -0
  83. data/test/unit/pool_test.rb +9 -0
  84. data/test/unit/repl_set_connection_test.rb +72 -0
  85. data/test/unit/safe_test.rb +125 -0
  86. data/test/uri_test.rb +91 -0
  87. metadata +202 -0
@@ -0,0 +1,449 @@
1
+ # encoding: UTF-8
2
+
3
+ # Copyright (C) 2008-2011 10gen Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Mongo
18
+
19
+ # A cursor over query results. Returned objects are hashes.
20
+ class Cursor
21
+ include Mongo::Conversions
22
+ include Enumerable
23
+
24
+ attr_reader :collection, :selector, :fields,
25
+ :order, :hint, :snapshot, :timeout,
26
+ :full_collection_name
27
+
28
+ # Create a new cursor.
29
+ #
30
+ # Note: cursors are created when executing queries using [Collection#find] and other
31
+ # similar methods. Application developers shouldn't have to create cursors manually.
32
+ #
33
+ # @return [Cursor]
34
+ #
35
+ # @core cursors constructor_details
36
+ def initialize(collection, opts={})
37
+ @db = collection.db
38
+ @collection = collection
39
+ @connection = @db.connection
40
+ @logger = @connection.logger
41
+
42
+ @selector = opts[:selector] || {}
43
+ @fields = convert_fields_for_query(opts[:fields])
44
+ @skip = opts[:skip] || 0
45
+ @limit = opts[:limit] || 0
46
+ @order = opts[:order]
47
+ @hint = opts[:hint]
48
+ @snapshot = opts[:snapshot]
49
+ @timeout = opts.fetch(:timeout, true)
50
+ @explain = opts[:explain]
51
+ @socket = opts[:socket]
52
+ @tailable = opts[:tailable] || false
53
+ @closed = false
54
+ @query_run = false
55
+ batch_size(opts[:batch_size] || 0)
56
+
57
+ @full_collection_name = "#{@collection.db.name}.#{@collection.name}"
58
+ @cache = []
59
+ @returned = 0
60
+
61
+ if @collection.name =~ /^\$cmd/ || @collection.name =~ /^system/
62
+ @command = true
63
+ else
64
+ @command = false
65
+ end
66
+ end
67
+
68
+ # Get the next document specified the cursor options.
69
+ #
70
+ # @return [Hash, Nil] the next document or Nil if no documents remain.
71
+ def next_document
72
+ refresh if @cache.length == 0
73
+ doc = @cache.shift
74
+
75
+ if doc && doc['$err']
76
+ err = doc['$err']
77
+
78
+ # If the server has stopped being the master (e.g., it's one of a
79
+ # pair but it has died or something like that) then we close that
80
+ # connection. The next request will re-open on master server.
81
+ if err == "not master"
82
+ @connection.close
83
+ raise ConnectionFailure, err
84
+ end
85
+
86
+ raise OperationFailure, err
87
+ end
88
+
89
+ doc
90
+ end
91
+ alias :next :next_document
92
+
93
+ # Reset this cursor on the server. Cursor options, such as the
94
+ # query string and the values for skip and limit, are preserved.
95
+ def rewind!
96
+ close
97
+ @cache.clear
98
+ @cursor_id = nil
99
+ @closed = false
100
+ @query_run = false
101
+ @n_received = nil
102
+ true
103
+ end
104
+
105
+ # Determine whether this cursor has any remaining results.
106
+ #
107
+ # @return [Boolean]
108
+ def has_next?
109
+ num_remaining > 0
110
+ end
111
+
112
+ # Get the size of the result set for this query.
113
+ #
114
+ # @param [Boolean] whether of not to take notice of skip and limit
115
+ #
116
+ # @return [Integer] the number of objects in the result set for this query.
117
+ #
118
+ # @raise [OperationFailure] on a database error.
119
+ def count(skip_and_limit = false)
120
+ command = BSON::OrderedHash["count", @collection.name, "query", @selector]
121
+
122
+ if skip_and_limit
123
+ command.merge!(BSON::OrderedHash["limit", @limit]) if @limit != 0
124
+ command.merge!(BSON::OrderedHash["skip", @skip]) if @skip != 0
125
+ end
126
+
127
+ command.merge!(BSON::OrderedHash["fields", @fields])
128
+
129
+ response = @db.command(command)
130
+ return response['n'].to_i if Mongo::Support.ok?(response)
131
+ return 0 if response['errmsg'] == "ns missing"
132
+ raise OperationFailure, "Count failed: #{response['errmsg']}"
133
+ end
134
+
135
+ # Sort this cursor's results.
136
+ #
137
+ # This method overrides any sort order specified in the Collection#find
138
+ # method, and only the last sort applied has an effect.
139
+ #
140
+ # @param [Symbol, Array] key_or_list either 1) a key to sort by or 2)
141
+ # an array of [key, direction] pairs to sort by. Direction should
142
+ # be specified as Mongo::ASCENDING (or :ascending / :asc) or Mongo::DESCENDING (or :descending / :desc)
143
+ #
144
+ # @raise [InvalidOperation] if this cursor has already been used.
145
+ #
146
+ # @raise [InvalidSortValueError] if the specified order is invalid.
147
+ def sort(key_or_list, direction=nil)
148
+ check_modifiable
149
+
150
+ if !direction.nil?
151
+ order = [[key_or_list, direction]]
152
+ else
153
+ order = key_or_list
154
+ end
155
+
156
+ @order = order
157
+ self
158
+ end
159
+
160
+ # Limit the number of results to be returned by this cursor.
161
+ #
162
+ # This method overrides any limit specified in the Collection#find method,
163
+ # and only the last limit applied has an effect.
164
+ #
165
+ # @return [Integer] the current number_to_return if no parameter is given.
166
+ #
167
+ # @raise [InvalidOperation] if this cursor has already been used.
168
+ #
169
+ # @core limit limit-instance_method
170
+ def limit(number_to_return=nil)
171
+ return @limit unless number_to_return
172
+ check_modifiable
173
+
174
+ @limit = number_to_return
175
+ self
176
+ end
177
+
178
+ # Skips the first +number_to_skip+ results of this cursor.
179
+ # Returns the current number_to_skip if no parameter is given.
180
+ #
181
+ # This method overrides any skip specified in the Collection#find method,
182
+ # and only the last skip applied has an effect.
183
+ #
184
+ # @return [Integer]
185
+ #
186
+ # @raise [InvalidOperation] if this cursor has already been used.
187
+ def skip(number_to_skip=nil)
188
+ return @skip unless number_to_skip
189
+ check_modifiable
190
+
191
+ @skip = number_to_skip
192
+ self
193
+ end
194
+
195
+ # Set the batch size for server responses.
196
+ #
197
+ # Note that the batch size will take effect only on queries
198
+ # where the number to be returned is greater than 100.
199
+ #
200
+ # @param [Integer] size either 0 or some integer greater than 1. If 0,
201
+ # the server will determine the batch size.
202
+ #
203
+ # @return [Cursor]
204
+ def batch_size(size=0)
205
+ check_modifiable
206
+ if size < 0 || size == 1
207
+ raise ArgumentError, "Invalid value for batch_size #{size}; must be 0 or > 1."
208
+ else
209
+ @batch_size = size > @limit ? @limit : size
210
+ end
211
+
212
+ self
213
+ end
214
+
215
+ # Iterate over each document in this cursor, yielding it to the given
216
+ # block.
217
+ #
218
+ # Iterating over an entire cursor will close it.
219
+ #
220
+ # @yield passes each document to a block for processing.
221
+ #
222
+ # @example if 'comments' represents a collection of comments:
223
+ # comments.find.each do |doc|
224
+ # puts doc['user']
225
+ # end
226
+ def each
227
+ #num_returned = 0
228
+ #while has_next? && (@limit <= 0 || num_returned < @limit)
229
+ while doc = next_document
230
+ yield doc #next_document
231
+ #num_returned += 1
232
+ end
233
+ end
234
+
235
+ # Receive all the documents from this cursor as an array of hashes.
236
+ #
237
+ # Notes:
238
+ #
239
+ # If you've already started iterating over the cursor, the array returned
240
+ # by this method contains only the remaining documents. See Cursor#rewind! if you
241
+ # need to reset the cursor.
242
+ #
243
+ # Use of this method is discouraged - in most cases, it's much more
244
+ # efficient to retrieve documents as you need them by iterating over the cursor.
245
+ #
246
+ # @return [Array] an array of documents.
247
+ def to_a
248
+ super
249
+ end
250
+
251
+ # Get the explain plan for this cursor.
252
+ #
253
+ # @return [Hash] a document containing the explain plan for this cursor.
254
+ #
255
+ # @core explain explain-instance_method
256
+ def explain
257
+ c = Cursor.new(@collection, query_options_hash.merge(:limit => -@limit.abs, :explain => true))
258
+ explanation = c.next_document
259
+ c.close
260
+
261
+ explanation
262
+ end
263
+
264
+ # Close the cursor.
265
+ #
266
+ # Note: if a cursor is read until exhausted (read until Mongo::Constants::OP_QUERY or
267
+ # Mongo::Constants::OP_GETMORE returns zero for the cursor id), there is no need to
268
+ # close it manually.
269
+ #
270
+ # Note also: Collection#find takes an optional block argument which can be used to
271
+ # ensure that your cursors get closed.
272
+ #
273
+ # @return [True]
274
+ def close
275
+ if @cursor_id && @cursor_id != 0
276
+ message = BSON::ByteBuffer.new([0, 0, 0, 0])
277
+ message.put_int(1)
278
+ message.put_long(@cursor_id)
279
+ @logger.debug("MONGODB cursor.close #{@cursor_id}") if @logger
280
+ @connection.send_message(Mongo::Constants::OP_KILL_CURSORS, message, nil)
281
+ end
282
+ @cursor_id = 0
283
+ @closed = true
284
+ end
285
+
286
+ # Is this cursor closed?
287
+ #
288
+ # @return [Boolean]
289
+ def closed?; @closed; end
290
+
291
+ # Returns an integer indicating which query options have been selected.
292
+ #
293
+ # @return [Integer]
294
+ #
295
+ # @see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
296
+ # The MongoDB wire protocol.
297
+ def query_opts
298
+ opts = 0
299
+ opts |= Mongo::Constants::OP_QUERY_NO_CURSOR_TIMEOUT unless @timeout
300
+ opts |= Mongo::Constants::OP_QUERY_SLAVE_OK if @connection.slave_ok?
301
+ opts |= Mongo::Constants::OP_QUERY_TAILABLE if @tailable
302
+ opts
303
+ end
304
+
305
+ # Get the query options for this Cursor.
306
+ #
307
+ # @return [Hash]
308
+ def query_options_hash
309
+ { :selector => @selector,
310
+ :fields => @fields,
311
+ :skip => @skip_num,
312
+ :limit => @limit_num,
313
+ :order => @order,
314
+ :hint => @hint,
315
+ :snapshot => @snapshot,
316
+ :timeout => @timeout }
317
+ end
318
+
319
+ # Clean output for inspect.
320
+ def inspect
321
+ "<Mongo::Cursor:0x#{object_id.to_s(16)} namespace='#{@db.name}.#{@collection.name}' " +
322
+ "@selector=#{@selector.inspect}>"
323
+ end
324
+
325
+ private
326
+
327
+ # Convert the +:fields+ parameter from a single field name or an array
328
+ # of fields names to a hash, with the field names for keys and '1' for each
329
+ # value.
330
+ def convert_fields_for_query(fields)
331
+ case fields
332
+ when String, Symbol
333
+ {fields => 1}
334
+ when Array
335
+ return nil if fields.length.zero?
336
+ fields.each_with_object({}) { |field, hash| hash[field] = 1 }
337
+ when Hash
338
+ return fields
339
+ end
340
+ end
341
+
342
+ # Return the number of documents remaining for this cursor.
343
+ def num_remaining
344
+ refresh if @cache.length == 0
345
+ @cache.length
346
+ end
347
+
348
+ def refresh
349
+ return if send_initial_query || @cursor_id.zero?
350
+ message = BSON::ByteBuffer.new([0, 0, 0, 0])
351
+
352
+ # DB name.
353
+ BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@collection.name}")
354
+
355
+ # Number of results to return.
356
+ if @limit > 0
357
+ limit = @limit - @returned
358
+ if @batch_size > 0
359
+ limit = limit < @batch_size ? limit : @batch_size
360
+ end
361
+ message.put_int(limit)
362
+ else
363
+ message.put_int(@batch_size)
364
+ end
365
+
366
+ # Cursor id.
367
+ message.put_long(@cursor_id)
368
+ @logger.debug("MONGODB cursor.refresh() for cursor #{@cursor_id}") if @logger
369
+ results, @n_received, @cursor_id = @connection.receive_message(
370
+ Mongo::Constants::OP_GET_MORE, message, nil, @socket, @command)
371
+ @returned += @n_received
372
+ @cache += results
373
+ close_cursor_if_query_complete
374
+ end
375
+
376
+ # Run query the first time we request an object from the wire
377
+ # TODO: should we be calling instrument_payload even if logging
378
+ # is disabled?
379
+ def send_initial_query
380
+ if @query_run
381
+ false
382
+ else
383
+ message = construct_query_message
384
+ @connection.instrument(:find, instrument_payload) do
385
+ results, @n_received, @cursor_id = @connection.receive_message(
386
+ Mongo::Constants::OP_QUERY, message, nil, @socket, @command)
387
+ @returned += @n_received
388
+ @cache += results
389
+ @query_run = true
390
+ close_cursor_if_query_complete
391
+ end
392
+ true
393
+ end
394
+ end
395
+
396
+ def construct_query_message
397
+ message = BSON::ByteBuffer.new
398
+ message.put_int(query_opts)
399
+ BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@collection.name}")
400
+ message.put_int(@skip)
401
+ message.put_int(@limit)
402
+ spec = query_contains_special_fields? ? construct_query_spec : @selector
403
+ message.put_binary(BSON::BSON_CODER.serialize(spec, false).to_s)
404
+ message.put_binary(BSON::BSON_CODER.serialize(@fields, false).to_s) if @fields
405
+ message
406
+ end
407
+
408
+ def instrument_payload
409
+ log = { :database => @db.name, :collection => @collection.name, :selector => selector }
410
+ log[:fields] = @fields if @fields
411
+ log[:skip] = @skip if @skip && (@skip > 0)
412
+ log[:limit] = @limit if @limit && (@limit > 0)
413
+ log[:order] = @order if @order
414
+ log
415
+ end
416
+
417
+ def construct_query_spec
418
+ return @selector if @selector.has_key?('$query')
419
+ spec = BSON::OrderedHash.new
420
+ spec['$query'] = @selector
421
+ spec['$orderby'] = Mongo::Support.format_order_clause(@order) if @order
422
+ spec['$hint'] = @hint if @hint && @hint.length > 0
423
+ spec['$explain'] = true if @explain
424
+ spec['$snapshot'] = true if @snapshot
425
+ spec
426
+ end
427
+
428
+ # Returns true if the query contains order, explain, hint, or snapshot.
429
+ def query_contains_special_fields?
430
+ @order || @explain || @hint || @snapshot
431
+ end
432
+
433
+ def to_s
434
+ "DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from)"
435
+ end
436
+
437
+ def close_cursor_if_query_complete
438
+ if @limit > 0 && @returned >= @limit
439
+ close
440
+ end
441
+ end
442
+
443
+ def check_modifiable
444
+ if @query_run || @closed
445
+ raise InvalidOperation, "Cannot modify the query once it has been run or closed."
446
+ end
447
+ end
448
+ end
449
+ end
data/lib/mongo/db.rb ADDED
@@ -0,0 +1,607 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+
19
+ require 'socket'
20
+ require 'timeout'
21
+ require 'thread'
22
+
23
+ module Mongo
24
+
25
+ # A MongoDB database.
26
+ class DB
27
+
28
+ SYSTEM_NAMESPACE_COLLECTION = "system.namespaces"
29
+ SYSTEM_INDEX_COLLECTION = "system.indexes"
30
+ SYSTEM_PROFILE_COLLECTION = "system.profile"
31
+ SYSTEM_USER_COLLECTION = "system.users"
32
+ SYSTEM_JS_COLLECTION = "system.js"
33
+ SYSTEM_COMMAND_COLLECTION = "$cmd"
34
+
35
+ # Counter for generating unique request ids.
36
+ @@current_request_id = 0
37
+
38
+ # Strict mode enforces collection existence checks. When +true+,
39
+ # asking for a collection that does not exist, or trying to create a
40
+ # collection that already exists, raises an error.
41
+ #
42
+ # Strict mode is disabled by default, but enabled (+true+) at any time.
43
+ attr_writer :strict
44
+
45
+ # Returns the value of the +strict+ flag.
46
+ def strict?; @strict; end
47
+
48
+ # The name of the database and the local safe option.
49
+ attr_reader :name, :safe
50
+
51
+ # The Mongo::Connection instance connecting to the MongoDB server.
52
+ attr_reader :connection
53
+
54
+ # The length of time that Collection.ensure_index should cache index calls
55
+ attr_accessor :cache_time
56
+
57
+ # Instances of DB are normally obtained by calling Mongo#db.
58
+ #
59
+ # @param [String] name the database name.
60
+ # @param [Mongo::Connection] connection a connection object pointing to MongoDB. Note
61
+ # that databases are usually instantiated via the Connection class. See the examples below.
62
+ #
63
+ # @option opts [Boolean] :strict (False) If true, collections must exist to be accessed and must
64
+ # not exist to be created. See DB#collection and DB#create_collection.
65
+ #
66
+ # @option opts [Object, #create_pk(doc)] :pk (Mongo::ObjectId) A primary key factory object,
67
+ # which should take a hash and return a hash which merges the original hash with any primary key
68
+ # fields the factory wishes to inject. (NOTE: if the object already has a primary key,
69
+ # the factory should not inject a new key).
70
+ #
71
+ # @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options
72
+ # propogated to Collection objects instantiated off of this DB. If no
73
+ # value is provided, the default value set on this instance's Connection object will be used. This
74
+ # default can be overridden upon instantiation of any collection by explicity setting a :safe value
75
+ # on initialization
76
+ # @option opts [Integer] :cache_time (300) Set the time that all ensure_index calls should cache the command.
77
+ #
78
+ # @core databases constructor_details
79
+ def initialize(name, connection, opts={})
80
+ @name = Mongo::Support.validate_db_name(name)
81
+ @connection = connection
82
+ @strict = opts[:strict]
83
+ @pk_factory = opts[:pk]
84
+ @safe = opts.fetch(:safe, @connection.safe)
85
+ @cache_time = opts[:cache_time] || 300 #5 minutes.
86
+ end
87
+
88
+ # Authenticate with the given username and password. Note that mongod
89
+ # must be started with the --auth option for authentication to be enabled.
90
+ #
91
+ # @param [String] username
92
+ # @param [String] password
93
+ # @param [Boolean] save_auth
94
+ # Save this authentication to the connection object using Connection#add_auth. This
95
+ # will ensure that the authentication will be applied on database reconnect. Note
96
+ # that this value must be true when using connection pooling.
97
+ #
98
+ # @return [Boolean]
99
+ #
100
+ # @raise [AuthenticationError]
101
+ #
102
+ # @core authenticate authenticate-instance_method
103
+ def authenticate(username, password, save_auth=true)
104
+ if @connection.pool_size > 1
105
+ if !save_auth
106
+ raise MongoArgumentError, "If using connection pooling, :save_auth must be set to true."
107
+ end
108
+ @connection.authenticate_pools
109
+ end
110
+
111
+ issue_authentication(username, password, save_auth)
112
+ end
113
+
114
+ def issue_authentication(username, password, save_auth=true, opts={})
115
+ doc = command({:getnonce => 1}, :check_response => false, :socket => opts[:socket])
116
+ raise MongoDBError, "Error retrieving nonce: #{doc}" unless ok?(doc)
117
+ nonce = doc['nonce']
118
+
119
+ auth = BSON::OrderedHash.new
120
+ auth['authenticate'] = 1
121
+ auth['user'] = username
122
+ auth['nonce'] = nonce
123
+ auth['key'] = Mongo::Support.auth_key(username, password, nonce)
124
+ if ok?(self.command(auth, :check_response => false, :socket => opts[:socket]))
125
+ if save_auth
126
+ @connection.add_auth(@name, username, password)
127
+ end
128
+ true
129
+ else
130
+ raise(Mongo::AuthenticationError, "Failed to authenticate user '#{username}' on db '#{self.name}'")
131
+ end
132
+ end
133
+
134
+ # Adds a stored Javascript function to the database which can executed
135
+ # server-side in map_reduce, db.eval and $where clauses.
136
+ #
137
+ # @param [String] function_name
138
+ # @param [String] code
139
+ #
140
+ # @return [String] the function name saved to the database
141
+ def add_stored_function(function_name, code)
142
+ self[SYSTEM_JS_COLLECTION].save(
143
+ {
144
+ "_id" => function_name,
145
+ :value => BSON::Code.new(code)
146
+ }
147
+ )
148
+ end
149
+
150
+ # Removes stored Javascript function from the database. Returns
151
+ # false if the function does not exist
152
+ #
153
+ # @param [String] function_name
154
+ #
155
+ # @return [Boolean]
156
+ def remove_stored_function(function_name)
157
+ if self[SYSTEM_JS_COLLECTION].find_one({"_id" => function_name})
158
+ self[SYSTEM_JS_COLLECTION].remove({"_id" => function_name}, :safe => true)
159
+ else
160
+ return false
161
+ end
162
+ end
163
+
164
+ # Adds a user to this database for use with authentication. If the user already
165
+ # exists in the system, the password will be updated.
166
+ #
167
+ # @param [String] username
168
+ # @param [String] password
169
+ #
170
+ # @return [Hash] an object representing the user.
171
+ def add_user(username, password)
172
+ users = self[SYSTEM_USER_COLLECTION]
173
+ user = users.find_one({:user => username}) || {:user => username}
174
+ user['pwd'] = Mongo::Support.hash_password(username, password)
175
+ users.save(user)
176
+ return user
177
+ end
178
+
179
+ # Remove the given user from this database. Returns false if the user
180
+ # doesn't exist in the system.
181
+ #
182
+ # @param [String] username
183
+ #
184
+ # @return [Boolean]
185
+ def remove_user(username)
186
+ if self[SYSTEM_USER_COLLECTION].find_one({:user => username})
187
+ self[SYSTEM_USER_COLLECTION].remove({:user => username}, :safe => true)
188
+ else
189
+ return false
190
+ end
191
+ end
192
+
193
+ # Deauthorizes use for this database for this connection. Also removes
194
+ # any saved authentication in the connection class associated with this
195
+ # database.
196
+ #
197
+ # @raise [MongoDBError] if logging out fails.
198
+ #
199
+ # @return [Boolean]
200
+ def logout(opts={})
201
+ if @connection.pool_size > 1
202
+ @connection.logout_pools(@name)
203
+ end
204
+
205
+ issue_logout(opts)
206
+ end
207
+
208
+ def issue_logout(opts={})
209
+ doc = command({:logout => 1}, :socket => opts[:socket])
210
+ if ok?(doc)
211
+ @connection.remove_auth(@name)
212
+ true
213
+ else
214
+ raise MongoDBError, "error logging out: #{doc.inspect}"
215
+ end
216
+ end
217
+
218
+ # Get an array of collection names in this database.
219
+ #
220
+ # @return [Array]
221
+ def collection_names
222
+ names = collections_info.collect { |doc| doc['name'] || '' }
223
+ names = names.delete_if {|name| name.index(@name).nil? || name.index('$')}
224
+ names.map {|name| name.sub(@name + '.', '')}
225
+ end
226
+
227
+ # Get an array of Collection instances, one for each collection in this database.
228
+ #
229
+ # @return [Array<Mongo::Collection>]
230
+ def collections
231
+ collection_names.map do |name|
232
+ Collection.new(name, self)
233
+ end
234
+ end
235
+
236
+ # Get info on system namespaces (collections). This method returns
237
+ # a cursor which can be iterated over. For each collection, a hash
238
+ # will be yielded containing a 'name' string and, optionally, an 'options' hash.
239
+ #
240
+ # @param [String] coll_name return info for the specifed collection only.
241
+ #
242
+ # @return [Mongo::Cursor]
243
+ def collections_info(coll_name=nil)
244
+ selector = {}
245
+ selector[:name] = full_collection_name(coll_name) if coll_name
246
+ Cursor.new(Collection.new(SYSTEM_NAMESPACE_COLLECTION, self), :selector => selector)
247
+ end
248
+
249
+ # Create a collection.
250
+ #
251
+ # new collection. If +strict+ is true, will raise an error if
252
+ # collection +name+ already exists.
253
+ #
254
+ # @param [String] name the name of the new collection.
255
+ #
256
+ # @option opts [Boolean] :capped (False) created a capped collection.
257
+ #
258
+ # @option opts [Integer] :size (Nil) If +capped+ is +true+, specifies the maximum number of
259
+ # bytes for the capped collection. If +false+, specifies the number of bytes allocated
260
+ # for the initial extent of the collection.
261
+ #
262
+ # @option opts [Integer] :max (Nil) If +capped+ is +true+, indicates the maximum number of records
263
+ # in a capped collection.
264
+ #
265
+ # @raise [MongoDBError] raised under two conditions: either we're in +strict+ mode and the collection
266
+ # already exists or collection creation fails on the server.
267
+ #
268
+ # @return [Mongo::Collection]
269
+ def create_collection(name, opts={})
270
+ # Does the collection already exist?
271
+ if collection_names.include?(name)
272
+ if strict?
273
+ raise MongoDBError, "Collection #{name} already exists. Currently in strict mode."
274
+ else
275
+ return Collection.new(name, self)
276
+ end
277
+ end
278
+
279
+ # Create a new collection.
280
+ oh = BSON::OrderedHash.new
281
+ oh[:create] = name
282
+ doc = command(oh.merge(opts || {}))
283
+ return Collection.new(name, self, :pk => @pk_factory) if ok?(doc)
284
+ raise MongoDBError, "Error creating collection: #{doc.inspect}"
285
+ end
286
+
287
+ # Get a collection by name.
288
+ #
289
+ # @param [String] name the collection name.
290
+ # @param [Hash] opts any valid options that can me passed to Collection#new.
291
+ #
292
+ # @raise [MongoDBError] if collection does not already exist and we're in +strict+ mode.
293
+ #
294
+ # @return [Mongo::Collection]
295
+ def collection(name, opts={})
296
+ if strict? && !collection_names.include?(name)
297
+ raise Mongo::MongoDBError, "Collection #{name} doesn't exist. Currently in strict mode."
298
+ else
299
+ opts[:safe] = opts.fetch(:safe, @safe)
300
+ opts.merge!(:pk => @pk_factory) unless opts[:pk]
301
+ Collection.new(name, self, opts)
302
+ end
303
+ end
304
+ alias_method :[], :collection
305
+
306
+ # Drop a collection by +name+.
307
+ #
308
+ # @param [String] name
309
+ #
310
+ # @return [Boolean] +true+ on success or +false+ if the collection name doesn't exist.
311
+ def drop_collection(name)
312
+ return true unless collection_names.include?(name)
313
+
314
+ ok?(command(:drop => name))
315
+ end
316
+
317
+ # Run the getlasterror command with the specified replication options.
318
+ #
319
+ # @option opts [Boolean] :fsync (false)
320
+ # @option opts [Integer] :w (nil)
321
+ # @option opts [Integer] :wtimeout (nil)
322
+ #
323
+ # @return [Hash] the entire response to getlasterror.
324
+ #
325
+ # @raise [MongoDBError] if the operation fails.
326
+ def get_last_error(opts={})
327
+ cmd = BSON::OrderedHash.new
328
+ cmd[:getlasterror] = 1
329
+ cmd.merge!(opts)
330
+ doc = command(cmd, :check_response => false)
331
+ raise MongoDBError, "error retrieving last error: #{doc.inspect}" unless ok?(doc)
332
+ doc
333
+ end
334
+
335
+ # Return +true+ if an error was caused by the most recently executed
336
+ # database operation.
337
+ #
338
+ # @return [Boolean]
339
+ def error?
340
+ get_last_error['err'] != nil
341
+ end
342
+
343
+ # Get the most recent error to have occured on this database.
344
+ #
345
+ # This command only returns errors that have occured since the last call to
346
+ # DB#reset_error_history - returns +nil+ if there is no such error.
347
+ #
348
+ # @return [String, Nil] the text of the error or +nil+ if no error has occurred.
349
+ def previous_error
350
+ error = command(:getpreverror => 1)
351
+ if error["err"]
352
+ error
353
+ else
354
+ nil
355
+ end
356
+ end
357
+
358
+ # Reset the error history of this database
359
+ #
360
+ # Calls to DB#previous_error will only return errors that have occurred
361
+ # since the most recent call to this method.
362
+ #
363
+ # @return [Hash]
364
+ def reset_error_history
365
+ command(:reseterror => 1)
366
+ end
367
+
368
+ # Dereference a DBRef, returning the document it points to.
369
+ #
370
+ # @param [Mongo::DBRef] dbref
371
+ #
372
+ # @return [Hash] the document indicated by the db reference.
373
+ #
374
+ # @see http://www.mongodb.org/display/DOCS/DB+Ref MongoDB DBRef spec.
375
+ def dereference(dbref)
376
+ collection(dbref.namespace).find_one("_id" => dbref.object_id)
377
+ end
378
+
379
+ # Evaluate a JavaScript expression in MongoDB.
380
+ #
381
+ # @param [String, Code] code a JavaScript expression to evaluate server-side.
382
+ # @param [Integer, Hash] args any additional argument to be passed to the +code+ expression when
383
+ # it's run on the server.
384
+ #
385
+ # @return [String] the return value of the function.
386
+ def eval(code, *args)
387
+ if not code.is_a? BSON::Code
388
+ code = BSON::Code.new(code)
389
+ end
390
+
391
+ oh = BSON::OrderedHash.new
392
+ oh[:$eval] = code
393
+ oh[:args] = args
394
+ doc = command(oh)
395
+ doc['retval']
396
+ end
397
+
398
+ # Rename a collection.
399
+ #
400
+ # @param [String] from original collection name.
401
+ # @param [String] to new collection name.
402
+ #
403
+ # @return [True] returns +true+ on success.
404
+ #
405
+ # @raise MongoDBError if there's an error renaming the collection.
406
+ def rename_collection(from, to)
407
+ oh = BSON::OrderedHash.new
408
+ oh[:renameCollection] = "#{@name}.#{from}"
409
+ oh[:to] = "#{@name}.#{to}"
410
+ doc = DB.new('admin', @connection).command(oh, :check_response => false)
411
+ ok?(doc) || raise(MongoDBError, "Error renaming collection: #{doc.inspect}")
412
+ end
413
+
414
+ # Drop an index from a given collection. Normally called from
415
+ # Collection#drop_index or Collection#drop_indexes.
416
+ #
417
+ # @param [String] collection_name
418
+ # @param [String] index_name
419
+ #
420
+ # @return [True] returns +true+ on success.
421
+ #
422
+ # @raise MongoDBError if there's an error renaming the collection.
423
+ def drop_index(collection_name, index_name)
424
+ oh = BSON::OrderedHash.new
425
+ oh[:deleteIndexes] = collection_name
426
+ oh[:index] = index_name.to_s
427
+ doc = command(oh, :check_response => false)
428
+ ok?(doc) || raise(MongoDBError, "Error with drop_index command: #{doc.inspect}")
429
+ end
430
+
431
+ # Get information on the indexes for the given collection.
432
+ # Normally called by Collection#index_information.
433
+ #
434
+ # @param [String] collection_name
435
+ #
436
+ # @return [Hash] keys are index names and the values are lists of [key, direction] pairs
437
+ # defining the index.
438
+ def index_information(collection_name)
439
+ sel = {:ns => full_collection_name(collection_name)}
440
+ info = {}
441
+ Cursor.new(Collection.new(SYSTEM_INDEX_COLLECTION, self), :selector => sel).each do |index|
442
+ info[index['name']] = index
443
+ end
444
+ info
445
+ end
446
+
447
+ # Return stats on this database. Uses MongoDB's dbstats command.
448
+ #
449
+ # @return [Hash]
450
+ def stats
451
+ self.command({:dbstats => 1})
452
+ end
453
+
454
+ # Return +true+ if the supplied +doc+ contains an 'ok' field with the value 1.
455
+ #
456
+ # @param [Hash] doc
457
+ #
458
+ # @return [Boolean]
459
+ def ok?(doc)
460
+ Mongo::Support.ok?(doc)
461
+ end
462
+
463
+ # Send a command to the database.
464
+ #
465
+ # Note: DB commands must start with the "command" key. For this reason,
466
+ # any selector containing more than one key must be an OrderedHash.
467
+ #
468
+ # Note also that a command in MongoDB is just a kind of query
469
+ # that occurs on the system command collection ($cmd). Examine this method's implementation
470
+ # to see how it works.
471
+ #
472
+ # @param [OrderedHash, Hash] selector an OrderedHash, or a standard Hash with just one
473
+ # key, specifying the command to be performed. In Ruby 1.9, OrderedHash isn't necessary since
474
+ # hashes are ordered by default.
475
+ #
476
+ # @option opts [Boolean] :check_response (true) If +true+, raises an exception if the
477
+ # command fails.
478
+ # @option opts [Socket] :socket a socket to use for sending the command. This is mainly for internal use.
479
+ #
480
+ # @return [Hash]
481
+ #
482
+ # @core commands command_instance-method
483
+ def command(selector, opts={})
484
+ check_response = opts.fetch(:check_response, true)
485
+ socket = opts[:socket]
486
+ raise MongoArgumentError, "command must be given a selector" unless selector.is_a?(Hash) && !selector.empty?
487
+ if selector.keys.length > 1 && RUBY_VERSION < '1.9' && selector.class != BSON::OrderedHash
488
+ raise MongoArgumentError, "DB#command requires an OrderedHash when hash contains multiple keys"
489
+ end
490
+
491
+ begin
492
+ result = Cursor.new(system_command_collection,
493
+ :limit => -1, :selector => selector, :socket => socket).next_document
494
+ rescue OperationFailure => ex
495
+ raise OperationFailure, "Database command '#{selector.keys.first}' failed: #{ex.message}"
496
+ end
497
+
498
+ if result.nil?
499
+ raise OperationFailure, "Database command '#{selector.keys.first}' failed: returned null."
500
+ elsif (check_response && !ok?(result))
501
+ raise OperationFailure, "Database command '#{selector.keys.first}' failed: #{result.inspect}"
502
+ else
503
+ result
504
+ end
505
+ end
506
+
507
+ # A shortcut returning db plus dot plus collection name.
508
+ #
509
+ # @param [String] collection_name
510
+ #
511
+ # @return [String]
512
+ def full_collection_name(collection_name)
513
+ "#{@name}.#{collection_name}"
514
+ end
515
+
516
+ # The primary key factory object (or +nil+).
517
+ #
518
+ # @return [Object, Nil]
519
+ def pk_factory
520
+ @pk_factory
521
+ end
522
+
523
+ # Specify a primary key factory if not already set.
524
+ #
525
+ # @raise [MongoArgumentError] if the primary key factory has already been set.
526
+ def pk_factory=(pk_factory)
527
+ if @pk_factory
528
+ raise MongoArgumentError, "Cannot change primary key factory once it's been set"
529
+ end
530
+
531
+ @pk_factory = pk_factory
532
+ end
533
+
534
+ # Return the current database profiling level. If profiling is enabled, you can
535
+ # get the results using DB#profiling_info.
536
+ #
537
+ # @return [Symbol] :off, :slow_only, or :all
538
+ #
539
+ # @core profiling profiling_level-instance_method
540
+ def profiling_level
541
+ oh = BSON::OrderedHash.new
542
+ oh[:profile] = -1
543
+ doc = command(oh, :check_response => false)
544
+ raise "Error with profile command: #{doc.inspect}" unless ok?(doc) && doc['was'].kind_of?(Numeric)
545
+ case doc['was'].to_i
546
+ when 0
547
+ :off
548
+ when 1
549
+ :slow_only
550
+ when 2
551
+ :all
552
+ else
553
+ raise "Error: illegal profiling level value #{doc['was']}"
554
+ end
555
+ end
556
+
557
+ # Set this database's profiling level. If profiling is enabled, you can
558
+ # get the results using DB#profiling_info.
559
+ #
560
+ # @param [Symbol] level acceptable options are +:off+, +:slow_only+, or +:all+.
561
+ def profiling_level=(level)
562
+ oh = BSON::OrderedHash.new
563
+ oh[:profile] = case level
564
+ when :off
565
+ 0
566
+ when :slow_only
567
+ 1
568
+ when :all
569
+ 2
570
+ else
571
+ raise "Error: illegal profiling level value #{level}"
572
+ end
573
+ doc = command(oh, :check_response => false)
574
+ ok?(doc) || raise(MongoDBError, "Error with profile command: #{doc.inspect}")
575
+ end
576
+
577
+ # Get the current profiling information.
578
+ #
579
+ # @return [Array] a list of documents containing profiling information.
580
+ def profiling_info
581
+ Cursor.new(Collection.new(SYSTEM_PROFILE_COLLECTION, self), :selector => {}).to_a
582
+ end
583
+
584
+ # Validate a named collection.
585
+ #
586
+ # @param [String] name the collection name.
587
+ #
588
+ # @return [Hash] validation information.
589
+ #
590
+ # @raise [MongoDBError] if the command fails or there's a problem with the validation
591
+ # data, or if the collection is invalid.
592
+ def validate_collection(name)
593
+ doc = command({:validate => name}, :check_response => false)
594
+ raise MongoDBError, "Error with validate command: #{doc.inspect}" unless ok?(doc)
595
+ result = doc['result']
596
+ raise MongoDBError, "Error with validation data: #{doc.inspect}" unless result.kind_of?(String)
597
+ raise MongoDBError, "Error: invalid collection #{name}: #{doc.inspect}" if result =~ /\b(exception|corrupt)\b/i
598
+ doc
599
+ end
600
+
601
+ private
602
+
603
+ def system_command_collection
604
+ Collection.new(SYSTEM_COMMAND_COLLECTION, self)
605
+ end
606
+ end
607
+ end