pahagon-mongo-abd 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/README.rdoc +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