Skip to content

Commit 43a8a6a

Browse files
authored
PYTHON-3075 [v4.0] bulk_write does not apply CodecOptions to upserted_ids result (#841)
1 parent 93334ca commit 43a8a6a

File tree

4 files changed

+116
-9
lines changed

4 files changed

+116
-9
lines changed

pymongo/message.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ def write_command(self, cmd, request_id, msg, docs):
817817
self._start(cmd, request_id, docs)
818818
start = datetime.datetime.now()
819819
try:
820-
reply = self.sock_info.write_command(request_id, msg)
820+
reply = self.sock_info.write_command(request_id, msg, self.codec)
821821
if self.publish:
822822
duration = (datetime.datetime.now() - start) + duration
823823
self._succeed(request_id, reply, duration)
@@ -886,7 +886,7 @@ def execute(self, cmd, docs, client):
886886
batched_cmd, to_send = self._batch_command(cmd, docs)
887887
result = self.sock_info.command(
888888
self.db_name, batched_cmd,
889-
codec_options=_UNICODE_REPLACE_CODEC_OPTIONS,
889+
codec_options=self.codec,
890890
session=self.session, client=client)
891891
return result, to_send
892892

@@ -1231,9 +1231,9 @@ def unpack_response(self, cursor_id=None,
12311231
return bson._decode_all_selective(
12321232
self.documents, codec_options, user_fields)
12331233

1234-
def command_response(self):
1234+
def command_response(self, codec_options):
12351235
"""Unpack a command response."""
1236-
docs = self.unpack_response()
1236+
docs = self.unpack_response(codec_options=codec_options)
12371237
assert self.number_returned == 1
12381238
return docs[0]
12391239

@@ -1299,9 +1299,9 @@ def unpack_response(self, cursor_id=None,
12991299
return bson._decode_all_selective(
13001300
self.payload_document, codec_options, user_fields)
13011301

1302-
def command_response(self):
1302+
def command_response(self, codec_options):
13031303
"""Unpack a command response."""
1304-
return self.unpack_response()[0]
1304+
return self.unpack_response(codec_options=codec_options)[0]
13051305

13061306
def raw_command_response(self):
13071307
"""Return the bytes of the command response."""

pymongo/pool.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -782,7 +782,7 @@ def unack_write(self, msg, max_doc_size):
782782
self._raise_if_not_writable(True)
783783
self.send_message(msg, max_doc_size)
784784

785-
def write_command(self, request_id, msg):
785+
def write_command(self, request_id, msg, codec_options):
786786
"""Send "insert" etc. command, returning response as a dict.
787787
788788
Can raise ConnectionFailure or OperationFailure.
@@ -793,7 +793,7 @@ def write_command(self, request_id, msg):
793793
"""
794794
self.send_message(msg, 0)
795795
reply = self.receive_message(request_id)
796-
result = reply.command_response()
796+
result = reply.command_response(codec_options)
797797

798798
# Raises NotPrimaryError or OperationFailure.
799799
helpers._check_command_response(result, self.max_wire_version)

test/test_bulk.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@
1515
"""Test the bulk API."""
1616

1717
import sys
18+
import uuid
19+
from bson.binary import UuidRepresentation
20+
from bson.codec_options import CodecOptions
1821

1922
sys.path[0:0] = [""]
2023

24+
from bson import Binary
2125
from bson.objectid import ObjectId
2226
from pymongo.common import partition_node
2327
from pymongo.errors import (BulkWriteError,
@@ -376,6 +380,78 @@ def test_client_generated_upsert_id(self):
376380
{'index': 2, '_id': 2}]},
377381
result.bulk_api_result)
378382

383+
def test_upsert_uuid_standard(self):
384+
options = CodecOptions(uuid_representation=UuidRepresentation.STANDARD)
385+
coll = self.coll.with_options(codec_options=options)
386+
uuids = [uuid.uuid4() for _ in range(3)]
387+
result = coll.bulk_write([
388+
UpdateOne({'_id': uuids[0]}, {'$set': {'a': 0}}, upsert=True),
389+
ReplaceOne({'a': 1}, {'_id': uuids[1]}, upsert=True),
390+
# This is just here to make the counts right in all cases.
391+
ReplaceOne({'_id': uuids[2]}, {'_id': uuids[2]}, upsert=True),
392+
])
393+
self.assertEqualResponse(
394+
{'nMatched': 0,
395+
'nModified': 0,
396+
'nUpserted': 3,
397+
'nInserted': 0,
398+
'nRemoved': 0,
399+
'upserted': [{'index': 0, '_id': uuids[0]},
400+
{'index': 1, '_id': uuids[1]},
401+
{'index': 2, '_id': uuids[2]}]},
402+
result.bulk_api_result)
403+
404+
def test_upsert_uuid_unspecified(self):
405+
options = CodecOptions(uuid_representation=UuidRepresentation.UNSPECIFIED)
406+
coll = self.coll.with_options(codec_options=options)
407+
uuids = [Binary.from_uuid(uuid.uuid4()) for _ in range(3)]
408+
result = coll.bulk_write([
409+
UpdateOne({'_id': uuids[0]}, {'$set': {'a': 0}}, upsert=True),
410+
ReplaceOne({'a': 1}, {'_id': uuids[1]}, upsert=True),
411+
# This is just here to make the counts right in all cases.
412+
ReplaceOne({'_id': uuids[2]}, {'_id': uuids[2]}, upsert=True),
413+
])
414+
self.assertEqualResponse(
415+
{'nMatched': 0,
416+
'nModified': 0,
417+
'nUpserted': 3,
418+
'nInserted': 0,
419+
'nRemoved': 0,
420+
'upserted': [{'index': 0, '_id': uuids[0]},
421+
{'index': 1, '_id': uuids[1]},
422+
{'index': 2, '_id': uuids[2]}]},
423+
result.bulk_api_result)
424+
425+
def test_upsert_uuid_standard_subdocuments(self):
426+
options = CodecOptions(uuid_representation=UuidRepresentation.STANDARD)
427+
coll = self.coll.with_options(codec_options=options)
428+
ids = [
429+
{'f': Binary(bytes(i)), 'f2': uuid.uuid4()}
430+
for i in range(3)
431+
]
432+
433+
result = coll.bulk_write([
434+
UpdateOne({'_id': ids[0]}, {'$set': {'a': 0}}, upsert=True),
435+
ReplaceOne({'a': 1}, {'_id': ids[1]}, upsert=True),
436+
# This is just here to make the counts right in all cases.
437+
ReplaceOne({'_id': ids[2]}, {'_id': ids[2]}, upsert=True),
438+
])
439+
440+
# The `Binary` values are returned as `bytes` objects.
441+
for _id in ids:
442+
_id['f'] = bytes(_id['f'])
443+
444+
self.assertEqualResponse(
445+
{'nMatched': 0,
446+
'nModified': 0,
447+
'nUpserted': 3,
448+
'nInserted': 0,
449+
'nRemoved': 0,
450+
'upserted': [{'index': 0, '_id': ids[0]},
451+
{'index': 1, '_id': ids[1]},
452+
{'index': 2, '_id': ids[2]}]},
453+
result.bulk_api_result)
454+
379455
def test_single_ordered_batch(self):
380456
result = self.coll.bulk_write([
381457
InsertOne({'a': 1}),

test/test_encryption.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
from bson import encode, json_util
3131
from bson.binary import (Binary,
32+
UuidRepresentation,
3233
JAVA_LEGACY,
3334
STANDARD,
3435
UUID_SUBTYPE)
@@ -50,13 +51,14 @@
5051
ServerSelectionTimeoutError,
5152
WriteError)
5253
from pymongo.mongo_client import MongoClient
53-
from pymongo.operations import InsertOne
54+
from pymongo.operations import InsertOne, ReplaceOne, UpdateOne
5455
from pymongo.write_concern import WriteConcern
5556

5657
from test import (unittest, CA_PEM, CLIENT_PEM,
5758
client_context,
5859
IntegrationTest,
5960
PyMongoTestCase)
61+
from test.test_bulk import BulkTestBase
6062
from test.utils import (TestCreator,
6163
camel_to_snake_args,
6264
OvertCommandListener,
@@ -313,6 +315,35 @@ def test_use_after_close(self):
313315
client.admin.command('ping')
314316

315317

318+
class TestEncryptedBulkWrite(BulkTestBase, EncryptionIntegrationTest):
319+
320+
def test_upsert_uuid_standard_encrypte(self):
321+
opts = AutoEncryptionOpts(KMS_PROVIDERS, 'keyvault.datakeys')
322+
client = rs_or_single_client(auto_encryption_opts=opts)
323+
self.addCleanup(client.close)
324+
325+
options = CodecOptions(uuid_representation=UuidRepresentation.STANDARD)
326+
encrypted_coll = client.pymongo_test.test
327+
coll = encrypted_coll.with_options(codec_options=options)
328+
uuids = [uuid.uuid4() for _ in range(3)]
329+
result = coll.bulk_write([
330+
UpdateOne({'_id': uuids[0]}, {'$set': {'a': 0}}, upsert=True),
331+
ReplaceOne({'a': 1}, {'_id': uuids[1]}, upsert=True),
332+
# This is just here to make the counts right in all cases.
333+
ReplaceOne({'_id': uuids[2]}, {'_id': uuids[2]}, upsert=True),
334+
])
335+
self.assertEqualResponse(
336+
{'nMatched': 0,
337+
'nModified': 0,
338+
'nUpserted': 3,
339+
'nInserted': 0,
340+
'nRemoved': 0,
341+
'upserted': [{'index': 0, '_id': uuids[0]},
342+
{'index': 1, '_id': uuids[1]},
343+
{'index': 2, '_id': uuids[2]}]},
344+
result.bulk_api_result)
345+
346+
316347
class TestClientMaxWireVersion(IntegrationTest):
317348

318349
@classmethod

0 commit comments

Comments
 (0)