mongo-lyon 1.2.4

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 (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