mongo 1.9.2 → 1.10.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/LICENSE +1 -1
  5. data/README.md +94 -334
  6. data/Rakefile +6 -4
  7. data/VERSION +1 -1
  8. data/bin/mongo_console +13 -6
  9. data/lib/mongo.rb +22 -27
  10. data/lib/mongo/bulk_write_collection_view.rb +352 -0
  11. data/lib/mongo/collection.rb +128 -188
  12. data/lib/mongo/collection_writer.rb +348 -0
  13. data/lib/mongo/connection.rb +19 -0
  14. data/lib/mongo/{util → connection}/node.rb +15 -1
  15. data/lib/mongo/{util → connection}/pool.rb +34 -19
  16. data/lib/mongo/{util → connection}/pool_manager.rb +8 -2
  17. data/lib/mongo/{util → connection}/sharding_pool_manager.rb +1 -1
  18. data/lib/mongo/connection/socket.rb +18 -0
  19. data/lib/mongo/{util → connection/socket}/socket_util.rb +5 -2
  20. data/lib/mongo/{util → connection/socket}/ssl_socket.rb +3 -4
  21. data/lib/mongo/{util → connection/socket}/tcp_socket.rb +25 -15
  22. data/lib/mongo/{util → connection/socket}/unix_socket.rb +6 -4
  23. data/lib/mongo/cursor.rb +113 -47
  24. data/lib/mongo/db.rb +203 -131
  25. data/lib/mongo/{exceptions.rb → exception.rb} +7 -1
  26. data/lib/mongo/functional.rb +19 -0
  27. data/lib/mongo/functional/authentication.rb +303 -0
  28. data/lib/mongo/{util → functional}/logging.rb +1 -1
  29. data/lib/mongo/{util → functional}/read_preference.rb +49 -1
  30. data/lib/mongo/{util → functional}/uri_parser.rb +81 -69
  31. data/lib/mongo/{util → functional}/write_concern.rb +2 -1
  32. data/{test/unit/pool_test.rb → lib/mongo/gridfs.rb} +5 -10
  33. data/lib/mongo/gridfs/grid.rb +1 -3
  34. data/lib/mongo/gridfs/grid_ext.rb +1 -1
  35. data/lib/mongo/gridfs/grid_file_system.rb +1 -1
  36. data/lib/mongo/gridfs/grid_io.rb +1 -1
  37. data/lib/mongo/legacy.rb +63 -8
  38. data/lib/mongo/mongo_client.rb +128 -154
  39. data/lib/mongo/mongo_replica_set_client.rb +17 -11
  40. data/lib/mongo/mongo_sharded_client.rb +2 -1
  41. data/lib/mongo/networking.rb +19 -10
  42. data/lib/mongo/utils.rb +19 -0
  43. data/lib/mongo/{util → utils}/conversions.rb +1 -1
  44. data/lib/mongo/{util → utils}/core_ext.rb +1 -1
  45. data/lib/mongo/{util → utils}/server_version.rb +1 -1
  46. data/lib/mongo/{util → utils}/support.rb +10 -57
  47. data/lib/mongo/{util → utils}/thread_local_variable_manager.rb +1 -1
  48. data/test/functional/authentication_test.rb +8 -21
  49. data/test/functional/bulk_write_collection_view_test.rb +782 -0
  50. data/test/functional/{connection_test.rb → client_test.rb} +153 -78
  51. data/test/functional/collection_test.rb +343 -97
  52. data/test/functional/collection_writer_test.rb +83 -0
  53. data/test/functional/conversions_test.rb +1 -3
  54. data/test/functional/cursor_fail_test.rb +3 -3
  55. data/test/functional/cursor_message_test.rb +3 -3
  56. data/test/functional/cursor_test.rb +38 -3
  57. data/test/functional/db_api_test.rb +5 -5
  58. data/test/functional/db_connection_test.rb +2 -2
  59. data/test/functional/db_test.rb +35 -11
  60. data/test/functional/grid_file_system_test.rb +2 -2
  61. data/test/functional/grid_io_test.rb +2 -2
  62. data/test/functional/grid_test.rb +2 -2
  63. data/test/functional/pool_test.rb +2 -3
  64. data/test/functional/safe_test.rb +5 -5
  65. data/test/functional/ssl_test.rb +22 -102
  66. data/test/functional/support_test.rb +1 -1
  67. data/test/functional/timeout_test.rb +6 -22
  68. data/test/functional/uri_test.rb +113 -12
  69. data/test/functional/write_concern_test.rb +6 -6
  70. data/test/helpers/general.rb +50 -0
  71. data/test/helpers/test_unit.rb +309 -0
  72. data/test/replica_set/authentication_test.rb +8 -23
  73. data/test/replica_set/basic_test.rb +41 -14
  74. data/test/replica_set/client_test.rb +179 -117
  75. data/test/replica_set/complex_connect_test.rb +6 -7
  76. data/test/replica_set/connection_test.rb +46 -38
  77. data/test/replica_set/count_test.rb +2 -2
  78. data/test/replica_set/cursor_test.rb +8 -8
  79. data/test/replica_set/insert_test.rb +64 -2
  80. data/test/replica_set/max_values_test.rb +59 -10
  81. data/test/replica_set/pinning_test.rb +2 -2
  82. data/test/replica_set/query_test.rb +2 -2
  83. data/test/replica_set/read_preference_test.rb +6 -6
  84. data/test/replica_set/refresh_test.rb +7 -7
  85. data/test/replica_set/replication_ack_test.rb +5 -5
  86. data/test/replica_set/ssl_test.rb +24 -106
  87. data/test/sharded_cluster/basic_test.rb +43 -15
  88. data/test/shared/authentication/basic_auth_shared.rb +215 -0
  89. data/test/shared/authentication/sasl_plain_shared.rb +96 -0
  90. data/test/shared/ssl_shared.rb +173 -0
  91. data/test/test_helper.rb +31 -199
  92. data/test/threading/basic_test.rb +29 -3
  93. data/test/tools/mongo_config.rb +45 -20
  94. data/test/tools/mongo_config_test.rb +1 -1
  95. data/test/unit/client_test.rb +136 -57
  96. data/test/unit/collection_test.rb +31 -55
  97. data/test/unit/connection_test.rb +135 -72
  98. data/test/unit/cursor_test.rb +2 -2
  99. data/test/unit/db_test.rb +19 -15
  100. data/test/unit/grid_test.rb +2 -2
  101. data/test/unit/mongo_sharded_client_test.rb +17 -15
  102. data/test/unit/node_test.rb +2 -2
  103. data/test/unit/pool_manager_test.rb +7 -5
  104. data/test/unit/read_pref_test.rb +82 -2
  105. data/test/unit/read_test.rb +14 -14
  106. data/test/unit/safe_test.rb +9 -9
  107. data/test/unit/sharding_pool_manager_test.rb +11 -5
  108. data/test/unit/write_concern_test.rb +9 -9
  109. metadata +71 -56
  110. metadata.gz.sig +0 -0
  111. data/test/functional/threading_test.rb +0 -109
  112. data/test/shared/authentication.rb +0 -121
  113. data/test/unit/util_test.rb +0 -69
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013 10gen Inc.
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -17,13 +17,15 @@ require 'rubygems'
17
17
  begin
18
18
  require 'bundler'
19
19
  rescue LoadError
20
- raise '[FAIL] Bundler not found! Install it with `gem install bundler; bundle install`.'
20
+ raise '[FAIL] Bundler not found! Install it with `gem install bundler && bundle`.'
21
21
  end
22
22
 
23
- if ENV.has_key?('TEST') || ENV.has_key?('TRAVIS_TEST')
23
+ rake_tasks = Dir.glob(File.join('tasks', '**', '*.rake')).sort
24
+ if ENV.keys.any? { |k| k.end_with?('_CI') }
24
25
  Bundler.require(:default, :testing)
26
+ rake_tasks.reject! { |r| r =~ /deploy/ }
25
27
  else
26
28
  Bundler.require(:default, :testing, :deploy, :development)
27
29
  end
28
30
 
29
- Dir.glob(File.join('tasks', '**', '*.rake')).sort.each { |rake| load File.expand_path(rake) }
31
+ rake_tasks.each { |rake| load File.expand_path(rake) }
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.9.2
1
+ 1.10.0.rc0
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Copyright (C) 2013 10gen Inc.
3
+ # Copyright (C) 2009-2013 MongoDB, Inc.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -17,11 +17,9 @@
17
17
  org_argv = ARGV.dup
18
18
  ARGV.clear
19
19
 
20
- require 'irb'
21
-
22
20
  $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
23
- require 'mongo'
24
21
 
22
+ require 'mongo'
25
23
  include Mongo
26
24
 
27
25
  host = org_argv[0] || ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
@@ -32,5 +30,14 @@ puts "Connecting to #{host}:#{port} (CLIENT) on with database #{dbnm} (DB)"
32
30
  CLIENT = MongoClient.new(host, port)
33
31
  DB = CLIENT.db(dbnm)
34
32
 
35
- puts "Starting IRB session..."
36
- IRB.start(__FILE__)
33
+ # try pry if available, fall back to irb
34
+ begin
35
+ require 'pry'
36
+ CONSOLE_CLASS = Pry
37
+ rescue LoadError
38
+ require 'irb'
39
+ CONSOLE_CLASS = IRB
40
+ end
41
+
42
+ puts "Starting #{CONSOLE_CLASS.name} session..."
43
+ CONSOLE_CLASS.start(__FILE__)
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013 10gen Inc.
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -56,39 +56,34 @@ module Mongo
56
56
  REPLY_SHARD_CONFIG_STALE = 2 ** 2
57
57
  REPLY_AWAIT_CAPABLE = 2 ** 3
58
58
  end
59
+
60
+ module ErrorCode # MongoDB Core Server src/mongo/base/error_codes.err
61
+ BAD_VALUE = 2
62
+ UNKNOWN_ERROR = 8
63
+ INVALID_BSON = 22
64
+ COMMAND_NOT_FOUND = 59
65
+ WRITE_CONCERN_FAILED = 64
66
+ MULTIPLE_ERRORS_OCCURRED = 65
67
+ end
59
68
  end
60
69
 
61
70
  require 'bson'
62
71
 
63
- require 'mongo/util/thread_local_variable_manager'
64
- require 'mongo/util/conversions'
65
- require 'mongo/util/support'
66
- require 'mongo/util/read_preference'
67
- require 'mongo/util/write_concern'
68
- require 'mongo/util/core_ext'
69
- require 'mongo/util/logging'
70
- require 'mongo/util/node'
71
- require 'mongo/util/pool'
72
- require 'mongo/util/pool_manager'
73
- require 'mongo/util/sharding_pool_manager'
74
- require 'mongo/util/server_version'
75
- require 'mongo/util/socket_util'
76
- require 'mongo/util/ssl_socket'
77
- require 'mongo/util/tcp_socket'
78
- require 'mongo/util/unix_socket'
79
- require 'mongo/util/uri_parser'
80
-
72
+ require 'set'
73
+ require 'thread'
81
74
 
75
+ require 'mongo/utils'
76
+ require 'mongo/exception'
77
+ require 'mongo/functional'
78
+ require 'mongo/connection'
79
+ require 'mongo/collection_writer'
80
+ require 'mongo/collection'
81
+ require 'mongo/bulk_write_collection_view'
82
+ require 'mongo/cursor'
83
+ require 'mongo/db'
84
+ require 'mongo/gridfs'
82
85
  require 'mongo/networking'
83
86
  require 'mongo/mongo_client'
84
87
  require 'mongo/mongo_replica_set_client'
85
88
  require 'mongo/mongo_sharded_client'
86
89
  require 'mongo/legacy'
87
- require 'mongo/collection'
88
- require 'mongo/cursor'
89
- require 'mongo/db'
90
- require 'mongo/exceptions'
91
- require 'mongo/gridfs/grid_ext'
92
- require 'mongo/gridfs/grid'
93
- require 'mongo/gridfs/grid_io'
94
- require 'mongo/gridfs/grid_file_system'
@@ -0,0 +1,352 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+
17
+ # A bulk write view to a collection of documents in a database.
18
+ class BulkWriteCollectionView
19
+ include Mongo::WriteConcern
20
+
21
+ DEFAULT_OP_ARGS = {:q => {}}
22
+ MULTIPLE_ERRORS_MSG = "batch item errors occurred"
23
+
24
+ attr_reader :collection, :options, :ops, :op_args
25
+
26
+ # Initialize a bulk-write-view object to a collection with default query selector {}.
27
+ #
28
+ # A bulk write operation is initialized from a collection object.
29
+ # For example, for an ordered bulk write view:
30
+ #
31
+ # bulk = collection.initialize_ordered_bulk_op
32
+ #
33
+ # or for an unordered bulk write view:
34
+ #
35
+ # bulk = collection.initialize_unordered_bulk_op
36
+ #
37
+ # The bulk write view collects individual write operations together so that they can be
38
+ # executed as a batch for significant performance gains.
39
+ # The ordered bulk operation will execute each operation serially in order.
40
+ # Execution will stop at the first occurrence of an error for an ordered bulk operation.
41
+ # The unordered bulk operation will be executed and may take advantage of parallelism.
42
+ # There are no guarantees for the order of execution of the operations on the server.
43
+ # Execution will continue even if there are errors for an unordered bulk operation.
44
+ #
45
+ # A bulk operation is programmed as a sequence of individual operations.
46
+ # An individual operation is composed of a method chain of modifiers or setters terminated by a write method.
47
+ # A modify method sets a value on the current object.
48
+ # A set methods returns a duplicate of the current object with a value set.
49
+ # A terminator write method appends a write operation to the bulk batch collected in the view.
50
+ #
51
+ # The API supports mixing of write operation types in a bulk operation.
52
+ # However, server support affects the implementation and performance of bulk operations.
53
+ #
54
+ # MongoDB version 2.6 servers currently support only bulk commands of the same type.
55
+ # With an ordered bulk operation,
56
+ # contiguous individual ops of the same type can be batched into the same db request,
57
+ # and the next op of a different type must be sent separately in the next request.
58
+ # Performance will improve if you can arrange your ops to reduce the number of db requests.
59
+ # With an unordered bulk operation,
60
+ # individual ops can be grouped by type and sent in at most three requests,
61
+ # one each per insert, update, or delete.
62
+ #
63
+ # MongoDB pre-version 2.6 servers do not support bulk write commands.
64
+ # The bulk operation must be sent one request per individual op.
65
+ # This also applies to inserts in order to have accurate counts and error reporting.
66
+ #
67
+ # Important note on pre-2.6 performance:
68
+ # Performance is very poor compared to version 2.6.
69
+ # We recommend bulk operation with pre-2.6 only for compatibility or
70
+ # for development in preparation for version 2.6.
71
+ # For better performance with pre-version 2.6, use bulk insertion with Collection#insert.
72
+ #
73
+ # @param [Collection] collection the parent collection object
74
+ #
75
+ # @option opts [Boolean] :ordered (true) Set bulk execution for ordered or unordered
76
+ #
77
+ # @return [BulkWriteCollectionView]
78
+ def initialize(collection, options = {})
79
+ @collection = collection
80
+ @options = options
81
+ @ops = []
82
+ @op_args = DEFAULT_OP_ARGS.dup
83
+ end
84
+
85
+ def inspect
86
+ vars = [:@options, :@ops, :@op_args]
87
+ vars_inspect = vars.collect{|var| "#{var}=#{instance_variable_get(var).inspect}"}
88
+ "#<Mongo::BulkWriteCollectionView:0x#{self.object_id} " <<
89
+ "@collection=#<Mongo::Collection:0x#{@collection.object_id}>, #{vars_inspect.join(', ')}>"
90
+ end
91
+
92
+ # Modify the query selector for subsequent bulk write operations.
93
+ # The default query selector on creation of the bulk write view is {}.
94
+ #
95
+ # @param [Hash] q the query selector
96
+ #
97
+ # @return [BulkWriteCollectionView]
98
+ def find(q)
99
+ op_args_set(:q, q)
100
+ end
101
+
102
+ # Modify the upsert option argument for subsequent bulk write operations.
103
+ #
104
+ # @param [Boolean] value (true) the upsert option value
105
+ #
106
+ # @return [BulkWriteCollectionView]
107
+ def upsert!(value = true)
108
+ op_args_set(:upsert, value)
109
+ end
110
+
111
+ # Set the upsert option argument for subsequent bulk write operations.
112
+ #
113
+ # @param [Boolean] value (true) the upsert option value
114
+ #
115
+ # @return [BulkWriteCollectionView] a duplicated object
116
+ def upsert(value = true)
117
+ dup.upsert!(value)
118
+ end
119
+
120
+ # Update one document matching the selector.
121
+ #
122
+ # bulk.find({"a" => 1}).update_one({"$inc" => {"x" => 1}})
123
+ #
124
+ # Use the upsert! or upsert method to specify an upsert. For example:
125
+ #
126
+ # bulk.find({"a" => 1}).upsert.updateOne({"$inc" => {"x" => 1}})
127
+ #
128
+ # @param [Hash] u the update document
129
+ #
130
+ # @return [BulkWriteCollectionView]
131
+ def update_one(u)
132
+ raise MongoArgumentError, "document must start with an operator" unless update_doc?(u)
133
+ op_push([:update, @op_args.merge(:u => u, :multi => false)])
134
+ end
135
+
136
+ # Update all documents matching the selector. For example:
137
+ #
138
+ # bulk.find({"a" => 2}).update({"$inc" => {"x" => 2}})
139
+ #
140
+ # Use the upsert! or upsert method to specify an upsert. For example:
141
+ #
142
+ # bulk.find({"a" => 2}).upsert.update({"$inc" => {"x" => 2}})
143
+ #
144
+ # @param [Hash] u the update document
145
+ #
146
+ # @return [BulkWriteCollectionView]
147
+ def update(u)
148
+ raise MongoArgumentError, "document must start with an operator" unless update_doc?(u)
149
+ op_push([:update, @op_args.merge(:u => u, :multi => true)])
150
+ end
151
+
152
+ # Replace entire document (update with whole doc replace). For example:
153
+ #
154
+ # bulk.find({"a" => 3}).replace_one({"x" => 3})
155
+ #
156
+ # @param [Hash] u the replacement document
157
+ #
158
+ # @return [BulkWriteCollectionView]
159
+ def replace_one(u)
160
+ raise MongoArgumentError, "document must not contain any operators" unless replace_doc?(u)
161
+ op_push([:update, @op_args.merge(:u => u, :multi => false)])
162
+ end
163
+
164
+ # Remove a single document matching the selector. For example:
165
+ #
166
+ # bulk.find({"a" => 4}).remove_one;
167
+ #
168
+ # @return [BulkWriteCollectionView]
169
+ def remove_one
170
+ op_push([:delete, @op_args.merge(:limit => 1)])
171
+ end
172
+
173
+ # Remove all documents matching the selector. For example:
174
+ #
175
+ # bulk.find({"a" => 5}).remove;
176
+ #
177
+ # @return [BulkWriteCollectionView]
178
+ def remove
179
+ op_push([:delete, @op_args.merge(:limit => 0)])
180
+ end
181
+
182
+ # Insert a document. For example:
183
+ #
184
+ # bulk.insert({"x" => 4})
185
+ #
186
+ # @return [BulkWriteCollectionView]
187
+ def insert(document)
188
+ # TODO - check keys
189
+ op_push([:insert, {:d => document}])
190
+ end
191
+
192
+ # Execute the bulk operation, with an optional write concern overwriting the default w:1.
193
+ # For example:
194
+ #
195
+ # write_concern = {:w => 1, :j => 1}
196
+ # bulk.execute({write_concern})
197
+ #
198
+ # On return from execute, the bulk operation is cleared,
199
+ # but the selector and upsert settings are preserved.
200
+ #
201
+ # @return [BulkWriteCollectionView]
202
+ def execute(opts = {})
203
+ write_concern = get_write_concern(opts, @collection)
204
+ @ops.each_with_index{|op, index| op.last.merge!(:ord => index)} # infuse ordinal here to avoid issues with upsert
205
+ if @collection.db.connection.use_write_command?(write_concern)
206
+ errors, exchanges = @collection.command_writer.bulk_execute(@ops, @options, opts)
207
+ else
208
+ errors, exchanges = @collection.operation_writer.bulk_execute(@ops, @options, opts)
209
+ end
210
+ @ops = []
211
+ return true if exchanges.first[:response] == true # w 0 without GLE
212
+ result = merge_result(errors, exchanges)
213
+ raise BulkWriteError.new(MULTIPLE_ERRORS_MSG, Mongo::ErrorCode::MULTIPLE_ERRORS_OCCURRED, result) if !errors.empty? || result["writeConcernError"]
214
+ result
215
+ end
216
+
217
+ private
218
+
219
+ def hash_except(h, *keys)
220
+ keys.each { |key| h.delete(key) }
221
+ h
222
+ end
223
+
224
+ def hash_select(h, *keys)
225
+ Hash[*keys.zip(h.values_at(*keys)).flatten]
226
+ end
227
+
228
+ def tally(h, key, n)
229
+ h[key] = h.fetch(key, 0) + n
230
+ end
231
+
232
+ def append(h, key, obj)
233
+ h[key] = h.fetch(key, []) << obj
234
+ end
235
+
236
+ def concat(h, key, a)
237
+ h[key] = h.fetch(key, []) + a
238
+ end
239
+
240
+ def merge_index(h, exchange)
241
+ h.merge("index" => exchange[:batch][h.fetch("index", 0)][:ord])
242
+ end
243
+
244
+ def merge_indexes(a, exchange)
245
+ a.collect{|h| merge_index(h, exchange)}
246
+ end
247
+
248
+ def merge_result(errors, exchanges)
249
+ ok = 0
250
+ result = {"ok" => 0, "n" => 0}
251
+ unless errors.empty?
252
+ unless (writeErrors = errors.select { |error| error.class != Mongo::OperationFailure }).empty? # assignment
253
+ concat(result, "writeErrors",
254
+ writeErrors.collect { |error|
255
+ {"index" => error.result[:ord], "code" => error.error_code, "errmsg" => error.result[:error].message}
256
+ })
257
+ end
258
+ result.merge!("code" => Mongo::ErrorCode::MULTIPLE_ERRORS_OCCURRED, "errmsg" => MULTIPLE_ERRORS_MSG)
259
+ end
260
+ exchanges.each do |exchange|
261
+ response = exchange[:response]
262
+ ok += response["ok"].to_i
263
+ n = response["n"] || 0
264
+ op_type = exchange[:op_type]
265
+ if op_type == :insert
266
+ n = 1 if response.key?("err") && (response["err"].nil? || response["err"] == "norepl" || response["err"] == "timeout") # OP_INSERT override n = 0 bug, n = exchange[:batch].size always 1
267
+ tally(result, "nInserted", n)
268
+ elsif op_type == :update
269
+ n_upserted = 0
270
+ if (upserted = response.fetch("upserted", nil)) # assignment
271
+ upserted = [{"_id" => upserted}] if upserted.class == BSON::ObjectId # OP_UPDATE non-array
272
+ n_upserted = upserted.size
273
+ concat(result, "upserted", merge_indexes(upserted, exchange))
274
+ end
275
+ tally(result, "nUpserted", n_upserted) if n_upserted > 0
276
+ tally(result, "nMatched", n - n_upserted)
277
+ tally(result, "nModified", response["nModified"] || n - n_upserted)
278
+ elsif op_type == :delete
279
+ tally(result, "nRemoved", n)
280
+ end
281
+ result["n"] += n
282
+ writeConcernError = nil
283
+ errmsg = response["errmsg"] || response["err"] # top level
284
+ if (writeErrors = response["writeErrors"] || response["errDetails"]) # assignment
285
+ concat(result, "writeErrors", merge_indexes(writeErrors, exchange))
286
+ elsif response["err"] == "timeout" # errmsg == "timed out waiting for slaves" # OP_*
287
+ writeConcernError = {"errmsg" => errmsg, "code" => Mongo::ErrorCode::WRITE_CONCERN_FAILED,
288
+ "errInfo" => {"wtimeout" => response["wtimeout"]}} # OP_* does not have "code"
289
+ elsif errmsg == "norepl" # OP_*
290
+ writeConcernError = {"errmsg" => errmsg, "code" => Mongo::ErrorCode::WRITE_CONCERN_FAILED} # OP_* does not have "code"
291
+ elsif errmsg # OP_INSERT, OP_UPDATE have "err"
292
+ append(result, "writeErrors", merge_index({"errmsg" => errmsg, "code" => response["code"]}, exchange))
293
+ end
294
+ if response["writeConcernError"]
295
+ writeConcernError = response["writeConcernError"]
296
+ elsif (wnote = response["wnote"]) # assignment - OP_*
297
+ writeConcernError = {"errmsg" => wnote, "code" => Mongo::ErrorCode::WRITE_CONCERN_FAILED} # OP_* does not have "code"
298
+ elsif (jnote = response["jnote"]) # assignment - OP_*
299
+ writeConcernError = {"errmsg" => jnote, "code" => Mongo::ErrorCode::BAD_VALUE} # OP_* does not have "code"
300
+ end
301
+ append(result, "writeConcernError", merge_index(writeConcernError, exchange)) if writeConcernError
302
+ end
303
+ result.merge!("ok" => [ok + result["n"], 1].min)
304
+ end
305
+
306
+ def initialize_copy(other)
307
+ other.instance_variable_set(:@options, other.options.dup)
308
+ end
309
+
310
+ def op_args_set(op, value)
311
+ @op_args[op] = value
312
+ self
313
+ end
314
+
315
+ def op_push(op)
316
+ @ops << op
317
+ self
318
+ end
319
+
320
+ def update_doc?(doc)
321
+ !doc.empty? && doc.keys.first.to_s =~ /^\$/
322
+ end
323
+
324
+ def replace_doc?(doc)
325
+ doc.keys.all?{|key| key !~ /^\$/}
326
+ end
327
+
328
+ end
329
+
330
+ class Collection
331
+
332
+ # Initialize an ordered bulk write view for this collection
333
+ # Execution will stop at the first occurrence of an error for an ordered bulk operation.
334
+ #
335
+ # @return [BulkWriteCollectionView]
336
+ def initialize_ordered_bulk_op
337
+ BulkWriteCollectionView.new(self, :ordered => true)
338
+ end
339
+
340
+ # Initialize an unordered bulk write view for this collection
341
+ # The unordered bulk operation will be executed and may take advantage of parallelism.
342
+ # There are no guarantees for the order of execution of the operations on the server.
343
+ # Execution will continue even if there are errors for an unordered bulk operation.
344
+ #
345
+ # @return [BulkWriteCollectionView]
346
+ def initialize_unordered_bulk_op
347
+ BulkWriteCollectionView.new(self, :ordered => false)
348
+ end
349
+
350
+ end
351
+
352
+ end