mongo-find_replace 0.18.3

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 (68) hide show
  1. data/LICENSE.txt +202 -0
  2. data/README.rdoc +358 -0
  3. data/Rakefile +133 -0
  4. data/bin/bson_benchmark.rb +59 -0
  5. data/bin/fail_if_no_c.rb +11 -0
  6. data/examples/admin.rb +42 -0
  7. data/examples/capped.rb +22 -0
  8. data/examples/cursor.rb +48 -0
  9. data/examples/gridfs.rb +88 -0
  10. data/examples/index_test.rb +126 -0
  11. data/examples/info.rb +31 -0
  12. data/examples/queries.rb +70 -0
  13. data/examples/simple.rb +24 -0
  14. data/examples/strict.rb +35 -0
  15. data/examples/types.rb +36 -0
  16. data/lib/mongo.rb +61 -0
  17. data/lib/mongo/admin.rb +95 -0
  18. data/lib/mongo/collection.rb +664 -0
  19. data/lib/mongo/connection.rb +555 -0
  20. data/lib/mongo/cursor.rb +393 -0
  21. data/lib/mongo/db.rb +527 -0
  22. data/lib/mongo/exceptions.rb +60 -0
  23. data/lib/mongo/gridfs.rb +22 -0
  24. data/lib/mongo/gridfs/chunk.rb +90 -0
  25. data/lib/mongo/gridfs/grid_store.rb +555 -0
  26. data/lib/mongo/types/binary.rb +48 -0
  27. data/lib/mongo/types/code.rb +36 -0
  28. data/lib/mongo/types/dbref.rb +38 -0
  29. data/lib/mongo/types/min_max_keys.rb +58 -0
  30. data/lib/mongo/types/objectid.rb +219 -0
  31. data/lib/mongo/types/regexp_of_holding.rb +45 -0
  32. data/lib/mongo/util/bson_c.rb +18 -0
  33. data/lib/mongo/util/bson_ruby.rb +595 -0
  34. data/lib/mongo/util/byte_buffer.rb +222 -0
  35. data/lib/mongo/util/conversions.rb +97 -0
  36. data/lib/mongo/util/ordered_hash.rb +135 -0
  37. data/lib/mongo/util/server_version.rb +69 -0
  38. data/lib/mongo/util/support.rb +26 -0
  39. data/lib/mongo/util/xml_to_ruby.rb +112 -0
  40. data/mongo-ruby-driver.gemspec +28 -0
  41. data/test/replica/count_test.rb +34 -0
  42. data/test/replica/insert_test.rb +50 -0
  43. data/test/replica/pooled_insert_test.rb +54 -0
  44. data/test/replica/query_test.rb +39 -0
  45. data/test/test_admin.rb +67 -0
  46. data/test/test_bson.rb +397 -0
  47. data/test/test_byte_buffer.rb +81 -0
  48. data/test/test_chunk.rb +82 -0
  49. data/test/test_collection.rb +534 -0
  50. data/test/test_connection.rb +160 -0
  51. data/test/test_conversions.rb +120 -0
  52. data/test/test_cursor.rb +386 -0
  53. data/test/test_db.rb +254 -0
  54. data/test/test_db_api.rb +783 -0
  55. data/test/test_db_connection.rb +16 -0
  56. data/test/test_grid_store.rb +306 -0
  57. data/test/test_helper.rb +42 -0
  58. data/test/test_objectid.rb +156 -0
  59. data/test/test_ordered_hash.rb +168 -0
  60. data/test/test_round_trip.rb +114 -0
  61. data/test/test_slave_connection.rb +36 -0
  62. data/test/test_threading.rb +87 -0
  63. data/test/threading/test_threading_large_pool.rb +90 -0
  64. data/test/unit/collection_test.rb +52 -0
  65. data/test/unit/connection_test.rb +59 -0
  66. data/test/unit/cursor_test.rb +94 -0
  67. data/test/unit/db_test.rb +97 -0
  68. metadata +123 -0
@@ -0,0 +1,555 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ require 'set'
18
+ require 'socket'
19
+ require 'thread'
20
+
21
+ module Mongo
22
+
23
+ # Instantiates and manages connections to MongoDB.
24
+ class Connection
25
+
26
+ # Abort connections if a ConnectionError is raised.
27
+ Thread.abort_on_exception = true
28
+
29
+ DEFAULT_PORT = 27017
30
+ STANDARD_HEADER_SIZE = 16
31
+ RESPONSE_HEADER_SIZE = 20
32
+
33
+ attr_reader :logger, :size, :host, :port, :nodes, :sockets, :checked_out
34
+
35
+ # Counter for generating unique request ids.
36
+ @@current_request_id = 0
37
+
38
+ # Create a connection to MongoDB. Specify either one or a pair of servers,
39
+ # along with a maximum connection pool size and timeout.
40
+ #
41
+ # If connecting to just one server, you may specify whether connection to slave is permitted.
42
+ # In all cases, the default host is "localhost" and the default port is 27017.
43
+ #
44
+ # When specifying a pair, +pair_or_host+, is a hash with two keys: :left and :right. Each key maps to either
45
+ # * a server name, in which case port is 27017,
46
+ # * a port number, in which case the server is "localhost", or
47
+ # * an array containing [server_name, port_number]
48
+ #
49
+ # Note that there are a few issues when using connection pooling with Ruby 1.9 on Windows. These
50
+ # should be resolved in the next release.
51
+ #
52
+ # @param [String, Hash] pair_or_host See explanation above.
53
+ # @param [Integer] port specify a port number here if only one host is being specified. Leave nil if
54
+ # specifying a pair of servers in +pair_or_host+.
55
+ #
56
+ # @option options [Boolean] :slave_ok (false) Must be set to +true+ when connecting
57
+ # to a single, slave node.
58
+ # @option options [Logger, #debug] :logger (nil) Logger instance to receive driver operation log.
59
+ # @option options [Boolean] :auto_reconnect DEPRECATED. See http://www.mongodb.org/display/DOCS/Replica+Pairs+in+Ruby
60
+ # @option options [Integer] :pool_size (1) The maximum number of socket connections that can be opened to the database.
61
+ # @option options [Float] :timeout (5.0) When all of the connections to the pool are checked out,
62
+ # this is the number of seconds to wait for a new connection to be released before throwing an exception.
63
+ #
64
+ #
65
+ # @example localhost, 27017
66
+ # Connection.new
67
+ #
68
+ # @example localhost, 27017
69
+ # Connection.new("localhost")
70
+ #
71
+ # @example localhost, 3000, max 5 connections, with max 5 seconds of wait time.
72
+ # Connection.new("localhost", 3000, :pool_size => 5, :timeout => 5)
73
+ #
74
+ # @example localhost, 3000, where this node may be a slave
75
+ # Connection.new("localhost", 3000, :slave_ok => true)
76
+ #
77
+ # @example A pair of servers. The driver will always talk to master.
78
+ # # On connection errors, Mongo::ConnectionFailure will be raised.
79
+ # Connection.new({:left => ["db1.example.com", 27017],
80
+ # :right => ["db2.example.com", 27017]})
81
+ #
82
+ # @example A pair of servers with connection pooling enabled. Note the nil param placeholder for port.
83
+ # Connection.new({:left => ["db1.example.com", 27017],
84
+ # :right => ["db2.example.com", 27017]}, nil,
85
+ # :pool_size => 20, :timeout => 5)
86
+ #
87
+ # @see http://www.mongodb.org/display/DOCS/Replica+Pairs+in+Ruby Replica pairs in Ruby
88
+ def initialize(pair_or_host=nil, port=nil, options={})
89
+ @nodes = format_pair(pair_or_host, port)
90
+
91
+ # Host and port of current master.
92
+ @host = @port = nil
93
+
94
+ # Lock for request ids.
95
+ @id_lock = Mutex.new
96
+
97
+ # Pool size and timeout.
98
+ @size = options[:pool_size] || 1
99
+ @timeout = options[:timeout] || 5.0
100
+
101
+ # Mutex for synchronizing pool access
102
+ @connection_mutex = Mutex.new
103
+ @safe_mutex = Mutex.new
104
+
105
+ # Condition variable for signal and wait
106
+ @queue = ConditionVariable.new
107
+
108
+ @sockets = []
109
+ @checked_out = []
110
+
111
+ if options[:auto_reconnect]
112
+ warn(":auto_reconnect is deprecated. see http://www.mongodb.org/display/DOCS/Replica+Pairs+in+Ruby")
113
+ end
114
+
115
+ # slave_ok can be true only if one node is specified
116
+ @slave_ok = options[:slave_ok] && @nodes.length == 1
117
+ @logger = options[:logger] || nil
118
+ @options = options
119
+
120
+ should_connect = options[:connect].nil? ? true : options[:connect]
121
+ connect_to_master if should_connect
122
+ end
123
+
124
+ # Return a hash with all database names
125
+ # and their respective sizes on disk.
126
+ #
127
+ # @return [Hash]
128
+ def database_info
129
+ doc = self['admin'].command(:listDatabases => 1)
130
+ returning({}) do |info|
131
+ doc['databases'].each { |db| info[db['name']] = db['sizeOnDisk'].to_i }
132
+ end
133
+ end
134
+
135
+ # Return an array of database names.
136
+ #
137
+ # @return [Array]
138
+ def database_names
139
+ database_info.keys
140
+ end
141
+
142
+ # Return a database with the given name.
143
+ # See DB#new for valid options hash parameters.
144
+ #
145
+ # @param [String] db_name a valid database name.
146
+ #
147
+ # @return [Mongo::DB]
148
+ def db(db_name, options={})
149
+ DB.new(db_name, self, options.merge(:logger => @logger))
150
+ end
151
+
152
+ # Shortcut for returning a database. Use DB#db to accept options.
153
+ #
154
+ # @param [String] db_name a valid database name.
155
+ #
156
+ # @return [Mongo::DB]
157
+ def [](db_name)
158
+ DB.new(db_name, self, :logger => @logger)
159
+ end
160
+
161
+ # Drop a database.
162
+ #
163
+ # @param [String] name name of an existing database.
164
+ def drop_database(name)
165
+ self[name].command(:dropDatabase => 1)
166
+ end
167
+
168
+ # Copy the database +from+ on the local server to +to+ on the specified +host+.
169
+ # +host+ defaults to 'localhost' if no value is provided.
170
+ #
171
+ # @param [String] from name of the database to copy from.
172
+ # @param [String] to name of the database to copy to.
173
+ # @param [String] from_host host of the 'from' database.
174
+ def copy_database(from, to, from_host="localhost")
175
+ oh = OrderedHash.new
176
+ oh[:copydb] = 1
177
+ oh[:fromhost] = from_host
178
+ oh[:fromdb] = from
179
+ oh[:todb] = to
180
+ self["admin"].command(oh)
181
+ end
182
+
183
+ # Increment and return the next available request id.
184
+ #
185
+ # return [Integer]
186
+ def get_request_id
187
+ request_id = ''
188
+ @id_lock.synchronize do
189
+ request_id = @@current_request_id += 1
190
+ end
191
+ request_id
192
+ end
193
+
194
+ # Get the build information for the current connection.
195
+ #
196
+ # @return [Hash]
197
+ def server_info
198
+ db("admin").command({:buildinfo => 1}, {:admin => true, :check_response => true})
199
+ end
200
+
201
+ # Get the build version of the current server.
202
+ #
203
+ # @return [Mongo::ServerVersion]
204
+ # object allowing easy comparability of version.
205
+ def server_version
206
+ ServerVersion.new(server_info["version"])
207
+ end
208
+
209
+ # Is it okay to connect to a slave?
210
+ #
211
+ # @return [Boolean]
212
+ def slave_ok?
213
+ @slave_ok
214
+ end
215
+
216
+
217
+ ## Connections and pooling ##
218
+
219
+ # Send a message to MongoDB, adding the necessary headers.
220
+ #
221
+ # @param [Integer] operation a MongoDB opcode.
222
+ # @param [ByteBuffer] message a message to send to the database.
223
+ # @param [String] log_message text version of +message+ for logging.
224
+ #
225
+ # @return [True]
226
+ def send_message(operation, message, log_message=nil)
227
+ @logger.debug(" MONGODB #{log_message || message}") if @logger
228
+ begin
229
+ packed_message = add_message_headers(operation, message).to_s
230
+ socket = checkout
231
+ send_message_on_socket(packed_message, socket)
232
+ ensure
233
+ checkin(socket)
234
+ end
235
+ end
236
+
237
+ # Sends a message to the database, waits for a response, and raises
238
+ # an exception if the operation has failed.
239
+ #
240
+ # @param [Integer] operation a MongoDB opcode.
241
+ # @param [ByteBuffer] message a message to send to the database.
242
+ # @param [String] db_name the name of the database. used on call to get_last_error.
243
+ # @param [String] log_message text version of +message+ for logging.
244
+ #
245
+ # @return [Array]
246
+ # An array whose indexes include [0] documents returned, [1] number of document received,
247
+ # and [3] a cursor_id.
248
+ def send_message_with_safe_check(operation, message, db_name, log_message=nil)
249
+ message_with_headers = add_message_headers(operation, message)
250
+ message_with_check = last_error_message(db_name)
251
+ @logger.debug(" MONGODB #{log_message || message}") if @logger
252
+ begin
253
+ sock = checkout
254
+ packed_message = message_with_headers.append!(message_with_check).to_s
255
+ docs = num_received = cursor_id = ''
256
+ @safe_mutex.synchronize do
257
+ send_message_on_socket(packed_message, sock)
258
+ docs, num_received, cursor_id = receive(sock)
259
+ end
260
+ ensure
261
+ checkin(sock)
262
+ end
263
+ if num_received == 1 && error = docs[0]['err']
264
+ raise Mongo::OperationFailure, error
265
+ end
266
+ [docs, num_received, cursor_id]
267
+ end
268
+
269
+ # Sends a message to the database and waits for the response.
270
+ #
271
+ # @param [Integer] operation a MongoDB opcode.
272
+ # @param [ByteBuffer] message a message to send to the database.
273
+ # @param [String] log_message text version of +message+ for logging.
274
+ # @param [Socket] socket a socket to use in lieu of checking out a new one.
275
+ #
276
+ # @return [Array]
277
+ # An array whose indexes include [0] documents returned, [1] number of document received,
278
+ # and [3] a cursor_id.
279
+ def receive_message(operation, message, log_message=nil, socket=nil)
280
+ packed_message = add_message_headers(operation, message).to_s
281
+ @logger.debug(" MONGODB #{log_message || message}") if @logger
282
+ begin
283
+ sock = socket || checkout
284
+
285
+ result = ''
286
+ @safe_mutex.synchronize do
287
+ send_message_on_socket(packed_message, sock)
288
+ result = receive(sock)
289
+ end
290
+ ensure
291
+ checkin(sock)
292
+ end
293
+ result
294
+ end
295
+
296
+ # Create a new socket and attempt to connect to master.
297
+ # If successful, sets host and port to master and returns the socket.
298
+ #
299
+ # @raise [ConnectionFailure] if unable to connect to any host or port.
300
+ def connect_to_master
301
+ close
302
+ @host = @port = nil
303
+ for node_pair in @nodes
304
+ host, port = *node_pair
305
+ begin
306
+ socket = TCPSocket.new(host, port)
307
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
308
+
309
+ # If we're connected to master, set the @host and @port
310
+ result = self['admin'].command({:ismaster => 1}, false, false, socket)
311
+ if result['ok'] == 1 && ((is_master = result['ismaster'] == 1) || @slave_ok)
312
+ @host, @port = host, port
313
+ end
314
+
315
+ # Note: slave_ok can be true only when connecting to a single node.
316
+ if @nodes.length == 1 && !is_master && !@slave_ok
317
+ raise ConfigurationError, "Trying to connect directly to slave; " +
318
+ "if this is what you want, specify :slave_ok => true."
319
+ end
320
+
321
+ break if is_master || @slave_ok
322
+ rescue SocketError, SystemCallError, IOError => ex
323
+ socket.close if socket
324
+ false
325
+ end
326
+ end
327
+ raise ConnectionFailure, "failed to connect to any given host:port" unless socket
328
+ end
329
+
330
+ # Are we connected to MongoDB? This is determined by checking whether
331
+ # host and port have values, since they're set to nil on calls to #close.
332
+ def connected?
333
+ @host && @port
334
+ end
335
+
336
+ # Close the connection to the database.
337
+ def close
338
+ @sockets.each do |sock|
339
+ sock.close
340
+ end
341
+ @host = @port = nil
342
+ @sockets.clear
343
+ @checked_out.clear
344
+ end
345
+
346
+ private
347
+
348
+ # Return a socket to the pool.
349
+ def checkin(socket)
350
+ @connection_mutex.synchronize do
351
+ @checked_out.delete(socket)
352
+ @queue.signal
353
+ end
354
+ true
355
+ end
356
+
357
+ # Adds a new socket to the pool and checks it out.
358
+ #
359
+ # This method is called exclusively from #checkout;
360
+ # therefore, it runs within a mutex.
361
+ def checkout_new_socket
362
+ begin
363
+ socket = TCPSocket.new(@host, @port)
364
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
365
+ rescue => ex
366
+ raise ConnectionFailure, "Failed to connect socket: #{ex}"
367
+ end
368
+ @sockets << socket
369
+ @checked_out << socket
370
+ socket
371
+ end
372
+
373
+ # Checks out the first available socket from the pool.
374
+ #
375
+ # This method is called exclusively from #checkout;
376
+ # therefore, it runs within a mutex.
377
+ def checkout_existing_socket
378
+ socket = (@sockets - @checked_out).first
379
+ @checked_out << socket
380
+ socket
381
+ end
382
+
383
+ # Check out an existing socket or create a new socket if the maximum
384
+ # pool size has not been exceeded. Otherwise, wait for the next
385
+ # available socket.
386
+ def checkout
387
+ connect_to_master if !connected?
388
+ start_time = Time.now
389
+ loop do
390
+ if (Time.now - start_time) > @timeout
391
+ raise ConnectionTimeoutError, "could not obtain connection within " +
392
+ "#{@timeout} seconds. The max pool size is currently #{@size}; " +
393
+ "consider increasing the pool size or timeout."
394
+ end
395
+
396
+ @connection_mutex.synchronize do
397
+ socket = if @checked_out.size < @sockets.size
398
+ checkout_existing_socket
399
+ elsif @sockets.size < @size
400
+ checkout_new_socket
401
+ end
402
+
403
+ return socket if socket
404
+
405
+ # Otherwise, wait
406
+ if @logger
407
+ @logger.warn "Waiting for available connection; #{@checked_out.size} of #{@size} connections checked out."
408
+ end
409
+ @queue.wait(@connection_mutex)
410
+ end
411
+ end
412
+ end
413
+
414
+ def receive(sock)
415
+ receive_header(sock)
416
+ number_received, cursor_id = receive_response_header(sock)
417
+ read_documents(number_received, cursor_id, sock)
418
+ end
419
+
420
+ def receive_header(sock)
421
+ header = ByteBuffer.new
422
+ header.put_array(receive_message_on_socket(16, sock).unpack("C*"))
423
+ unless header.size == STANDARD_HEADER_SIZE
424
+ raise "Short read for DB response header: " +
425
+ "expected #{STANDARD_HEADER_SIZE} bytes, saw #{header.size}"
426
+ end
427
+ header.rewind
428
+ size = header.get_int
429
+ request_id = header.get_int
430
+ response_to = header.get_int
431
+ op = header.get_int
432
+ end
433
+
434
+ def receive_response_header(sock)
435
+ header_buf = ByteBuffer.new
436
+ header_buf.put_array(receive_message_on_socket(RESPONSE_HEADER_SIZE, sock).unpack("C*"))
437
+ if header_buf.length != RESPONSE_HEADER_SIZE
438
+ raise "Short read for DB response header; " +
439
+ "expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}"
440
+ end
441
+ header_buf.rewind
442
+ result_flags = header_buf.get_int
443
+ cursor_id = header_buf.get_long
444
+ starting_from = header_buf.get_int
445
+ number_remaining = header_buf.get_int
446
+ [number_remaining, cursor_id]
447
+ end
448
+
449
+ def read_documents(number_received, cursor_id, sock)
450
+ docs = []
451
+ number_remaining = number_received
452
+ while number_remaining > 0 do
453
+ buf = ByteBuffer.new
454
+ buf.put_array(receive_message_on_socket(4, sock).unpack("C*"))
455
+ buf.rewind
456
+ size = buf.get_int
457
+ buf.put_array(receive_message_on_socket(size - 4, sock).unpack("C*"), 4)
458
+ number_remaining -= 1
459
+ buf.rewind
460
+ docs << BSON.deserialize(buf)
461
+ end
462
+ [docs, number_received, cursor_id]
463
+ end
464
+
465
+ def last_error_message(db_name)
466
+ message = ByteBuffer.new
467
+ message.put_int(0)
468
+ BSON_RUBY.serialize_cstr(message, "#{db_name}.$cmd")
469
+ message.put_int(0)
470
+ message.put_int(-1)
471
+ message.put_array(BSON.serialize({:getlasterror => 1}, false).unpack("C*"))
472
+ add_message_headers(Mongo::Constants::OP_QUERY, message)
473
+ end
474
+
475
+ # Prepares a message for transmission to MongoDB by
476
+ # constructing a valid message header.
477
+ def add_message_headers(operation, message)
478
+ headers = ByteBuffer.new
479
+
480
+ # Message size.
481
+ headers.put_int(16 + message.size)
482
+
483
+ # Unique request id.
484
+ headers.put_int(get_request_id)
485
+
486
+ # Response id.
487
+ headers.put_int(0)
488
+
489
+ # Opcode.
490
+ headers.put_int(operation)
491
+ message.prepend!(headers)
492
+ end
493
+
494
+ # Low-level method for sending a message on a socket.
495
+ # Requires a packed message and an available socket,
496
+ def send_message_on_socket(packed_message, socket)
497
+ begin
498
+ socket.send(packed_message, 0)
499
+ rescue => ex
500
+ close
501
+ raise ConnectionFailure, "Operation failed with the following exception: #{ex}"
502
+ end
503
+ end
504
+
505
+ # Low-level method for receiving data from socket.
506
+ # Requires length and an available socket.
507
+ def receive_message_on_socket(length, socket)
508
+ message = ""
509
+ begin
510
+ while message.length < length do
511
+ chunk = socket.recv(length - message.length)
512
+ raise ConnectionFailure, "connection closed" unless chunk.length > 0
513
+ message += chunk
514
+ end
515
+ rescue => ex
516
+ raise ConnectionFailure, "Operation failed with the following exception: #{ex}"
517
+ end
518
+ message
519
+ 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
+ end
555
+ end