mongo 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +134 -0
- data/Rakefile +86 -0
- data/bin/mongo_console +21 -0
- data/lib/mongo.rb +7 -0
- data/lib/mongo/admin.rb +86 -0
- data/lib/mongo/collection.rb +136 -0
- data/lib/mongo/cursor.rb +180 -0
- data/lib/mongo/db.rb +307 -0
- data/lib/mongo/message.rb +4 -0
- data/lib/mongo/message/get_more_message.rb +21 -0
- data/lib/mongo/message/insert_message.rb +19 -0
- data/lib/mongo/message/kill_cursors_message.rb +20 -0
- data/lib/mongo/message/message.rb +68 -0
- data/lib/mongo/message/message_header.rb +34 -0
- data/lib/mongo/message/msg_message.rb +17 -0
- data/lib/mongo/message/opcodes.rb +16 -0
- data/lib/mongo/message/query_message.rb +45 -0
- data/lib/mongo/message/remove_message.rb +20 -0
- data/lib/mongo/message/update_message.rb +21 -0
- data/lib/mongo/mongo.rb +52 -0
- data/lib/mongo/objectid.rb +107 -0
- data/lib/mongo/query.rb +103 -0
- data/lib/mongo/util/bson.rb +339 -0
- data/lib/mongo/util/byte_buffer.rb +163 -0
- data/lib/mongo/util/ordered_hash.rb +54 -0
- data/tests/test_admin.rb +59 -0
- data/tests/test_bson.rb +79 -0
- data/tests/test_byte_buffer.rb +69 -0
- data/tests/test_db_api.rb +357 -0
- data/tests/test_db_connection.rb +17 -0
- data/tests/test_message.rb +35 -0
- data/tests/test_objectid.rb +68 -0
- data/tests/test_ordered_hash.rb +82 -0
- metadata +85 -0
data/lib/mongo/cursor.rb
ADDED
@@ -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
|
+
|
data/lib/mongo/db.rb
ADDED
@@ -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
|