em-mongo 0.3.6 → 0.4.0
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/VERSION +1 -1
- data/lib/em-mongo/collection.rb +756 -23
- data/lib/em-mongo/connection.rb +100 -95
- data/lib/em-mongo/conversions.rb +2 -2
- data/lib/em-mongo/core_ext.rb +20 -0
- data/lib/em-mongo/cursor.rb +537 -0
- data/lib/em-mongo/database.rb +348 -20
- data/lib/em-mongo/exceptions.rb +2 -2
- data/lib/em-mongo/prev.rb +53 -0
- data/lib/em-mongo/request_response.rb +34 -0
- data/lib/em-mongo/server_response.rb +32 -0
- data/lib/em-mongo/support.rb +4 -4
- data/lib/em-mongo.rb +5 -0
- data/spec/integration/collection_spec.rb +654 -154
- data/spec/integration/cursor_spec.rb +350 -0
- data/spec/integration/database_spec.rb +149 -3
- data/spec/integration/request_response_spec.rb +63 -0
- metadata +12 -2
@@ -0,0 +1,34 @@
|
|
1
|
+
module EM
|
2
|
+
module Mongo
|
3
|
+
class RequestResponse
|
4
|
+
include EM::Deferrable
|
5
|
+
|
6
|
+
def status
|
7
|
+
@deferred_status
|
8
|
+
end
|
9
|
+
|
10
|
+
def completed?
|
11
|
+
[:succeeded, :failed].include?(status)
|
12
|
+
end
|
13
|
+
|
14
|
+
def succeeded?
|
15
|
+
status == :succeeded
|
16
|
+
end
|
17
|
+
|
18
|
+
def failed?
|
19
|
+
status == :failed
|
20
|
+
end
|
21
|
+
|
22
|
+
def data
|
23
|
+
@deferred_args[-1] if succeeded? && @deferred_args
|
24
|
+
end
|
25
|
+
|
26
|
+
def error
|
27
|
+
@deferred_args[-1] if failed? && @deferred_args
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module EM::Mongo
|
2
|
+
class ServerResponse
|
3
|
+
|
4
|
+
attr_reader :size, :request_id, :response_to, :op,
|
5
|
+
:result_flags, :cursor_id, :starting_from,
|
6
|
+
:number_returned, :docs, :connection
|
7
|
+
|
8
|
+
|
9
|
+
def initialize(buffer, connection)
|
10
|
+
@connection = connection
|
11
|
+
# Header
|
12
|
+
@size = buffer.get_int
|
13
|
+
@request_id = buffer.get_int
|
14
|
+
@response_to = buffer.get_int
|
15
|
+
@op = buffer.get_int
|
16
|
+
|
17
|
+
# Response Header
|
18
|
+
@result_flags = buffer.get_int
|
19
|
+
@cursor_id = buffer.get_long
|
20
|
+
@starting_from = buffer.get_int
|
21
|
+
@number_returned = buffer.get_int
|
22
|
+
|
23
|
+
# Documents
|
24
|
+
@docs = (1..number_returned).map do
|
25
|
+
size= @connection.peek_size(buffer)
|
26
|
+
buf = buffer.get(size)
|
27
|
+
BSON::BSON_CODER.deserialize(buf)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
data/lib/em-mongo/support.rb
CHANGED
@@ -18,9 +18,9 @@
|
|
18
18
|
|
19
19
|
require 'digest/md5'
|
20
20
|
|
21
|
-
module Mongo
|
21
|
+
module EM::Mongo
|
22
22
|
module Support
|
23
|
-
include Mongo::Conversions
|
23
|
+
include EM::Mongo::Conversions
|
24
24
|
extend self
|
25
25
|
|
26
26
|
# Generate an MD5 for authentication.
|
@@ -52,10 +52,10 @@ module Mongo
|
|
52
52
|
|
53
53
|
[" ", ".", "$", "/", "\\"].each do |invalid_char|
|
54
54
|
if db_name.include? invalid_char
|
55
|
-
raise Mongo::InvalidNSName, "database names cannot contain the character '#{invalid_char}'"
|
55
|
+
raise EM::Mongo::InvalidNSName, "database names cannot contain the character '#{invalid_char}'"
|
56
56
|
end
|
57
57
|
end
|
58
|
-
raise Mongo::InvalidNSName, "database name cannot be the empty string" if db_name.empty?
|
58
|
+
raise EM::Mongo::InvalidNSName, "database name cannot be the empty string" if db_name.empty?
|
59
59
|
db_name
|
60
60
|
end
|
61
61
|
|
data/lib/em-mongo.rb
CHANGED
@@ -22,5 +22,10 @@ require File.join(EM::Mongo::LIBPATH, "em-mongo/support")
|
|
22
22
|
require File.join(EM::Mongo::LIBPATH, "em-mongo/database")
|
23
23
|
require File.join(EM::Mongo::LIBPATH, "em-mongo/connection")
|
24
24
|
require File.join(EM::Mongo::LIBPATH, "em-mongo/collection")
|
25
|
+
require File.join(EM::Mongo::LIBPATH, "em-mongo/exceptions")
|
26
|
+
require File.join(EM::Mongo::LIBPATH, "em-mongo/cursor")
|
27
|
+
require File.join(EM::Mongo::LIBPATH, "em-mongo/request_response")
|
28
|
+
require File.join(EM::Mongo::LIBPATH, "em-mongo/server_response")
|
29
|
+
require File.join(EM::Mongo::LIBPATH, "em-mongo/core_ext")
|
25
30
|
|
26
31
|
EMMongo = EM::Mongo
|
@@ -3,240 +3,740 @@ require File.expand_path('spec_helper', File.dirname(__FILE__) + '/../')
|
|
3
3
|
describe EMMongo::Collection do
|
4
4
|
include EM::Spec
|
5
5
|
|
6
|
-
it 'should
|
6
|
+
it 'should return a sub collection via the indexer method' do
|
7
7
|
@conn, @coll = connection_and_collection
|
8
|
-
|
9
|
-
doc = {'hello' => 'world'}
|
10
|
-
id = @coll.insert(doc)
|
11
|
-
id.should be_a_kind_of(BSON::ObjectId)
|
12
|
-
doc[:_id].should be_a_kind_of(BSON::ObjectId)
|
8
|
+
@coll["child"].name.should == "#{@coll.name}.child"
|
13
9
|
done
|
14
10
|
end
|
15
11
|
|
16
|
-
it
|
12
|
+
it "should drop the collection" do
|
17
13
|
@conn, @coll = connection_and_collection
|
14
|
+
@coll.insert({:x => "x"})
|
15
|
+
@coll.drop.callback do
|
16
|
+
@coll.db.collection_names.callback do |names|
|
17
|
+
names.should_not include @ns
|
18
|
+
done
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
18
22
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
+
describe "find" do
|
24
|
+
it 'should return a cursor' do
|
25
|
+
@conn, @coll = connection_and_collection
|
26
|
+
cursor = @coll.find(:hi=>"there")
|
27
|
+
cursor.should be_a_kind_of(EM::Mongo::Cursor)
|
23
28
|
done
|
24
29
|
end
|
25
|
-
end
|
26
30
|
|
27
|
-
|
28
|
-
|
31
|
+
it 'should find an object by attribute' do
|
32
|
+
@conn, @coll = connection_and_collection
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
@coll.insert("hello" => 'world')
|
35
|
+
@coll.find({"hello" => "world"},{}).to_a.callback do |res|
|
36
|
+
res.size.should >= 1
|
37
|
+
res[0]["hello"].should == "world"
|
38
|
+
done
|
39
|
+
end
|
35
40
|
end
|
36
|
-
end
|
37
41
|
|
38
|
-
|
39
|
-
|
42
|
+
it 'should take strings or symbols for hashes' do
|
43
|
+
@conn, @coll = connection_and_collection
|
40
44
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
45
|
+
obj = @coll.insert({:_id => 1234, 'foo' => 'bar', :hello => 'world'})
|
46
|
+
@coll.first({:_id => 1234},{}).callback do |res|
|
47
|
+
res['hello'].should == 'world'
|
48
|
+
res['foo'].should == 'bar'
|
49
|
+
done
|
50
|
+
end
|
46
51
|
end
|
47
|
-
end
|
48
52
|
|
49
|
-
|
50
|
-
|
53
|
+
it 'should find an object by symbol' do
|
54
|
+
@conn, @coll = connection_and_collection
|
51
55
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
56
|
+
@coll.insert('hello' => 'world')
|
57
|
+
@coll.find({:hello => "world"},{}).to_a.callback do |res|
|
58
|
+
res.size.should >= 1
|
59
|
+
res[0]["hello"].should == "world"
|
60
|
+
done
|
61
|
+
end
|
57
62
|
end
|
58
|
-
end
|
59
63
|
|
60
|
-
|
61
|
-
|
64
|
+
it 'should find an object by id' do
|
65
|
+
@conn, @coll = connection_and_collection
|
62
66
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
67
|
+
id = @coll.insert('hello' => 'world')
|
68
|
+
@coll.find({:_id => id},{}).to_a.callback do |res|
|
69
|
+
res.size.should >= 1
|
70
|
+
res[0]['hello'].should == "world"
|
71
|
+
done
|
72
|
+
end
|
68
73
|
end
|
69
|
-
end
|
70
74
|
|
71
|
-
|
72
|
-
|
75
|
+
it 'should find all objects' do
|
76
|
+
@conn, @coll = connection_and_collection
|
73
77
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
78
|
+
@coll.insert('one' => 'one')
|
79
|
+
@coll.insert('two' => 'two')
|
80
|
+
@coll.find.to_a.callback do |res|
|
81
|
+
res.size.should >= 2
|
82
|
+
done
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should find objects and sort by the order field' do
|
87
|
+
@conn, @coll = connection_and_collection
|
88
|
+
|
89
|
+
@coll.insert(:name => 'one', :position => 0)
|
90
|
+
@coll.insert(:name => 'three', :position => 2)
|
91
|
+
@coll.insert(:name => 'two', :position => 1)
|
92
|
+
|
93
|
+
@coll.find({}, {:order => 'position'}).to_a.callback do |res|
|
94
|
+
res[0]["name"].should == 'one'
|
95
|
+
res[1]["name"].should == 'two'
|
96
|
+
res[2]["name"].should == 'three'
|
97
|
+
done
|
98
|
+
end
|
99
|
+
|
100
|
+
@coll.find({}, {:order => [:position, :desc]}).to_a.callback do |res|
|
101
|
+
res[0]["name"].should == 'three'
|
102
|
+
res[1]["name"].should == 'two'
|
103
|
+
res[2]["name"].should == 'one'
|
104
|
+
done
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should find a single document with find_one/first" do
|
109
|
+
@conn, @coll = connection_and_collection
|
110
|
+
|
111
|
+
@coll.insert(:name => 'one', :position => 0)
|
112
|
+
@coll.insert(:name => 'three', :position => 2)
|
113
|
+
@coll.insert(:name => 'two', :position => 1)
|
114
|
+
|
115
|
+
@coll.find_one({},:sort => [:position,-1]).callback do |first|
|
116
|
+
first["position"].should == 2
|
117
|
+
done
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should find an object using nested properties' do
|
122
|
+
@conn, @coll = connection_and_collection
|
123
|
+
|
124
|
+
@coll.insert({
|
125
|
+
'name' => 'Google',
|
126
|
+
'address' => {
|
127
|
+
'cxity' => 'Mountain View',
|
128
|
+
'state' => 'California'}
|
129
|
+
})
|
130
|
+
|
131
|
+
@coll.first('address.cxity' => 'Mountain View').callback do |res|
|
132
|
+
res['name'].should == 'Google'
|
133
|
+
done
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should find objects wxith specific values' do
|
138
|
+
@conn, @coll = connection_and_collection
|
139
|
+
|
140
|
+
number_hash.each do |num, word|
|
141
|
+
@coll.insert({'num' => num, 'word' => word})
|
142
|
+
end
|
143
|
+
|
144
|
+
@coll.find({'num' => {'$in' => [1,3,5]}}).to_a.callback do |res|
|
145
|
+
res.size.should == 3
|
146
|
+
res.map{|r| r['num'] }.sort.should == [1,3,5]
|
147
|
+
done
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should find objects greater than something' do
|
152
|
+
@conn, @coll = connection_and_collection
|
153
|
+
|
154
|
+
number_hash.each do |num, word|
|
155
|
+
@coll.insert('num' => num, 'word' => word)
|
156
|
+
end
|
157
|
+
|
158
|
+
@coll.find({'num' => {'$gt' => 3}}).to_a.callback do |res|
|
159
|
+
res.size.should == 6
|
160
|
+
res.map{|r| r['num'] }.sort.should == [4,5,6,7,8,9]
|
161
|
+
done
|
162
|
+
end
|
79
163
|
end
|
80
164
|
end
|
81
165
|
|
82
|
-
|
83
|
-
@conn, @coll = connection_and_collection
|
166
|
+
describe "insert" do
|
84
167
|
|
85
|
-
|
86
|
-
|
87
|
-
@coll.insert(:name => 'two', :position => 1)
|
168
|
+
it 'should insert an object' do
|
169
|
+
@conn, @coll = connection_and_collection
|
88
170
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
171
|
+
doc = {'hello' => 'world'}
|
172
|
+
id = @coll.insert(doc)
|
173
|
+
id.should be_a_kind_of(BSON::ObjectId)
|
174
|
+
doc[:_id].should be_a_kind_of(BSON::ObjectId)
|
93
175
|
done
|
94
176
|
end
|
95
177
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
178
|
+
it "should insert multiple documents" do
|
179
|
+
@conn, @coll = connection_and_collection
|
180
|
+
|
181
|
+
docs = [{'hello' => 'world'}, {'goodbye' => 'womb'}]
|
182
|
+
ids = @coll.insert(docs)
|
183
|
+
ids.should be_a_kind_of(Array)
|
184
|
+
ids[0].should == docs[0][:_id]
|
185
|
+
ids[1].should == docs[1][:_id]
|
100
186
|
done
|
101
187
|
end
|
188
|
+
|
189
|
+
it 'should insert an object with a custom _id' do
|
190
|
+
@conn, @coll = connection_and_collection
|
191
|
+
|
192
|
+
id = @coll.insert(:_id => 1234, 'hello' => 'world')
|
193
|
+
id.should == 1234
|
194
|
+
@coll.first({'hello' => 'world'}).callback do |res|
|
195
|
+
res['_id'].should == 1234
|
196
|
+
done
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'should insert a Time' do
|
201
|
+
@conn, @coll = connection_and_collection
|
202
|
+
|
203
|
+
t = Time.now.utc.freeze
|
204
|
+
@coll.insert('date' => t)
|
205
|
+
@coll.find.to_a.callback do |res|
|
206
|
+
res[0]['date'].to_s.should == t.to_s
|
207
|
+
done
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'should insert a complex object' do
|
212
|
+
@conn, @coll = connection_and_collection
|
213
|
+
|
214
|
+
obj = {
|
215
|
+
'array' => [1,2,3],
|
216
|
+
'float' => 123.456,
|
217
|
+
'hash' => {'boolean' => true},
|
218
|
+
'nil' => nil,
|
219
|
+
'symbol' => :name,
|
220
|
+
'string' => 'hello world',
|
221
|
+
'time' => Time.now.to_f,
|
222
|
+
'regex' => /abc$/ix
|
223
|
+
}
|
224
|
+
retobj = @coll.insert(obj)
|
225
|
+
@coll.find({:_id => obj[:_id]}).to_a.callback do |ret|
|
226
|
+
ret.size.should == 1
|
227
|
+
ret[0].each_key do |key|
|
228
|
+
next if key == '_id'
|
229
|
+
ret[0][key].should == obj[key]
|
230
|
+
end
|
231
|
+
done
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
context "safe_insert" do
|
236
|
+
it "should succesfully save a document with no errors" do
|
237
|
+
@conn, @coll = connection_and_collection('safe.test')
|
238
|
+
@coll.safe_insert({"hello" => "world"}).callback do |ok|
|
239
|
+
ok.should be_a_kind_of BSON::ObjectId
|
240
|
+
done
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
it "should respond with an error when an invalid document is saved" do
|
245
|
+
@conn, @coll = connection_and_collection('safe.test')
|
246
|
+
@coll.create_index("hello", :unique => true)
|
247
|
+
a = {"hello" => "world"}
|
248
|
+
@coll.insert(a)
|
249
|
+
resp = @coll.safe_insert(a).errback do |err|
|
250
|
+
err[0].should == EM::Mongo::OperationFailure
|
251
|
+
done
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
102
256
|
end
|
103
257
|
|
104
|
-
|
105
|
-
|
258
|
+
describe "update" do
|
259
|
+
|
260
|
+
it 'should update an object' do
|
261
|
+
@conn, @coll = connection_and_collection
|
106
262
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
res.size.should == 1500
|
263
|
+
id = @coll.insert('hello' => 'world')
|
264
|
+
@coll.update({'hello' => 'world'}, {'hello' => 'newworld'})
|
265
|
+
@coll.find({:_id => id},{}).to_a.callback do |res|
|
266
|
+
res[0]['hello'].should == 'newworld'
|
112
267
|
done
|
113
268
|
end
|
114
269
|
end
|
270
|
+
|
271
|
+
it 'should update an object wxith $inc' do
|
272
|
+
@conn, @coll = connection_and_collection
|
273
|
+
|
274
|
+
id = @coll.insert('hello' => 'world')
|
275
|
+
@coll.update({'hello' => 'world'}, {'$inc' => {'count' => 1}})
|
276
|
+
@coll.find({:_id => id},{}).to_a.callback do |res|
|
277
|
+
res.first['hello'].should == 'world'
|
278
|
+
res.first['count'].should == 1
|
279
|
+
done
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
context "safe_update" do
|
284
|
+
it "should respond with an error when an invalid document is updated" do
|
285
|
+
@conn, @coll = connection_and_collection('safe.update.test')
|
286
|
+
@coll.create_index("x", :unique => true)
|
287
|
+
@coll.insert({"x" => 5})
|
288
|
+
@coll.insert({"x" => 10})
|
289
|
+
|
290
|
+
@coll.safe_update({},{"x" => 10}).errback do |err|
|
291
|
+
err[0].should == EM::Mongo::OperationFailure
|
292
|
+
done
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
115
297
|
end
|
116
298
|
|
117
|
-
|
118
|
-
|
299
|
+
describe "save" do
|
300
|
+
|
301
|
+
it "should insert a record when no id is present" do
|
302
|
+
@conn, @coll = connection_and_collection
|
303
|
+
id = @coll.save("x" => 1)
|
304
|
+
@coll.find("x" => 1).to_a.callback do |result|
|
305
|
+
result[0]["_id"].should == id
|
306
|
+
done
|
307
|
+
end
|
308
|
+
end
|
119
309
|
|
120
|
-
id
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
310
|
+
it "should update a record when id is present" do
|
311
|
+
@conn, @coll = connection_and_collection
|
312
|
+
doc = {"x" => 1}
|
313
|
+
id = @coll.save(doc)
|
314
|
+
doc["x"] = 2
|
315
|
+
@coll.save(doc).should be_true
|
316
|
+
@coll.find().to_a.callback do |result|
|
317
|
+
result.count.should == 1
|
318
|
+
result[0]["x"].should == 2
|
319
|
+
done
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
context "safe_save" do
|
324
|
+
it "should respond with an error when an invalid document is updated" do
|
325
|
+
@conn, @coll = connection_and_collection('safe.save.test')
|
326
|
+
@coll.create_index("x", :unique => true)
|
327
|
+
@coll.save({"x" => 5})
|
328
|
+
@coll.save({"x" => 5})
|
329
|
+
|
330
|
+
@coll.safe_save({"x" => 5}).errback do |err|
|
331
|
+
err[0].should == EM::Mongo::OperationFailure
|
332
|
+
done
|
333
|
+
end
|
334
|
+
end
|
125
335
|
end
|
126
336
|
end
|
127
337
|
|
128
|
-
|
129
|
-
@conn, @coll = connection_and_collection
|
338
|
+
describe "remove" do
|
130
339
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
340
|
+
it 'should remove an object' do
|
341
|
+
@conn, @coll = connection_and_collection
|
342
|
+
|
343
|
+
id = @coll.insert('hello' => 'world')
|
344
|
+
@coll.remove(:_id => id)
|
345
|
+
@coll.find({'hello' => "world"}).to_a.callback do |res|
|
346
|
+
res.size.should == 0
|
347
|
+
done
|
348
|
+
end
|
137
349
|
end
|
350
|
+
|
351
|
+
it 'should remove all objects' do
|
352
|
+
@conn, @coll = connection_and_collection
|
353
|
+
|
354
|
+
@coll.insert('one' => 'one')
|
355
|
+
@coll.insert('two' => 'two')
|
356
|
+
@coll.remove
|
357
|
+
@coll.find.to_a.callback do |res|
|
358
|
+
res.size.should == 0
|
359
|
+
done
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
138
363
|
end
|
139
364
|
|
140
|
-
|
141
|
-
|
365
|
+
describe "find_and_modify" do
|
366
|
+
|
367
|
+
it "should find and modify a document" do
|
368
|
+
@conn, @coll = connection_and_collection
|
369
|
+
@coll << { :a => 1, :processed => false }
|
370
|
+
@coll << { :a => 2, :processed => false }
|
371
|
+
@coll << { :a => 3, :processed => false }
|
372
|
+
|
373
|
+
resp = @coll.find_and_modify(:query => {}, :sort => [['a', -1]], :update => {"$set" => {:processed => true}})
|
374
|
+
resp.callback do |doc|
|
375
|
+
doc['processed'].should_not be_true
|
376
|
+
@coll.find_one({:a=>3}).callback do |updated|
|
377
|
+
updated['processed'].should be_true
|
378
|
+
done
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
142
382
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
383
|
+
it "should fail with invalid options" do
|
384
|
+
@conn, @coll = connection_and_collection
|
385
|
+
@coll << { :a => 1, :processed => false }
|
386
|
+
@coll << { :a => 2, :processed => false }
|
387
|
+
@coll << { :a => 3, :processed => false }
|
388
|
+
|
389
|
+
resp = @coll.find_and_modify(:blimey => {})
|
390
|
+
resp.errback do |err|
|
391
|
+
err[0].should == EM::Mongo::OperationFailure
|
392
|
+
done
|
393
|
+
end
|
148
394
|
end
|
395
|
+
|
149
396
|
end
|
150
397
|
|
151
|
-
|
152
|
-
|
398
|
+
describe "mapreduce" do
|
399
|
+
it "should map, and then reduce" do
|
400
|
+
@conn, @coll = connection_and_collection
|
401
|
+
@coll << { "user_id" => 1 }
|
402
|
+
@coll << { "user_id" => 2 }
|
403
|
+
|
404
|
+
m = "function() { emit(this.user_id, 1); }"
|
405
|
+
r = "function(k,vals) { return 1; }"
|
406
|
+
|
407
|
+
res = @coll.map_reduce(m, r, :out => 'foo')
|
408
|
+
res.callback do |collection|
|
409
|
+
collection.find_one({"_id" => 1}).callback do |doc|
|
410
|
+
doc.should_not be_nil
|
411
|
+
collection.find_one({"_id" => 2}).callback do |doc2|
|
412
|
+
doc2.should_not be_nil
|
413
|
+
done
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
153
418
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
419
|
+
it "should work with code objects" do
|
420
|
+
@conn, @coll = connection_and_collection
|
421
|
+
@coll << { "user_id" => 1 }
|
422
|
+
@coll << { "user_id" => 2 }
|
423
|
+
|
424
|
+
m = BSON::Code.new "function() { emit(this.user_id, 1); }"
|
425
|
+
r = BSON::Code.new "function(k,vals) { return 1; }"
|
426
|
+
|
427
|
+
res = @coll.map_reduce(m, r, :out => 'foo')
|
428
|
+
res.callback do |collection|
|
429
|
+
collection.find_one({"_id" => 1}).callback do |doc|
|
430
|
+
doc.should_not be_nil
|
431
|
+
collection.find_one({"_id" => 2}).callback do |doc2|
|
432
|
+
doc2.should_not be_nil
|
433
|
+
done
|
434
|
+
end
|
435
|
+
end
|
436
|
+
end
|
160
437
|
end
|
438
|
+
|
439
|
+
it "should respect a query" do
|
440
|
+
@conn, @coll = connection_and_collection
|
441
|
+
@coll << { "user_id" => 1 }
|
442
|
+
@coll << { "user_id" => 2 }
|
443
|
+
@coll << { "user_id" => 3 }
|
444
|
+
|
445
|
+
m = BSON::Code.new "function() { emit(this.user_id, 1); }"
|
446
|
+
r = BSON::Code.new "function(k,vals) { return 1; }"
|
447
|
+
|
448
|
+
res = @coll.map_reduce(m, r, :query => {"user_id" => {"$gt" => 1}}, :out => 'foo')
|
449
|
+
res.callback do |collection|
|
450
|
+
collection.count .callback do |c|
|
451
|
+
c.should == 2
|
452
|
+
collection.find_one({"_id" => 2}).callback do |doc|
|
453
|
+
doc.should_not be_nil
|
454
|
+
collection.find_one({"_id" => 3}).callback do |doc2|
|
455
|
+
doc2.should_not be_nil
|
456
|
+
done
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
it "should return a raw response if requested" do
|
464
|
+
@conn, @coll = connection_and_collection
|
465
|
+
m = BSON::Code.new("function() { emit(this.user_id, 1); }")
|
466
|
+
r = BSON::Code.new("function(k,vals) { return 1; }")
|
467
|
+
res = @coll.map_reduce(m, r, :raw => true, :out => 'foo')
|
468
|
+
res.callback do |res|
|
469
|
+
res["result"].should_not be_nil
|
470
|
+
res["counts"].should_not be_nil
|
471
|
+
res["timeMillis"].should_not be_nil
|
472
|
+
done
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
it "should use an output collection if specified" do
|
477
|
+
@conn, @coll = connection_and_collection
|
478
|
+
output_collection = "test-map-coll"
|
479
|
+
m = BSON::Code.new("function() { emit(this.user_id, 1); }")
|
480
|
+
r = BSON::Code.new("function(k,vals) { return 1; }")
|
481
|
+
res = @coll.map_reduce(m, r, :raw => true, :out => output_collection)
|
482
|
+
res.callback do |res|
|
483
|
+
res["result"].should == output_collection
|
484
|
+
res["counts"].should_not be_nil
|
485
|
+
res["timeMillis"].should_not be_nil
|
486
|
+
done
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
161
490
|
end
|
162
491
|
|
163
|
-
|
164
|
-
|
492
|
+
describe "distinct" do
|
493
|
+
it "shoud perform a distinct query" do
|
494
|
+
@conn, @coll = connection_and_collection
|
495
|
+
@coll.insert([{:a => 0, :b => {:c => "a"}},
|
496
|
+
{:a => 1, :b => {:c => "b"}},
|
497
|
+
{:a => 1, :b => {:c => "c"}},
|
498
|
+
{:a => 2, :b => {:c => "a"}},
|
499
|
+
{:a => 3},
|
500
|
+
{:a => 3}])
|
501
|
+
|
502
|
+
@coll.distinct(:a).callback do |vals|
|
503
|
+
vals.sort.should == [0,1,2,3]
|
504
|
+
@coll.distinct("b.c").callback do |vals2|
|
505
|
+
vals2.sort.should == ["a","b","c"]
|
506
|
+
done
|
507
|
+
end
|
508
|
+
end
|
509
|
+
end
|
510
|
+
it "should respect a query" do
|
511
|
+
@conn, @coll = connection_and_collection
|
512
|
+
@coll.insert([{:a => 0, :b => {:c => "a"}},
|
513
|
+
{:a => 1, :b => {:c => "b"}},
|
514
|
+
{:a => 1, :b => {:c => "c"}},
|
515
|
+
{:a => 2, :b => {:c => "a"}},
|
516
|
+
{:a => 3},
|
517
|
+
{:a => 3}])
|
518
|
+
|
519
|
+
@coll.distinct(:a, {:a => {"$gt" => 1}}).callback do |vals|
|
520
|
+
vals.sort.should == [2,3]
|
521
|
+
done
|
522
|
+
end
|
523
|
+
end
|
524
|
+
it "should respect a query and nested objects" do
|
525
|
+
@conn, @coll = connection_and_collection
|
526
|
+
@coll.insert([{:a => 0, :b => {:c => "a"}},
|
527
|
+
{:a => 1, :b => {:c => "b"}},
|
528
|
+
{:a => 1, :b => {:c => "c"}},
|
529
|
+
{:a => 2, :b => {:c => "a"}},
|
530
|
+
{:a => 3},
|
531
|
+
{:a => 3}])
|
532
|
+
|
533
|
+
@coll.distinct("b.c", {"b.c" => {"$ne" => "c"}}).callback do |vals|
|
534
|
+
vals.sort.should == ["a","b"]
|
535
|
+
done
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
165
539
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
540
|
+
describe "group" do
|
541
|
+
it "should fail if missing required options" do
|
542
|
+
@conn, @coll = connection_and_collection
|
543
|
+
lambda { @coll.group(:initial => {}) }.should raise_error EM::Mongo::MongoArgumentError
|
544
|
+
lambda { @coll.group(:reduce => "foo") }.should raise_error EM::Mongo::MongoArgumentError
|
170
545
|
done
|
171
546
|
end
|
547
|
+
it "should group results using eval form" do
|
548
|
+
@conn, @coll = connection_and_collection
|
549
|
+
@coll.save("a" => 1)
|
550
|
+
@coll.save("b" => 1)
|
551
|
+
@initial = {"count" => 0}
|
552
|
+
@reduce_function = "function (obj, prev) { prev.count += inc_value; }"
|
553
|
+
@coll.group(:initial => @initial, :reduce => BSON::Code.new(@reduce_function, {"inc_value" => 0.5})).callback do |result|
|
554
|
+
result[0]["count"].should == 1
|
555
|
+
done
|
556
|
+
end
|
557
|
+
@coll.group(:initial => @initial, :reduce => BSON::Code.new(@reduce_function, {"inc_value" => 1})).callback do |result|
|
558
|
+
result[0]["count"].should == 2
|
559
|
+
done
|
560
|
+
end
|
561
|
+
@coll.group(:initial => @initial, :reduce => BSON::Code.new(@reduce_function, {"inc_value" => 2})).callback do |result|
|
562
|
+
result[0]["count"].should == 4
|
563
|
+
done
|
564
|
+
end
|
565
|
+
end
|
566
|
+
it "should finalize grouped results" do
|
567
|
+
@conn, @coll = connection_and_collection
|
568
|
+
@coll.save("a" => 1)
|
569
|
+
@coll.save("b" => 1)
|
570
|
+
@initial = {"count" => 0}
|
571
|
+
@reduce_function = "function (obj, prev) { prev.count += inc_value; }"
|
572
|
+
@finalize = "function(doc) {doc.f = doc.count + 200; }"
|
573
|
+
@coll.group(:initial => @initial, :reduce => BSON::Code.new(@reduce_function, {"inc_value" => 1}), :finalize => BSON::Code.new(@finalize)).callback do |results|
|
574
|
+
results[0]["f"].should == 202
|
575
|
+
done
|
576
|
+
end
|
577
|
+
end
|
172
578
|
end
|
173
579
|
|
174
|
-
|
175
|
-
|
580
|
+
describe "grouping with a key" do
|
581
|
+
it "should group" do
|
582
|
+
@conn, @coll = connection_and_collection
|
583
|
+
@coll.save("a" => 1, "pop" => 100)
|
584
|
+
@coll.save("a" => 1, "pop" => 100)
|
585
|
+
@coll.save("a" => 2, "pop" => 100)
|
586
|
+
@coll.save("a" => 2, "pop" => 100)
|
587
|
+
@initial = {"count" => 0, "foo" => 1}
|
588
|
+
@reduce_function = "function (obj, prev) { prev.count += obj.pop; }"
|
589
|
+
@coll.group(:key => :a, :initial => @initial, :reduce => @reduce_function).callback do |result|
|
590
|
+
result.all? {|r| r['count'] = 200 }.should be_true
|
591
|
+
done
|
592
|
+
end
|
593
|
+
end
|
594
|
+
end
|
176
595
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
}
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
596
|
+
describe "grouping with a function" do
|
597
|
+
it "should group results" do
|
598
|
+
@conn, @coll = connection_and_collection
|
599
|
+
@coll.save("a" => 1)
|
600
|
+
@coll.save("a" => 2)
|
601
|
+
@coll.save("a" => 3)
|
602
|
+
@coll.save("a" => 4)
|
603
|
+
@coll.save("a" => 5)
|
604
|
+
@initial = {"count" => 0}
|
605
|
+
@keyf = "function (doc) { if(doc.a % 2 == 0) { return {even: true}; } else {return {odd: true}} };"
|
606
|
+
@reduce = "function (obj, prev) { prev.count += 1; }"
|
607
|
+
@coll.group(:keyf => @keyf, :initial => @initial, :reduce => @reduce).callback do |results|
|
608
|
+
res = results.sort {|a,b| a['count'] <=> b['count']}
|
609
|
+
(res[0]['even'] && res[0]['count']).should == 2.0
|
610
|
+
(res[1]['odd'] && res[1]['count']) == 3.0
|
611
|
+
done
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
it "should group filtered results" do
|
616
|
+
@conn, @coll = connection_and_collection
|
617
|
+
@coll.save("a" => 1)
|
618
|
+
@coll.save("a" => 2)
|
619
|
+
@coll.save("a" => 3)
|
620
|
+
@coll.save("a" => 4)
|
621
|
+
@coll.save("a" => 5)
|
622
|
+
@initial = {"count" => 0}
|
623
|
+
@keyf = "function (doc) { if(doc.a % 2 == 0) { return {even: true}; } else {return {odd: true}} };"
|
624
|
+
@reduce = "function (obj, prev) { prev.count += 1; }"
|
625
|
+
@coll.group(:keyf => @keyf, :cond => {:a => {'$ne' => 2}},
|
626
|
+
:initial => @initial, :reduce => @reduce).callback do |results|
|
627
|
+
res = results.sort {|a, b| a['count'] <=> b['count']}
|
628
|
+
(res[0]['even'] && res[0]['count']).should == 1.0
|
629
|
+
(res[1]['odd'] && res[1]['count']) == 3.0
|
630
|
+
done
|
193
631
|
end
|
194
|
-
done
|
195
632
|
end
|
196
633
|
end
|
197
634
|
|
198
|
-
|
199
|
-
|
635
|
+
context "indexes" do
|
636
|
+
it "should create an index using symbols" do
|
637
|
+
@conn, @collection = connection_and_collection('test-collection')
|
638
|
+
@collection.create_index :foo, :name => :bar
|
639
|
+
@collection.index_information.callback do |info|
|
640
|
+
info['bar'].should_not be_nil
|
641
|
+
done
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
it "should create a geospatial index" do
|
646
|
+
@conn, @geo = connection_and_collection('geo')
|
647
|
+
@geo.save({'loc' => [-100, 100]})
|
648
|
+
@geo.create_index([['loc', EM::Mongo::GEO2D]])
|
649
|
+
@geo.index_information.callback do |info|
|
650
|
+
info['loc_2d'].should_not be_nil
|
651
|
+
done
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
655
|
+
it "should create a unique index" do
|
656
|
+
@conn, @collection = connection_and_collection('test-collection')
|
657
|
+
@collection.create_index([['a', EM::Mongo::ASCENDING]], :unique => true)
|
658
|
+
@collection.index_information.callback do |info|
|
659
|
+
info['a_1']['unique'].should == true
|
660
|
+
done
|
661
|
+
end
|
662
|
+
end
|
200
663
|
|
201
|
-
|
202
|
-
|
203
|
-
'
|
204
|
-
|
205
|
-
'
|
206
|
-
|
664
|
+
it "should create an index in the background" do
|
665
|
+
@conn, @collection = connection_and_collection('test-collection')
|
666
|
+
@collection.create_index([['b', EM::Mongo::ASCENDING]], :background => true)
|
667
|
+
@collection.index_information.callback do |info|
|
668
|
+
info['b_1']['background'].should == true
|
669
|
+
done
|
670
|
+
end
|
671
|
+
end
|
207
672
|
|
208
|
-
|
209
|
-
|
673
|
+
it "should require an array of arrays" do
|
674
|
+
@conn, @collection = connection_and_collection('test-collection')
|
675
|
+
proc { @collection.create_index(['c', EM::Mongo::ASCENDING]) }.should raise_error
|
210
676
|
done
|
211
677
|
end
|
212
|
-
end
|
213
678
|
|
214
|
-
|
215
|
-
|
679
|
+
it "should enforce proper index types" do
|
680
|
+
@conn, @collection = connection_and_collection('test-collection')
|
681
|
+
proc { @collection.create_index([['c', 'blah']]) }.should raise_error
|
682
|
+
done
|
683
|
+
end
|
216
684
|
|
217
|
-
|
218
|
-
@
|
685
|
+
it "should allow an alernate name to be specified" do
|
686
|
+
@conn, @collection = connection_and_collection('test-collection')
|
687
|
+
@collection.create_index :bar, :name => 'foo_index'
|
688
|
+
@collection.index_information.callback do |info|
|
689
|
+
info['foo_index'].should_not be_nil
|
690
|
+
done
|
691
|
+
end
|
219
692
|
end
|
220
693
|
|
221
|
-
|
222
|
-
|
223
|
-
|
694
|
+
it "should generate indexes in the proper order" do
|
695
|
+
@conn, @collection = connection_and_collection('test-collection')
|
696
|
+
@collection.should_receive(:insert_documents) do |sel, coll|
|
697
|
+
sel[0][:name].should == 'b_1_a_1'
|
698
|
+
end
|
699
|
+
@collection.create_index([['b',1],['a',1]])
|
224
700
|
done
|
225
701
|
end
|
226
|
-
end
|
227
|
-
|
228
|
-
it 'should find objects greater than something' do
|
229
|
-
@conn, @coll = connection_and_collection
|
230
702
|
|
231
|
-
|
232
|
-
@
|
703
|
+
it "should allow multiple calls to create_index" do
|
704
|
+
@conn, @collection = connection_and_collection('test-collection')
|
705
|
+
@collection.create_index([['a',1]]).should be_true
|
706
|
+
@collection.create_index([['a',1]]).should be_true
|
707
|
+
done
|
233
708
|
end
|
234
709
|
|
235
|
-
|
236
|
-
|
237
|
-
|
710
|
+
it "should allow the creation of multiple indexes" do
|
711
|
+
@conn, @collection = connection_and_collection('test-collection')
|
712
|
+
@collection.create_index([['a',1]]).should be_true
|
713
|
+
@collection.create_index([['b',1]]).should be_true
|
238
714
|
done
|
239
715
|
end
|
716
|
+
|
717
|
+
it "should return a properly ordered index info" do
|
718
|
+
@conn, @collection = connection_and_collection('test-collection')
|
719
|
+
@collection.create_index([['b',1],['a',1]])
|
720
|
+
@collection.index_information.callback do |info|
|
721
|
+
info['b_1_a_1'].should_not be_nil
|
722
|
+
done
|
723
|
+
end
|
724
|
+
end
|
725
|
+
|
726
|
+
it "should drop an index" do
|
727
|
+
@conn, @collection = connection_and_collection('test-collection')
|
728
|
+
@collection.create_index([['a',EM::Mongo::ASCENDING]])
|
729
|
+
@collection.index_information.callback do |info|
|
730
|
+
info['a_1'].should_not be_nil
|
731
|
+
@collection.drop_index([['a',EM::Mongo::ASCENDING]]).callback do
|
732
|
+
@collection.index_information.callback do |info|
|
733
|
+
info['a_1'].should be_nil
|
734
|
+
done
|
735
|
+
end
|
736
|
+
end
|
737
|
+
end
|
738
|
+
|
739
|
+
end
|
240
740
|
end
|
241
741
|
|
242
742
|
it 'should handle multiple pending queries' do
|
@@ -246,7 +746,7 @@ describe EMMongo::Collection do
|
|
246
746
|
received = 0
|
247
747
|
|
248
748
|
10.times do |n|
|
249
|
-
@coll.first("_id" => id) do |res|
|
749
|
+
@coll.first("_id" => id).callback do |res|
|
250
750
|
received += 1
|
251
751
|
done
|
252
752
|
end
|