mongodb-mongo 0.12 → 0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/README.rdoc +12 -12
  2. data/Rakefile +1 -1
  3. data/bin/bson_benchmark.rb +1 -1
  4. data/bin/mongo_console +3 -3
  5. data/bin/run_test_script +2 -2
  6. data/bin/standard_benchmark +3 -3
  7. data/examples/admin.rb +3 -3
  8. data/examples/benchmarks.rb +2 -2
  9. data/examples/blog.rb +4 -4
  10. data/examples/capped.rb +3 -3
  11. data/examples/cursor.rb +3 -3
  12. data/examples/gridfs.rb +4 -4
  13. data/examples/index_test.rb +11 -11
  14. data/examples/info.rb +3 -3
  15. data/examples/queries.rb +3 -3
  16. data/examples/simple.rb +3 -3
  17. data/examples/strict.rb +3 -3
  18. data/examples/types.rb +4 -9
  19. data/lib/mongo.rb +35 -3
  20. data/lib/mongo/admin.rb +56 -60
  21. data/lib/mongo/collection.rb +368 -320
  22. data/lib/mongo/connection.rb +166 -0
  23. data/lib/mongo/cursor.rb +206 -209
  24. data/lib/mongo/db.rb +478 -489
  25. data/lib/mongo/errors.rb +8 -9
  26. data/lib/mongo/gridfs/chunk.rb +66 -70
  27. data/lib/mongo/gridfs/grid_store.rb +406 -410
  28. data/lib/mongo/message/get_more_message.rb +8 -13
  29. data/lib/mongo/message/insert_message.rb +7 -11
  30. data/lib/mongo/message/kill_cursors_message.rb +7 -12
  31. data/lib/mongo/message/message.rb +58 -62
  32. data/lib/mongo/message/message_header.rb +19 -24
  33. data/lib/mongo/message/msg_message.rb +5 -9
  34. data/lib/mongo/message/opcodes.rb +10 -15
  35. data/lib/mongo/message/query_message.rb +42 -46
  36. data/lib/mongo/message/remove_message.rb +8 -12
  37. data/lib/mongo/message/update_message.rb +9 -13
  38. data/lib/mongo/query.rb +84 -88
  39. data/lib/mongo/types/binary.rb +13 -17
  40. data/lib/mongo/types/code.rb +9 -13
  41. data/lib/mongo/types/dbref.rb +10 -14
  42. data/lib/mongo/types/objectid.rb +103 -107
  43. data/lib/mongo/types/regexp_of_holding.rb +18 -22
  44. data/lib/mongo/types/undefined.rb +7 -10
  45. data/lib/mongo/util/bson.rb +4 -9
  46. data/lib/mongo/util/xml_to_ruby.rb +1 -3
  47. data/mongo-ruby-driver.gemspec +33 -32
  48. data/{tests → test}/mongo-qa/_common.rb +1 -1
  49. data/{tests → test}/mongo-qa/admin +1 -1
  50. data/{tests → test}/mongo-qa/capped +1 -1
  51. data/{tests → test}/mongo-qa/count1 +4 -4
  52. data/{tests → test}/mongo-qa/dbs +1 -1
  53. data/{tests → test}/mongo-qa/find +1 -1
  54. data/{tests → test}/mongo-qa/find1 +1 -1
  55. data/{tests → test}/mongo-qa/gridfs_in +2 -2
  56. data/{tests → test}/mongo-qa/gridfs_out +2 -2
  57. data/{tests → test}/mongo-qa/indices +2 -2
  58. data/{tests → test}/mongo-qa/remove +1 -1
  59. data/{tests → test}/mongo-qa/stress1 +1 -1
  60. data/{tests → test}/mongo-qa/test1 +1 -1
  61. data/{tests → test}/mongo-qa/update +1 -1
  62. data/{tests → test}/test_admin.rb +3 -3
  63. data/{tests → test}/test_bson.rb +4 -4
  64. data/{tests → test}/test_byte_buffer.rb +0 -0
  65. data/{tests → test}/test_chunk.rb +4 -4
  66. data/{tests → test}/test_collection.rb +42 -4
  67. data/{tests/test_mongo.rb → test/test_connection.rb} +35 -11
  68. data/test/test_cursor.rb +223 -0
  69. data/{tests → test}/test_db.rb +12 -12
  70. data/{tests → test}/test_db_api.rb +28 -33
  71. data/{tests → test}/test_db_connection.rb +3 -3
  72. data/{tests → test}/test_grid_store.rb +4 -4
  73. data/{tests → test}/test_message.rb +1 -1
  74. data/{tests → test}/test_objectid.rb +3 -3
  75. data/{tests → test}/test_ordered_hash.rb +0 -0
  76. data/{tests → test}/test_round_trip.rb +6 -2
  77. data/{tests → test}/test_threading.rb +3 -3
  78. data/test/test_xgen.rb +73 -0
  79. metadata +33 -32
  80. data/lib/mongo/mongo.rb +0 -164
  81. data/tests/test_cursor.rb +0 -121
data/lib/mongo/db.rb CHANGED
@@ -23,549 +23,538 @@ require 'mongo/query'
23
23
  require 'mongo/util/ordered_hash.rb'
24
24
  require 'mongo/admin'
25
25
 
26
- module XGen
27
- module Mongo
28
- module Driver
29
-
30
- # A Mongo database.
31
- class DB
32
-
33
- SYSTEM_NAMESPACE_COLLECTION = "system.namespaces"
34
- SYSTEM_INDEX_COLLECTION = "system.indexes"
35
- SYSTEM_PROFILE_COLLECTION = "system.profile"
36
- SYSTEM_USER_COLLECTION = "system.users"
37
- SYSTEM_COMMAND_COLLECTION = "$cmd"
38
-
39
- # Strict mode enforces collection existence checks. When +true+,
40
- # asking for a collection that does not exist or trying to create a
41
- # collection that already exists raises an error.
42
- #
43
- # Strict mode is off (+false+) by default. Its value can be changed at
44
- # any time.
45
- attr_writer :strict
46
-
47
- # Returns the value of the +strict+ flag.
48
- def strict?; @strict; end
49
-
50
- # The name of the database.
51
- attr_reader :name
52
-
53
- # Host to which we are currently connected.
54
- attr_reader :host
55
- # Port to which we are currently connected.
56
- attr_reader :port
57
-
58
- # An array of [host, port] pairs.
59
- attr_reader :nodes
60
-
61
- # The database's socket. For internal (and Cursor) use only.
62
- attr_reader :socket
63
-
64
- def slave_ok?; @slave_ok; end
65
- def auto_reconnect?; @auto_reconnect; end
66
-
67
- # A primary key factory object (or +nil+). See the README.doc file or
68
- # DB#new for details.
69
- attr_reader :pk_factory
70
-
71
- def pk_factory=(pk_factory)
72
- raise "error: can not change PK factory" if @pk_factory
73
- @pk_factory = pk_factory
74
- end
26
+ module Mongo
75
27
 
76
- # Instances of DB are normally obtained by calling Mongo#db.
77
- #
78
- # db_name :: The database name
79
- #
80
- # nodes :: An array of [host, port] pairs. See Mongo#new, which offers
81
- # a more flexible way of defining nodes.
82
- #
83
- # options :: A hash of options.
84
- #
85
- # Options:
86
- #
87
- # :strict :: If true, collections must exist to be accessed and must
88
- # not exist to be created. See #collection and
89
- # #create_collection.
90
- #
91
- # :pk :: A primary key factory object that must respond to :create_pk,
92
- # which should take a hash and return a hash which merges the
93
- # original hash with any primary key fields the factory wishes
94
- # to inject. (NOTE: if the object already has a primary key,
95
- # the factory should not inject a new key; this means that the
96
- # object is being used in a repsert but it already exists.) The
97
- # idea here is that when ever a record is inserted, the :pk
98
- # object's +create_pk+ method will be called and the new hash
99
- # returned will be inserted.
100
- #
101
- # :slave_ok :: Only used if +nodes+ contains only one host/port. If
102
- # false, when connecting to that host/port we check to
103
- # see if the server is the master. If it is not, an error
104
- # is thrown.
105
- #
106
- # :auto_reconnect :: If the connection gets closed (for example, we
107
- # have a server pair and saw the "not master"
108
- # error, which closes the connection), then
109
- # automatically try to reconnect to the master or
110
- # to the single server we have been given. Defaults
111
- # to +false+.
112
- #
113
- # When a DB object first connects to a pair, it will find the master
114
- # instance and connect to that one. On socket error or if we recieve a
115
- # "not master" error, we again find the master of the pair.
116
- def initialize(db_name, nodes, options={})
117
- case db_name
118
- when Symbol, String
119
- else
120
- raise TypeError, "db_name must be a string or symbol"
121
- end
28
+ # A Mongo database.
29
+ class DB
122
30
 
123
- [" ", ".", "$", "/", "\\"].each do |invalid_char|
124
- if db_name.include? invalid_char
125
- raise InvalidName, "database names cannot contain the character '#{invalid_char}'"
126
- end
127
- end
128
- if db_name.empty?
129
- raise InvalidName, "database name cannot be the empty string"
130
- end
31
+ SYSTEM_NAMESPACE_COLLECTION = "system.namespaces"
32
+ SYSTEM_INDEX_COLLECTION = "system.indexes"
33
+ SYSTEM_PROFILE_COLLECTION = "system.profile"
34
+ SYSTEM_USER_COLLECTION = "system.users"
35
+ SYSTEM_COMMAND_COLLECTION = "$cmd"
131
36
 
132
- @name, @nodes = db_name, nodes
133
- @strict = options[:strict]
134
- @pk_factory = options[:pk]
135
- @slave_ok = options[:slave_ok] && @nodes.length == 1 # only OK if one node
136
- @auto_reconnect = options[:auto_reconnect]
137
- @semaphore = Object.new
138
- @semaphore.extend Mutex_m
139
- @socket = nil
140
- connect_to_master
141
- end
37
+ # Strict mode enforces collection existence checks. When +true+,
38
+ # asking for a collection that does not exist or trying to create a
39
+ # collection that already exists raises an error.
40
+ #
41
+ # Strict mode is off (+false+) by default. Its value can be changed at
42
+ # any time.
43
+ attr_writer :strict
142
44
 
143
- def connect_to_master
144
- close if @socket
145
- @host = @port = nil
146
- @nodes.detect { |hp|
147
- @host, @port = *hp
148
- begin
149
- @socket = TCPSocket.new(@host, @port)
150
- @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
151
-
152
- # Check for master. Can't call master? because it uses mutex,
153
- # which may already be in use during this call.
154
- semaphore_is_locked = @semaphore.locked?
155
- @semaphore.unlock if semaphore_is_locked
156
- is_master = master?
157
- @semaphore.lock if semaphore_is_locked
158
-
159
- break if @slave_ok || is_master
160
- rescue SocketError, SystemCallError, IOError => ex
161
- close if @socket
162
- end
163
- @socket
164
- }
165
- raise "error: failed to connect to any given host:port" unless @socket
166
- end
45
+ # Returns the value of the +strict+ flag.
46
+ def strict?; @strict; end
167
47
 
168
- # Returns true if +username+ has +password+ in
169
- # +SYSTEM_USER_COLLECTION+. +name+ is username, +password+ is
170
- # plaintext password.
171
- def authenticate(username, password)
172
- doc = db_command(:getnonce => 1)
173
- raise "error retrieving nonce: #{doc}" unless ok?(doc)
174
- nonce = doc['nonce']
175
-
176
- auth = OrderedHash.new
177
- auth['authenticate'] = 1
178
- auth['user'] = username
179
- auth['nonce'] = nonce
180
- auth['key'] = Digest::MD5.hexdigest("#{nonce}#{username}#{hash_password(username, password)}")
181
- ok?(db_command(auth))
182
- end
48
+ # The name of the database.
49
+ attr_reader :name
183
50
 
184
- # Deauthorizes use for this database for this connection.
185
- def logout
186
- doc = db_command(:logout => 1)
187
- raise "error logging out: #{doc.inspect}" unless ok?(doc)
188
- end
51
+ # Host to which we are currently connected.
52
+ attr_reader :host
53
+ # Port to which we are currently connected.
54
+ attr_reader :port
189
55
 
190
- # Returns an array of collection names in this database.
191
- def collection_names
192
- names = collections_info.collect { |doc| doc['name'] || '' }
193
- names = names.delete_if {|name| name.index(@name).nil? || name.index('$')}
194
- names.map {|name| name.sub(@name + '.', '')}
195
- end
56
+ # An array of [host, port] pairs.
57
+ attr_reader :nodes
196
58
 
197
- # Returns a cursor over query result hashes. Each hash contains a
198
- # 'name' string and optionally an 'options' hash. If +coll_name+ is
199
- # specified, an array of length 1 is returned.
200
- def collections_info(coll_name=nil)
201
- selector = {}
202
- selector[:name] = full_coll_name(coll_name) if coll_name
203
- query(Collection.new(self, SYSTEM_NAMESPACE_COLLECTION), Query.new(selector))
204
- end
59
+ # The database's socket. For internal (and Cursor) use only.
60
+ attr_reader :socket
205
61
 
206
- # Create a collection. If +strict+ is false, will return existing or
207
- # new collection. If +strict+ is true, will raise an error if
208
- # collection +name+ already exists.
209
- #
210
- # Options is an optional hash:
211
- #
212
- # :capped :: Boolean. If not specified, capped is +false+.
213
- #
214
- # :size :: If +capped+ is +true+, specifies the maximum number of
215
- # bytes. If +false+, specifies the initial extent of the
216
- # collection.
217
- #
218
- # :max :: Max number of records in a capped collection. Optional.
219
- def create_collection(name, options={})
220
- # First check existence
221
- if collection_names.include?(name)
222
- if strict?
223
- raise "Collection #{name} already exists. Currently in strict mode."
224
- else
225
- return Collection.new(self, name)
226
- end
227
- end
62
+ def slave_ok?; @slave_ok; end
63
+ def auto_reconnect?; @auto_reconnect; end
228
64
 
229
- # Create new collection
230
- oh = OrderedHash.new
231
- oh[:create] = name
232
- doc = db_command(oh.merge(options || {}))
233
- ok = doc['ok']
234
- return Collection.new(self, name) if ok.kind_of?(Numeric) && (ok.to_i == 1 || ok.to_i == 0)
235
- raise "Error creating collection: #{doc.inspect}"
236
- end
65
+ # A primary key factory object (or +nil+). See the README.doc file or
66
+ # DB#new for details.
67
+ attr_reader :pk_factory
237
68
 
238
- def admin
239
- Admin.new(self)
240
- end
69
+ def pk_factory=(pk_factory)
70
+ raise "error: can not change PK factory" if @pk_factory
71
+ @pk_factory = pk_factory
72
+ end
241
73
 
242
- # Return a collection. If +strict+ is false, will return existing or
243
- # new collection. If +strict+ is true, will raise an error if
244
- # collection +name+ does not already exists.
245
- def collection(name)
246
- return Collection.new(self, name) if !strict? || collection_names.include?(name)
247
- raise "Collection #{name} doesn't exist. Currently in strict mode."
74
+ # Instances of DB are normally obtained by calling Mongo#db.
75
+ #
76
+ # db_name :: The database name
77
+ #
78
+ # nodes :: An array of [host, port] pairs. See Connection#new, which offers
79
+ # a more flexible way of defining nodes.
80
+ #
81
+ # options :: A hash of options.
82
+ #
83
+ # Options:
84
+ #
85
+ # :strict :: If true, collections must exist to be accessed and must
86
+ # not exist to be created. See #collection and
87
+ # #create_collection.
88
+ #
89
+ # :pk :: A primary key factory object that must respond to :create_pk,
90
+ # which should take a hash and return a hash which merges the
91
+ # original hash with any primary key fields the factory wishes
92
+ # to inject. (NOTE: if the object already has a primary key,
93
+ # the factory should not inject a new key; this means that the
94
+ # object is being used in a repsert but it already exists.) The
95
+ # idea here is that when ever a record is inserted, the :pk
96
+ # object's +create_pk+ method will be called and the new hash
97
+ # returned will be inserted.
98
+ #
99
+ # :slave_ok :: Only used if +nodes+ contains only one host/port. If
100
+ # false, when connecting to that host/port we check to
101
+ # see if the server is the master. If it is not, an error
102
+ # is thrown.
103
+ #
104
+ # :auto_reconnect :: If the connection gets closed (for example, we
105
+ # have a server pair and saw the "not master"
106
+ # error, which closes the connection), then
107
+ # automatically try to reconnect to the master or
108
+ # to the single server we have been given. Defaults
109
+ # to +false+.
110
+ #
111
+ # When a DB object first connects to a pair, it will find the master
112
+ # instance and connect to that one. On socket error or if we recieve a
113
+ # "not master" error, we again find the master of the pair.
114
+ def initialize(db_name, nodes, options={})
115
+ case db_name
116
+ when Symbol, String
117
+ else
118
+ raise TypeError, "db_name must be a string or symbol"
119
+ end
120
+
121
+ [" ", ".", "$", "/", "\\"].each do |invalid_char|
122
+ if db_name.include? invalid_char
123
+ raise InvalidName, "database names cannot contain the character '#{invalid_char}'"
248
124
  end
249
- alias_method :[], :collection
125
+ end
126
+ if db_name.empty?
127
+ raise InvalidName, "database name cannot be the empty string"
128
+ end
250
129
 
251
- # Drop collection +name+. Returns +true+ on success or if the
252
- # collection does not exist, +false+ otherwise.
253
- def drop_collection(name)
254
- return true unless collection_names.include?(name)
130
+ @name, @nodes = db_name, nodes
131
+ @strict = options[:strict]
132
+ @pk_factory = options[:pk]
133
+ @slave_ok = options[:slave_ok] && @nodes.length == 1 # only OK if one node
134
+ @auto_reconnect = options[:auto_reconnect]
135
+ @semaphore = Object.new
136
+ @semaphore.extend Mutex_m
137
+ @socket = nil
138
+ connect_to_master
139
+ end
255
140
 
256
- ok?(db_command(:drop => name))
141
+ def connect_to_master
142
+ close if @socket
143
+ @host = @port = nil
144
+ @nodes.detect { |hp|
145
+ @host, @port = *hp
146
+ begin
147
+ @socket = TCPSocket.new(@host, @port)
148
+ @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
149
+
150
+ # Check for master. Can't call master? because it uses mutex,
151
+ # which may already be in use during this call.
152
+ semaphore_is_locked = @semaphore.locked?
153
+ @semaphore.unlock if semaphore_is_locked
154
+ is_master = master?
155
+ @semaphore.lock if semaphore_is_locked
156
+
157
+ break if @slave_ok || is_master
158
+ rescue SocketError, SystemCallError, IOError => ex
159
+ close if @socket
257
160
  end
161
+ @socket
162
+ }
163
+ raise "error: failed to connect to any given host:port" unless @socket
164
+ end
258
165
 
259
- # Returns the error message from the most recently executed database
260
- # operation for this connection, or +nil+ if there was no error.
261
- #
262
- # Note: as of this writing, errors are only detected on the db server
263
- # for certain kinds of operations (writes). The plan is to change this
264
- # so that all operations will set the error if needed.
265
- def error
266
- doc = db_command(:getlasterror => 1)
267
- raise "error retrieving last error: #{doc}" unless ok?(doc)
268
- doc['err']
269
- end
166
+ # Returns true if +username+ has +password+ in
167
+ # +SYSTEM_USER_COLLECTION+. +name+ is username, +password+ is
168
+ # plaintext password.
169
+ def authenticate(username, password)
170
+ doc = db_command(:getnonce => 1)
171
+ raise "error retrieving nonce: #{doc}" unless ok?(doc)
172
+ nonce = doc['nonce']
173
+
174
+ auth = OrderedHash.new
175
+ auth['authenticate'] = 1
176
+ auth['user'] = username
177
+ auth['nonce'] = nonce
178
+ auth['key'] = Digest::MD5.hexdigest("#{nonce}#{username}#{hash_password(username, password)}")
179
+ ok?(db_command(auth))
180
+ end
270
181
 
271
- # Returns +true+ if an error was caused by the most recently executed
272
- # database operation.
273
- #
274
- # Note: as of this writing, errors are only detected on the db server
275
- # for certain kinds of operations (writes). The plan is to change this
276
- # so that all operations will set the error if needed.
277
- def error?
278
- error != nil
279
- end
182
+ # Deauthorizes use for this database for this connection.
183
+ def logout
184
+ doc = db_command(:logout => 1)
185
+ raise "error logging out: #{doc.inspect}" unless ok?(doc)
186
+ end
280
187
 
281
- # Get the most recent error to have occured on this database
282
- #
283
- # Only returns errors that have occured since the last call to
284
- # DB#reset_error_history - returns +nil+ if there is no such error.
285
- def previous_error
286
- error = db_command(:getpreverror => 1)
287
- if error["err"]
288
- error
289
- else
290
- nil
291
- end
292
- end
188
+ # Returns an array of collection names in this database.
189
+ def collection_names
190
+ names = collections_info.collect { |doc| doc['name'] || '' }
191
+ names = names.delete_if {|name| name.index(@name).nil? || name.index('$')}
192
+ names.map {|name| name.sub(@name + '.', '')}
193
+ end
293
194
 
294
- # Reset the error history of this database
295
- #
296
- # Calls to DB#previous_error will only return errors that have occurred
297
- # since the most recent call to this method.
298
- def reset_error_history
299
- db_command(:reseterror => 1)
300
- end
195
+ # Returns a cursor over query result hashes. Each hash contains a
196
+ # 'name' string and optionally an 'options' hash. If +coll_name+ is
197
+ # specified, an array of length 1 is returned.
198
+ def collections_info(coll_name=nil)
199
+ selector = {}
200
+ selector[:name] = full_coll_name(coll_name) if coll_name
201
+ query(Collection.new(self, SYSTEM_NAMESPACE_COLLECTION), Query.new(selector))
202
+ end
301
203
 
302
- # Returns true if this database is a master (or is not paired with any
303
- # other database), false if it is a slave.
304
- def master?
305
- doc = db_command(:ismaster => 1)
306
- is_master = doc['ismaster']
307
- ok?(doc) && is_master.kind_of?(Numeric) && is_master.to_i == 1
204
+ # Create a collection. If +strict+ is false, will return existing or
205
+ # new collection. If +strict+ is true, will raise an error if
206
+ # collection +name+ already exists.
207
+ #
208
+ # Options is an optional hash:
209
+ #
210
+ # :capped :: Boolean. If not specified, capped is +false+.
211
+ #
212
+ # :size :: If +capped+ is +true+, specifies the maximum number of
213
+ # bytes. If +false+, specifies the initial extent of the
214
+ # collection.
215
+ #
216
+ # :max :: Max number of records in a capped collection. Optional.
217
+ def create_collection(name, options={})
218
+ # First check existence
219
+ if collection_names.include?(name)
220
+ if strict?
221
+ raise "Collection #{name} already exists. Currently in strict mode."
222
+ else
223
+ return Collection.new(self, name)
308
224
  end
225
+ end
309
226
 
310
- # Returns a string of the form "host:port" that points to the master
311
- # database. Works even if this is the master database.
312
- def master
313
- doc = db_command(:ismaster => 1)
314
- is_master = doc['ismaster']
315
- raise "Error retrieving master database: #{doc.inspect}" unless ok?(doc) && is_master.kind_of?(Numeric)
316
- case is_master.to_i
317
- when 1
318
- "#@host:#@port"
319
- else
320
- doc['remote']
321
- end
322
- end
227
+ # Create new collection
228
+ oh = OrderedHash.new
229
+ oh[:create] = name
230
+ doc = db_command(oh.merge(options || {}))
231
+ ok = doc['ok']
232
+ return Collection.new(self, name) if ok.kind_of?(Numeric) && (ok.to_i == 1 || ok.to_i == 0)
233
+ raise "Error creating collection: #{doc.inspect}"
234
+ end
323
235
 
324
- # Close the connection to the database.
325
- def close
326
- if @socket
327
- s = @socket
328
- @socket = nil
329
- s.close
330
- end
331
- end
236
+ def admin
237
+ Admin.new(self)
238
+ end
332
239
 
333
- def connected?
334
- @socket != nil
335
- end
240
+ # Return a collection. If +strict+ is false, will return existing or
241
+ # new collection. If +strict+ is true, will raise an error if
242
+ # collection +name+ does not already exists.
243
+ def collection(name)
244
+ return Collection.new(self, name) if !strict? || collection_names.include?(name)
245
+ raise "Collection #{name} doesn't exist. Currently in strict mode."
246
+ end
247
+ alias_method :[], :collection
336
248
 
337
- def receive_full(length)
338
- message = ""
339
- while message.length < length do
340
- chunk = @socket.recv(length - message.length)
341
- raise "connection closed" unless chunk.length > 0
342
- message += chunk
343
- end
344
- message
345
- end
249
+ # Drop collection +name+. Returns +true+ on success or if the
250
+ # collection does not exist, +false+ otherwise.
251
+ def drop_collection(name)
252
+ return true unless collection_names.include?(name)
346
253
 
347
- # Send a MsgMessage to the database.
348
- def send_message(msg)
349
- send_to_db(MsgMessage.new(msg))
350
- end
254
+ ok?(db_command(:drop => name))
255
+ end
351
256
 
352
- # Returns a Cursor over the query results.
353
- #
354
- # Note that the query gets sent lazily; the cursor calls
355
- # #send_query_message when needed. If the caller never requests an
356
- # object from the cursor, the query never gets sent.
357
- def query(collection, query, admin=false)
358
- Cursor.new(self, collection, query, admin)
359
- end
257
+ # Returns the error message from the most recently executed database
258
+ # operation for this connection, or +nil+ if there was no error.
259
+ #
260
+ # Note: as of this writing, errors are only detected on the db server
261
+ # for certain kinds of operations (writes). The plan is to change this
262
+ # so that all operations will set the error if needed.
263
+ def error
264
+ doc = db_command(:getlasterror => 1)
265
+ raise "error retrieving last error: #{doc}" unless ok?(doc)
266
+ doc['err']
267
+ end
360
268
 
361
- # Used by a Cursor to lazily send the query to the database.
362
- def send_query_message(query_message)
363
- send_to_db(query_message)
364
- end
269
+ # Returns +true+ if an error was caused by the most recently executed
270
+ # database operation.
271
+ #
272
+ # Note: as of this writing, errors are only detected on the db server
273
+ # for certain kinds of operations (writes). The plan is to change this
274
+ # so that all operations will set the error if needed.
275
+ def error?
276
+ error != nil
277
+ end
365
278
 
366
- # Remove the records that match +selector+ from +collection_name+.
367
- # Normally called by Collection#remove or Collection#clear.
368
- def remove_from_db(collection_name, selector)
369
- _synchronize {
370
- send_to_db(RemoveMessage.new(@name, collection_name, selector))
371
- }
372
- end
279
+ # Get the most recent error to have occured on this database
280
+ #
281
+ # Only returns errors that have occured since the last call to
282
+ # DB#reset_error_history - returns +nil+ if there is no such error.
283
+ def previous_error
284
+ error = db_command(:getpreverror => 1)
285
+ if error["err"]
286
+ error
287
+ else
288
+ nil
289
+ end
290
+ end
373
291
 
374
- # Update records in +collection_name+ that match +selector+ by
375
- # applying +obj+ as an update. Normally called by Collection#replace.
376
- def replace_in_db(collection_name, selector, obj)
377
- _synchronize {
378
- send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, false))
379
- }
380
- end
292
+ # Reset the error history of this database
293
+ #
294
+ # Calls to DB#previous_error will only return errors that have occurred
295
+ # since the most recent call to this method.
296
+ def reset_error_history
297
+ db_command(:reseterror => 1)
298
+ end
381
299
 
382
- # DEPRECATED - use Collection#update instead
383
- def modify_in_db(collection_name, selector, obj)
384
- warn "DB#modify_in_db is deprecated and will be removed. Please use Collection#update instead."
385
- replace_in_db(collection_name, selector, obj)
386
- end
300
+ # Returns true if this database is a master (or is not paired with any
301
+ # other database), false if it is a slave.
302
+ def master?
303
+ doc = db_command(:ismaster => 1)
304
+ is_master = doc['ismaster']
305
+ ok?(doc) && is_master.kind_of?(Numeric) && is_master.to_i == 1
306
+ end
387
307
 
388
- # Update records in +collection_name+ that match +selector+ by
389
- # applying +obj+ as an update. If no match, inserts (???). Normally
390
- # called by Collection#repsert.
391
- def repsert_in_db(collection_name, selector, obj)
392
- _synchronize {
393
- obj = @pk_factory.create_pk(obj) if @pk_factory
394
- send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, true))
395
- obj
396
- }
397
- end
308
+ # Returns a string of the form "host:port" that points to the master
309
+ # database. Works even if this is the master database.
310
+ def master
311
+ doc = db_command(:ismaster => 1)
312
+ is_master = doc['ismaster']
313
+ raise "Error retrieving master database: #{doc.inspect}" unless ok?(doc) && is_master.kind_of?(Numeric)
314
+ case is_master.to_i
315
+ when 1
316
+ "#@host:#@port"
317
+ else
318
+ doc['remote']
319
+ end
320
+ end
398
321
 
399
- # Return the number of records in +collection_name+ that match
400
- # +selector+. If +selector+ is +nil+ or an empty hash, returns the
401
- # count of all records. Normally called by Collection#count.
402
- def count(collection_name, selector={})
403
- oh = OrderedHash.new
404
- oh[:count] = collection_name
405
- oh[:query] = selector || {}
406
- doc = db_command(oh)
407
- return doc['n'].to_i if ok?(doc)
408
- return 0 if doc['errmsg'] == "ns missing"
409
- raise "Error with count command: #{doc.inspect}"
410
- end
322
+ # Close the connection to the database.
323
+ def close
324
+ if @socket
325
+ s = @socket
326
+ @socket = nil
327
+ s.close
328
+ end
329
+ end
411
330
 
412
- # Dereference a DBRef, getting the document it points to.
413
- def dereference(dbref)
414
- collection(dbref.namespace).find_one("_id" => dbref.object_id)
415
- end
331
+ def connected?
332
+ @socket != nil
333
+ end
416
334
 
417
- # Evaluate a JavaScript expression on MongoDB.
418
- # +code+ should be a string or Code instance containing a JavaScript
419
- # expression. Additional arguments will be passed to that expression
420
- # when it is run on the server.
421
- def eval(code, *args)
422
- if not code.is_a? Code
423
- code = Code.new(code)
424
- end
335
+ def receive_full(length)
336
+ message = ""
337
+ while message.length < length do
338
+ chunk = @socket.recv(length - message.length)
339
+ raise "connection closed" unless chunk.length > 0
340
+ message += chunk
341
+ end
342
+ message
343
+ end
425
344
 
426
- oh = OrderedHash.new
427
- oh[:$eval] = code
428
- oh[:args] = args
429
- doc = db_command(oh)
430
- return doc['retval'] if ok?(doc)
431
- raise "Error with eval command: #{doc.inspect}"
432
- end
345
+ # Send a MsgMessage to the database.
346
+ def send_message(msg)
347
+ send_to_db(MsgMessage.new(msg))
348
+ end
433
349
 
434
- # Rename collection +from+ to +to+. Meant to be called by
435
- # Collection#rename.
436
- def rename_collection(from, to)
437
- oh = OrderedHash.new
438
- oh[:renameCollection] = "#{@name}.#{from}"
439
- oh[:to] = "#{@name}.#{to}"
440
- doc = db_command(oh, true)
441
- raise "Error renaming collection: #{doc.inspect}" unless ok?(doc)
442
- end
350
+ # Returns a Cursor over the query results.
351
+ #
352
+ # Note that the query gets sent lazily; the cursor calls
353
+ # #send_query_message when needed. If the caller never requests an
354
+ # object from the cursor, the query never gets sent.
355
+ def query(collection, query, admin=false)
356
+ Cursor.new(self, collection, query, admin)
357
+ end
443
358
 
444
- # Drop index +name+ from +collection_name+. Normally called from
445
- # Collection#drop_index or Collection#drop_indexes.
446
- def drop_index(collection_name, name)
447
- oh = OrderedHash.new
448
- oh[:deleteIndexes] = collection_name
449
- oh[:index] = name
450
- doc = db_command(oh)
451
- raise "Error with drop_index command: #{doc.inspect}" unless ok?(doc)
452
- end
359
+ # Used by a Cursor to lazily send the query to the database.
360
+ def send_query_message(query_message)
361
+ send_to_db(query_message)
362
+ end
453
363
 
454
- # Get information on the indexes for the collection +collection_name+.
455
- # Normally called by Collection#index_information. Returns a hash where
456
- # the keys are index names (as returned by Collection#create_index and
457
- # the values are lists of [key, direction] pairs specifying the index
458
- # (as passed to Collection#create_index).
459
- def index_information(collection_name)
460
- sel = {:ns => full_coll_name(collection_name)}
461
- info = {}
462
- query(Collection.new(self, SYSTEM_INDEX_COLLECTION), Query.new(sel)).each { |index|
463
- info[index['name']] = index['key'].to_a
464
- }
465
- info
466
- end
364
+ # Remove the records that match +selector+ from +collection_name+.
365
+ # Normally called by Collection#remove or Collection#clear.
366
+ def remove_from_db(collection_name, selector)
367
+ _synchronize {
368
+ send_to_db(RemoveMessage.new(@name, collection_name, selector))
369
+ }
370
+ end
467
371
 
468
- # Create a new index on +collection_name+. +field_or_spec+
469
- # should be either a single field name or a Array of [field name,
470
- # direction] pairs. Directions should be specified as
471
- # XGen::Mongo::ASCENDING or XGen::Mongo::DESCENDING. Normally called
472
- # by Collection#create_index. If +unique+ is true the index will
473
- # enforce a uniqueness constraint.
474
- def create_index(collection_name, field_or_spec, unique=false)
475
- field_h = OrderedHash.new
476
- if field_or_spec.is_a?(String) || field_or_spec.is_a?(Symbol)
477
- field_h[field_or_spec.to_s] = 1
478
- else
479
- field_or_spec.each { |f| field_h[f[0].to_s] = f[1] }
480
- end
481
- name = gen_index_name(field_h)
482
- sel = {
483
- :name => name,
484
- :ns => full_coll_name(collection_name),
485
- :key => field_h,
486
- :unique => unique
487
- }
488
- _synchronize {
489
- send_to_db(InsertMessage.new(@name, SYSTEM_INDEX_COLLECTION, false, sel))
490
- }
491
- name
492
- end
372
+ # Update records in +collection_name+ that match +selector+ by
373
+ # applying +obj+ as an update. Normally called by Collection#replace.
374
+ def replace_in_db(collection_name, selector, obj)
375
+ _synchronize {
376
+ send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, false))
377
+ }
378
+ end
493
379
 
494
- # Insert +objects+ into +collection_name+. Normally called by
495
- # Collection#insert. Returns a new array containing the _ids
496
- # of the inserted documents.
497
- def insert_into_db(collection_name, objects)
498
- _synchronize {
499
- if @pk_factory
500
- objects.collect! { |o|
501
- @pk_factory.create_pk(o)
502
- }
503
- else
504
- objects = objects.collect do |o|
505
- o[:_id] || o['_id'] ? o : o.merge!(:_id => ObjectID.new)
506
- end
507
- end
508
- send_to_db(InsertMessage.new(@name, collection_name, true, *objects))
509
- objects.collect { |o| o[:_id] || o['_id'] }
510
- }
511
- end
380
+ # DEPRECATED - use Collection#update instead
381
+ def modify_in_db(collection_name, selector, obj)
382
+ warn "DB#modify_in_db is deprecated and will be removed. Please use Collection#update instead."
383
+ replace_in_db(collection_name, selector, obj)
384
+ end
512
385
 
513
- def send_to_db(message)
514
- connect_to_master if !connected? && @auto_reconnect
515
- begin
516
- @socket.print(message.buf.to_s)
517
- @socket.flush
518
- rescue => ex
519
- close
520
- raise ex
521
- end
522
- end
386
+ # Update records in +collection_name+ that match +selector+ by
387
+ # applying +obj+ as an update. If no match, inserts (???). Normally
388
+ # called by Collection#repsert.
389
+ def repsert_in_db(collection_name, selector, obj)
390
+ _synchronize {
391
+ obj = @pk_factory.create_pk(obj) if @pk_factory
392
+ send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, true))
393
+ obj
394
+ }
395
+ end
523
396
 
524
- def full_coll_name(collection_name)
525
- "#{@name}.#{collection_name}"
526
- end
397
+ # DEPRECATED - use Collection.find(selector).count() instead
398
+ def count(collection_name, selector={})
399
+ warn "DB#count is deprecated and will be removed. Please use Collection.find(selector).count instead."
400
+ collection(collection_name).find(selector).count()
401
+ end
527
402
 
528
- # Return +true+ if +doc+ contains an 'ok' field with the value 1.
529
- def ok?(doc)
530
- ok = doc['ok']
531
- ok.kind_of?(Numeric) && ok.to_i == 1
532
- end
403
+ # Dereference a DBRef, getting the document it points to.
404
+ def dereference(dbref)
405
+ collection(dbref.namespace).find_one("_id" => dbref.object_id)
406
+ end
533
407
 
534
- # DB commands need to be ordered, so selector must be an OrderedHash
535
- # (or a Hash with only one element). What DB commands really need is
536
- # that the "command" key be first.
537
- #
538
- # Do not call this. Intended for driver use only.
539
- def db_command(selector, use_admin_db=false)
540
- if !selector.kind_of?(OrderedHash)
541
- if !selector.kind_of?(Hash) || selector.keys.length > 1
542
- raise "db_command must be given an OrderedHash when there is more than one key"
543
- end
544
- end
408
+ # Evaluate a JavaScript expression on MongoDB.
409
+ # +code+ should be a string or Code instance containing a JavaScript
410
+ # expression. Additional arguments will be passed to that expression
411
+ # when it is run on the server.
412
+ def eval(code, *args)
413
+ if not code.is_a? Code
414
+ code = Code.new(code)
415
+ end
545
416
 
546
- q = Query.new(selector)
547
- q.number_to_return = 1
548
- query(Collection.new(self, SYSTEM_COMMAND_COLLECTION), q, use_admin_db).next_object
549
- end
417
+ oh = OrderedHash.new
418
+ oh[:$eval] = code
419
+ oh[:args] = args
420
+ doc = db_command(oh)
421
+ return doc['retval'] if ok?(doc)
422
+ raise OperationFailure, "eval failed: #{doc['errmsg']}"
423
+ end
550
424
 
551
- def _synchronize &block
552
- @semaphore.synchronize &block
553
- end
425
+ # Rename collection +from+ to +to+. Meant to be called by
426
+ # Collection#rename.
427
+ def rename_collection(from, to)
428
+ oh = OrderedHash.new
429
+ oh[:renameCollection] = "#{@name}.#{from}"
430
+ oh[:to] = "#{@name}.#{to}"
431
+ doc = db_command(oh, true)
432
+ raise "Error renaming collection: #{doc.inspect}" unless ok?(doc)
433
+ end
554
434
 
555
- private
435
+ # Drop index +name+ from +collection_name+. Normally called from
436
+ # Collection#drop_index or Collection#drop_indexes.
437
+ def drop_index(collection_name, name)
438
+ oh = OrderedHash.new
439
+ oh[:deleteIndexes] = collection_name
440
+ oh[:index] = name
441
+ doc = db_command(oh)
442
+ raise "Error with drop_index command: #{doc.inspect}" unless ok?(doc)
443
+ end
556
444
 
557
- def hash_password(username, plaintext)
558
- Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}")
559
- end
445
+ # Get information on the indexes for the collection +collection_name+.
446
+ # Normally called by Collection#index_information. Returns a hash where
447
+ # the keys are index names (as returned by Collection#create_index and
448
+ # the values are lists of [key, direction] pairs specifying the index
449
+ # (as passed to Collection#create_index).
450
+ def index_information(collection_name)
451
+ sel = {:ns => full_coll_name(collection_name)}
452
+ info = {}
453
+ query(Collection.new(self, SYSTEM_INDEX_COLLECTION), Query.new(sel)).each { |index|
454
+ info[index['name']] = index['key'].to_a
455
+ }
456
+ info
457
+ end
458
+
459
+ # Create a new index on +collection_name+. +field_or_spec+
460
+ # should be either a single field name or a Array of [field name,
461
+ # direction] pairs. Directions should be specified as
462
+ # Mongo::ASCENDING or Mongo::DESCENDING. Normally called
463
+ # by Collection#create_index. If +unique+ is true the index will
464
+ # enforce a uniqueness constraint.
465
+ def create_index(collection_name, field_or_spec, unique=false)
466
+ field_h = OrderedHash.new
467
+ if field_or_spec.is_a?(String) || field_or_spec.is_a?(Symbol)
468
+ field_h[field_or_spec.to_s] = 1
469
+ else
470
+ field_or_spec.each { |f| field_h[f[0].to_s] = f[1] }
471
+ end
472
+ name = gen_index_name(field_h)
473
+ sel = {
474
+ :name => name,
475
+ :ns => full_coll_name(collection_name),
476
+ :key => field_h,
477
+ :unique => unique
478
+ }
479
+ _synchronize {
480
+ send_to_db(InsertMessage.new(@name, SYSTEM_INDEX_COLLECTION, false, sel))
481
+ }
482
+ name
483
+ end
560
484
 
561
- def gen_index_name(spec)
562
- temp = []
563
- spec.each_pair { |field, direction|
564
- temp = temp.push("#{field}_#{direction}")
485
+ # Insert +objects+ into +collection_name+. Normally called by
486
+ # Collection#insert. Returns a new array containing the _ids
487
+ # of the inserted documents.
488
+ def insert_into_db(collection_name, objects)
489
+ _synchronize {
490
+ if @pk_factory
491
+ objects.collect! { |o|
492
+ @pk_factory.create_pk(o)
565
493
  }
566
- return temp.join("_")
494
+ else
495
+ objects = objects.collect do |o|
496
+ o[:_id] || o['_id'] ? o : o.merge!(:_id => ObjectID.new)
497
+ end
567
498
  end
499
+ send_to_db(InsertMessage.new(@name, collection_name, true, *objects))
500
+ objects.collect { |o| o[:_id] || o['_id'] }
501
+ }
502
+ end
503
+
504
+ def send_to_db(message)
505
+ connect_to_master if !connected? && @auto_reconnect
506
+ begin
507
+ @socket.print(message.buf.to_s)
508
+ @socket.flush
509
+ rescue => ex
510
+ close
511
+ raise ex
568
512
  end
569
513
  end
514
+
515
+ def full_coll_name(collection_name)
516
+ "#{@name}.#{collection_name}"
517
+ end
518
+
519
+ # Return +true+ if +doc+ contains an 'ok' field with the value 1.
520
+ def ok?(doc)
521
+ ok = doc['ok']
522
+ ok.kind_of?(Numeric) && ok.to_i == 1
523
+ end
524
+
525
+ # DB commands need to be ordered, so selector must be an OrderedHash
526
+ # (or a Hash with only one element). What DB commands really need is
527
+ # that the "command" key be first.
528
+ #
529
+ # Do not call this. Intended for driver use only.
530
+ def db_command(selector, use_admin_db=false)
531
+ if !selector.kind_of?(OrderedHash)
532
+ if !selector.kind_of?(Hash) || selector.keys.length > 1
533
+ raise "db_command must be given an OrderedHash when there is more than one key"
534
+ end
535
+ end
536
+
537
+ q = Query.new(selector)
538
+ q.number_to_return = 1
539
+ query(Collection.new(self, SYSTEM_COMMAND_COLLECTION), q, use_admin_db).next_object
540
+ end
541
+
542
+ def _synchronize &block
543
+ @semaphore.synchronize &block
544
+ end
545
+
546
+ private
547
+
548
+ def hash_password(username, plaintext)
549
+ Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}")
550
+ end
551
+
552
+ def gen_index_name(spec)
553
+ temp = []
554
+ spec.each_pair { |field, direction|
555
+ temp = temp.push("#{field}_#{direction}")
556
+ }
557
+ return temp.join("_")
558
+ end
570
559
  end
571
560
  end