mongo 1.6.4 → 1.7.0.rc0
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.md +13 -13
- data/Rakefile +7 -10
- data/docs/{GridFS.md → GRID_FS.md} +0 -0
- data/docs/HISTORY.md +16 -0
- data/docs/READ_PREFERENCE.md +70 -10
- data/docs/TUTORIAL.md +2 -2
- data/lib/mongo.rb +2 -0
- data/lib/mongo/collection.rb +62 -11
- data/lib/mongo/connection.rb +31 -41
- data/lib/mongo/cursor.rb +42 -86
- data/lib/mongo/db.rb +10 -8
- data/lib/mongo/networking.rb +30 -65
- data/lib/mongo/repl_set_connection.rb +91 -170
- data/lib/mongo/sharded_connection.rb +221 -0
- data/lib/mongo/util/node.rb +29 -36
- data/lib/mongo/util/pool.rb +10 -3
- data/lib/mongo/util/pool_manager.rb +77 -90
- data/lib/mongo/util/sharding_pool_manager.rb +143 -0
- data/lib/mongo/util/support.rb +22 -2
- data/lib/mongo/util/tcp_socket.rb +10 -15
- data/lib/mongo/util/uri_parser.rb +17 -10
- data/lib/mongo/version.rb +1 -1
- data/test/collection_test.rb +133 -1
- data/test/connection_test.rb +50 -4
- data/test/db_api_test.rb +3 -3
- data/test/db_test.rb +6 -1
- data/test/replica_sets/basic_test.rb +3 -6
- data/test/replica_sets/complex_connect_test.rb +14 -2
- data/test/replica_sets/complex_read_preference_test.rb +237 -0
- data/test/replica_sets/connect_test.rb +47 -67
- data/test/replica_sets/count_test.rb +1 -1
- data/test/replica_sets/cursor_test.rb +70 -0
- data/test/replica_sets/read_preference_test.rb +171 -118
- data/test/replica_sets/refresh_test.rb +3 -3
- data/test/replica_sets/refresh_with_threads_test.rb +2 -2
- data/test/replica_sets/rs_test_helper.rb +2 -2
- data/test/sharded_cluster/basic_test.rb +112 -0
- data/test/sharded_cluster/mongo_config_test.rb +126 -0
- data/test/sharded_cluster/sc_test_helper.rb +39 -0
- data/test/test_helper.rb +3 -3
- data/test/threading/threading_with_large_pool_test.rb +1 -1
- data/test/tools/mongo_config.rb +307 -0
- data/test/tools/repl_set_manager.rb +12 -12
- data/test/unit/collection_test.rb +1 -1
- data/test/unit/cursor_test.rb +11 -6
- data/test/unit/db_test.rb +4 -0
- data/test/unit/grid_test.rb +2 -0
- data/test/unit/read_test.rb +39 -8
- data/test/uri_test.rb +4 -8
- metadata +144 -127
@@ -0,0 +1,143 @@
|
|
1
|
+
|
2
|
+
module Mongo
|
3
|
+
module ShardingNode
|
4
|
+
def set_config
|
5
|
+
begin
|
6
|
+
@config = @connection['admin'].command({:ismaster => 1}, :socket => @socket)
|
7
|
+
|
8
|
+
# warning: instance variable @logger not initialized
|
9
|
+
#if @config['msg'] && @logger
|
10
|
+
# @connection.log(:warn, "#{config['msg']}")
|
11
|
+
#end
|
12
|
+
|
13
|
+
rescue ConnectionFailure, OperationFailure, OperationTimeout, SocketError, SystemCallError, IOError => ex
|
14
|
+
@connection.log(:warn, "Attempted connection to node #{host_string} raised " +
|
15
|
+
"#{ex.class}: #{ex.message}")
|
16
|
+
|
17
|
+
# Socket may already be nil from issuing command
|
18
|
+
if @socket && !@socket.closed?
|
19
|
+
@socket.close
|
20
|
+
end
|
21
|
+
|
22
|
+
return nil
|
23
|
+
end
|
24
|
+
|
25
|
+
@config
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return a list of sharded cluster nodes from the config - currently just the current node.
|
29
|
+
def node_list
|
30
|
+
connect unless connected?
|
31
|
+
set_config unless @config
|
32
|
+
|
33
|
+
return [] unless config
|
34
|
+
|
35
|
+
["#{@host}:#{@port}"]
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
class ShardingPoolManager < PoolManager
|
41
|
+
|
42
|
+
attr_reader :connection, :primary, :primary_pool, :hosts, :nodes,
|
43
|
+
:max_bson_size, :members
|
44
|
+
|
45
|
+
# Create a new set of connection pools.
|
46
|
+
#
|
47
|
+
# The pool manager will by default use the original seed list passed
|
48
|
+
# to the connection objects, accessible via connection.seeds. In addition,
|
49
|
+
# the user may pass an additional list of seeds nodes discovered in real
|
50
|
+
# time. The union of these lists will be used when attempting to connect,
|
51
|
+
# with the newly-discovered nodes being used first.
|
52
|
+
def initialize(connection, seeds=[])
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
def inspect
|
57
|
+
"<Mongo::ShardingPoolManager:0x#{self.object_id.to_s(16)} @seeds=#{@seeds}>"
|
58
|
+
end
|
59
|
+
|
60
|
+
# "Best" should be the member with the fastest ping time
|
61
|
+
# but connect/connect_to_members reinitializes @members
|
62
|
+
def best(members)
|
63
|
+
Array(members.first)
|
64
|
+
end
|
65
|
+
|
66
|
+
def connect
|
67
|
+
close if @previously_connected
|
68
|
+
|
69
|
+
initialize_data
|
70
|
+
members = connect_to_members
|
71
|
+
initialize_pools(best(members))
|
72
|
+
|
73
|
+
@members = members
|
74
|
+
@previously_connected = true
|
75
|
+
end
|
76
|
+
|
77
|
+
# We want to refresh to the member with the fastest ping time
|
78
|
+
# but also want to minimize refreshes
|
79
|
+
# We're healthy if the primary is pingable. If this isn't the case,
|
80
|
+
# or the members have changed, set @refresh_required to true, and return.
|
81
|
+
# The config.mongos find can't be part of the connect call chain due to infinite recursion
|
82
|
+
def check_connection_health
|
83
|
+
begin
|
84
|
+
seeds = @connection['config']['mongos'].find.to_a.map{|doc| doc['_id']}
|
85
|
+
if @seeds != seeds
|
86
|
+
@seeds = seeds
|
87
|
+
@refresh_required = true
|
88
|
+
end
|
89
|
+
rescue Mongo::OperationFailure
|
90
|
+
@refresh_required = true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# Connect to each member of the sharded cluster
|
97
|
+
# as reported by the given seed node, and return
|
98
|
+
# as a list of Mongo::Node objects.
|
99
|
+
def connect_to_members
|
100
|
+
members = []
|
101
|
+
|
102
|
+
seed = get_valid_seed_node
|
103
|
+
|
104
|
+
seed.node_list.each do |host|
|
105
|
+
node = Mongo::Node.new(self.connection, host)
|
106
|
+
node.extend ShardingNode
|
107
|
+
if node.connect && node.set_config
|
108
|
+
members << node
|
109
|
+
end
|
110
|
+
end
|
111
|
+
seed.close
|
112
|
+
|
113
|
+
if members.empty?
|
114
|
+
raise ConnectionFailure, "Failed to connect to any given member."
|
115
|
+
end
|
116
|
+
|
117
|
+
members
|
118
|
+
end
|
119
|
+
|
120
|
+
# Iterate through the list of provided seed
|
121
|
+
# nodes until we've gotten a response from the
|
122
|
+
# sharded cluster we're trying to connect to.
|
123
|
+
#
|
124
|
+
# If we don't get a response, raise an exception.
|
125
|
+
def get_valid_seed_node
|
126
|
+
@seeds.each do |seed|
|
127
|
+
node = Mongo::Node.new(self.connection, seed)
|
128
|
+
node.extend ShardingNode
|
129
|
+
if !node.connect
|
130
|
+
next
|
131
|
+
elsif node.set_config && node.healthy?
|
132
|
+
return node
|
133
|
+
else
|
134
|
+
node.close
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
raise ConnectionFailure, "Cannot connect to a sharded cluster using seeds " +
|
139
|
+
"#{@seeds.map {|s| "#{s[0]}:#{s[1]}" }.join(', ')}"
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
data/lib/mongo/util/support.rb
CHANGED
@@ -23,6 +23,13 @@ module Mongo
|
|
23
23
|
include Mongo::Conversions
|
24
24
|
extend self
|
25
25
|
|
26
|
+
READ_PREFERENCES = [:primary, :primary_preferred, :secondary, :secondary_preferred, :nearest]
|
27
|
+
|
28
|
+
# Commands that may be sent to replica-set secondaries, depending on
|
29
|
+
# read preference and tags. All other commands are always run on the primary.
|
30
|
+
SECONDARY_OK_COMMANDS = ['group', 'aggregate', 'collstats', 'dbstats', 'count', 'distinct',
|
31
|
+
'geonear', 'geosearch', 'geowalk']
|
32
|
+
|
26
33
|
# Generate an MD5 for authentication.
|
27
34
|
#
|
28
35
|
# @param [String] username
|
@@ -58,12 +65,25 @@ module Mongo
|
|
58
65
|
db_name
|
59
66
|
end
|
60
67
|
|
68
|
+
def secondary_ok?(selector)
|
69
|
+
command = selector.keys.first.to_s.downcase
|
70
|
+
|
71
|
+
if command == 'mapreduce'
|
72
|
+
map_reduce = selector[command]
|
73
|
+
if map_reduce && map_reduce.is_a?(Hash) && map_reduce.has_key?('out')
|
74
|
+
map_reduce['out'] == 'inline' ? false : true
|
75
|
+
end
|
76
|
+
else
|
77
|
+
SECONDARY_OK_COMMANDS.member?(command)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
61
81
|
def validate_read_preference(value)
|
62
|
-
if
|
82
|
+
if READ_PREFERENCES.include?(value)
|
63
83
|
return true
|
64
84
|
else
|
65
85
|
raise MongoArgumentError, "#{value} is not a valid read preference. " +
|
66
|
-
"Please specify
|
86
|
+
"Please specify one of the following read preferences as a symbol: #{READ_PREFERENCES}"
|
67
87
|
end
|
68
88
|
end
|
69
89
|
|
@@ -43,23 +43,18 @@ module Mongo
|
|
43
43
|
# Block on data to read for @op_timeout seconds
|
44
44
|
begin
|
45
45
|
ready = IO.select([@socket], nil, [@socket], @op_timeout)
|
46
|
+
unless ready
|
47
|
+
raise OperationTimeout
|
48
|
+
end
|
46
49
|
rescue IOError
|
47
|
-
raise
|
50
|
+
raise ConnectionFailure
|
48
51
|
end
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
raise ConnectionFailure, ex
|
56
|
-
rescue Errno::ENOTCONN, Errno::EBADF, Errno::ECONNRESET, Errno::EPIPE, Errno::ETIMEDOUT, EOFError => ex
|
57
|
-
raise ConnectionFailure, ex
|
58
|
-
rescue Errno::EINTR, Errno::EIO, IOError => ex
|
59
|
-
raise OperationFailure, ex
|
60
|
-
end
|
61
|
-
else
|
62
|
-
raise OperationTimeout
|
52
|
+
|
53
|
+
# Read data from socket
|
54
|
+
begin
|
55
|
+
@socket.sysread(maxlen, buffer)
|
56
|
+
rescue SystemCallError, IOError => ex
|
57
|
+
raise ConnectionFailure, ex
|
63
58
|
end
|
64
59
|
end
|
65
60
|
|
@@ -76,7 +76,7 @@ module Mongo
|
|
76
76
|
:wtimeoutms => lambda {|arg| arg.to_i }
|
77
77
|
}
|
78
78
|
|
79
|
-
attr_reader :
|
79
|
+
attr_reader :auths, :connect, :replicaset, :slaveok, :safe, :w, :wtimeout, :fsync, :journal, :connecttimeoutms, :sockettimeoutms, :wtimeoutms
|
80
80
|
|
81
81
|
# Parse a MongoDB URI. This method is used by Connection.from_uri.
|
82
82
|
# Returns an array of nodes and an array of db authorizations, if applicable.
|
@@ -87,7 +87,7 @@ module Mongo
|
|
87
87
|
# @param [Hash,nil] extra_opts Extra options. Will override anything already specified in the URI.
|
88
88
|
#
|
89
89
|
# @core connections
|
90
|
-
def initialize(uri
|
90
|
+
def initialize(uri)
|
91
91
|
if uri.start_with?('mongodb://')
|
92
92
|
uri = uri[10..-1]
|
93
93
|
else
|
@@ -96,7 +96,7 @@ module Mongo
|
|
96
96
|
|
97
97
|
hosts, opts = uri.split('?')
|
98
98
|
parse_hosts(hosts)
|
99
|
-
parse_options(opts
|
99
|
+
parse_options(opts)
|
100
100
|
validate_connect
|
101
101
|
end
|
102
102
|
|
@@ -105,11 +105,12 @@ module Mongo
|
|
105
105
|
# @note Don't confuse this with attribute getter method #connect.
|
106
106
|
#
|
107
107
|
# @return [Connection,ReplSetConnection]
|
108
|
-
def connection
|
108
|
+
def connection(extra_opts)
|
109
|
+
opts = connection_options.merge! extra_opts
|
109
110
|
if replicaset?
|
110
|
-
ReplSetConnection.new(
|
111
|
+
ReplSetConnection.new(nodes, opts)
|
111
112
|
else
|
112
|
-
Connection.new(host, port,
|
113
|
+
Connection.new(host, port, opts)
|
113
114
|
end
|
114
115
|
end
|
115
116
|
|
@@ -207,6 +208,14 @@ module Mongo
|
|
207
208
|
opts
|
208
209
|
end
|
209
210
|
|
211
|
+
def nodes
|
212
|
+
if @nodes.length == 1
|
213
|
+
@nodes
|
214
|
+
else
|
215
|
+
@nodes.collect {|node| "#{node[0]}:#{node[1]}"}
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
210
219
|
private
|
211
220
|
|
212
221
|
def parse_hosts(uri_without_proto)
|
@@ -254,13 +263,13 @@ module Mongo
|
|
254
263
|
|
255
264
|
# This method uses the lambdas defined in OPT_VALID and OPT_CONV to validate
|
256
265
|
# and convert the given options.
|
257
|
-
def parse_options(string_opts
|
266
|
+
def parse_options(string_opts)
|
258
267
|
# initialize instance variables for available options
|
259
268
|
OPT_VALID.keys.each { |k| instance_variable_set("@#{k}", nil) }
|
260
269
|
|
261
270
|
string_opts ||= ''
|
262
271
|
|
263
|
-
return if string_opts.empty?
|
272
|
+
return if string_opts.empty?
|
264
273
|
|
265
274
|
if string_opts.include?(';') and string_opts.include?('&')
|
266
275
|
raise MongoArgumentError, "must not mix URL separators ; and &"
|
@@ -272,8 +281,6 @@ module Mongo
|
|
272
281
|
memo
|
273
282
|
end
|
274
283
|
|
275
|
-
opts.merge! extra_opts
|
276
|
-
|
277
284
|
opts.each do |key, value|
|
278
285
|
if !OPT_ATTRS.include?(key)
|
279
286
|
raise MongoArgumentError, "Invalid Mongo URI option #{key}"
|
data/lib/mongo/version.rb
CHANGED
data/test/collection_test.rb
CHANGED
@@ -247,7 +247,7 @@ class TestCollection < Test::Unit::TestCase
|
|
247
247
|
def test_maximum_insert_size
|
248
248
|
docs = []
|
249
249
|
16.times do
|
250
|
-
docs << {'foo' => 'a' *
|
250
|
+
docs << {'foo' => 'a' * 1024 * 1024}
|
251
251
|
end
|
252
252
|
|
253
253
|
assert_raise InvalidOperation do
|
@@ -290,6 +290,16 @@ class TestCollection < Test::Unit::TestCase
|
|
290
290
|
assert_equal 1, @@test.find_one(:_id => id2)["x"]
|
291
291
|
end
|
292
292
|
|
293
|
+
def test_update_check_keys
|
294
|
+
@@test.save("x" => 1)
|
295
|
+
@@test.update({"x" => 1}, {"$set" => {"a.b" => 2}})
|
296
|
+
assert_equal 2, @@test.find_one("x" => 1)["a"]["b"]
|
297
|
+
|
298
|
+
assert_raise_error BSON::InvalidKeyName, "key a.b must not contain '.'" do
|
299
|
+
@@test.update({"x" => 1}, {"a.b" => 3})
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
293
303
|
if @@version >= "1.1.3"
|
294
304
|
def test_multi_update
|
295
305
|
@@test.save("num" => 10)
|
@@ -525,6 +535,128 @@ class TestCollection < Test::Unit::TestCase
|
|
525
535
|
end
|
526
536
|
assert c.closed?
|
527
537
|
end
|
538
|
+
|
539
|
+
def setup_aggregate_data
|
540
|
+
# save some data
|
541
|
+
@@test.save( {
|
542
|
+
"_id" => 1,
|
543
|
+
"title" => "this is my title",
|
544
|
+
"author" => "bob",
|
545
|
+
"posted" => Time.utc(1500),
|
546
|
+
"pageViews" => 5 ,
|
547
|
+
"tags" => [ "fun" , "good" , "fun" ],
|
548
|
+
"comments" => [
|
549
|
+
{ "author" => "joe", "text" => "this is cool" },
|
550
|
+
{ "author" => "sam", "text" => "this is bad" }
|
551
|
+
],
|
552
|
+
"other" => { "foo" => 5 }
|
553
|
+
} )
|
554
|
+
|
555
|
+
@@test.save( {
|
556
|
+
"_id" => 2,
|
557
|
+
"title" => "this is your title",
|
558
|
+
"author" => "dave",
|
559
|
+
"posted" => Time.utc(1600),
|
560
|
+
"pageViews" => 7,
|
561
|
+
"tags" => [ "fun" , "nasty" ],
|
562
|
+
"comments" => [
|
563
|
+
{ "author" => "barbara" , "text" => "this is interesting" },
|
564
|
+
{ "author" => "jenny", "text" => "i like to play pinball", "votes" => 10 }
|
565
|
+
],
|
566
|
+
"other" => { "bar" => 14 }
|
567
|
+
})
|
568
|
+
|
569
|
+
@@test.save( {
|
570
|
+
"_id" => 3,
|
571
|
+
"title" => "this is some other title",
|
572
|
+
"author" => "jane",
|
573
|
+
"posted" => Time.utc(1700),
|
574
|
+
"pageViews" => 6 ,
|
575
|
+
"tags" => [ "nasty", "filthy" ],
|
576
|
+
"comments" => [
|
577
|
+
{ "author" => "will" , "text" => "i don't like the color" } ,
|
578
|
+
{ "author" => "jenny" , "text" => "can i get that in green?" }
|
579
|
+
],
|
580
|
+
"other" => { "bar" => 14 }
|
581
|
+
})
|
582
|
+
|
583
|
+
end
|
584
|
+
|
585
|
+
if @@version > '2.1.1'
|
586
|
+
def test_reponds_to_aggregate
|
587
|
+
assert_respond_to @@test, :aggregate
|
588
|
+
end
|
589
|
+
|
590
|
+
def test_aggregate_requires_arguments
|
591
|
+
assert_raise MongoArgumentError do
|
592
|
+
@@test.aggregate()
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
def test_aggregate_requires_valid_arguments
|
597
|
+
assert_raise MongoArgumentError do
|
598
|
+
@@test.aggregate({})
|
599
|
+
end
|
600
|
+
end
|
601
|
+
|
602
|
+
def test_aggregate_pipeline_operator_format
|
603
|
+
assert_raise Mongo::OperationFailure do
|
604
|
+
@@test.aggregate([{"$project" => "_id"}])
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
def test_aggregate_pipeline_operators_using_strings
|
609
|
+
setup_aggregate_data
|
610
|
+
desired_results = [ {"_id"=>1, "pageViews"=>5, "tags"=>["fun", "good", "fun"]},
|
611
|
+
{"_id"=>2, "pageViews"=>7, "tags"=>["fun", "nasty"]},
|
612
|
+
{"_id"=>3, "pageViews"=>6, "tags"=>["nasty", "filthy"]} ]
|
613
|
+
results = @@test.aggregate([{"$project" => {"tags" => 1, "pageViews" => 1}}])
|
614
|
+
assert_equal desired_results, results
|
615
|
+
end
|
616
|
+
|
617
|
+
def test_aggregate_pipeline_operators_using_symbols
|
618
|
+
setup_aggregate_data
|
619
|
+
desired_results = [ {"_id"=>1, "pageViews"=>5, "tags"=>["fun", "good", "fun"]},
|
620
|
+
{"_id"=>2, "pageViews"=>7, "tags"=>["fun", "nasty"]},
|
621
|
+
{"_id"=>3, "pageViews"=>6, "tags"=>["nasty", "filthy"]} ]
|
622
|
+
results = @@test.aggregate([{"$project" => {:tags => 1, :pageViews => 1}}])
|
623
|
+
assert_equal desired_results, results
|
624
|
+
end
|
625
|
+
|
626
|
+
def test_aggregate_pipeline_multiple_operators
|
627
|
+
setup_aggregate_data
|
628
|
+
results = @@test.aggregate([{"$project" => {"tags" => 1, "pageViews" => 1}}, {"$match" => {"pageViews" => 7}}])
|
629
|
+
assert_equal 1, results.length
|
630
|
+
end
|
631
|
+
|
632
|
+
def test_aggregate_pipeline_unwind
|
633
|
+
setup_aggregate_data
|
634
|
+
desired_results = [ {"_id"=>1, "title"=>"this is my title", "author"=>"bob", "posted"=>Time.utc(1500),
|
635
|
+
"pageViews"=>5, "tags"=>"fun", "comments"=>[{"author"=>"joe", "text"=>"this is cool"},
|
636
|
+
{"author"=>"sam", "text"=>"this is bad"}], "other"=>{"foo"=>5 } },
|
637
|
+
{"_id"=>1, "title"=>"this is my title", "author"=>"bob", "posted"=>Time.utc(1500),
|
638
|
+
"pageViews"=>5, "tags"=>"good", "comments"=>[{"author"=>"joe", "text"=>"this is cool"},
|
639
|
+
{"author"=>"sam", "text"=>"this is bad"}], "other"=>{"foo"=>5 } },
|
640
|
+
{"_id"=>1, "title"=>"this is my title", "author"=>"bob", "posted"=>Time.utc(1500),
|
641
|
+
"pageViews"=>5, "tags"=>"fun", "comments"=>[{"author"=>"joe", "text"=>"this is cool"},
|
642
|
+
{"author"=>"sam", "text"=>"this is bad"}], "other"=>{"foo"=>5 } },
|
643
|
+
{"_id"=>2, "title"=>"this is your title", "author"=>"dave", "posted"=>Time.utc(1600),
|
644
|
+
"pageViews"=>7, "tags"=>"fun", "comments"=>[{"author"=>"barbara", "text"=>"this is interesting"},
|
645
|
+
{"author"=>"jenny", "text"=>"i like to play pinball", "votes"=>10 }], "other"=>{"bar"=>14 } },
|
646
|
+
{"_id"=>2, "title"=>"this is your title", "author"=>"dave", "posted"=>Time.utc(1600),
|
647
|
+
"pageViews"=>7, "tags"=>"nasty", "comments"=>[{"author"=>"barbara", "text"=>"this is interesting"},
|
648
|
+
{"author"=>"jenny", "text"=>"i like to play pinball", "votes"=>10 }], "other"=>{"bar"=>14 } },
|
649
|
+
{"_id"=>3, "title"=>"this is some other title", "author"=>"jane", "posted"=>Time.utc(1700),
|
650
|
+
"pageViews"=>6, "tags"=>"nasty", "comments"=>[{"author"=>"will", "text"=>"i don't like the color"},
|
651
|
+
{"author"=>"jenny", "text"=>"can i get that in green?"}], "other"=>{"bar"=>14 } },
|
652
|
+
{"_id"=>3, "title"=>"this is some other title", "author"=>"jane", "posted"=>Time.utc(1700),
|
653
|
+
"pageViews"=>6, "tags"=>"filthy", "comments"=>[{"author"=>"will", "text"=>"i don't like the color"},
|
654
|
+
{"author"=>"jenny", "text"=>"can i get that in green?"}], "other"=>{"bar"=>14 } }
|
655
|
+
]
|
656
|
+
results = @@test.aggregate([{"$unwind"=> "$tags"}])
|
657
|
+
assert_equal desired_results, results
|
658
|
+
end
|
659
|
+
end
|
528
660
|
|
529
661
|
if @@version > "1.1.1"
|
530
662
|
def test_map_reduce
|