mongodb-mongo 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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