mongodb-mongo 0.1.3

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 (48) hide show
  1. data/README.rdoc +216 -0
  2. data/Rakefile +54 -0
  3. data/bin/mongo_console +21 -0
  4. data/bin/validate +51 -0
  5. data/examples/benchmarks.rb +38 -0
  6. data/examples/blog.rb +76 -0
  7. data/examples/index_test.rb +128 -0
  8. data/examples/simple.rb +17 -0
  9. data/lib/mongo/admin.rb +86 -0
  10. data/lib/mongo/collection.rb +161 -0
  11. data/lib/mongo/cursor.rb +230 -0
  12. data/lib/mongo/db.rb +399 -0
  13. data/lib/mongo/message/get_more_message.rb +21 -0
  14. data/lib/mongo/message/insert_message.rb +19 -0
  15. data/lib/mongo/message/kill_cursors_message.rb +20 -0
  16. data/lib/mongo/message/message.rb +68 -0
  17. data/lib/mongo/message/message_header.rb +34 -0
  18. data/lib/mongo/message/msg_message.rb +17 -0
  19. data/lib/mongo/message/opcodes.rb +16 -0
  20. data/lib/mongo/message/query_message.rb +67 -0
  21. data/lib/mongo/message/remove_message.rb +20 -0
  22. data/lib/mongo/message/update_message.rb +21 -0
  23. data/lib/mongo/message.rb +4 -0
  24. data/lib/mongo/mongo.rb +98 -0
  25. data/lib/mongo/query.rb +110 -0
  26. data/lib/mongo/types/binary.rb +34 -0
  27. data/lib/mongo/types/dbref.rb +37 -0
  28. data/lib/mongo/types/objectid.rb +137 -0
  29. data/lib/mongo/types/regexp_of_holding.rb +44 -0
  30. data/lib/mongo/types/undefined.rb +31 -0
  31. data/lib/mongo/util/bson.rb +431 -0
  32. data/lib/mongo/util/byte_buffer.rb +163 -0
  33. data/lib/mongo/util/ordered_hash.rb +68 -0
  34. data/lib/mongo/util/xml_to_ruby.rb +102 -0
  35. data/lib/mongo.rb +12 -0
  36. data/mongo-ruby-driver.gemspec +62 -0
  37. data/tests/test_admin.rb +60 -0
  38. data/tests/test_bson.rb +135 -0
  39. data/tests/test_byte_buffer.rb +69 -0
  40. data/tests/test_cursor.rb +66 -0
  41. data/tests/test_db.rb +85 -0
  42. data/tests/test_db_api.rb +354 -0
  43. data/tests/test_db_connection.rb +17 -0
  44. data/tests/test_message.rb +35 -0
  45. data/tests/test_objectid.rb +98 -0
  46. data/tests/test_ordered_hash.rb +85 -0
  47. data/tests/test_round_trip.rb +116 -0
  48. metadata +100 -0
data/lib/mongo/db.rb ADDED
@@ -0,0 +1,399 @@
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/collection'
20
+ require 'mongo/message'
21
+ require 'mongo/query'
22
+ require 'mongo/util/ordered_hash.rb'
23
+ require 'mongo/admin'
24
+
25
+ module XGen
26
+ module Mongo
27
+ module Driver
28
+
29
+ # A Mongo database.
30
+ class DB
31
+
32
+ SYSTEM_NAMESPACE_COLLECTION = "system.namespaces"
33
+ SYSTEM_INDEX_COLLECTION = "system.indexes"
34
+ SYSTEM_PROFILE_COLLECTION = "system.profile"
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
+ # A primary key factory object (or +nil+). See the README.doc file or
63
+ # DB#new for details.
64
+ attr_reader :pk_factory
65
+
66
+ def pk_factory=(pk_factory)
67
+ raise "error: can not change PK factory" if @pk_factory
68
+ @pk_factory = pk_factory
69
+ end
70
+
71
+ # db_name :: The database name
72
+ #
73
+ # nodes :: An array of [host, port] pairs.
74
+ #
75
+ # options :: A hash of options.
76
+ #
77
+ # Options:
78
+ #
79
+ # :strict :: If true, collections must exist to be accessed and must
80
+ # not exist to be created. See #collection and
81
+ # #create_collection.
82
+ #
83
+ # :pk :: A primary key factory object that must respond to :create_pk,
84
+ # which should take a hash and return a hash which merges the
85
+ # original hash with any primary key fields the factory wishes
86
+ # to inject. (NOTE: if the object already has a primary key,
87
+ # the factory should not inject a new key; this means that the
88
+ # object is being used in a repsert but it already exists.) The
89
+ # idea here is that when ever a record is inserted, the :pk
90
+ # object's +create_pk+ method will be called and the new hash
91
+ # returned will be inserted.
92
+ #
93
+ # When a DB object first connects, it tries the first node. If that
94
+ # fails, it keeps trying to connect to the remaining nodes until it
95
+ # sucessfully connects.
96
+ def initialize(db_name, nodes, options={})
97
+ raise "Invalid DB name" if !db_name || (db_name && db_name.length > 0 && db_name.include?("."))
98
+ @name, @nodes = db_name, nodes
99
+ @strict = options[:strict]
100
+ @pk_factory = options[:pk]
101
+ @semaphore = Object.new
102
+ @semaphore.extend Mutex_m
103
+ connect_to_first_available_host
104
+ end
105
+
106
+ def connect_to_first_available_host
107
+ close if @socket
108
+ @host = @port = nil
109
+ @nodes.detect { |hp|
110
+ @host, @port = *hp
111
+ begin
112
+ @socket = TCPSocket.new(@host, @port)
113
+ break if ok?(db_command(:ismaster => 1)) # success
114
+ rescue => ex
115
+ close if @socket
116
+ end
117
+ @socket
118
+ }
119
+ raise "error: failed to connect to any given host:port" unless @socket
120
+ end
121
+
122
+ # Returns an array of collection names. Each name is of the form
123
+ # "database_name.collection_name".
124
+ def collection_names
125
+ names = collections_info.collect { |doc| doc['name'] || '' }
126
+ names.delete('')
127
+ names
128
+ end
129
+
130
+ # Returns a cursor over query result hashes. Each hash contains a
131
+ # 'name' string and optionally an 'options' hash. If +coll_name+ is
132
+ # specified, an array of length 1 is returned.
133
+ def collections_info(coll_name=nil)
134
+ selector = {}
135
+ selector[:name] = full_coll_name(coll_name) if coll_name
136
+ query(Collection.new(self, SYSTEM_NAMESPACE_COLLECTION), Query.new(selector))
137
+ end
138
+
139
+ # Create a collection. If +strict+ is false, will return existing or
140
+ # new collection. If +strict+ is true, will raise an error if
141
+ # collection +name+ already exists.
142
+ #
143
+ # Options is an optional hash:
144
+ #
145
+ # :capped :: Boolean. If not specified, capped is +false+.
146
+ #
147
+ # :size :: If +capped+ is +true+, specifies the maximum number of
148
+ # bytes. If +false+, specifies the initial extent of the
149
+ # collection.
150
+ #
151
+ # :max :: Max number of records in a capped collection. Optional.
152
+ def create_collection(name, options={})
153
+ # First check existence
154
+ if collection_names.include?(full_coll_name(name))
155
+ if strict?
156
+ raise "Collection #{name} already exists. Currently in strict mode."
157
+ else
158
+ return Collection.new(self, name)
159
+ end
160
+ end
161
+
162
+ # Create new collection
163
+ oh = OrderedHash.new
164
+ oh[:create] = name
165
+ doc = db_command(oh.merge(options || {}))
166
+ ok = doc['ok']
167
+ return Collection.new(self, name) if ok.kind_of?(Numeric) && (ok.to_i == 1 || ok.to_i == 0)
168
+ raise "Error creating collection: #{doc.inspect}"
169
+ end
170
+
171
+ def admin
172
+ Admin.new(self)
173
+ end
174
+
175
+ # Return a collection. If +strict+ is false, will return existing or
176
+ # new collection. If +strict+ is true, will raise an error if
177
+ # collection +name+ does not already exists.
178
+ def collection(name)
179
+ return Collection.new(self, name) if !strict? || collection_names.include?(full_coll_name(name))
180
+ raise "Collection #{name} doesn't exist. Currently in strict mode."
181
+ end
182
+
183
+ # Drop collection +name+. Returns +true+ on success or if the
184
+ # collection does not exist, +false+ otherwise.
185
+ def drop_collection(name)
186
+ return true unless collection_names.include?(full_coll_name(name))
187
+
188
+ coll = collection(name)
189
+ coll.drop_indexes # Mongo requires that we drop indexes manually
190
+ ok?(db_command(:drop => name))
191
+ end
192
+
193
+ # Returns true if this database is a master (or is not paired with any
194
+ # other database), false if it is a slave.
195
+ def master?
196
+ doc = db_command(:ismaster => 1)
197
+ is_master = doc['ismaster']
198
+ ok?(doc) && is_master.kind_of?(Numeric) && is_master.to_i == 1
199
+ end
200
+
201
+ # Returns a string of the form "host:port" that points to the master
202
+ # database. Works even if this is the master database.
203
+ def master
204
+ doc = db_command(:ismaster => 1)
205
+ is_master = doc['ismaster']
206
+ raise "Error retrieving master database" unless ok?(doc) && is_master.kind_of?(Numeric)
207
+ case is_master.to_i
208
+ when 1
209
+ "#@host:#@port"
210
+ else
211
+ doc['remote']
212
+ end
213
+ end
214
+
215
+ # Switches our socket to the master database. If we are already the
216
+ # master, no change is made.
217
+ def switch_to_master
218
+ master_str = master()
219
+ unless master_str == "#@host:#@port"
220
+ @semaphore.synchronize {
221
+ master_str =~ /(.+):(\d+)/
222
+ @host, @port = $1, $2
223
+ close()
224
+ @socket = TCPSocket.new(@host, @port)
225
+ }
226
+ end
227
+ end
228
+
229
+ # Close the connection to the database.
230
+ def close
231
+ @socket.close if @socket
232
+ @socket = nil
233
+ end
234
+
235
+ def connected?
236
+ @socket != nil
237
+ end
238
+
239
+ # Send a MsgMessage to the database.
240
+ def send_message(msg)
241
+ send_to_db(MsgMessage.new(msg))
242
+ end
243
+
244
+ # Returns a Cursor over the query results.
245
+ #
246
+ # Note that the query gets sent lazily; the cursor calls
247
+ # #send_query_message when needed. If the caller never requests an
248
+ # object from the cursor, the query never gets sent.
249
+ def query(collection, query)
250
+ Cursor.new(self, collection, query)
251
+ end
252
+
253
+ # Used by a Cursor to lazily send the query to the database.
254
+ def send_query_message(query_message)
255
+ @semaphore.synchronize {
256
+ send_to_db(query_message)
257
+ }
258
+ end
259
+
260
+ # Remove the records that match +selector+ from +collection_name+.
261
+ # Normally called by Collection#remove or Collection#clear.
262
+ def remove_from_db(collection_name, selector)
263
+ @semaphore.synchronize {
264
+ send_to_db(RemoveMessage.new(@name, collection_name, selector))
265
+ }
266
+ end
267
+
268
+ # Update records in +collection_name+ that match +selector+ by
269
+ # applying +obj+ as an update. Normally called by Collection#replace.
270
+ def replace_in_db(collection_name, selector, obj)
271
+ @semaphore.synchronize {
272
+ send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, false))
273
+ }
274
+ end
275
+
276
+ # Alias for #replace_in_db. Normally called by Collection.modify.
277
+ alias_method :modify_in_db, :replace_in_db
278
+
279
+ # Update records in +collection_name+ that match +selector+ by
280
+ # applying +obj+ as an update. If no match, inserts (???). Normally
281
+ # called by Collection#repsert.
282
+ def repsert_in_db(collection_name, selector, obj)
283
+ @semaphore.synchronize {
284
+ obj = @pk_factory.create_pk(obj) if @pk_factory
285
+ send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, true))
286
+ obj
287
+ }
288
+ end
289
+
290
+ # Return the number of records in +collection_name+ that match
291
+ # +selector+. If +selector+ is +nil+ or an empty hash, returns the
292
+ # count of all records. Normally called by Collection#count.
293
+ def count(collection_name, selector={})
294
+ oh = OrderedHash.new
295
+ oh[:count] = collection_name
296
+ oh[:query] = selector || {}
297
+ doc = db_command(oh)
298
+ return doc['n'].to_i if ok?(doc)
299
+ raise "Error with count command: #{doc.inspect}"
300
+ end
301
+
302
+ # Drop index +name+ from +collection_name+. Normally called from
303
+ # Collection#drop_index or Collection#drop_indexes.
304
+ def drop_index(collection_name, name)
305
+ oh = OrderedHash.new
306
+ oh[:deleteIndexes] = collection_name
307
+ oh[:index] = name
308
+ doc = db_command(oh)
309
+ raise "Error with drop_index command: #{doc.inspect}" unless ok?(doc)
310
+ end
311
+
312
+ # Return an array of hashes, one for each index on +collection_name+.
313
+ # Normally called by Collection#index_information. Each hash contains:
314
+ #
315
+ # :name :: Index name
316
+ #
317
+ # :keys :: Hash whose keys are the names of the fields that make up
318
+ # the key and values are integers.
319
+ #
320
+ # :ns :: Namespace; same as +collection_name+.
321
+ def index_information(collection_name)
322
+ sel = {:ns => full_coll_name(collection_name)}
323
+ query(Collection.new(self, SYSTEM_INDEX_COLLECTION), Query.new(sel)).collect { |row|
324
+ h = {:name => row['name']}
325
+ raise "Name of index on return from db was nil. Coll = #{full_coll_name(collection_name)}" unless h[:name]
326
+
327
+ h[:keys] = row['key']
328
+ raise "Keys for index on return from db was nil. Coll = #{full_coll_name(collection_name)}" unless h[:keys]
329
+
330
+ h[:ns] = row['ns']
331
+ raise "Namespace for index on return from db was nil. Coll = #{full_coll_name(collection_name)}" unless h[:ns]
332
+ h[:ns].sub!(/.*\./, '')
333
+ raise "Error: ns != collection" unless h[:ns] == collection_name
334
+
335
+ h
336
+ }
337
+ end
338
+
339
+ # Create a new index on +collection_name+ named +index_name+. +fields+
340
+ # should be an array of field names. Normally called by
341
+ # Collection#create_index.
342
+ def create_index(collection_name, index_name, fields)
343
+ sel = {:name => index_name, :ns => full_coll_name(collection_name)}
344
+ field_h = {}
345
+ fields.each { |f| field_h[f] = 1 }
346
+ sel[:key] = field_h
347
+ @semaphore.synchronize {
348
+ send_to_db(InsertMessage.new(@name, SYSTEM_INDEX_COLLECTION, sel))
349
+ }
350
+ end
351
+
352
+ # Insert +objects+ into +collection_name+. Normally called by
353
+ # Collection#insert. Returns a new array containing +objects+,
354
+ # possibly modified by @pk_factory.
355
+ def insert_into_db(collection_name, objects)
356
+ @semaphore.synchronize {
357
+ objects.collect { |o|
358
+ o = @pk_factory.create_pk(o) if @pk_factory
359
+ send_to_db(InsertMessage.new(@name, collection_name, o))
360
+ o
361
+ }
362
+ }
363
+ end
364
+
365
+ def send_to_db(message)
366
+ @socket.print(message.buf.to_s)
367
+ end
368
+
369
+ def full_coll_name(collection_name)
370
+ "#{@name}.#{collection_name}"
371
+ end
372
+
373
+ # Return +true+ if +doc+ contains an 'ok' field with the value 1.
374
+ def ok?(doc)
375
+ ok = doc['ok']
376
+ ok.kind_of?(Numeric) && ok.to_i == 1
377
+ end
378
+
379
+ # DB commands need to be ordered, so selector must be an OrderedHash
380
+ # (or a Hash with only one element). What DB commands really need is
381
+ # that the "command" key be first.
382
+ #
383
+ # Do not call this. Intended for driver use only.
384
+ def db_command(selector)
385
+ if !selector.kind_of?(OrderedHash)
386
+ if !selector.kind_of?(Hash) || selector.keys.length > 1
387
+ raise "db_command must be given an OrderedHash when there is more than one key"
388
+ end
389
+ end
390
+
391
+ q = Query.new(selector)
392
+ q.number_to_return = 1
393
+ query(Collection.new(self, SYSTEM_COMMAND_COLLECTION), q).next_object
394
+ end
395
+
396
+ end
397
+ end
398
+ end
399
+ end
@@ -0,0 +1,21 @@
1
+ require 'mongo/message/message'
2
+ require 'mongo/message/opcodes'
3
+
4
+ module XGen
5
+ module Mongo
6
+ module Driver
7
+
8
+ class GetMoreMessage < Message
9
+
10
+ def initialize(db_name, collection_name, cursor)
11
+ super(OP_GET_MORE)
12
+ write_int(0)
13
+ write_string("#{db_name}.#{collection_name}")
14
+ write_int(0) # num to return; leave it up to the db for now
15
+ write_long(cursor)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,19 @@
1
+ require 'mongo/message/message'
2
+ require 'mongo/message/opcodes'
3
+
4
+ module XGen
5
+ module Mongo
6
+ module Driver
7
+
8
+ class InsertMessage < Message
9
+
10
+ def initialize(db_name, collection_name, *objs)
11
+ super(OP_INSERT)
12
+ write_int(0)
13
+ write_string("#{db_name}.#{collection_name}")
14
+ objs.each { |o| write_doc(o) }
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ require 'mongo/message/message'
2
+ require 'mongo/message/opcodes'
3
+
4
+ module XGen
5
+ module Mongo
6
+ module Driver
7
+
8
+ class KillCursorsMessage < Message
9
+
10
+ def initialize(*cursors)
11
+ super(OP_KILL_CURSORS)
12
+ write_int(0)
13
+ write_int(cursors.length)
14
+ cursors.each { |c| write_long c }
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,68 @@
1
+ require 'mongo/util/bson'
2
+ require 'mongo/util/byte_buffer'
3
+
4
+ module XGen
5
+ module Mongo
6
+ module Driver
7
+
8
+ class Message
9
+
10
+ HEADER_SIZE = 16 # size, id, response_to, opcode
11
+
12
+ @@class_req_id = 0
13
+
14
+ attr_reader :buf # for testing
15
+
16
+ def initialize(op)
17
+ @op = op
18
+ @message_length = HEADER_SIZE
19
+ @data_length = 0
20
+ @request_id = (@@class_req_id += 1)
21
+ @response_id = 0
22
+ @buf = ByteBuffer.new
23
+
24
+ @buf.put_int(16) # holder for length
25
+ @buf.put_int(@request_id)
26
+ @buf.put_int(0) # response_to
27
+ @buf.put_int(op)
28
+ end
29
+
30
+ def write_int(i)
31
+ @buf.put_int(i)
32
+ update_message_length
33
+ end
34
+
35
+ def write_long(i)
36
+ @buf.put_long(i)
37
+ update_message_length
38
+ end
39
+
40
+ def write_string(s)
41
+ BSON.serialize_cstr(@buf, s)
42
+ update_message_length
43
+ end
44
+
45
+ def write_doc(hash)
46
+ @buf.put_array(BSON.new.serialize(hash).to_a)
47
+ update_message_length
48
+ end
49
+
50
+ def to_a
51
+ @buf.to_a
52
+ end
53
+
54
+ def dump
55
+ @buf.dump
56
+ end
57
+
58
+ # Do not call. Private, but kept public for testing.
59
+ def update_message_length
60
+ pos = @buf.position
61
+ @buf.put_int(@buf.size, 0)
62
+ @buf.position = pos
63
+ end
64
+
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,34 @@
1
+ require 'mongo/util/byte_buffer'
2
+
3
+ module XGen
4
+ module Mongo
5
+ module Driver
6
+
7
+ class MessageHeader
8
+
9
+ HEADER_SIZE = 16
10
+
11
+ def initialize()
12
+ @buf = ByteBuffer.new
13
+ end
14
+
15
+ def read_header(socket)
16
+ @buf.rewind
17
+ @buf.put_array(socket.recv(HEADER_SIZE).unpack("C*"))
18
+ raise "Short read for DB response header: expected #{HEADER_SIZE} bytes, saw #{@buf.size}" unless @buf.size == HEADER_SIZE
19
+ @buf.rewind
20
+ @size = @buf.get_int
21
+ @request_id = @buf.get_int
22
+ @response_to = @buf.get_int
23
+ @op = @buf.get_int
24
+ self
25
+ end
26
+
27
+ def dump
28
+ @buf.dump
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,17 @@
1
+ require 'mongo/message/message'
2
+ require 'mongo/message/opcodes'
3
+
4
+ module XGen
5
+ module Mongo
6
+ module Driver
7
+
8
+ class MsgMessage < Message
9
+
10
+ def initialize(msg)
11
+ super(OP_MSG)
12
+ write_string(msg)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module XGen
2
+ module Mongo
3
+ module Driver
4
+ OP_REPLY = 1 # reply. responseTo is set.
5
+ OP_MSG = 1000 # generic msg command followed by a string
6
+ OP_UPDATE = 2001 # update object
7
+ OP_INSERT = 2002
8
+ # GET_BY_OID = 2003
9
+ OP_QUERY = 2004
10
+ OP_GET_MORE = 2005
11
+ OP_DELETE = 2006
12
+ OP_KILL_CURSORS = 2007
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,67 @@
1
+ require 'mongo/message/message'
2
+ require 'mongo/message/opcodes'
3
+ require 'mongo/util/ordered_hash'
4
+
5
+ module XGen
6
+ module Mongo
7
+ module Driver
8
+
9
+ class QueryMessage < Message
10
+
11
+ attr_reader :query
12
+
13
+ def initialize(db_name, collection_name, query)
14
+ super(OP_QUERY)
15
+ @query = query
16
+ write_int(0)
17
+ write_string("#{db_name}.#{collection_name}")
18
+ write_int(query.number_to_skip)
19
+ write_int(query.number_to_return)
20
+ sel = query.selector
21
+ if query.contains_special_fields
22
+ sel = OrderedHash.new
23
+ sel['query'] = query.selector
24
+ if query.order_by && query.order_by.length > 0
25
+ sel['orderby'] = case query.order_by
26
+ when String
27
+ {query.order_by => 1}
28
+ when Array
29
+ h = OrderedHash.new
30
+ query.order_by.each { |ob|
31
+ case ob
32
+ when String
33
+ h[ob] = 1
34
+ when Hash # should have one entry; will handle all
35
+ ob.each { |k,v| h[k] = v }
36
+ else
37
+ raise "illegal query order_by value #{query.order_by.inspect}"
38
+ end
39
+ }
40
+ h
41
+ when Hash # Should be an ordered hash, but this message doesn't care
42
+ query.order_by
43
+ else
44
+ raise "illegal order_by: is a #{query.order_by.class.name}, must be String, Array, Hash, or OrderedHash"
45
+ end
46
+ end
47
+ if query.hint_fields && query.hint_fields.length > 0
48
+ hints = OrderedHash.new
49
+ query.hint_fields.each { |hf| hints[hf] = 1 }
50
+ sel['$hint'] = hints
51
+ end
52
+ if query.explain
53
+ sel['$explain'] = true
54
+ end
55
+
56
+ end
57
+ write_doc(sel)
58
+ write_doc(query.fields) if query.fields
59
+ end
60
+
61
+ def first_key(key)
62
+ @first_key = key
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,20 @@
1
+ require 'mongo/message/message'
2
+ require 'mongo/message/opcodes'
3
+
4
+ module XGen
5
+ module Mongo
6
+ module Driver
7
+
8
+ class RemoveMessage < Message
9
+
10
+ def initialize(db_name, collection_name, sel)
11
+ super(OP_DELETE)
12
+ write_int(0)
13
+ write_string("#{db_name}.#{collection_name}")
14
+ write_int(0) # flags?
15
+ write_doc(sel)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end