mongo 1.0 → 1.1.5

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 (95) hide show
  1. data/LICENSE.txt +1 -13
  2. data/{README.rdoc → README.md} +129 -149
  3. data/Rakefile +94 -58
  4. data/bin/mongo_console +21 -0
  5. data/docs/1.0_UPGRADE.md +21 -0
  6. data/docs/CREDITS.md +123 -0
  7. data/docs/FAQ.md +112 -0
  8. data/docs/GridFS.md +158 -0
  9. data/docs/HISTORY.md +185 -0
  10. data/docs/REPLICA_SETS.md +75 -0
  11. data/docs/TUTORIAL.md +247 -0
  12. data/docs/WRITE_CONCERN.md +28 -0
  13. data/lib/mongo/collection.rb +225 -105
  14. data/lib/mongo/connection.rb +374 -315
  15. data/lib/mongo/cursor.rb +122 -77
  16. data/lib/mongo/db.rb +109 -85
  17. data/lib/mongo/exceptions.rb +6 -0
  18. data/lib/mongo/gridfs/grid.rb +19 -11
  19. data/lib/mongo/gridfs/grid_ext.rb +36 -9
  20. data/lib/mongo/gridfs/grid_file_system.rb +15 -9
  21. data/lib/mongo/gridfs/grid_io.rb +49 -16
  22. data/lib/mongo/gridfs/grid_io_fix.rb +38 -0
  23. data/lib/mongo/repl_set_connection.rb +290 -0
  24. data/lib/mongo/util/conversions.rb +3 -1
  25. data/lib/mongo/util/core_ext.rb +17 -4
  26. data/lib/mongo/util/pool.rb +125 -0
  27. data/lib/mongo/util/server_version.rb +2 -0
  28. data/lib/mongo/util/support.rb +12 -0
  29. data/lib/mongo/util/uri_parser.rb +71 -0
  30. data/lib/mongo.rb +23 -7
  31. data/{mongo-ruby-driver.gemspec → mongo.gemspec} +9 -7
  32. data/test/auxillary/1.4_features.rb +2 -2
  33. data/test/auxillary/authentication_test.rb +1 -1
  34. data/test/auxillary/autoreconnect_test.rb +1 -1
  35. data/test/{slave_connection_test.rb → auxillary/slave_connection_test.rb} +6 -6
  36. data/test/bson/binary_test.rb +15 -0
  37. data/test/bson/bson_test.rb +537 -0
  38. data/test/bson/byte_buffer_test.rb +190 -0
  39. data/test/bson/hash_with_indifferent_access_test.rb +38 -0
  40. data/test/bson/json_test.rb +17 -0
  41. data/test/bson/object_id_test.rb +141 -0
  42. data/test/bson/ordered_hash_test.rb +197 -0
  43. data/test/collection_test.rb +195 -15
  44. data/test/connection_test.rb +93 -56
  45. data/test/conversions_test.rb +1 -1
  46. data/test/cursor_fail_test.rb +75 -0
  47. data/test/cursor_message_test.rb +43 -0
  48. data/test/cursor_test.rb +93 -32
  49. data/test/db_api_test.rb +28 -55
  50. data/test/db_connection_test.rb +2 -3
  51. data/test/db_test.rb +45 -40
  52. data/test/grid_file_system_test.rb +14 -6
  53. data/test/grid_io_test.rb +36 -7
  54. data/test/grid_test.rb +54 -10
  55. data/test/replica_sets/connect_test.rb +84 -0
  56. data/test/replica_sets/count_test.rb +35 -0
  57. data/test/{replica → replica_sets}/insert_test.rb +17 -14
  58. data/test/replica_sets/pooled_insert_test.rb +55 -0
  59. data/test/replica_sets/query_secondaries.rb +80 -0
  60. data/test/replica_sets/query_test.rb +41 -0
  61. data/test/replica_sets/replication_ack_test.rb +64 -0
  62. data/test/replica_sets/rs_test_helper.rb +29 -0
  63. data/test/safe_test.rb +68 -0
  64. data/test/support/hash_with_indifferent_access.rb +199 -0
  65. data/test/support/keys.rb +45 -0
  66. data/test/support_test.rb +19 -0
  67. data/test/test_helper.rb +53 -15
  68. data/test/threading/{test_threading_large_pool.rb → threading_with_large_pool_test.rb} +2 -2
  69. data/test/threading_test.rb +2 -2
  70. data/test/tools/repl_set_manager.rb +241 -0
  71. data/test/tools/test.rb +13 -0
  72. data/test/unit/collection_test.rb +70 -7
  73. data/test/unit/connection_test.rb +18 -39
  74. data/test/unit/cursor_test.rb +7 -8
  75. data/test/unit/db_test.rb +14 -17
  76. data/test/unit/grid_test.rb +49 -0
  77. data/test/unit/pool_test.rb +9 -0
  78. data/test/unit/repl_set_connection_test.rb +82 -0
  79. data/test/unit/safe_test.rb +125 -0
  80. metadata +132 -51
  81. data/bin/bson_benchmark.rb +0 -59
  82. data/bin/fail_if_no_c.rb +0 -11
  83. data/examples/admin.rb +0 -43
  84. data/examples/capped.rb +0 -22
  85. data/examples/cursor.rb +0 -48
  86. data/examples/gridfs.rb +0 -44
  87. data/examples/index_test.rb +0 -126
  88. data/examples/info.rb +0 -31
  89. data/examples/queries.rb +0 -70
  90. data/examples/simple.rb +0 -24
  91. data/examples/strict.rb +0 -35
  92. data/examples/types.rb +0 -36
  93. data/test/replica/count_test.rb +0 -34
  94. data/test/replica/pooled_insert_test.rb +0 -54
  95. data/test/replica/query_test.rb +0 -39
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  # --
2
4
  # Copyright (C) 2008-2010 10gen Inc.
3
5
  #
@@ -30,7 +32,7 @@ module Mongo
30
32
  PROTECTED_ATTRS = [:files_id, :file_length, :client_md5, :server_md5]
31
33
 
32
34
  attr_reader :content_type, :chunk_size, :upload_date, :files_id, :filename,
33
- :metadata, :server_md5, :client_md5, :file_length
35
+ :metadata, :server_md5, :client_md5, :file_length, :file_position
34
36
 
35
37
  # Create a new GridIO object. Note that most users will not need to use this class directly;
36
38
  # the Grid and GridFileSystem classes will instantiate this class
@@ -43,14 +45,14 @@ module Mongo
43
45
  # @option opts [Hash] :query a query selector used when opening the file in 'r' mode.
44
46
  # @option opts [Hash] :query_opts any query options to be used when opening the file in 'r' mode.
45
47
  # @option opts [String] :fs_name the file system prefix.
46
- # @options opts [Integer] (262144) :chunk_size size of file chunks in bytes.
47
- # @options opts [Hash] :metadata ({}) any additional data to store with the file.
48
- # @options opts [ObjectID] :_id (ObjectID) a unique id for
48
+ # @option opts [Integer] (262144) :chunk_size size of file chunks in bytes.
49
+ # @option opts [Hash] :metadata ({}) any additional data to store with the file.
50
+ # @option opts [ObjectId] :_id (ObjectId) a unique id for
49
51
  # the file to be use in lieu of an automatically generated one.
50
- # @options opts [String] :content_type ('binary/octet-stream') If no content type is specified,
52
+ # @option opts [String] :content_type ('binary/octet-stream') If no content type is specified,
51
53
  # the content type will may be inferred from the filename extension if the mime-types gem can be
52
54
  # loaded. Otherwise, the content type 'binary/octet-stream' will be used.
53
- # @options opts [Boolean] :safe (false) When safe mode is enabled, the chunks sent to the server
55
+ # @option opts [Boolean] :safe (false) When safe mode is enabled, the chunks sent to the server
54
56
  # will be validated using an md5 hash. If validation fails, an exception will be raised.
55
57
  def initialize(files, chunks, filename, mode, opts={})
56
58
  @files = files
@@ -178,7 +180,7 @@ module Mongo
178
180
  # This method will be invoked automatically when
179
181
  # on GridIO#open is passed a block. Otherwise, it must be called manually.
180
182
  #
181
- # @return [BSON::ObjectID]
183
+ # @return [BSON::ObjectId]
182
184
  def close
183
185
  if @mode[0] == ?w
184
186
  if @current_chunk['n'].zero? && @chunk_position.zero?
@@ -190,6 +192,23 @@ module Mongo
190
192
  id
191
193
  end
192
194
 
195
+ # Read a chunk of the data from the file and yield it to the given
196
+ # block.
197
+ #
198
+ # Note that this method reads from the current file position.
199
+ #
200
+ # @yield Yields on chunk per iteration as defined by this file's
201
+ # chunk size.
202
+ #
203
+ # @return [Mongo::GridIO] self
204
+ def each
205
+ return read_all unless block_given?
206
+ while chunk = read(chunk_size)
207
+ yield chunk
208
+ end
209
+ self
210
+ end
211
+
193
212
  def inspect
194
213
  "#<GridIO _id: #{@files_id}>"
195
214
  end
@@ -197,8 +216,8 @@ module Mongo
197
216
  private
198
217
 
199
218
  def create_chunk(n)
200
- chunk = OrderedHash.new
201
- chunk['_id'] = BSON::ObjectID.new
219
+ chunk = BSON::OrderedHash.new
220
+ chunk['_id'] = BSON::ObjectId.new
202
221
  chunk['n'] = n
203
222
  chunk['files_id'] = @files_id
204
223
  chunk['data'] = ''
@@ -223,11 +242,14 @@ module Mongo
223
242
  # Read a file in its entirety.
224
243
  def read_all
225
244
  buf = ''
226
- while true
245
+ if @current_chunk
227
246
  buf << @current_chunk['data'].to_s
228
- @current_chunk = get_chunk(@current_chunk['n'] + 1)
229
- break unless @current_chunk
247
+ while chunk = get_chunk(@current_chunk['n'] + 1)
248
+ buf << chunk['data'].to_s
249
+ @current_chunk = chunk
250
+ end
230
251
  end
252
+ @file_position = @file_length
231
253
  buf
232
254
  end
233
255
 
@@ -235,7 +257,11 @@ module Mongo
235
257
  def read_length(length)
236
258
  cache_chunk_data
237
259
  remaining = (@file_length - @file_position)
238
- to_read = length > remaining ? remaining : length
260
+ if length.nil?
261
+ to_read = remaining
262
+ else
263
+ to_read = length > remaining ? remaining : length
264
+ end
239
265
  return nil unless remaining > 0
240
266
 
241
267
  buf = ''
@@ -306,20 +332,27 @@ module Mongo
306
332
 
307
333
  # Initialize the class for writing a file.
308
334
  def init_write(opts)
309
- @files_id = opts.delete(:_id) || BSON::ObjectID.new
335
+ @files_id = opts.delete(:_id) || BSON::ObjectId.new
310
336
  @content_type = opts.delete(:content_type) || (defined? MIME) && get_content_type || DEFAULT_CONTENT_TYPE
311
337
  @chunk_size = opts.delete(:chunk_size) || DEFAULT_CHUNK_SIZE
312
338
  @metadata = opts.delete(:metadata) if opts[:metadata]
313
339
  @aliases = opts.delete(:aliases) if opts[:aliases]
314
340
  @file_length = 0
315
341
  opts.each {|k, v| self[k] = v}
342
+ check_existing_file if @safe
316
343
 
317
344
  @current_chunk = create_chunk(0)
318
345
  @file_position = 0
319
346
  end
320
347
 
348
+ def check_existing_file
349
+ if @files.find_one('_id' => @files_id)
350
+ raise GridError, "Attempting to overwrite with Grid#put. You must delete the file first."
351
+ end
352
+ end
353
+
321
354
  def to_mongo_object
322
- h = OrderedHash.new
355
+ h = BSON::OrderedHash.new
323
356
  h['_id'] = @files_id
324
357
  h['filename'] = @filename if @filename
325
358
  h['contentType'] = @content_type
@@ -335,7 +368,7 @@ module Mongo
335
368
 
336
369
  # Get a server-side md5 and validate against the client if running in safe mode.
337
370
  def get_md5
338
- md5_command = OrderedHash.new
371
+ md5_command = BSON::OrderedHash.new
339
372
  md5_command['filemd5'] = @files_id
340
373
  md5_command['root'] = @fs_name
341
374
  @server_md5 = @files.db.command(md5_command)['md5']
@@ -0,0 +1,38 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2010 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
+ module Mongo
20
+ class GridIO
21
+
22
+ # This fixes a comparson issue in JRuby 1.9
23
+ def get_md5
24
+ md5_command = BSON::OrderedHash.new
25
+ md5_command['filemd5'] = @files_id
26
+ md5_command['root'] = @fs_name
27
+ @server_md5 = @files.db.command(md5_command)['md5']
28
+ if @safe
29
+ @client_md5 = @local_md5.hexdigest
30
+ if @local_md5.to_s != @server_md5.to_s
31
+ raise GridMD5Failure, "File on server failed MD5 check"
32
+ end
33
+ else
34
+ @server_md5
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,290 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2010 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
+ module Mongo
20
+
21
+ # Instantiates and manages connections to a MongoDB replica set.
22
+ class ReplSetConnection < Connection
23
+ attr_reader :nodes, :secondaries, :arbiters, :read_pool, :secondary_pools
24
+
25
+ # Create a connection to a MongoDB replica set.
26
+ #
27
+ # Once connected to a replica set, you can find out which nodes are primary, secondary, and
28
+ # arbiters with the corresponding accessors: Connection#primary, Connection#secondaries, and
29
+ # Connection#arbiters. This is useful if your application needs to connect manually to nodes other
30
+ # than the primary.
31
+ #
32
+ # @param [Array] args A list of host-port pairs ending with a hash containing any options. See
33
+ # the examples below for exactly how to use the constructor.
34
+ #
35
+ # @option options [Boolean, Hash] :safe (false) Set the default safe-mode options
36
+ # propogated to DB objects instantiated off of this Connection. This
37
+ # default can be overridden upon instantiation of any DB by explicity setting a :safe value
38
+ # on initialization.
39
+ # @option options [Boolean] :read_secondary(false) If true, a random secondary node will be chosen,
40
+ # and all reads will be directed to that node.
41
+ # @option options [Logger, #debug] :logger (nil) Logger instance to receive driver operation log.
42
+ # @option options [Integer] :pool_size (1) The maximum number of socket connections allowed per
43
+ # connection pool. Note: this setting is relevant only for multi-threaded applications.
44
+ # @option options [Float] :timeout (5.0) When all of the connections a pool are checked out,
45
+ # this is the number of seconds to wait for a new connection to be released before throwing an exception.
46
+ # Note: this setting is relevant only for multi-threaded applications.
47
+ #
48
+ # @example Connect to a replica set and provide two seed nodes:
49
+ # ReplSetConnection.new(['localhost', 30000], ['localhost', 30001])
50
+ #
51
+ # @example Connect to a replica set providing two seed nodes and allowing reads from a
52
+ # secondary node:
53
+ # ReplSetConnection.new(['localhost', 30000], ['localhost', 30001], :read_secondary => true)
54
+ #
55
+ # @see http://api.mongodb.org/ruby/current/file.REPLICA_SETS.html Replica sets in Ruby
56
+ #
57
+ # @raise [ReplicaSetConnectionError] This is raised if a replica set name is specified and the
58
+ # driver fails to connect to a replica set with that name.
59
+ def initialize(*args)
60
+ if args.last.is_a?(Hash)
61
+ opts = args.pop
62
+ else
63
+ opts = {}
64
+ end
65
+
66
+ unless args.length > 0
67
+ raise MongoArgumentError, "A ReplSetConnection requires at least one node."
68
+ end
69
+
70
+ # Get seed nodes
71
+ @nodes = args
72
+
73
+ # Replica set name
74
+ @replica_set = opts[:rs_name]
75
+
76
+ # Cache the various node types when connecting to a replica set.
77
+ @secondaries = []
78
+ @arbiters = []
79
+
80
+ # Connection pools for each secondary node
81
+ @secondary_pools = []
82
+ @read_pool = nil
83
+
84
+ # Are we allowing reads from secondaries?
85
+ @read_secondary = opts.fetch(:read_secondary, false)
86
+
87
+ setup(opts)
88
+ end
89
+
90
+ # Create a new socket and attempt to connect to master.
91
+ # If successful, sets host and port to master and returns the socket.
92
+ #
93
+ # If connecting to a replica set, this method will replace the
94
+ # initially-provided seed list with any nodes known to the set.
95
+ #
96
+ # @raise [ConnectionFailure] if unable to connect to any host or port.
97
+ def connect
98
+ reset_connection
99
+ @nodes_to_try = @nodes.clone
100
+
101
+ while connecting?
102
+ node = @nodes_to_try.shift
103
+ config = check_is_master(node)
104
+
105
+ if is_primary?(config)
106
+ set_primary(node)
107
+ else
108
+ set_auxillary(node, config)
109
+ end
110
+ end
111
+
112
+ pick_secondary_for_read if @read_secondary
113
+
114
+ if !connected?
115
+ if @secondary_pools.empty?
116
+ raise ConnectionFailure, "Failed to connect any given host:port"
117
+ else
118
+ raise ConnectionFailure, "Failed to connect to primary node."
119
+ end
120
+ end
121
+ end
122
+
123
+ def connecting?
124
+ @nodes_to_try.length > 0
125
+ end
126
+
127
+ # Close the connection to the database.
128
+ def close
129
+ super
130
+ @read_pool = nil
131
+ @secondary_pools.each do |pool|
132
+ pool.close
133
+ end
134
+ end
135
+
136
+ # If a ConnectionFailure is raised, this method will be called
137
+ # to close the connection and reset connection values.
138
+ # TODO: what's the point of this method?
139
+ def reset_connection
140
+ super
141
+ @secondaries = []
142
+ @secondary_pools = []
143
+ @arbiters = []
144
+ @nodes_tried = []
145
+ @nodes_to_try = []
146
+ end
147
+
148
+ # Is it okay to connect to a slave?
149
+ #
150
+ # @return [Boolean]
151
+ def slave_ok?
152
+ @read_secondary || @slave_ok
153
+ end
154
+
155
+ private
156
+
157
+ def check_is_master(node)
158
+ begin
159
+ host, port = *node
160
+ socket = TCPSocket.new(host, port)
161
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
162
+
163
+ config = self['admin'].command({:ismaster => 1}, :sock => socket)
164
+
165
+ check_set_name(config, socket)
166
+ rescue OperationFailure, SocketError, SystemCallError, IOError => ex
167
+ close unless connected?
168
+ ensure
169
+ @nodes_tried << node
170
+ if config
171
+ nodes = []
172
+ nodes += config['hosts'] if config['hosts']
173
+ nodes += config['arbiters'] if config['arbiters']
174
+ nodes += config['passives'] if config['passives']
175
+ update_node_list(nodes)
176
+
177
+ if config['msg'] && @logger
178
+ @logger.warn("MONGODB #{config['msg']}")
179
+ end
180
+ end
181
+
182
+ socket.close if socket
183
+ end
184
+
185
+ config
186
+ end
187
+
188
+ # Primary, when connecting to a replica can, can only be a true primary node.
189
+ # (And not a slave, which is possible when connecting with the standard
190
+ # Connection class.
191
+ def is_primary?(config)
192
+ config && (config['ismaster'] == 1 || config['ismaster'] == true)
193
+ end
194
+
195
+ # Pick a node randomly from the set of possible secondaries.
196
+ def pick_secondary_for_read
197
+ if (size = @secondary_pools.size) > 0
198
+ @read_pool = @secondary_pools[rand(size)]
199
+ end
200
+ end
201
+
202
+ # Make sure that we're connected to the expected replica set.
203
+ def check_set_name(config, socket)
204
+ if @replica_set
205
+ config = self['admin'].command({:replSetGetStatus => 1},
206
+ :sock => socket, :check_response => false)
207
+
208
+ if !Mongo::Support.ok?(config)
209
+ raise ReplicaSetConnectionError, config['errmsg']
210
+ elsif config['set'] != @replica_set
211
+ raise ReplicaSetConnectionError,
212
+ "Attempting to connect to replica set '#{config['set']}' but expected '#{@replica_set}'"
213
+ end
214
+ end
215
+ end
216
+
217
+ # Determines what kind of node we have and caches its host
218
+ # and port so that users can easily connect manually.
219
+ def set_auxillary(node, config)
220
+ if config
221
+ if config['secondary']
222
+ host, port = *node
223
+ @secondaries << node unless @secondaries.include?(node)
224
+ @secondary_pools << Pool.new(self, host, port, :size => @pool_size, :timeout => @timeout)
225
+ elsif config['arbiterOnly']
226
+ @arbiters << node unless @arbiters.include?(node)
227
+ end
228
+ end
229
+ end
230
+
231
+ # Update the list of known nodes. Only applies to replica sets,
232
+ # where the response to the ismaster command will return a list
233
+ # of known hosts.
234
+ #
235
+ # @param hosts [Array] a list of hosts, specified as string-encoded
236
+ # host-port values. Example: ["myserver-1.org:27017", "myserver-1.org:27017"]
237
+ #
238
+ # @return [Array] the updated list of nodes
239
+ def update_node_list(hosts)
240
+ new_nodes = hosts.map do |host|
241
+ if !host.respond_to?(:split)
242
+ warn "Could not parse host #{host.inspect}."
243
+ next
244
+ end
245
+
246
+ host, port = host.split(':')
247
+ [host, port ? port.to_i : Connection::DEFAULT_PORT]
248
+ end
249
+
250
+ # Replace the list of seed nodes with the canonical list.
251
+ @nodes = new_nodes.clone
252
+
253
+ @nodes_to_try = new_nodes - @nodes_tried
254
+ end
255
+
256
+ # Checkout a socket for reading (i.e., a secondary node).
257
+ def checkout_reader
258
+ connect unless connected?
259
+
260
+ if @read_pool
261
+ @read_pool.checkout
262
+ else
263
+ checkout_writer
264
+ end
265
+ end
266
+
267
+ # Checkout a socket for writing (i.e., a primary node).
268
+ def checkout_writer
269
+ connect unless connected?
270
+
271
+ @primary_pool.checkout
272
+ end
273
+
274
+ # Checkin a socket used for reading.
275
+ def checkin_reader(socket)
276
+ if @read_pool
277
+ @read_pool.checkin(socket)
278
+ else
279
+ checkin_writer(socket)
280
+ end
281
+ end
282
+
283
+ # Checkin a socket used for writing.
284
+ def checkin_writer(socket)
285
+ if @primary_pool
286
+ @primary_pool.checkin(socket)
287
+ end
288
+ end
289
+ end
290
+ end
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  # --
2
4
  # Copyright (C) 2008-2010 10gen Inc.
3
5
  #
@@ -31,7 +33,7 @@ module Mongo #:nodoc:
31
33
  # <tt>array_as_sort_parameters([["field1", :asc], ["field2", :desc]])</tt> =>
32
34
  # <tt>{ "field1" => 1, "field2" => -1}</tt>
33
35
  def array_as_sort_parameters(value)
34
- order_by = OrderedHash.new
36
+ order_by = BSON::OrderedHash.new
35
37
  if value.first.is_a? Array
36
38
  value.each do |param|
37
39
  if (param.class.name == "String")
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  # --
2
4
  # Copyright (C) 2008-2010 10gen Inc.
3
5
  #
@@ -18,10 +20,21 @@
18
20
  class Object
19
21
 
20
22
  #:nodoc:
21
- def returning(value)
22
- yield value
23
- value
24
- end
23
+ def tap
24
+ yield self
25
+ self
26
+ end unless respond_to? :tap
27
+
28
+ end
29
+
30
+ #:nodoc:
31
+ module Enumerable
32
+
33
+ #:nodoc:
34
+ def each_with_object(memo)
35
+ each { |element| yield(element, memo) }
36
+ memo
37
+ end unless [].respond_to?(:each_with_object)
25
38
 
26
39
  end
27
40
 
@@ -0,0 +1,125 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2010 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
+ module Mongo
19
+ class Pool
20
+
21
+ attr_accessor :host, :port, :size, :timeout, :safe, :checked_out
22
+
23
+ # Create a new pool of connections.
24
+ #
25
+ def initialize(connection, host, port, options={})
26
+ @connection = connection
27
+
28
+ @host, @port = host, port
29
+
30
+ # Pool size and timeout.
31
+ @size = options[:size] || 1
32
+ @timeout = options[:timeout] || 5.0
33
+
34
+ # Mutex for synchronizing pool access
35
+ @connection_mutex = Mutex.new
36
+
37
+ # Condition variable for signal and wait
38
+ @queue = ConditionVariable.new
39
+
40
+ @sockets = []
41
+ @checked_out = []
42
+ end
43
+
44
+ def close
45
+ @sockets.each do |sock|
46
+ begin
47
+ sock.close
48
+ rescue IOError => ex
49
+ warn "IOError when attempting to close socket connected "
50
+ + "to #{@host}:#{@port}: #{ex.inspect}"
51
+ end
52
+ end
53
+ @host = @port = nil
54
+ @sockets.clear
55
+ @checked_out.clear
56
+ end
57
+
58
+ # Return a socket to the pool.
59
+ def checkin(socket)
60
+ @connection_mutex.synchronize do
61
+ @checked_out.delete(socket)
62
+ @queue.signal
63
+ end
64
+ true
65
+ end
66
+
67
+ # Adds a new socket to the pool and checks it out.
68
+ #
69
+ # This method is called exclusively from #checkout;
70
+ # therefore, it runs within a mutex.
71
+ def checkout_new_socket
72
+ begin
73
+ socket = TCPSocket.new(@host, @port)
74
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
75
+ rescue => ex
76
+ raise ConnectionFailure, "Failed to connect socket: #{ex}"
77
+ end
78
+ @sockets << socket
79
+ @checked_out << socket
80
+ socket
81
+ end
82
+
83
+ # Checks out the first available socket from the pool.
84
+ #
85
+ # This method is called exclusively from #checkout;
86
+ # therefore, it runs within a mutex.
87
+ def checkout_existing_socket
88
+ socket = (@sockets - @checked_out).first
89
+ @checked_out << socket
90
+ socket
91
+ end
92
+
93
+ # Check out an existing socket or create a new socket if the maximum
94
+ # pool size has not been exceeded. Otherwise, wait for the next
95
+ # available socket.
96
+ def checkout
97
+ @connection.connect if !@connection.connected?
98
+ start_time = Time.now
99
+ loop do
100
+ if (Time.now - start_time) > @timeout
101
+ raise ConnectionTimeoutError, "could not obtain connection within " +
102
+ "#{@timeout} seconds. The max pool size is currently #{@size}; " +
103
+ "consider increasing the pool size or timeout."
104
+ end
105
+
106
+ @connection_mutex.synchronize do
107
+ socket = if @checked_out.size < @sockets.size
108
+ checkout_existing_socket
109
+ elsif @sockets.size < @size
110
+ checkout_new_socket
111
+ end
112
+
113
+ return socket if socket
114
+
115
+ # Otherwise, wait
116
+ if @logger
117
+ @logger.warn "MONGODB Waiting for available connection; " +
118
+ "#{@checked_out.size} of #{@size} connections checked out."
119
+ end
120
+ @queue.wait(@connection_mutex)
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  # --
2
4
  # Copyright (C) 2008-2010 10gen Inc.
3
5
  #
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  # --
2
4
  # Copyright (C) 2008-2010 10gen Inc.
3
5
  #
@@ -66,5 +68,15 @@ module Mongo
66
68
  "[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"
67
69
  end
68
70
  end
71
+
72
+ # Determine if a database command has succeeded by
73
+ # checking the document response.
74
+ #
75
+ # @param [Hash] doc
76
+ #
77
+ # @return [Boolean] true if the 'ok' key is either 1 or *true*.
78
+ def ok?(doc)
79
+ doc['ok'] == 1.0 || doc['ok'] == true
80
+ end
69
81
  end
70
82
  end