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