mongodb-mongo 0.12 → 0.13

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