pahagon-mongo-abd 0.14.1

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