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
@@ -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.
@@ -209,9 +209,9 @@ module Mongo
209
209
  if @manager.pools.empty?
210
210
  close
211
211
  raise ConnectionFailure, "Failed to connect to any node."
212
- else
213
- @connected = true
214
212
  end
213
+ check_wire_version_in_range
214
+ @connected = true
215
215
  end
216
216
  end
217
217
 
@@ -325,14 +325,6 @@ module Mongo
325
325
  @read != :primary
326
326
  end
327
327
 
328
- def authenticate_pools
329
- @manager.pools.each { |pool| pool.authenticate_existing }
330
- end
331
-
332
- def logout_pools(db)
333
- @manager.pools.each { |pool| pool.logout_existing(db) }
334
- end
335
-
336
328
  # Generic socket checkout
337
329
  # Takes a block that returns a socket from pool
338
330
  def checkout
@@ -457,6 +449,20 @@ module Mongo
457
449
  max_bson_size * MESSAGE_SIZE_FACTOR
458
450
  end
459
451
 
452
+ def max_wire_version
453
+ return local_manager.max_wire_version if local_manager
454
+ 0
455
+ end
456
+
457
+ def min_wire_version
458
+ return local_manager.min_wire_version if local_manager
459
+ 0
460
+ end
461
+
462
+ def primary_wire_version_feature?(feature)
463
+ local_manager && local_manager.primary_pool && local_manager.primary_pool.node.wire_version_feature?(feature)
464
+ end
465
+
460
466
  private
461
467
 
462
468
  # Parse option hash
@@ -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.
@@ -94,6 +94,7 @@ module Mongo
94
94
  @manager = ShardingPoolManager.new(self, @seeds)
95
95
  ensure_manager
96
96
  @manager.connect
97
+ check_wire_version_in_range
97
98
  end
98
99
  ensure
99
100
  thread_local[:locks][:connecting] = false
@@ -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.
@@ -58,6 +58,7 @@ module Mongo
58
58
  sock.checkin
59
59
  end
60
60
  end
61
+ true
61
62
  end
62
63
 
63
64
  # Sends a message to the database, waits for a response, and raises
@@ -99,9 +100,13 @@ module Mongo
99
100
  close
100
101
  raise ConnectionFailure.new(docs[0]['code'].to_s + ': ' + error, docs[0]['code'], docs[0])
101
102
  else
103
+ code = docs[0]['code'] || Mongo::ErrorCode::UNKNOWN_ERROR
102
104
  error = "wtimeout" if error == "timeout"
103
- raise OperationFailure.new(docs[0]['code'].to_s + ': ' + error, docs[0]['code'], docs[0])
105
+ raise OperationFailure.new(code.to_s + ': ' + error, code, docs[0])
104
106
  end
107
+ elsif num_received == 1 && (jnote = docs[0]['jnote']) # assignment
108
+ code = Mongo::ErrorCode::BAD_VALUE # as of server version 2.5.5
109
+ raise OperationFailure.new(code.to_s + ': ' + jnote, code, docs[0])
105
110
  end
106
111
 
107
112
  docs[0]
@@ -122,15 +127,17 @@ module Mongo
122
127
  # An array whose indexes include [0] documents returned, [1] number of document received,
123
128
  # and [3] a cursor_id.
124
129
  def receive_message(operation, message, log_message=nil, socket=nil, command=false,
125
- read=:primary, exhaust=false)
126
- request_id = add_message_headers(message, operation)
130
+ read=:primary, exhaust=false, compile_regex=true)
131
+ request_id = add_message_headers(message, operation)
127
132
  packed_message = message.to_s
133
+ opts = { :exhaust => exhaust,
134
+ :compile_regex => compile_regex }
128
135
 
129
136
  result = ''
130
137
 
131
138
  begin
132
139
  send_message_on_socket(packed_message, socket)
133
- result = receive(socket, request_id, exhaust)
140
+ result = receive(socket, request_id, opts)
134
141
  rescue ConnectionFailure => ex
135
142
  socket.close
136
143
  checkin(socket)
@@ -149,7 +156,9 @@ module Mongo
149
156
 
150
157
  private
151
158
 
152
- def receive(sock, cursor_id, exhaust=false)
159
+ def receive(sock, cursor_id, opts={})
160
+ exhaust = !!opts.delete(:exhaust)
161
+
153
162
  if exhaust
154
163
  docs = []
155
164
  num_received = 0
@@ -157,7 +166,7 @@ module Mongo
157
166
  while(cursor_id != 0) do
158
167
  receive_header(sock, cursor_id, exhaust)
159
168
  number_received, cursor_id = receive_response_header(sock)
160
- new_docs, n = read_documents(number_received, sock)
169
+ new_docs, n = read_documents(number_received, sock, opts)
161
170
  docs += new_docs
162
171
  num_received += n
163
172
  end
@@ -166,7 +175,7 @@ module Mongo
166
175
  else
167
176
  receive_header(sock, cursor_id, exhaust)
168
177
  number_received, cursor_id = receive_response_header(sock)
169
- docs, num_received = read_documents(number_received, sock)
178
+ docs, num_received = read_documents(number_received, sock, opts)
170
179
 
171
180
  return [docs, num_received, cursor_id]
172
181
  end
@@ -212,7 +221,7 @@ module Mongo
212
221
  end
213
222
  end
214
223
 
215
- def read_documents(number_received, sock)
224
+ def read_documents(number_received, sock, opts)
216
225
  docs = []
217
226
  number_remaining = number_received
218
227
  while number_remaining > 0 do
@@ -220,7 +229,7 @@ module Mongo
220
229
  size = buf.unpack('V')[0]
221
230
  buf << receive_message_on_socket(size - 4, sock)
222
231
  number_remaining -= 1
223
- docs << BSON::BSON_CODER.deserialize(buf)
232
+ docs << BSON::BSON_CODER.deserialize(buf, opts)
224
233
  end
225
234
  [docs, number_received]
226
235
  end
@@ -0,0 +1,19 @@
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
+ require 'mongo/utils/conversions'
16
+ require 'mongo/utils/core_ext'
17
+ require 'mongo/utils/server_version'
18
+ require 'mongo/utils/support'
19
+ require 'mongo/utils/thread_local_variable_manager'
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -12,52 +12,12 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- require 'digest/md5'
16
-
17
15
  module Mongo
18
16
  module Support
19
17
 
20
18
  include Mongo::Conversions
21
19
  extend self
22
20
 
23
- # Commands that may be sent to replica-set secondaries, depending on
24
- # read preference and tags. All other commands are always run on the primary.
25
- SECONDARY_OK_COMMANDS = [
26
- 'group',
27
- 'aggregate',
28
- 'collstats',
29
- 'dbstats',
30
- 'count',
31
- 'distinct',
32
- 'geonear',
33
- 'geosearch',
34
- 'geowalk',
35
- 'mapreduce',
36
- 'replsetgetstatus',
37
- 'ismaster',
38
- ]
39
-
40
- # Generate an MD5 for authentication.
41
- #
42
- # @param [String] username
43
- # @param [String] password
44
- # @param [String] nonce
45
- #
46
- # @return [String] a key for db authentication.
47
- def auth_key(username, password, nonce)
48
- Digest::MD5.hexdigest("#{nonce}#{username}#{hash_password(username, password)}")
49
- end
50
-
51
- # Return a hashed password for auth.
52
- #
53
- # @param [String] username
54
- # @param [String] plaintext
55
- #
56
- # @return [String]
57
- def hash_password(username, plaintext)
58
- Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}")
59
- end
60
-
61
21
  def validate_db_name(db_name)
62
22
  unless [String, Symbol].include?(db_name.class)
63
23
  raise TypeError, "db_name must be a string or symbol"
@@ -72,19 +32,6 @@ module Mongo
72
32
  db_name
73
33
  end
74
34
 
75
- def secondary_ok?(selector)
76
- command = selector.keys.first.to_s.downcase
77
-
78
- if command == 'mapreduce'
79
- out = selector.select { |k, v| k.to_s.downcase == 'out' }.first.last
80
- # mongo looks at the first key in the out object, and doesn't
81
- # look at the value
82
- out.is_a?(Hash) && out.keys.first.to_s.downcase == 'inline' ? true : false
83
- else
84
- SECONDARY_OK_COMMANDS.member?(command)
85
- end
86
- end
87
-
88
35
  def format_order_clause(order)
89
36
  case order
90
37
  when Hash, BSON::OrderedHash then hash_as_sort_parameters(order)
@@ -101,8 +48,13 @@ module Mongo
101
48
  pairs = [ seeds ] if pairs.last.is_a?(Fixnum)
102
49
  pairs = pairs.collect do |hostport|
103
50
  if hostport.is_a?(String)
104
- host, port = hostport.split(':')
105
- [ host, port && port.to_i || MongoClient::DEFAULT_PORT ]
51
+ if hostport[0,1] == '['
52
+ host, port = hostport.split(']:') << MongoClient::DEFAULT_PORT
53
+ host = host.end_with?(']') ? host[1...-1] : host[1..-1]
54
+ else
55
+ host, port = hostport.split(':') << MongoClient::DEFAULT_PORT
56
+ end
57
+ [ host, port.to_i ]
106
58
  else
107
59
  hostport
108
60
  end
@@ -121,7 +73,8 @@ module Mongo
121
73
  #
122
74
  # @return [Boolean] true if the 'ok' key is either 1 or *true*.
123
75
  def ok?(doc)
124
- doc['ok'] == 1.0 || doc['ok'] == true
76
+ ok = doc['ok']
77
+ ok == 1 || ok == 1.0 || ok == true
125
78
  end
126
79
  end
127
80
  end
@@ -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.
@@ -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.
@@ -13,31 +13,18 @@
13
13
  # limitations under the License.
14
14
 
15
15
  require 'test_helper'
16
- require 'shared/authentication'
16
+ require 'shared/authentication/basic_auth_shared'
17
+ require 'shared/authentication/sasl_plain_shared'
17
18
 
18
19
  class AuthenticationTest < Test::Unit::TestCase
19
20
  include Mongo
20
- include AuthenticationTests
21
+ include BasicAuthTests
22
+ include SASLPlainTests
21
23
 
22
24
  def setup
23
- @client = MongoClient.new
24
- @db = @client[MONGO_TEST_DB]
25
+ @client = MongoClient.new(TEST_HOST, TEST_PORT)
26
+ @db = @client[TEST_DB]
27
+ @host_info = host_port
25
28
  init_auth
26
29
  end
27
-
28
- def test_authenticate_with_connection_uri
29
- @db.add_user('eunice', 'uritest')
30
-
31
- client =
32
- MongoClient.from_uri("mongodb://eunice:uritest@#{host_port}/#{@db.name}")
33
-
34
- assert client
35
- assert_equal client.auths.size, 1
36
- assert client[MONGO_TEST_DB]['auth_test'].count
37
-
38
- auth = client.auths.first
39
- assert_equal @db.name, auth[:db_name]
40
- assert_equal 'eunice', auth[:username]
41
- assert_equal 'uritest', auth[:password]
42
- end
43
30
  end
@@ -0,0 +1,782 @@
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
+ require 'test_helper'
16
+ require 'json'
17
+
18
+ module Mongo
19
+ class Collection
20
+ public :batch_write
21
+ end
22
+
23
+ class BulkWriteCollectionView
24
+ public :update_doc?, :replace_doc?, :merge_result
25
+
26
+ # for reference and future server direction
27
+ def generate_batch_commands(groups, write_concern)
28
+ groups.collect do |op_type, documents|
29
+ {
30
+ op_type => @collection.name,
31
+ Mongo::CollectionWriter::WRITE_COMMAND_ARG_KEY[op_type] => documents,
32
+ :ordered => @options[:ordered],
33
+ :writeConcern => write_concern
34
+ }
35
+ end
36
+ end
37
+ end
38
+
39
+ class MongoDBError
40
+ def inspect
41
+ "#{self.class.name}.new(#{message.inspect},#{error_code.inspect},#{result.inspect})"
42
+ end
43
+ end
44
+ end
45
+
46
+ module BSON
47
+ class InvalidDocument
48
+ def inspect
49
+ "#{self.class.name}.new(#{message.inspect})"
50
+ end
51
+ end
52
+ end
53
+
54
+ class BulkWriteCollectionViewTest < Test::Unit::TestCase
55
+ @@client ||= standard_connection(:op_timeout => 10)
56
+ @@db = @@client.db(TEST_DB)
57
+ @@test = @@db.collection("test")
58
+ @@version = @@client.server_version
59
+
60
+ DATABASE_NAME = 'ruby_test_bulk_write_collection_view'
61
+ COLLECTION_NAME = 'test'
62
+ DUPLICATE_KEY_ERROR_CODE_SET = [11000, 11001, 12582, 16460].to_set
63
+
64
+ def assert_bulk_op_pushed(expected, view)
65
+ assert_equal expected, view.ops.last
66
+ end
67
+
68
+ def assert_is_bulk_write_collection_view(view)
69
+ assert_equal Mongo::BulkWriteCollectionView, view.class
70
+ end
71
+
72
+ def assert_bulk_exception(expected, message = '')
73
+ ex = assert_raise BulkWriteError, message do
74
+ pp yield
75
+ end
76
+ assert_equal(Mongo::ErrorCode::MULTIPLE_ERRORS_OCCURRED, ex.error_code, message)
77
+ assert_match_document(expected, ex.result, message)
78
+ end
79
+
80
+ def default_setup
81
+ @client = MongoClient.new
82
+ @db = @client[DATABASE_NAME]
83
+ @collection = @db[COLLECTION_NAME]
84
+ @collection.drop
85
+ @bulk = @collection.initialize_ordered_bulk_op
86
+ @q = {:a => 1}
87
+ @u = {"$inc" => {:x => 1}}
88
+ @r = {:b => 2}
89
+ end
90
+
91
+ def sort_docs(docs)
92
+ docs.sort{|a,b| [a.keys, a.values] <=> [b.keys, b.values]}
93
+ end
94
+
95
+ def generate_sized_doc(size)
96
+ doc = {"_id" => BSON::ObjectId.new, "x" => "y"}
97
+ serialize_doc = BSON::BSON_CODER.serialize(doc, false, false, size)
98
+ doc = {"_id" => BSON::ObjectId.new, "x" => "y" * (1 + size - serialize_doc.size)}
99
+ assert_equal size, BSON::BSON_CODER.serialize(doc, false, false, size).size
100
+ doc
101
+ end
102
+
103
+ context "Bulk API Collection" do
104
+ setup do
105
+ default_setup
106
+ end
107
+
108
+ should "inspect" do
109
+ assert_equal String, @bulk.inspect.class
110
+ end
111
+
112
+ should "check first key is operation for #update_doc?" do
113
+ assert_not_nil @bulk.update_doc?({"$inc" => {:x => 1}})
114
+ assert_false @bulk.update_doc?({})
115
+ assert_nil @bulk.update_doc?({:x => 1})
116
+ end
117
+
118
+ should "check no top-level key is operation for #replace_doc?" do
119
+ assert_true @bulk.replace_doc?({:x => 1})
120
+ assert_true @bulk.replace_doc?({})
121
+ assert_false @bulk.replace_doc?({"$inc" => {:x => 1}})
122
+ assert_false @bulk.replace_doc?({:a => 1, "$inc" => {:x => 1}})
123
+ end
124
+
125
+ should "generate_batch_commands" do
126
+ groups = [
127
+ [:insert, [{:n => 0}]],
128
+ [:update, [{:n => 1}, {:n => 2}]],
129
+ [:delete, [{:n => 3}]],
130
+ [:insert, [{:n => 5}, {:n => 6}, {:n => 7}]],
131
+ [:update, [{:n => 8}]],
132
+ [:delete, [{:n => 9}, {:n => 10}]]
133
+ ]
134
+ write_concern = {:w => 1}
135
+ result = @bulk.generate_batch_commands(groups, write_concern)
136
+ expected = [
137
+ {:insert => COLLECTION_NAME, :documents => [{:n => 0}], :ordered => true, :writeConcern => {:w => 1}},
138
+ {:update => COLLECTION_NAME, :updates => [{:n => 1}, {:n => 2}], :ordered => true, :writeConcern => {:w => 1}},
139
+ {:delete => COLLECTION_NAME, :deletes => [{:n => 3}], :ordered => true, :writeConcern => {:w => 1}},
140
+ {:insert => COLLECTION_NAME, :documents => [{:n => 5}, {:n => 6}, {:n => 7}], :ordered => true, :writeConcern => {:w => 1}},
141
+ {:update => COLLECTION_NAME, :updates => [{:n => 8}], :ordered => true, :writeConcern => {:w => 1}},
142
+ {:delete => COLLECTION_NAME, :deletes => [{:n => 9}, {:n => 10}], :ordered => true, :writeConcern => {:w => 1}}
143
+ ]
144
+ assert_equal expected, result
145
+ end
146
+
147
+ should "Initialize an unordered bulk op - spec Bulk Operation Builder" do
148
+ @bulk = @collection.initialize_unordered_bulk_op
149
+ assert_is_bulk_write_collection_view(@bulk)
150
+ assert_equal @collection, @bulk.collection
151
+ assert_equal false, @bulk.options[:ordered]
152
+ end
153
+
154
+ should "Initialize an ordered bulk op - spec Bulk Operation Builder" do
155
+ assert_is_bulk_write_collection_view(@bulk)
156
+ assert_equal @collection, @bulk.collection
157
+ assert_equal true, @bulk.options[:ordered]
158
+ end
159
+ end
160
+
161
+ def big_example(bulk)
162
+ bulk.insert({:a => 1})
163
+ bulk.insert({:a => 2})
164
+ bulk.insert({:a => 3})
165
+ bulk.insert({:a => 4})
166
+ bulk.insert({:a => 5})
167
+ # Update one document matching the selector
168
+ bulk.find({:a => 1}).update_one({"$inc" => {:x => 1}})
169
+ # Update all documents matching the selector
170
+ bulk.find({:a => 2}).update({"$inc" => {:x => 2}})
171
+ # Replace entire document (update with whole doc replace)
172
+ bulk.find({:a => 3}).replace_one({:x => 3})
173
+ # Update one document matching the selector or upsert
174
+ bulk.find({:a => 1}).upsert.update_one({"$inc" => {:x => 1}})
175
+ # Update all documents matching the selector or upsert
176
+ bulk.find({:a => 2}).upsert.update({"$inc" => {:x => 2}})
177
+ # Replaces a single document matching the selector or upsert
178
+ bulk.find({:a => 3}).upsert.replace_one({:x => 3})
179
+ # Remove a single document matching the selector
180
+ bulk.find({:a => 4}).remove_one()
181
+ # Remove all documents matching the selector
182
+ bulk.find({:a => 5}).remove()
183
+ # Insert a document
184
+ bulk.insert({:x => 4})
185
+ end
186
+
187
+ context "Bulk API CollectionView" do
188
+ setup do
189
+ default_setup
190
+ end
191
+
192
+ should "set :q and return view for #find" do
193
+ result = @bulk.find(@q)
194
+ assert_is_bulk_write_collection_view(result)
195
+ assert_equal @q, @bulk.op_args[:q]
196
+ end
197
+
198
+ should "set :upsert for #upsert" do
199
+ result = @bulk.find(@q).upsert
200
+ assert_is_bulk_write_collection_view(result)
201
+ assert_true result.op_args[:upsert]
202
+ end
203
+
204
+ should "check arg for update, set :update, :u, :multi, terminate and return view for #update_one" do
205
+ assert_raise MongoArgumentError do
206
+ @bulk.find(@q).update_one(@r)
207
+ end
208
+ result = @bulk.find(@q).update_one(@u)
209
+ assert_is_bulk_write_collection_view(result)
210
+ assert_bulk_op_pushed [:update, {:q => @q, :u => @u, :multi => false}], @bulk
211
+ end
212
+
213
+ should "check arg for update, set :update, :u, :multi, terminate and return view for #update" do
214
+ assert_raise MongoArgumentError do
215
+ @bulk.find(@q).replace_one(@u)
216
+ end
217
+ result = @bulk.find(@q).update(@u)
218
+ assert_is_bulk_write_collection_view(result)
219
+ assert_bulk_op_pushed [:update, {:q => @q, :u => @u, :multi => true}], @bulk
220
+ end
221
+
222
+ should "check arg for replacement, set :update, :u, :multi, terminate and return view for #replace_one" do
223
+ assert_raise MongoArgumentError do
224
+ @bulk.find(@q).replace_one(@u)
225
+ end
226
+ result = @bulk.find(@q).replace_one(@r)
227
+ assert_is_bulk_write_collection_view(result)
228
+ assert_bulk_op_pushed [:update, {:q => @q, :u => @r, :multi => false}], @bulk
229
+ end
230
+
231
+ should "set :remove, :q, :limit, terminate and return view for #remove_one" do
232
+ result = @bulk.find(@q).remove_one
233
+ assert_is_bulk_write_collection_view(result)
234
+ assert_bulk_op_pushed [:delete, {:q => @q, :limit => 1}], @bulk
235
+
236
+ end
237
+
238
+ should "set :remove, :q, :limit, terminate and return view for #remove" do
239
+ result = @bulk.find(@q).remove
240
+ assert_is_bulk_write_collection_view(result)
241
+ assert_bulk_op_pushed [:delete, {:q => @q, :limit => 0}], @bulk
242
+ end
243
+
244
+ should "set :insert, :documents, terminate and return view for #insert" do
245
+ document = {:a => 5}
246
+ result = @bulk.insert(document)
247
+ assert_is_bulk_write_collection_view(result)
248
+ assert_bulk_op_pushed [:insert, {:d => document}], @bulk
249
+ end
250
+
251
+ should "provide spec Operations Possible On Bulk Instance" do
252
+ @bulk = @collection.initialize_ordered_bulk_op
253
+
254
+ # Update one document matching the selector
255
+ @bulk.find({:a => 1}).update_one({"$inc" => {:x => 1}})
256
+
257
+ # Update all documents matching the selector
258
+ @bulk.find({:a => 2}).update({"$inc" => {:x => 2}})
259
+
260
+ # Replace entire document (update with whole doc replace)
261
+ @bulk.find({:a => 3}).replace_one({:x => 3})
262
+
263
+ # Update one document matching the selector or upsert
264
+ @bulk.find({:a => 1}).upsert.update_one({"$inc" => {:x => 1}})
265
+
266
+ # Update all documents matching the selector or upsert
267
+ @bulk.find({:a => 2}).upsert.update({"$inc" => {:x => 2}})
268
+
269
+ # Replaces a single document matching the selector or upsert
270
+ @bulk.find({:a => 3}).upsert.replace_one({:x => 3})
271
+
272
+ # Remove a single document matching the selector
273
+ @bulk.find({:a => 4}).remove_one
274
+
275
+ # Remove all documents matching the selector
276
+ @bulk.find({:a => 5}).remove
277
+
278
+ # Insert a document
279
+ @bulk.insert({:x => 4})
280
+
281
+ # Execute the bulk operation, with an optional writeConcern overwriting the default w:1
282
+ write_concern = {:w => 1} #{:w => 1. :j => 1} #nojournal for tests
283
+ #@bulk.execute(write_concern)
284
+ end
285
+
286
+ should "execute, return result and reset @ops for #execute" do
287
+ with_write_commands_and_operations(@db.connection) do |wire_version|
288
+ @collection.remove
289
+ @bulk.insert({:x => 1})
290
+ @bulk.insert({:x => 2})
291
+ write_concern = {:w => 1}
292
+ result = @bulk.execute(write_concern)
293
+ assert_equal({"ok" => 1, "n" => 2, "nInserted" => 2}, result, "wire_version:#{wire_version}")
294
+ assert_equal 2, @collection.count
295
+ assert_equal [], @bulk.ops
296
+ end
297
+ end
298
+
299
+ should "run ordered big example" do
300
+ with_write_commands_and_operations(@db.connection) do |wire_version|
301
+ @collection.remove
302
+ big_example(@bulk)
303
+ write_concern = {:w => 1} #{:w => 1. :j => 1} #nojournal for tests
304
+ result = @bulk.execute(write_concern)
305
+ assert_match_document(
306
+ {
307
+ "ok" => 1,
308
+ "n" => 14,
309
+ "nInserted" => 6,
310
+ "nMatched" => 5,
311
+ "nUpserted" => 1,
312
+ "nModified" => 5,
313
+ "nRemoved" => 2,
314
+ "upserted" => [
315
+ {
316
+ "index" => 10,
317
+ "_id" => BSON::ObjectId('52a1e4a4bb67fbc77e26a340')
318
+ }
319
+ ]
320
+ }, result, "wire_version:#{wire_version}")
321
+ assert_false(@collection.find.to_a.empty?, "wire_version:#{wire_version}")
322
+ assert_equal [{"a"=>1, "x"=>2}, {"a"=>2, "x"=>4}, {"x"=>3}, {"x"=>3}, {"x"=>4}], sort_docs(@collection.find.to_a.collect { |doc| doc.delete("_id"); doc })
323
+ end
324
+ end
325
+
326
+ should "run ordered big example with w 0" do
327
+ with_write_commands_and_operations(@db.connection) do |wire_version|
328
+ @collection.remove
329
+ big_example(@bulk)
330
+ write_concern = {:w => 0}
331
+ result = @bulk.execute(write_concern)
332
+ assert_equal(true, result, "wire_version:#{wire_version}")
333
+ assert_false(@collection.find.to_a.empty?, "wire_version:#{wire_version}")
334
+ assert_equal [{"a"=>1, "x"=>2}, {"a"=>2, "x"=>4}, {"x"=>3}, {"x"=>3}, {"x"=>4}], sort_docs(@collection.find.to_a.collect { |doc| doc.delete("_id"); doc })
335
+ end
336
+ end
337
+
338
+ should "run unordered big example" do
339
+ with_write_commands_and_operations(@db.connection) do |wire_version|
340
+ @collection.remove
341
+ @bulk = @collection.initialize_unordered_bulk_op
342
+ big_example(@bulk)
343
+ write_concern = {:w => 1} #{:w => 1. :j => 1} #nojournal for tests
344
+ result = @bulk.execute(write_concern) # unordered varies, don't use assert_match_document
345
+ assert_true(result["n"] > 0, "wire_version:#{wire_version}")
346
+ assert_false(@collection.find.to_a.empty?, "wire_version:#{wire_version}")
347
+ end
348
+ end
349
+
350
+ should "run unordered big example with w 0" do
351
+ with_write_commands_and_operations(@db.connection) do |wire_version|
352
+ @collection.remove
353
+ @bulk = @collection.initialize_unordered_bulk_op
354
+ big_example(@bulk)
355
+ write_concern = {:w => 0}
356
+ result = @bulk.execute(write_concern) # unordered varies, don't use assert_match_document
357
+ assert_equal(true, result, "wire_version:#{wire_version}")
358
+ assert_false(@collection.find.to_a.empty?, "wire_version:#{wire_version}")
359
+ end
360
+ end
361
+
362
+ should "run unordered bulk operations in one batch per write-type" do
363
+ with_write_commands(@db.connection) do
364
+ @collection.expects(:batch_write).at_most(3).returns([[], [], [], []])
365
+ bulk = @collection.initialize_unordered_bulk_op
366
+ bulk.insert({:_id => 1, :a => 1})
367
+ bulk.find({:_id => 1, :a => 1}).update({"$inc" => {:x => 1}})
368
+ bulk.find({:_id => 1, :a => 1}).remove
369
+ bulk.insert({:_id => 2, :a => 2})
370
+ bulk.find({:_id => 2, :a => 2}).update({"$inc" => {:x => 2}})
371
+ bulk.find({:_id => 2, :a => 2}).remove
372
+ bulk.insert({:_id => 3, :a => 3})
373
+ bulk.find({:_id => 3, :a => 3}).update({"$inc" => {:x => 3}})
374
+ bulk.find({:_id => 3, :a => 3}).remove
375
+ result = bulk.execute # unordered varies, don't use assert_match_document
376
+ end
377
+ end
378
+
379
+ should "handle error for duplicate key with offset" do
380
+ with_write_commands_and_operations(@db.connection) do |wire_version|
381
+ @collection.remove
382
+ @bulk.find({:a => 1}).update_one({"$inc" => {:x => 1}})
383
+ @bulk.insert({:_id => 1, :a => 1})
384
+ @bulk.insert({:_id => 1, :a => 2})
385
+ @bulk.insert({:_id => 3, :a => 3})
386
+ ex = assert_raise BulkWriteError do
387
+ @bulk.execute
388
+ end
389
+ result = ex.result
390
+ assert_match_document(
391
+ {
392
+ "ok" => 1,
393
+ "n" => 1,
394
+ "writeErrors" =>
395
+ [{
396
+ "index" => 2,
397
+ "code" => 11000,
398
+ "errmsg" => /duplicate key error/
399
+ }],
400
+ "code" => 65,
401
+ "errmsg" => "batch item errors occurred",
402
+ "nInserted" => 1,
403
+ "nMatched" => 0,
404
+ "nModified" => 0
405
+ }, result, "wire_version:#{wire_version}")
406
+ end
407
+ end
408
+
409
+ should "handle error for unordered multiple duplicate key with offset" do
410
+ with_write_commands_and_operations(@db.connection) do |wire_version|
411
+ @collection.remove
412
+ @bulk = @collection.initialize_unordered_bulk_op
413
+ @bulk.find({:a => 1}).remove
414
+ @bulk.insert({:_id => 1, :a => 1})
415
+ @bulk.insert({:_id => 1, :a => 2})
416
+ @bulk.insert({:_id => 3, :a => 3})
417
+ @bulk.insert({:_id => 3, :a => 3})
418
+ ex = assert_raise BulkWriteError do
419
+ @bulk.execute
420
+ end
421
+ result = ex.result # unordered varies, don't use assert_bulk_exception
422
+ assert_not_nil(result["writeErrors"], "wire_version:#{wire_version}")
423
+ end
424
+ end
425
+
426
+ should "handle error for serialization with offset" do
427
+ with_write_commands_and_operations(@db.connection) do |wire_version|
428
+ @collection.remove
429
+ assert_equal 16777216, @@client.max_bson_size
430
+ @bulk.find({:a => 1}).update_one({"$inc" => {:x => 1}})
431
+ @bulk.insert({:_id => 1, :a => 1})
432
+ @bulk.insert(generate_sized_doc(@@client.max_message_size + 1))
433
+ @bulk.insert({:_id => 3, :a => 3})
434
+ ex = assert_raise BulkWriteError do
435
+ @bulk.execute
436
+ end
437
+ result = ex.result
438
+ assert_match_document(
439
+ {
440
+ "ok" => 1,
441
+ "n" => 1,
442
+ "writeErrors" =>
443
+ [{
444
+ "index" => 2,
445
+ "code" => 22,
446
+ "errmsg" => /too large/
447
+ }],
448
+ "code" => 65,
449
+ "errmsg" => "batch item errors occurred",
450
+ "nInserted" => 1,
451
+ "nMatched" => 0,
452
+ "nModified" => 0
453
+ }, result, "wire_version:#{wire_version}")
454
+ end
455
+ end
456
+
457
+ should "run ordered bulk op - spec Modes of Execution" do # spec fix pending
458
+ with_write_commands_and_operations(@db.connection) do |wire_version|
459
+ @collection.remove
460
+ @collection.ensure_index(BSON::OrderedHash[:a, Mongo::ASCENDING], {:unique => true})
461
+ @bulk.insert({:a => 1})
462
+ @bulk.insert({:a => 2})
463
+ @bulk.find({:a => 2}).update({'$set' => {:a => 1}}) # Clashes with unique index
464
+ @bulk.find({:a => 1}).remove
465
+ ex = assert_raise BulkWriteError do
466
+ @bulk.execute
467
+ end
468
+ assert_equal(2, @collection.count)
469
+ end
470
+ end
471
+
472
+ should "run unordered bulk op - spec Modes of Execution" do # spec fix pending
473
+ with_write_commands_and_operations(@db.connection) do |wire_version|
474
+ @collection.remove
475
+ @collection.ensure_index(BSON::OrderedHash[:a, Mongo::ASCENDING], {:unique => true})
476
+ bulk = @collection.initialize_unordered_bulk_op
477
+ bulk.insert({:a => 1})
478
+ bulk.insert({:a => 2})
479
+ bulk.find({:a => 2}).update({'$set' => {:a => 1}}) # Clashes with unique index
480
+ bulk.find({:a => 3}).remove
481
+ bulk.find({:a => 2}).update({'$set' => {:a => 1}}) # Clashes with unique index
482
+ ex = assert_raise BulkWriteError do
483
+ bulk.execute
484
+ end
485
+ result = ex.result
486
+ assert(result["writeErrors"].size > 1, "wire_version:#{wire_version}")
487
+ end
488
+ end
489
+
490
+ should "run spec Ordered Bulk Operations" do # spec fix pending
491
+ with_write_commands_and_operations(@db.connection) do |wire_version|
492
+ @bulk.insert({:a => 1})
493
+ @bulk.insert({:a => 2})
494
+ @bulk.insert({:a => 3})
495
+ @bulk.find({:a => 2}).upsert.update({'$set' => {:a => 4}})
496
+ @bulk.find({:a => 1}).remove_one
497
+ @bulk.insert({:a => 5})
498
+ result = @bulk.execute({:w => 1})
499
+ assert_match_document(
500
+ {
501
+ "ok" => 1,
502
+ "n" => 6,
503
+ "nInserted" => 4,
504
+ "nMatched" => 1,
505
+ "nModified" => 1,
506
+ "nRemoved" => 1,
507
+ }, result, "wire_version:#{wire_version}")
508
+ # for write commands there will be in sequence insert, update, remove, insert
509
+ end
510
+ end
511
+
512
+ should "run spec Unordered Bulk Operations" do
513
+ with_write_commands_and_operations(@db.connection) do |wire_version|
514
+ bulk = @collection.initialize_unordered_bulk_op
515
+ bulk.insert({:_id => 1})
516
+ bulk.find({:_id => 2}).update_one({'$inc' => { :x => 1 }})
517
+ bulk.find({:_id => 3}).remove_one
518
+ bulk.insert({:_id => 4})
519
+ bulk.find({:_id => 5}).update_one({'$inc' => { :x => 1 }})
520
+ bulk.find({:_id => 6}).remove_one
521
+ result = nil
522
+ begin
523
+ result = bulk.execute
524
+ rescue => ex
525
+ result = ex.result
526
+ end
527
+ # for write commands internally the driver will execute 3. One each for the inserts, updates and removes.
528
+ end
529
+ end
530
+
531
+ should "handle duplicate key error - spec Merging Results" do # spec fix pending
532
+ with_write_commands_and_operations(@db.connection) do |wire_version|
533
+ @collection.remove
534
+ @collection.ensure_index(BSON::OrderedHash[:a, Mongo::ASCENDING], {:unique => true})
535
+ bulk = @collection.initialize_ordered_bulk_op
536
+ bulk.insert({:a => 1})
537
+ bulk.insert({:a => 2})
538
+ bulk.find({:a => 2}).upsert.update({'$set' => {:a => 1}})
539
+ bulk.insert({:a => 3})
540
+ ex = assert_raise BulkWriteError do
541
+ bulk.execute
542
+ end
543
+ result = ex.result
544
+ assert_match_document(
545
+ {
546
+ "ok" => 1,
547
+ "n" => 2,
548
+ "writeErrors" =>
549
+ [{
550
+ "index" => 2,
551
+ "code" => DUPLICATE_KEY_ERROR_CODE_SET,
552
+ "errmsg" => /duplicate key error/
553
+ }],
554
+ "code" => 65,
555
+ "errmsg" => "batch item errors occurred",
556
+ "nInserted" => 2,
557
+ "nMatched" => 0,
558
+ "nModified" => 0
559
+ }, result, "wire_version:#{wire_version}")
560
+ end
561
+ end
562
+
563
+ # should "handle error with deferred write concern error - spec Merging Results" do
564
+ # end # see test/replica_set/insert_test.rb
565
+
566
+ should "handle unordered errors - spec Merging Results" do # spec fix pending
567
+ with_write_commands_and_operations(@db.connection) do |wire_version|
568
+ @collection.remove
569
+ @collection.ensure_index(BSON::OrderedHash[:a, Mongo::ASCENDING], {:unique => true})
570
+ bulk = @collection.initialize_unordered_bulk_op
571
+ bulk.insert({:a => 1})
572
+ bulk.find({:a => 1}).upsert.update({'$set' => {:a => 2}})
573
+ bulk.insert({:a => 2})
574
+ ex = assert_raise BulkWriteError do
575
+ bulk.execute
576
+ end
577
+ result = ex.result # unordered varies, don't use assert_bulk_exception
578
+ assert_equal(1, result['ok'], "wire_version:#{wire_version}")
579
+ assert_equal(2, result['n'], "wire_version:#{wire_version}")
580
+ err_details = result['writeErrors']
581
+ assert_equal([2,nil,1][wire_version], err_details.first['index'], "wire_version:#{wire_version}")
582
+ assert_match(/duplicate key error/, err_details.first['errmsg'], "wire_version:#{wire_version}")
583
+ end
584
+ end
585
+
586
+ # should "handle unordered errors with deferred write concern error - spec Merging Results" do # TODO - spec review
587
+ # end # see test/replica_set/insert_test.rb
588
+
589
+ should "report user index - spec Merging errors" do
590
+ with_write_commands_and_operations(@db.connection) do |wire_version|
591
+ @collection.remove
592
+ @collection.ensure_index(BSON::OrderedHash[:a, Mongo::ASCENDING], {:unique => true})
593
+ bulk = @collection.initialize_ordered_bulk_op
594
+ bulk.insert({:a => 1})
595
+ bulk.insert({:a => 2})
596
+ bulk.find({:a => 2}).update_one({'$set' => {:a => 1}});
597
+ bulk.find({:a => 4}).remove_one();
598
+ ex = assert_raise BulkWriteError do
599
+ bulk.execute({:w => 1})
600
+ end
601
+ result = ex.result
602
+ assert_match_document(
603
+ {
604
+ "ok" => 1,
605
+ "n" => 2,
606
+ "writeErrors" =>
607
+ [{
608
+ "index" => 2,
609
+ "code" => DUPLICATE_KEY_ERROR_CODE_SET,
610
+ "errmsg" => /duplicate key error/
611
+ }],
612
+ "code" => 65,
613
+ "errmsg" => "batch item errors occurred",
614
+ "nInserted" => 2,
615
+ "nMatched" => 0,
616
+ "nModified" => 0
617
+ }, result, "wire_version:#{wire_version}")
618
+ end
619
+ end
620
+
621
+ should "handle single upsert - spec Handling upserts" do # chose array always for upserted value
622
+ with_write_commands_and_operations(@db.connection) do |wire_version|
623
+ @collection.remove
624
+ @collection.ensure_index(BSON::OrderedHash[:a, Mongo::ASCENDING], {:unique => true})
625
+ bulk = @collection.initialize_ordered_bulk_op
626
+ bulk.find({:a => 1}).upsert.update({'$set' => {:a => 2}})
627
+ result = bulk.execute
628
+ assert_match_document(
629
+ {
630
+ "ok" => 1,
631
+ "n" => 1,
632
+ "nMatched" => 0,
633
+ "nUpserted" => 1,
634
+ "nModified" => 0,
635
+ "upserted" => [
636
+ {"_id" => BSON::ObjectId('52a16767bb67fbc77e26a310'), "index" => 0}
637
+ ]
638
+ }, result, "wire_version:#{wire_version}")
639
+ end
640
+ end
641
+
642
+ should "handle multiple upsert - spec Handling upserts" do
643
+ with_write_commands_and_operations(@db.connection) do |wire_version|
644
+ @collection.remove
645
+ @collection.ensure_index(BSON::OrderedHash[:a, Mongo::ASCENDING], {:unique => true})
646
+ bulk = @collection.initialize_ordered_bulk_op
647
+ bulk.find({:a => 1}).upsert.update({'$set' => {:a => 2}})
648
+ bulk.find({:a => 3}).upsert.update({'$set' => {:a => 4}})
649
+ result = bulk.execute
650
+ assert_match_document(
651
+ {
652
+ "ok" => 1,
653
+ "n" => 2,
654
+ "nMatched" => 0,
655
+ "nUpserted" => 2,
656
+ "nModified" => 0,
657
+ "upserted" => [
658
+ {"index" => 0, "_id" => BSON::ObjectId('52a1e37cbb67fbc77e26a338')},
659
+ {"index" => 1, "_id" => BSON::ObjectId('52a1e37cbb67fbc77e26a339')}
660
+ ]
661
+ }, result, "wire_version:#{wire_version}")
662
+ end
663
+ end
664
+
665
+ should "handle multiple errors for unordered bulk write" do
666
+ with_write_commands_and_operations(@db.connection) do |wire_version|
667
+ @collection.remove
668
+ @bulk = @collection.initialize_unordered_bulk_op
669
+ @bulk.insert({:_id => 1, :a => 1})
670
+ @bulk.insert({:_id => 1, :a => 2})
671
+ @bulk.insert(generate_sized_doc(@@client.max_message_size + 1))
672
+ @bulk.insert({:_id => 3, :a => 3})
673
+ @bulk.find({:a => 4}).upsert.replace_one({:x => 3})
674
+ ex = assert_raise BulkWriteError do
675
+ @bulk.execute
676
+ end
677
+ result = ex.result # unordered varies, don't use assert_bulk_exception
678
+ assert_equal(1, result['ok'], "wire_version:#{wire_version}")
679
+ assert_equal(3, result['n'], "wire_version:#{wire_version}")
680
+ err_details = result['writeErrors']
681
+ assert_match(/duplicate key error/, err_details.find { |e| e['code']==11000 }['errmsg'], "wire_version:#{wire_version}")
682
+ assert_match(/too large/, err_details.find { |e| e['index']==2 }['errmsg'], "wire_version:#{wire_version}")
683
+ assert_not_nil(result['upserted'].find { |e| e['index']==4 }, "wire_version:#{wire_version}")
684
+ end
685
+ end
686
+
687
+ should "handle replication usage error" do
688
+ with_no_replication(@db.connection) do
689
+ with_write_commands_and_operations(@db.connection) do |wire_version|
690
+ @collection.remove
691
+ @bulk = @collection.initialize_ordered_bulk_op
692
+ @bulk.insert({:_id => 1, :a => 1})
693
+ write_concern = {:w => 5}
694
+ ex = assert_raise BulkWriteError do
695
+ @bulk.execute(write_concern)
696
+ end
697
+ result = ex.result
698
+ if @@version >= "2.5.5"
699
+ assert_match_document(
700
+ {
701
+ "ok" => 0,
702
+ "n" => 0,
703
+ "code" => 65,
704
+ "errmsg" => "batch item errors occurred",
705
+ "writeErrors" => [
706
+ {
707
+ "errmsg" => "cannot use 'w' > 1 when a host is not replicated",
708
+ "code" => 2,
709
+ "index" => 0}
710
+ ],
711
+ "nInserted" => 0,
712
+ }, result, "wire_version:#{wire_version}")
713
+ else
714
+ assert_match_document(
715
+ {
716
+ "ok" => 1,
717
+ "n" => 1,
718
+ "code" => 65,
719
+ "errmsg" => "batch item errors occurred",
720
+ "writeConcernError" => [
721
+ {
722
+ "errmsg" => /no replication has been enabled/,
723
+ "code" => 64,
724
+ "index" => 0
725
+ }
726
+ ],
727
+ "nInserted" => 1,
728
+ }, result, "wire_version:#{wire_version}")
729
+ end
730
+ end
731
+ end
732
+ end
733
+
734
+ should "handle journaling error" do
735
+ with_no_journaling(@db.connection) do
736
+ with_write_commands_and_operations(@db.connection) do |wire_version|
737
+ @collection.remove
738
+ @bulk = @collection.initialize_ordered_bulk_op
739
+ @bulk.insert({:_id => 1, :a => 1})
740
+ write_concern = {:w => 1, :j => 1}
741
+ ex = assert_raise BulkWriteError do
742
+ @bulk.execute(write_concern)
743
+ end
744
+ result = ex.result
745
+ if @@version >= "2.5.5"
746
+ assert_match_document(
747
+ {
748
+ "ok" => 0,
749
+ "n" => 0,
750
+ "writeErrors" => [
751
+ {
752
+ "code" => 2,
753
+ "errmsg" => "cannot use 'j' option when a host does not have journaling enabled", "index" => 0
754
+ }
755
+ ],
756
+ "code" => 65,
757
+ "errmsg" => "batch item errors occurred",
758
+ "nInserted" => 0
759
+ }, result, "wire_version:#{wire_version}")
760
+ else
761
+ assert_match_document(
762
+ {
763
+ "ok" => 1,
764
+ "n" => 1,
765
+ "writeConcernError" => [
766
+ {
767
+ "code" => 2,
768
+ "errmsg" => "journaling not enabled on this server",
769
+ "index" => 0
770
+ }
771
+ ],
772
+ "code" => 65,
773
+ "errmsg" => "batch item errors occurred",
774
+ "nInserted" => 1
775
+ }, result, "wire_version:#{wire_version}")
776
+ end
777
+ end
778
+ end
779
+ end
780
+ end
781
+
782
+ end