mongo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,180 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify it
5
+ # under the terms of the GNU Affero General Public License, version 3, as
6
+ # published by the Free Software Foundation.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
11
+ # for more details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
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
+ def initialize(db, collection, num_to_return=0)
33
+ @db, @collection, @num_to_return = db, collection, num_to_return
34
+ @cache = []
35
+ @closed = false
36
+ @can_call_to_a = true
37
+ read_all
38
+ end
39
+
40
+ # Return +true+ if there are more records to retrieve. We do not check
41
+ # @num_to_return; #each is responsible for doing that.
42
+ def more?
43
+ num_remaining > 0
44
+ end
45
+
46
+ # Return the next object. Raises an error if necessary.
47
+ def next_object
48
+ refill_via_get_more if num_remaining == 0
49
+ o = @cache.shift
50
+ raise o['$err'] if o['$err']
51
+ o
52
+ end
53
+
54
+ # Iterate over each object, yielding it to the given block. At most
55
+ # @num_to_return records are returned (or all of them, if
56
+ # @num_to_return is 0).
57
+ #
58
+ # If #to_a has already been called then this method uses the array
59
+ # that we store internally. In that case, #each can be called multiple
60
+ # times because it re-uses that array.
61
+ #
62
+ # You can call #each after calling #to_a (multiple times even, because
63
+ # it will use the internally-stored array), but you can't call #to_a
64
+ # after calling #each unless you also called it before calling #each.
65
+ # If you try to do that, an error will be raised.
66
+ def each
67
+ if @rows # Already turned into an array
68
+ @rows.each { |row| yield row }
69
+ else
70
+ num_returned = 0
71
+ while more? && (@num_to_return <= 0 || num_returned < @num_to_return)
72
+ yield next_object()
73
+ num_returned += 1
74
+ end
75
+ @can_call_to_a = false
76
+ end
77
+ end
78
+
79
+ # Return all of the rows (up to the +num_to_return+ value specified in
80
+ # #new) as an array. Calling this multiple times will work fine; it
81
+ # always returns the same array.
82
+ #
83
+ # Don't use this if you're expecting large amounts of data, of course.
84
+ # All of the returned rows are kept in an array stored in this object
85
+ # so it can be reused.
86
+ #
87
+ # You can call #each after calling #to_a (multiple times even, because
88
+ # it will use the internally-stored array), but you can't call #to_a
89
+ # after calling #each unless you also called it before calling #each.
90
+ # If you try to do that, an error will be raised.
91
+ def to_a
92
+ return @rows if @rows
93
+ raise "can't call Cursor#to_a after calling Cursor#each" unless @can_call_to_a
94
+ @rows = []
95
+ num_returned = 0
96
+ while more? && (@num_to_return <= 0 || num_returned < @num_to_return)
97
+ @rows << next_object()
98
+ num_returned += 1
99
+ end
100
+ @rows
101
+ end
102
+
103
+ # Close the cursor.
104
+ def close
105
+ @db.send_to_db(KillCursorMessage(@cursor_id)) if @cursor_id
106
+ @cache = []
107
+ @cursor_id = 0
108
+ @closed = true
109
+ end
110
+
111
+ protected
112
+
113
+ def read_all
114
+ read_message_header
115
+ read_response_header
116
+ read_objects_off_wire
117
+ end
118
+
119
+ def read_objects_off_wire
120
+ while doc = next_object_on_wire
121
+ @cache << doc
122
+ end
123
+ end
124
+
125
+ def read_message_header
126
+ MessageHeader.new.read_header(@db.socket)
127
+ end
128
+
129
+ def read_response_header
130
+ header_buf = ByteBuffer.new
131
+ header_buf.put_array(@db.socket.recv(RESPONSE_HEADER_SIZE).unpack("C*"))
132
+ raise "Short read for DB response header; expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}" unless header_buf.length == RESPONSE_HEADER_SIZE
133
+ header_buf.rewind
134
+ @result_flags = header_buf.get_int
135
+ @cursor_id = header_buf.get_long
136
+ @starting_from = header_buf.get_int
137
+ @n_returned = header_buf.get_int
138
+ @n_remaining = @n_returned
139
+ end
140
+
141
+ def num_remaining
142
+ refill_via_get_more if @cache.length == 0
143
+ @cache.length
144
+ end
145
+
146
+ private
147
+
148
+ def next_object_on_wire
149
+ # if @n_remaining is 0 but we have a non-zero cursor, there are more
150
+ # to fetch, so do a GetMore operation, but don't do it here - do it
151
+ # when someone pulls an object out of the cache and it's empty
152
+ return nil if @n_remaining == 0
153
+ object_from_stream
154
+ end
155
+
156
+ def refill_via_get_more
157
+ return if @cursor_id == 0
158
+ @db.send_to_db(GetMoreMessage.new(@db.name, @collection, @cursor_id))
159
+ read_all
160
+ end
161
+
162
+ def object_from_stream
163
+ buf = ByteBuffer.new
164
+ buf.put_array(@db.socket.recv(4).unpack("C*"))
165
+ buf.rewind
166
+ size = buf.get_int
167
+ buf.put_array(@db.socket.recv(size-4).unpack("C*"), 4)
168
+ @n_remaining -= 1
169
+ buf.rewind
170
+ BSON.new.deserialize(buf)
171
+ end
172
+
173
+ def to_s
174
+ "DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from, n_returned=#@n_returned)"
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+
@@ -0,0 +1,307 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify it
5
+ # under the terms of the GNU Affero General Public License, version 3, as
6
+ # published by the Free Software Foundation.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
11
+ # for more details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+ # ++
16
+
17
+ require 'socket'
18
+ require 'mutex_m'
19
+ require 'mongo/mongo'
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_COMMAND_COLLECTION = "$cmd"
37
+
38
+ # Strict mode enforces collection existence checks. When +true+,
39
+ # asking for a collection that does not exist or trying to create a
40
+ # collection that already exists raises an error.
41
+ #
42
+ # Strict mode is off (+false+) by default. Its value can be changed at
43
+ # any time.
44
+ attr_writer :strict
45
+
46
+ # Returns the value of the +strict+ flag.
47
+ def strict?; @strict; end
48
+
49
+ # The name of the database.
50
+ attr_reader :name
51
+
52
+ # The database's socket. For internal use only.
53
+ attr_reader :socket
54
+
55
+ # db_name :: The database name
56
+ #
57
+ # host :: The database host name or IP address. Defaults to 'localhost'.
58
+ #
59
+ # port :: The database port number. Defaults to
60
+ # XGen::Mongo::Driver::Mongo::DEFAULT_PORT.
61
+ #
62
+ def initialize(db_name, host='localhost', port=XGen::Mongo::Driver::Mongo::DEFAULT_PORT)
63
+ raise "Invalid DB name" if !db_name || (db_name && db_name.length > 0 && db_name.include?("."))
64
+ @name, @host, @port = db_name, host, port
65
+ @socket = TCPSocket.new(@host, @port)
66
+ @strict = false
67
+ @semaphore = Object.new
68
+ @semaphore.extend Mutex_m
69
+ end
70
+
71
+ # Returns an array of collection names. Each name is of the form
72
+ # "database_name.collection_name".
73
+ def collection_names
74
+ names = collections_info.collect { |doc| doc['name'] || '' }
75
+ names.delete('')
76
+ names
77
+ end
78
+
79
+ # Returns a cursor over query result hashes. Each hash contains a
80
+ # 'name' string and optionally an 'options' hash. If +coll_name+ is
81
+ # specified, an array of length 1 is returned.
82
+ def collections_info(coll_name=nil)
83
+ selector = {}
84
+ selector[:name] = full_coll_name(coll_name) if coll_name
85
+ query(SYSTEM_NAMESPACE_COLLECTION, Query.new(selector))
86
+ end
87
+
88
+ # Create a collection. If +strict+ is false, will return existing or
89
+ # new collection. If +strict+ is true, will raise an error if
90
+ # collection +name+ already exists.
91
+ #
92
+ # Options is an optional hash:
93
+ #
94
+ # :capped :: Boolean. If not specified, capped is +false+.
95
+ #
96
+ # :size :: If +capped+ is +true+, specifies the maximum number of
97
+ # bytes. If +false+, specifies the initial extent of the
98
+ # collection.
99
+ #
100
+ # :max :: Max number of records in a capped collection. Optional.
101
+ def create_collection(name, options={})
102
+ # First check existence
103
+ if collection_names.include?(full_coll_name(name))
104
+ if strict?
105
+ raise "Collection #{name} already exists. Currently in strict mode."
106
+ else
107
+ return Collection.new(self, name)
108
+ end
109
+ end
110
+
111
+ # Create new collection
112
+ oh = OrderedHash.new
113
+ oh[:create] = name
114
+ doc = db_command(oh.merge(options || {}))
115
+ ok = doc['ok']
116
+ return Collection.new(self, name) if ok.kind_of?(Numeric) && (ok.to_i == 1 || ok.to_i == 0)
117
+ raise "Error creating collection: #{doc.inspect}"
118
+ end
119
+
120
+ def admin
121
+ Admin.new(self)
122
+ end
123
+
124
+ # Return a collection. If +strict+ is false, will return existing or
125
+ # new collection. If +strict+ is true, will raise an error if
126
+ # collection +name+ does not already exists.
127
+ def collection(name)
128
+ return Collection.new(self, name) if collection_names.include?(full_coll_name(name))
129
+ if strict?
130
+ raise "Collection #{name} doesn't exist. Currently in strict mode."
131
+ else
132
+ create_collection(name)
133
+ end
134
+ end
135
+
136
+ # Drop collection +name+. Returns +true+ on success or if the
137
+ # collection does not exist, +false+ otherwise.
138
+ def drop_collection(name)
139
+ return true unless collection_names.include?(full_coll_name(name))
140
+
141
+ coll = collection(name)
142
+ coll.drop_indexes # Mongo requires that we drop indexes manually
143
+ ok?(db_command(:drop => name))
144
+ end
145
+
146
+ # Returns true if this database is a master (or is not paired with any
147
+ # other database), false if it is a slave.
148
+ def master?
149
+ doc = db_command(:ismaster => 1)
150
+ is_master = doc['ismaster']
151
+ ok?(doc) && is_master.kind_of?(Numeric) && is_master.to_i == 1
152
+ end
153
+
154
+ # Close the connection to the database.
155
+ def close
156
+ @socket.close
157
+ end
158
+
159
+ # Send a MsgMessage to the database.
160
+ def send_message(msg)
161
+ send_to_db(MsgMessage.new(msg))
162
+ end
163
+
164
+ # Send a Query to +collection_name+ and return a Cursor over the
165
+ # results.
166
+ def query(collection_name, query)
167
+ @semaphore.synchronize {
168
+ send_to_db(QueryMessage.new(@name, collection_name, query))
169
+ Cursor.new(self, collection_name, query.number_to_return)
170
+ }
171
+ end
172
+
173
+ # Remove the records that match +selector+ from +collection_name+.
174
+ # Normally called by Collection#remove or Collection#clear.
175
+ def remove_from_db(collection_name, selector)
176
+ @semaphore.synchronize {
177
+ send_to_db(RemoveMessage.new(@name, collection_name, selector))
178
+ }
179
+ end
180
+
181
+ # Update records in +collection_name+ that match +selector+ by
182
+ # applying +obj+ as an update. Normally called by Collection#replace.
183
+ def replace_in_db(collection_name, selector, obj)
184
+ @semaphore.synchronize {
185
+ send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, false))
186
+ }
187
+ end
188
+
189
+ # Alias for #replace_in_db. Normally called by Collection.modify.
190
+ alias_method :modify_in_db, :replace_in_db
191
+
192
+ # Update records in +collection_name+ that match +selector+ by
193
+ # applying +obj+ as an update. If no match, inserts (???). Normally
194
+ # called by Collection#repsert.
195
+ def repsert_in_db(collection_name, selector, obj)
196
+ # TODO if PKInjector, inject
197
+ @semaphore.synchronize {
198
+ send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, true))
199
+ obj
200
+ }
201
+ end
202
+
203
+ # Return the number of records in +collection_name+ that match
204
+ # +selector+. If +selector+ is +nil+ or an empty hash, returns the
205
+ # count of all records. Normally called by Collection#count.
206
+ def count(collection_name, selector={})
207
+ oh = OrderedHash.new
208
+ oh[:count] = collection_name
209
+ oh[:query] = selector || {}
210
+ doc = db_command(oh)
211
+ return doc['n'].to_i if ok?(doc)
212
+ raise "Error with count command: #{doc.inspect}"
213
+ end
214
+
215
+ # Drop index +name+ from +collection_name+. Normally called from
216
+ # Collection#drop_index or Collection#drop_indexes.
217
+ def drop_index(collection_name, name)
218
+ oh = OrderedHash.new
219
+ oh[:deleteIndexes] = collection_name
220
+ oh[:index] = name
221
+ doc = db_command(oh)
222
+ raise "Error with drop_index command: #{doc.inspect}" unless ok?(doc)
223
+ end
224
+
225
+ # Return an array of hashes, one for each index on +collection_name+.
226
+ # Normally called by Collection#index_information. Each hash contains:
227
+ #
228
+ # :name :: Index name
229
+ #
230
+ # :keys :: Hash whose keys are the names of the fields that make up
231
+ # the key and values are integers.
232
+ #
233
+ # :ns :: Namespace; same as +collection_name+.
234
+ def index_information(collection_name)
235
+ sel = {:ns => full_coll_name(collection_name)}
236
+ query(SYSTEM_INDEX_COLLECTION, Query.new(sel)).collect { |row|
237
+ h = {:name => row['name']}
238
+ raise "Name of index on return from db was nil. Coll = #{full_coll_name(collection_name)}" unless h[:name]
239
+
240
+ h[:keys] = row['key']
241
+ raise "Keys for index on return from db was nil. Coll = #{full_coll_name(collection_name)}" unless h[:keys]
242
+
243
+ h[:ns] = row['ns']
244
+ raise "Namespace for index on return from db was nil. Coll = #{full_coll_name(collection_name)}" unless h[:ns]
245
+ h[:ns].sub!(/.*\./, '')
246
+ raise "Error: ns != collection" unless h[:ns] == collection_name
247
+
248
+ h
249
+ }
250
+ end
251
+
252
+ # Create a new index on +collection_name+ named +index_name+. +fields+
253
+ # should be an array of field names. Normally called by
254
+ # Collection#create_index.
255
+ def create_index(collection_name, index_name, fields)
256
+ sel = {:name => index_name, :ns => full_coll_name(collection_name)}
257
+ field_h = {}
258
+ fields.each { |f| field_h[f] = 1 }
259
+ sel[:key] = field_h
260
+ @semaphore.synchronize {
261
+ send_to_db(InsertMessage.new(@name, SYSTEM_INDEX_COLLECTION, sel))
262
+ }
263
+ end
264
+
265
+ # Insert +objects+ into +collection_name+. Normally called by
266
+ # Collection#insert.
267
+ def insert_into_db(collection_name, objects)
268
+ @semaphore.synchronize {
269
+ objects.each { |o| send_to_db(InsertMessage.new(@name, collection_name, o)) }
270
+ }
271
+ end
272
+
273
+ def send_to_db(message)
274
+ @socket.print(message.buf.to_s)
275
+ end
276
+
277
+ def full_coll_name(collection_name)
278
+ "#{@name}.#{collection_name}"
279
+ end
280
+
281
+ # Return +true+ if +doc+ contains an 'ok' field with the value 1.
282
+ def ok?(doc)
283
+ ok = doc['ok']
284
+ ok.kind_of?(Numeric) && ok.to_i == 1
285
+ end
286
+
287
+ # DB commands need to be ordered, so selector must be an OrderedHash
288
+ # (or a Hash with only one element). What DB commands really need is
289
+ # that the "command" key be first.
290
+ #
291
+ # Do not call this. Intended for driver use only.
292
+ def db_command(selector)
293
+ if !selector.kind_of?(OrderedHash)
294
+ if !selector.kind_of?(Hash) || selector.keys.length > 1
295
+ raise "db_command must be given an OrderedHash when there is more than one key"
296
+ end
297
+ end
298
+
299
+ q = Query.new(selector)
300
+ q.number_to_return = 1
301
+ query(SYSTEM_COMMAND_COLLECTION, q).next_object
302
+ end
303
+
304
+ end
305
+ end
306
+ end
307
+ end