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.
- data/README.rdoc +216 -0
- data/Rakefile +54 -0
- data/bin/mongo_console +21 -0
- data/bin/validate +51 -0
- data/examples/benchmarks.rb +38 -0
- data/examples/blog.rb +76 -0
- data/examples/index_test.rb +128 -0
- data/examples/simple.rb +17 -0
- data/lib/mongo/admin.rb +86 -0
- data/lib/mongo/collection.rb +161 -0
- data/lib/mongo/cursor.rb +230 -0
- data/lib/mongo/db.rb +399 -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 +67 -0
- data/lib/mongo/message/remove_message.rb +20 -0
- data/lib/mongo/message/update_message.rb +21 -0
- data/lib/mongo/message.rb +4 -0
- data/lib/mongo/mongo.rb +98 -0
- data/lib/mongo/query.rb +110 -0
- data/lib/mongo/types/binary.rb +34 -0
- data/lib/mongo/types/dbref.rb +37 -0
- data/lib/mongo/types/objectid.rb +137 -0
- data/lib/mongo/types/regexp_of_holding.rb +44 -0
- data/lib/mongo/types/undefined.rb +31 -0
- data/lib/mongo/util/bson.rb +431 -0
- data/lib/mongo/util/byte_buffer.rb +163 -0
- data/lib/mongo/util/ordered_hash.rb +68 -0
- data/lib/mongo/util/xml_to_ruby.rb +102 -0
- data/lib/mongo.rb +12 -0
- data/mongo-ruby-driver.gemspec +62 -0
- data/tests/test_admin.rb +60 -0
- data/tests/test_bson.rb +135 -0
- data/tests/test_byte_buffer.rb +69 -0
- data/tests/test_cursor.rb +66 -0
- data/tests/test_db.rb +85 -0
- data/tests/test_db_api.rb +354 -0
- data/tests/test_db_connection.rb +17 -0
- data/tests/test_message.rb +35 -0
- data/tests/test_objectid.rb +98 -0
- data/tests/test_ordered_hash.rb +85 -0
- data/tests/test_round_trip.rb +116 -0
- metadata +100 -0
@@ -0,0 +1,161 @@
|
|
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/query'
|
18
|
+
|
19
|
+
module XGen
|
20
|
+
module Mongo
|
21
|
+
module Driver
|
22
|
+
|
23
|
+
# A named collection of records in a database.
|
24
|
+
class Collection
|
25
|
+
|
26
|
+
attr_reader :db, :name, :hint_fields
|
27
|
+
|
28
|
+
def initialize(db, name)
|
29
|
+
@db = db
|
30
|
+
@name = name
|
31
|
+
end
|
32
|
+
|
33
|
+
# Set hint fields to use and return +self+. hint_fields may be a
|
34
|
+
# single field name, array of field names, or a hash whose keys will
|
35
|
+
# become the hint field names. May be +nil+.
|
36
|
+
def hint(hint_fields)
|
37
|
+
@hint_fields = case hint_fields
|
38
|
+
when String
|
39
|
+
[hint_fields]
|
40
|
+
when Hash
|
41
|
+
hint_fields.keys
|
42
|
+
when nil
|
43
|
+
nil
|
44
|
+
else
|
45
|
+
hint_fields.to_a
|
46
|
+
end
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return records that match a +selector+ hash. See Mongo docs for
|
51
|
+
# details.
|
52
|
+
#
|
53
|
+
# Options:
|
54
|
+
# :fields :: Array of collection field names; only those will be returned (plus _id if defined)
|
55
|
+
# :offset :: Start at this record when returning records
|
56
|
+
# :limit :: Maximum number of records to return
|
57
|
+
# :sort :: Either hash of field names as keys and 1/-1 as values; 1 ==
|
58
|
+
# ascending, -1 == descending, or array of field names (all
|
59
|
+
# assumed to be sorted in ascending order).
|
60
|
+
def find(selector={}, options={})
|
61
|
+
fields = options.delete(:fields)
|
62
|
+
fields = nil if fields && fields.empty?
|
63
|
+
offset = options.delete(:offset) || 0
|
64
|
+
limit = options.delete(:limit) || 0
|
65
|
+
sort = options.delete(:sort)
|
66
|
+
raise RuntimeError, "Unknown options [#{options.inspect}]" unless options.empty?
|
67
|
+
@db.query(self, Query.new(selector, fields, offset, limit, sort))
|
68
|
+
end
|
69
|
+
|
70
|
+
# Insert +objects+, which are hashes. "<<" is aliased to this method.
|
71
|
+
# Returns either the single inserted object or a new array containing
|
72
|
+
# +objects+. The object(s) may have been modified by the database's PK
|
73
|
+
# factory, if it has one.
|
74
|
+
def insert(*objects)
|
75
|
+
objects = objects.first if objects.size == 1 && objects.first.is_a?(Array)
|
76
|
+
res = @db.insert_into_db(@name, objects)
|
77
|
+
res.size > 1 ? res : res.first
|
78
|
+
end
|
79
|
+
alias_method :<<, :insert
|
80
|
+
|
81
|
+
# Remove the records that match +selector+.
|
82
|
+
def remove(selector={})
|
83
|
+
@db.remove_from_db(@name, selector)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Remove all records.
|
87
|
+
def clear
|
88
|
+
remove({})
|
89
|
+
end
|
90
|
+
|
91
|
+
# Update records that match +selector+ by applying +obj+ as an update.
|
92
|
+
# If no match, inserts (???).
|
93
|
+
def repsert(selector, obj)
|
94
|
+
@db.repsert_in_db(@name, selector, obj)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Update records that match +selector+ by applying +obj+ as an update.
|
98
|
+
def replace(selector, obj)
|
99
|
+
@db.replace_in_db(@name, selector, obj)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Update records that match +selector+ by applying +obj+ as an update.
|
103
|
+
# Both +selector+ and +modifier_obj+ are required.
|
104
|
+
def modify(selector, modifier_obj)
|
105
|
+
raise "no object" unless modifier_obj
|
106
|
+
raise "no selector" unless selector
|
107
|
+
@db.modify_in_db(@name, selector, modifier_obj)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Create a new index named +index_name+. +fields+ should be an array
|
111
|
+
# of field names.
|
112
|
+
def create_index(name, *fields)
|
113
|
+
@db.create_index(@name, name, fields)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Drop index +name+.
|
117
|
+
def drop_index(name)
|
118
|
+
@db.drop_index(@name, name)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Drop all indexes.
|
122
|
+
def drop_indexes
|
123
|
+
# just need to call drop indexes with no args; will drop them all
|
124
|
+
@db.drop_index(@name, '*')
|
125
|
+
end
|
126
|
+
|
127
|
+
# Drop the entire collection. USE WITH CAUTION.
|
128
|
+
def drop
|
129
|
+
@db.drop_collection(@name)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Return an array of hashes, one for each index. Each hash contains:
|
133
|
+
#
|
134
|
+
# :name :: Index name
|
135
|
+
#
|
136
|
+
# :keys :: Hash whose keys are the names of the fields that make up
|
137
|
+
# the key and values are integers.
|
138
|
+
#
|
139
|
+
# :ns :: Namespace; same as this collection's name.
|
140
|
+
def index_information
|
141
|
+
@db.index_information(@name)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Return a hash containing options that apply to this collection.
|
145
|
+
# 'create' will be the collection name. For the other possible keys
|
146
|
+
# and values, see DB#create_collection.
|
147
|
+
def options
|
148
|
+
@db.collections_info(@name).next_object()['options']
|
149
|
+
end
|
150
|
+
|
151
|
+
# Return the number of records that match +selector+. If +selector+ is
|
152
|
+
# +nil+ or an empty hash, returns the count of all records.
|
153
|
+
def count(selector={})
|
154
|
+
@db.count(@name, selector || {})
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
data/lib/mongo/cursor.rb
ADDED
@@ -0,0 +1,230 @@
|
|
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
|
+
attr_reader :db, :collection, :query
|
33
|
+
|
34
|
+
def initialize(db, collection, query)
|
35
|
+
@db, @collection, @query = db, collection, query
|
36
|
+
@num_to_return = @query.number_to_return || 0
|
37
|
+
@cache = []
|
38
|
+
@closed = false
|
39
|
+
@can_call_to_a = true
|
40
|
+
@query_run = false
|
41
|
+
end
|
42
|
+
|
43
|
+
def closed?; @closed; end
|
44
|
+
|
45
|
+
# Set hint fields to use and return +self+. hint_fields may be a
|
46
|
+
# single field name, array of field names, or a hash whose keys will
|
47
|
+
# become the hint field names. May be +nil+. If no hint fields are
|
48
|
+
# specified, the ones in the collection are used if they exist.
|
49
|
+
def hint(hint_fields)
|
50
|
+
@hint_fields = case hint_fields
|
51
|
+
when String
|
52
|
+
[hint_fields]
|
53
|
+
when Hash
|
54
|
+
hint_fields.keys
|
55
|
+
when nil
|
56
|
+
nil
|
57
|
+
else
|
58
|
+
hint_fields.to_a
|
59
|
+
end
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
# Return +true+ if there are more records to retrieve. We do not check
|
64
|
+
# @num_to_return; #each is responsible for doing that.
|
65
|
+
def more?
|
66
|
+
num_remaining > 0
|
67
|
+
end
|
68
|
+
|
69
|
+
# Return the next object. Raises an error if necessary.
|
70
|
+
def next_object
|
71
|
+
refill_via_get_more if num_remaining == 0
|
72
|
+
o = @cache.shift
|
73
|
+
raise o['$err'] if o && o['$err']
|
74
|
+
o
|
75
|
+
end
|
76
|
+
|
77
|
+
# Iterate over each object, yielding it to the given block. At most
|
78
|
+
# @num_to_return records are returned (or all of them, if
|
79
|
+
# @num_to_return is 0).
|
80
|
+
#
|
81
|
+
# If #to_a has already been called then this method uses the array
|
82
|
+
# that we store internally. In that case, #each can be called multiple
|
83
|
+
# times because it re-uses that array.
|
84
|
+
#
|
85
|
+
# You can call #each after calling #to_a (multiple times even, because
|
86
|
+
# it will use the internally-stored array), but you can't call #to_a
|
87
|
+
# after calling #each unless you also called it before calling #each.
|
88
|
+
# If you try to do that, an error will be raised.
|
89
|
+
def each
|
90
|
+
if @rows # Already turned into an array
|
91
|
+
@rows.each { |row| yield row }
|
92
|
+
else
|
93
|
+
num_returned = 0
|
94
|
+
while more? && (@num_to_return <= 0 || num_returned < @num_to_return)
|
95
|
+
yield next_object()
|
96
|
+
num_returned += 1
|
97
|
+
end
|
98
|
+
@can_call_to_a = false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Return all of the rows (up to the +num_to_return+ value specified in
|
103
|
+
# #new) as an array. Calling this multiple times will work fine; it
|
104
|
+
# always returns the same array.
|
105
|
+
#
|
106
|
+
# Don't use this if you're expecting large amounts of data, of course.
|
107
|
+
# All of the returned rows are kept in an array stored in this object
|
108
|
+
# so it can be reused.
|
109
|
+
#
|
110
|
+
# You can call #each after calling #to_a (multiple times even, because
|
111
|
+
# it will use the internally-stored array), but you can't call #to_a
|
112
|
+
# after calling #each unless you also called it before calling #each.
|
113
|
+
# If you try to do that, an error will be raised.
|
114
|
+
def to_a
|
115
|
+
return @rows if @rows
|
116
|
+
raise "can't call Cursor#to_a after calling Cursor#each" unless @can_call_to_a
|
117
|
+
@rows = []
|
118
|
+
num_returned = 0
|
119
|
+
while more? && (@num_to_return <= 0 || num_returned < @num_to_return)
|
120
|
+
@rows << next_object()
|
121
|
+
num_returned += 1
|
122
|
+
end
|
123
|
+
@rows
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns an explain plan record.
|
127
|
+
def explain
|
128
|
+
old_val = @query.explain
|
129
|
+
@query.explain = true
|
130
|
+
|
131
|
+
c = Cursor.new(@db, @collection, @query)
|
132
|
+
explanation = c.next_object
|
133
|
+
c.close
|
134
|
+
|
135
|
+
@query.explain = old_val
|
136
|
+
explanation
|
137
|
+
end
|
138
|
+
|
139
|
+
# Close the cursor.
|
140
|
+
def close
|
141
|
+
@db.send_to_db(KillCursorsMessage.new(@cursor_id)) if @cursor_id
|
142
|
+
@cache = []
|
143
|
+
@cursor_id = 0
|
144
|
+
@closed = true
|
145
|
+
end
|
146
|
+
|
147
|
+
protected
|
148
|
+
|
149
|
+
def read_all
|
150
|
+
read_message_header
|
151
|
+
read_response_header
|
152
|
+
read_objects_off_wire
|
153
|
+
end
|
154
|
+
|
155
|
+
def read_objects_off_wire
|
156
|
+
while doc = next_object_on_wire
|
157
|
+
@cache << doc
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def read_message_header
|
162
|
+
MessageHeader.new.read_header(@db.socket)
|
163
|
+
end
|
164
|
+
|
165
|
+
def read_response_header
|
166
|
+
header_buf = ByteBuffer.new
|
167
|
+
header_buf.put_array(@db.socket.recv(RESPONSE_HEADER_SIZE).unpack("C*"))
|
168
|
+
raise "Short read for DB response header; expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}" unless header_buf.length == RESPONSE_HEADER_SIZE
|
169
|
+
header_buf.rewind
|
170
|
+
@result_flags = header_buf.get_int
|
171
|
+
@cursor_id = header_buf.get_long
|
172
|
+
@starting_from = header_buf.get_int
|
173
|
+
@n_returned = header_buf.get_int
|
174
|
+
@n_remaining = @n_returned
|
175
|
+
end
|
176
|
+
|
177
|
+
def num_remaining
|
178
|
+
refill_via_get_more if @cache.length == 0
|
179
|
+
@cache.length
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
def next_object_on_wire
|
185
|
+
send_query_if_needed
|
186
|
+
# if @n_remaining is 0 but we have a non-zero cursor, there are more
|
187
|
+
# to fetch, so do a GetMore operation, but don't do it here - do it
|
188
|
+
# when someone pulls an object out of the cache and it's empty
|
189
|
+
return nil if @n_remaining == 0
|
190
|
+
object_from_stream
|
191
|
+
end
|
192
|
+
|
193
|
+
def refill_via_get_more
|
194
|
+
send_query_if_needed
|
195
|
+
return if @cursor_id == 0
|
196
|
+
@db.send_to_db(GetMoreMessage.new(@db.name, @collection.name, @cursor_id))
|
197
|
+
read_all
|
198
|
+
end
|
199
|
+
|
200
|
+
def object_from_stream
|
201
|
+
buf = ByteBuffer.new
|
202
|
+
buf.put_array(@db.socket.recv(4).unpack("C*"))
|
203
|
+
buf.rewind
|
204
|
+
size = buf.get_int
|
205
|
+
buf.put_array(@db.socket.recv(size-4).unpack("C*"), 4)
|
206
|
+
@n_remaining -= 1
|
207
|
+
buf.rewind
|
208
|
+
BSON.new(@db).deserialize(buf)
|
209
|
+
end
|
210
|
+
|
211
|
+
def send_query_if_needed
|
212
|
+
# Run query first time we request an object from the wire
|
213
|
+
unless @query_run
|
214
|
+
hints = @hint_fields || @collection.hint_fields
|
215
|
+
old_hints = @query.hint_fields
|
216
|
+
@query.hint_fields = hints
|
217
|
+
@db.send_query_message(QueryMessage.new(@db.name, @collection.name, @query))
|
218
|
+
@query_run = true
|
219
|
+
@query.hint_fields = old_hints
|
220
|
+
read_all
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def to_s
|
225
|
+
"DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from, n_returned=#@n_returned)"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|