animehunter-mongo 0.9

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 (79) hide show
  1. data/README.rdoc +311 -0
  2. data/Rakefile +62 -0
  3. data/bin/bson_benchmark.rb +59 -0
  4. data/bin/mongo_console +21 -0
  5. data/bin/run_test_script +19 -0
  6. data/bin/standard_benchmark +109 -0
  7. data/examples/admin.rb +41 -0
  8. data/examples/benchmarks.rb +42 -0
  9. data/examples/blog.rb +76 -0
  10. data/examples/capped.rb +23 -0
  11. data/examples/cursor.rb +47 -0
  12. data/examples/gridfs.rb +87 -0
  13. data/examples/index_test.rb +125 -0
  14. data/examples/info.rb +30 -0
  15. data/examples/queries.rb +69 -0
  16. data/examples/simple.rb +23 -0
  17. data/examples/strict.rb +34 -0
  18. data/examples/types.rb +40 -0
  19. data/lib/mongo.rb +19 -0
  20. data/lib/mongo/admin.rb +87 -0
  21. data/lib/mongo/collection.rb +235 -0
  22. data/lib/mongo/cursor.rb +227 -0
  23. data/lib/mongo/db.rb +538 -0
  24. data/lib/mongo/gridfs.rb +16 -0
  25. data/lib/mongo/gridfs/chunk.rb +96 -0
  26. data/lib/mongo/gridfs/grid_store.rb +468 -0
  27. data/lib/mongo/message.rb +20 -0
  28. data/lib/mongo/message/get_more_message.rb +37 -0
  29. data/lib/mongo/message/insert_message.rb +35 -0
  30. data/lib/mongo/message/kill_cursors_message.rb +36 -0
  31. data/lib/mongo/message/message.rb +84 -0
  32. data/lib/mongo/message/message_header.rb +50 -0
  33. data/lib/mongo/message/msg_message.rb +33 -0
  34. data/lib/mongo/message/opcodes.rb +32 -0
  35. data/lib/mongo/message/query_message.rb +77 -0
  36. data/lib/mongo/message/remove_message.rb +36 -0
  37. data/lib/mongo/message/update_message.rb +37 -0
  38. data/lib/mongo/mongo.rb +164 -0
  39. data/lib/mongo/query.rb +119 -0
  40. data/lib/mongo/types/binary.rb +42 -0
  41. data/lib/mongo/types/code.rb +34 -0
  42. data/lib/mongo/types/dbref.rb +37 -0
  43. data/lib/mongo/types/objectid.rb +137 -0
  44. data/lib/mongo/types/regexp_of_holding.rb +44 -0
  45. data/lib/mongo/types/undefined.rb +31 -0
  46. data/lib/mongo/util/bson.rb +534 -0
  47. data/lib/mongo/util/byte_buffer.rb +167 -0
  48. data/lib/mongo/util/ordered_hash.rb +96 -0
  49. data/lib/mongo/util/xml_to_ruby.rb +107 -0
  50. data/mongo-ruby-driver.gemspec +99 -0
  51. data/tests/mongo-qa/_common.rb +8 -0
  52. data/tests/mongo-qa/admin +26 -0
  53. data/tests/mongo-qa/capped +22 -0
  54. data/tests/mongo-qa/count1 +18 -0
  55. data/tests/mongo-qa/dbs +22 -0
  56. data/tests/mongo-qa/find +10 -0
  57. data/tests/mongo-qa/find1 +15 -0
  58. data/tests/mongo-qa/gridfs_in +16 -0
  59. data/tests/mongo-qa/gridfs_out +17 -0
  60. data/tests/mongo-qa/indices +49 -0
  61. data/tests/mongo-qa/remove +25 -0
  62. data/tests/mongo-qa/stress1 +35 -0
  63. data/tests/mongo-qa/test1 +11 -0
  64. data/tests/mongo-qa/update +18 -0
  65. data/tests/test_admin.rb +69 -0
  66. data/tests/test_bson.rb +246 -0
  67. data/tests/test_byte_buffer.rb +69 -0
  68. data/tests/test_chunk.rb +84 -0
  69. data/tests/test_cursor.rb +121 -0
  70. data/tests/test_db.rb +160 -0
  71. data/tests/test_db_api.rb +701 -0
  72. data/tests/test_db_connection.rb +18 -0
  73. data/tests/test_grid_store.rb +284 -0
  74. data/tests/test_message.rb +35 -0
  75. data/tests/test_mongo.rb +78 -0
  76. data/tests/test_objectid.rb +98 -0
  77. data/tests/test_ordered_hash.rb +129 -0
  78. data/tests/test_round_trip.rb +116 -0
  79. metadata +133 -0
@@ -0,0 +1,227 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ require 'mongo/message'
18
+ require 'mongo/util/byte_buffer'
19
+ require 'mongo/util/bson'
20
+
21
+ module XGen
22
+ module Mongo
23
+ module Driver
24
+
25
+ # A cursor over query results. Returned objects are hashes.
26
+ class Cursor
27
+
28
+ include Enumerable
29
+
30
+ RESPONSE_HEADER_SIZE = 20
31
+
32
+ attr_reader :db, :collection, :query
33
+
34
+ def initialize(db, collection, query)
35
+ @db, @collection, @query = db, collection, query
36
+ @num_to_return = @query.number_to_return || 0
37
+ @cache = []
38
+ @closed = false
39
+ @can_call_to_a = true
40
+ @query_run = false
41
+ @rows = nil
42
+ end
43
+
44
+ def closed?; @closed; end
45
+
46
+ # Internal method, not for general use. Return +true+ if there are
47
+ # more records to retrieve. We do not check @num_to_return; #each is
48
+ # responsible for doing that.
49
+ def more?
50
+ num_remaining > 0
51
+ end
52
+
53
+ # Return the next object or nil if there are no more. Raises an error
54
+ # if necessary.
55
+ def next_object
56
+ refill_via_get_more if num_remaining == 0
57
+ o = @cache.shift
58
+
59
+ if o && o['$err']
60
+ err = o['$err']
61
+
62
+ # If the server has stopped being the master (e.g., it's one of a
63
+ # pair but it has died or something like that) then we close that
64
+ # connection. If the db has auto connect option and a pair of
65
+ # servers, next request will re-open on master server.
66
+ @db.close if err == "not master"
67
+
68
+ raise err
69
+ end
70
+
71
+ o
72
+ end
73
+
74
+ # Iterate over each object, yielding it to the given block. At most
75
+ # @num_to_return records are returned (or all of them, if
76
+ # @num_to_return is 0).
77
+ #
78
+ # If #to_a has already been called then this method uses the array
79
+ # that we store internally. In that case, #each can be called multiple
80
+ # times because it re-uses that array.
81
+ #
82
+ # You can call #each after calling #to_a (multiple times even, because
83
+ # it will use the internally-stored array), but you can't call #to_a
84
+ # after calling #each unless you also called it before calling #each.
85
+ # If you try to do that, an error will be raised.
86
+ def each
87
+ if @rows # Already turned into an array
88
+ @rows.each { |row| yield row }
89
+ else
90
+ num_returned = 0
91
+ while more? && (@num_to_return <= 0 || num_returned < @num_to_return)
92
+ yield next_object()
93
+ num_returned += 1
94
+ end
95
+ @can_call_to_a = false
96
+ end
97
+ end
98
+
99
+ # Return all of the rows (up to the +num_to_return+ value specified in
100
+ # #new) as an array. Calling this multiple times will work fine; it
101
+ # always returns the same array.
102
+ #
103
+ # Don't use this if you're expecting large amounts of data, of course.
104
+ # All of the returned rows are kept in an array stored in this object
105
+ # so it can be reused.
106
+ #
107
+ # You can call #each after calling #to_a (multiple times even, because
108
+ # it will use the internally-stored array), but you can't call #to_a
109
+ # after calling #each unless you also called it before calling #each.
110
+ # If you try to do that, an error will be raised.
111
+ def to_a
112
+ return @rows if @rows
113
+ raise "can't call Cursor#to_a after calling Cursor#each" unless @can_call_to_a
114
+ @rows = []
115
+ num_returned = 0
116
+ while more? && (@num_to_return <= 0 || num_returned < @num_to_return)
117
+ @rows << next_object()
118
+ num_returned += 1
119
+ end
120
+ @rows
121
+ end
122
+
123
+ # Returns an explain plan record.
124
+ def explain
125
+ old_val = @query.explain
126
+ @query.explain = true
127
+
128
+ c = Cursor.new(@db, @collection, @query)
129
+ explanation = c.next_object
130
+ c.close
131
+
132
+ @query.explain = old_val
133
+ explanation
134
+ end
135
+
136
+ # Close the cursor.
137
+ #
138
+ # Note: if a cursor is read until exhausted (read until OP_QUERY or
139
+ # OP_GETMORE returns zero for the cursor id), there is no need to
140
+ # close it by calling this method.
141
+ def close
142
+ @db.send_to_db(KillCursorsMessage.new(@cursor_id)) if @cursor_id
143
+ @cache = []
144
+ @cursor_id = 0
145
+ @closed = true
146
+ end
147
+
148
+ protected
149
+
150
+ def read_all
151
+ read_message_header
152
+ read_response_header
153
+ read_objects_off_wire
154
+ end
155
+
156
+ def read_objects_off_wire
157
+ while doc = next_object_on_wire
158
+ @cache << doc
159
+ end
160
+ end
161
+
162
+ def read_message_header
163
+ MessageHeader.new.read_header(@db)
164
+ end
165
+
166
+ def read_response_header
167
+ header_buf = ByteBuffer.new
168
+ header_buf.put_array(@db.receive_full(RESPONSE_HEADER_SIZE).unpack("C*"))
169
+ raise "Short read for DB response header; expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}" unless header_buf.length == RESPONSE_HEADER_SIZE
170
+ header_buf.rewind
171
+ @result_flags = header_buf.get_int
172
+ @cursor_id = header_buf.get_long
173
+ @starting_from = header_buf.get_int
174
+ @n_returned = header_buf.get_int
175
+ @n_remaining = @n_returned
176
+ end
177
+
178
+ def num_remaining
179
+ refill_via_get_more if @cache.length == 0
180
+ @cache.length
181
+ end
182
+
183
+ private
184
+
185
+ def next_object_on_wire
186
+ send_query_if_needed
187
+ # if @n_remaining is 0 but we have a non-zero cursor, there are more
188
+ # to fetch, so do a GetMore operation, but don't do it here - do it
189
+ # when someone pulls an object out of the cache and it's empty
190
+ return nil if @n_remaining == 0
191
+ object_from_stream
192
+ end
193
+
194
+ def refill_via_get_more
195
+ send_query_if_needed
196
+ return if @cursor_id == 0
197
+ @db.send_to_db(GetMoreMessage.new(@db.name, @collection.name, @cursor_id))
198
+ read_all
199
+ end
200
+
201
+ def object_from_stream
202
+ buf = ByteBuffer.new
203
+ buf.put_array(@db.receive_full(4).unpack("C*"))
204
+ buf.rewind
205
+ size = buf.get_int
206
+ buf.put_array(@db.receive_full(size - 4).unpack("C*"), 4)
207
+ @n_remaining -= 1
208
+ buf.rewind
209
+ BSON.new.deserialize(buf)
210
+ end
211
+
212
+ def send_query_if_needed
213
+ # Run query first time we request an object from the wire
214
+ unless @query_run
215
+ @db.send_query_message(QueryMessage.new(@db.name, @collection.name, @query))
216
+ @query_run = true
217
+ read_all
218
+ end
219
+ end
220
+
221
+ def to_s
222
+ "DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from, n_returned=#@n_returned)"
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,538 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ require 'socket'
18
+ require 'digest/md5'
19
+ require 'mutex_m'
20
+ require 'mongo/collection'
21
+ require 'mongo/message'
22
+ require 'mongo/query'
23
+ require 'mongo/util/ordered_hash.rb'
24
+ require 'mongo/admin'
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
75
+
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
+ raise "Invalid DB name \"#{db_name}\" (must be non-nil, non-zero-length, and can not contain \".\")" if !db_name || (db_name && db_name.length > 0 && db_name.include?("."))
118
+ @name, @nodes = db_name, nodes
119
+ @strict = options[:strict]
120
+ @pk_factory = options[:pk]
121
+ @slave_ok = options[:slave_ok] && @nodes.length == 1 # only OK if one node
122
+ @auto_reconnect = options[:auto_reconnect]
123
+ @semaphore = Object.new
124
+ @semaphore.extend Mutex_m
125
+ @socket = nil
126
+ connect_to_master
127
+ end
128
+
129
+ def connect_to_master
130
+ close if @socket
131
+ @host = @port = nil
132
+ @nodes.detect { |hp|
133
+ @host, @port = *hp
134
+ begin
135
+ @socket = TCPSocket.new(@host, @port)
136
+ @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
137
+
138
+ # Check for master. Can't call master? because it uses mutex,
139
+ # which may already be in use during this call.
140
+ semaphore_is_locked = @semaphore.locked?
141
+ @semaphore.unlock if semaphore_is_locked
142
+ is_master = master?
143
+ @semaphore.lock if semaphore_is_locked
144
+
145
+ break if @slave_ok || is_master
146
+ rescue SocketError, SystemCallError, IOError => ex
147
+ close if @socket
148
+ end
149
+ @socket
150
+ }
151
+ raise "error: failed to connect to any given host:port" unless @socket
152
+ end
153
+
154
+ # Returns true if +username+ has +password+ in
155
+ # +SYSTEM_USER_COLLECTION+. +name+ is username, +password+ is
156
+ # plaintext password.
157
+ def authenticate(username, password)
158
+ doc = db_command(:getnonce => 1)
159
+ raise "error retrieving nonce: #{doc}" unless ok?(doc)
160
+ nonce = doc['nonce']
161
+
162
+ auth = OrderedHash.new
163
+ auth['authenticate'] = 1
164
+ auth['user'] = username
165
+ auth['nonce'] = nonce
166
+ auth['key'] = Digest::MD5.hexdigest("#{nonce}#{username}#{hash_password(username, password)}")
167
+ ok?(db_command(auth))
168
+ end
169
+
170
+ # Deauthorizes use for this database for this connection.
171
+ def logout
172
+ doc = db_command(:logout => 1)
173
+ raise "error logging out: #{doc.inspect}" unless ok?(doc)
174
+ end
175
+
176
+ # Returns an array of collection names. Each name is of the form
177
+ # "database_name.collection_name".
178
+ def collection_names
179
+ names = collections_info.collect { |doc| doc['name'] || '' }
180
+ names.delete('')
181
+ names
182
+ end
183
+
184
+ # Returns a cursor over query result hashes. Each hash contains a
185
+ # 'name' string and optionally an 'options' hash. If +coll_name+ is
186
+ # specified, an array of length 1 is returned.
187
+ def collections_info(coll_name=nil)
188
+ selector = {}
189
+ selector[:name] = full_coll_name(coll_name) if coll_name
190
+ query(Collection.new(self, SYSTEM_NAMESPACE_COLLECTION), Query.new(selector))
191
+ end
192
+
193
+ # Create a collection. If +strict+ is false, will return existing or
194
+ # new collection. If +strict+ is true, will raise an error if
195
+ # collection +name+ already exists.
196
+ #
197
+ # Options is an optional hash:
198
+ #
199
+ # :capped :: Boolean. If not specified, capped is +false+.
200
+ #
201
+ # :size :: If +capped+ is +true+, specifies the maximum number of
202
+ # bytes. If +false+, specifies the initial extent of the
203
+ # collection.
204
+ #
205
+ # :max :: Max number of records in a capped collection. Optional.
206
+ def create_collection(name, options={})
207
+ # First check existence
208
+ if collection_names.include?(full_coll_name(name))
209
+ if strict?
210
+ raise "Collection #{name} already exists. Currently in strict mode."
211
+ else
212
+ return Collection.new(self, name)
213
+ end
214
+ end
215
+
216
+ # Create new collection
217
+ oh = OrderedHash.new
218
+ oh[:create] = name
219
+ doc = db_command(oh.merge(options || {}))
220
+ ok = doc['ok']
221
+ return Collection.new(self, name) if ok.kind_of?(Numeric) && (ok.to_i == 1 || ok.to_i == 0)
222
+ raise "Error creating collection: #{doc.inspect}"
223
+ end
224
+
225
+ def admin
226
+ Admin.new(self)
227
+ end
228
+
229
+ # Return a collection. If +strict+ is false, will return existing or
230
+ # new collection. If +strict+ is true, will raise an error if
231
+ # collection +name+ does not already exists.
232
+ def collection(name)
233
+ return Collection.new(self, name) if !strict? || collection_names.include?(full_coll_name(name))
234
+ raise "Collection #{name} doesn't exist. Currently in strict mode."
235
+ end
236
+
237
+ # Drop collection +name+. Returns +true+ on success or if the
238
+ # collection does not exist, +false+ otherwise.
239
+ def drop_collection(name)
240
+ return true unless collection_names.include?(full_coll_name(name))
241
+
242
+ ok?(db_command(:drop => name))
243
+ end
244
+
245
+ # Returns the error message from the most recently executed database
246
+ # operation for this connection, or +nil+ if there was no error.
247
+ #
248
+ # Note: as of this writing, errors are only detected on the db server
249
+ # for certain kinds of operations (writes). The plan is to change this
250
+ # so that all operations will set the error if needed.
251
+ def error
252
+ doc = db_command(:getlasterror => 1)
253
+ raise "error retrieving last error: #{doc}" unless ok?(doc)
254
+ doc['err']
255
+ end
256
+
257
+ # Returns +true+ if an error was caused by the most recently executed
258
+ # database operation.
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
+ error != nil
265
+ end
266
+
267
+ # Get the most recent error to have occured on this database
268
+ #
269
+ # Only returns errors that have occured since the last call to
270
+ # DB#reset_error_history - returns +nil+ if there is no such error.
271
+ def previous_error
272
+ error = db_command(:getpreverror => 1)
273
+ if error["err"]
274
+ error
275
+ else
276
+ nil
277
+ end
278
+ end
279
+
280
+ # Reset the error history of this database
281
+ #
282
+ # Calls to DB#previous_error will only return errors that have occurred
283
+ # since the most recent call to this method.
284
+ def reset_error_history
285
+ db_command(:reseterror => 1)
286
+ end
287
+
288
+ # Returns true if this database is a master (or is not paired with any
289
+ # other database), false if it is a slave.
290
+ def master?
291
+ doc = db_command(:ismaster => 1)
292
+ is_master = doc['ismaster']
293
+ ok?(doc) && is_master.kind_of?(Numeric) && is_master.to_i == 1
294
+ end
295
+
296
+ # Returns a string of the form "host:port" that points to the master
297
+ # database. Works even if this is the master database.
298
+ def master
299
+ doc = db_command(:ismaster => 1)
300
+ is_master = doc['ismaster']
301
+ raise "Error retrieving master database: #{doc.inspect}" unless ok?(doc) && is_master.kind_of?(Numeric)
302
+ case is_master.to_i
303
+ when 1
304
+ "#@host:#@port"
305
+ else
306
+ doc['remote']
307
+ end
308
+ end
309
+
310
+ # Close the connection to the database.
311
+ def close
312
+ if @socket
313
+ s = @socket
314
+ @socket = nil
315
+ s.close
316
+ end
317
+ end
318
+
319
+ def connected?
320
+ @socket != nil
321
+ end
322
+
323
+ def receive_full(length)
324
+ message = ""
325
+ while message.length < length do
326
+ chunk = @socket.recv(length - message.length)
327
+ raise "connection closed" unless chunk.length > 0
328
+ message += chunk
329
+ end
330
+ message
331
+ end
332
+
333
+ # Send a MsgMessage to the database.
334
+ def send_message(msg)
335
+ send_to_db(MsgMessage.new(msg))
336
+ end
337
+
338
+ # Returns a Cursor over the query results.
339
+ #
340
+ # Note that the query gets sent lazily; the cursor calls
341
+ # #send_query_message when needed. If the caller never requests an
342
+ # object from the cursor, the query never gets sent.
343
+ def query(collection, query)
344
+ Cursor.new(self, collection, query)
345
+ end
346
+
347
+ # Used by a Cursor to lazily send the query to the database.
348
+ def send_query_message(query_message)
349
+ @semaphore.synchronize {
350
+ send_to_db(query_message)
351
+ }
352
+ end
353
+
354
+ # Remove the records that match +selector+ from +collection_name+.
355
+ # Normally called by Collection#remove or Collection#clear.
356
+ def remove_from_db(collection_name, selector)
357
+ @semaphore.synchronize {
358
+ send_to_db(RemoveMessage.new(@name, collection_name, selector))
359
+ }
360
+ end
361
+
362
+ # Update records in +collection_name+ that match +selector+ by
363
+ # applying +obj+ as an update. Normally called by Collection#replace.
364
+ def replace_in_db(collection_name, selector, obj)
365
+ @semaphore.synchronize {
366
+ send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, false))
367
+ }
368
+ end
369
+
370
+ # Alias for #replace_in_db. Normally called by Collection.modify.
371
+ alias_method :modify_in_db, :replace_in_db
372
+
373
+ # Update records in +collection_name+ that match +selector+ by
374
+ # applying +obj+ as an update. If no match, inserts (???). Normally
375
+ # called by Collection#repsert.
376
+ def repsert_in_db(collection_name, selector, obj)
377
+ @semaphore.synchronize {
378
+ obj = @pk_factory.create_pk(obj) if @pk_factory
379
+ send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, true))
380
+ obj
381
+ }
382
+ end
383
+
384
+ # Return the number of records in +collection_name+ that match
385
+ # +selector+. If +selector+ is +nil+ or an empty hash, returns the
386
+ # count of all records. Normally called by Collection#count.
387
+ def count(collection_name, selector={})
388
+ oh = OrderedHash.new
389
+ oh[:count] = collection_name
390
+ oh[:query] = selector || {}
391
+ doc = db_command(oh)
392
+ return doc['n'].to_i if ok?(doc)
393
+ return 0 if doc['errmsg'] == "ns missing"
394
+ raise "Error with count command: #{doc.inspect}"
395
+ end
396
+
397
+ # Dereference a DBRef, getting the document it points to.
398
+ def dereference(dbref)
399
+ collection(dbref.namespace).find_first("_id" => dbref.object_id)
400
+ end
401
+
402
+ # Evaluate a JavaScript expression on MongoDB.
403
+ # +code+ should be a string or Code instance containing a JavaScript
404
+ # expression. Additional arguments will be passed to that expression
405
+ # when it is run on the server.
406
+ def eval(code, *args)
407
+ if not code.is_a? Code
408
+ code = Code.new(code)
409
+ end
410
+
411
+ oh = OrderedHash.new
412
+ oh[:$eval] = code
413
+ oh[:args] = args
414
+ doc = db_command(oh)
415
+ return doc['retval'] if ok?(doc)
416
+ raise "Error with eval command: #{doc.inspect}"
417
+ end
418
+
419
+ # Drop index +name+ from +collection_name+. Normally called from
420
+ # Collection#drop_index or Collection#drop_indexes.
421
+ def drop_index(collection_name, name)
422
+ oh = OrderedHash.new
423
+ oh[:deleteIndexes] = collection_name
424
+ oh[:index] = name
425
+ doc = db_command(oh)
426
+ raise "Error with drop_index command: #{doc.inspect}" unless ok?(doc)
427
+ end
428
+
429
+ # Get information on the indexes for the collection +collection_name+.
430
+ # Normally called by Collection#index_information. Returns a hash where
431
+ # the keys are index names (as returned by Collection#create_index and
432
+ # the values are lists of [key, direction] pairs specifying the index
433
+ # (as passed to Collection#create_index).
434
+ def index_information(collection_name)
435
+ sel = {:ns => full_coll_name(collection_name)}
436
+ info = {}
437
+ query(Collection.new(self, SYSTEM_INDEX_COLLECTION), Query.new(sel)).each { |index|
438
+ info[index['name']] = index['key'].to_a
439
+ }
440
+ info
441
+ end
442
+
443
+ # Create a new index on +collection_name+. +field_or_spec+
444
+ # should be either a single field name or a Array of [field name,
445
+ # direction] pairs. Directions should be specified as
446
+ # XGen::Mongo::ASCENDING or XGen::Mongo::DESCENDING. Normally called
447
+ # by Collection#create_index. If +unique+ is true the index will
448
+ # enforce a uniqueness constraint.
449
+ def create_index(collection_name, field_or_spec, unique=false)
450
+ field_h = OrderedHash.new
451
+ if field_or_spec.is_a?(String) || field_or_spec.is_a?(Symbol)
452
+ field_h[field_or_spec.to_s] = 1
453
+ else
454
+ field_or_spec.each { |f| field_h[f[0].to_s] = f[1] }
455
+ end
456
+ name = gen_index_name(field_h)
457
+ sel = {
458
+ :name => name,
459
+ :ns => full_coll_name(collection_name),
460
+ :key => field_h,
461
+ :unique => unique
462
+ }
463
+ @semaphore.synchronize {
464
+ send_to_db(InsertMessage.new(@name, SYSTEM_INDEX_COLLECTION, false, sel))
465
+ }
466
+ name
467
+ end
468
+
469
+ # Insert +objects+ into +collection_name+. Normally called by
470
+ # Collection#insert. Returns a new array containing +objects+,
471
+ # possibly modified by @pk_factory.
472
+ def insert_into_db(collection_name, objects)
473
+ @semaphore.synchronize {
474
+ if @pk_factory
475
+ objects.collect! { |o|
476
+ @pk_factory.create_pk(o)
477
+ }
478
+ end
479
+ send_to_db(InsertMessage.new(@name, collection_name, true, *objects))
480
+ objects
481
+ }
482
+ end
483
+
484
+ def send_to_db(message)
485
+ connect_to_master if !connected? && @auto_reconnect
486
+ begin
487
+ @socket.print(message.buf.to_s)
488
+ @socket.flush
489
+ rescue => ex
490
+ close
491
+ raise ex
492
+ end
493
+ end
494
+
495
+ def full_coll_name(collection_name)
496
+ "#{@name}.#{collection_name}"
497
+ end
498
+
499
+ # Return +true+ if +doc+ contains an 'ok' field with the value 1.
500
+ def ok?(doc)
501
+ ok = doc['ok']
502
+ ok.kind_of?(Numeric) && ok.to_i == 1
503
+ end
504
+
505
+ # DB commands need to be ordered, so selector must be an OrderedHash
506
+ # (or a Hash with only one element). What DB commands really need is
507
+ # that the "command" key be first.
508
+ #
509
+ # Do not call this. Intended for driver use only.
510
+ def db_command(selector)
511
+ if !selector.kind_of?(OrderedHash)
512
+ if !selector.kind_of?(Hash) || selector.keys.length > 1
513
+ raise "db_command must be given an OrderedHash when there is more than one key"
514
+ end
515
+ end
516
+
517
+ q = Query.new(selector)
518
+ q.number_to_return = 1
519
+ query(Collection.new(self, SYSTEM_COMMAND_COLLECTION), q).next_object
520
+ end
521
+
522
+ private
523
+
524
+ def hash_password(username, plaintext)
525
+ Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}")
526
+ end
527
+
528
+ def gen_index_name(spec)
529
+ temp = []
530
+ spec.each_pair { |field, direction|
531
+ temp = temp.push("#{field}_#{direction}")
532
+ }
533
+ return temp.join("_")
534
+ end
535
+ end
536
+ end
537
+ end
538
+ end