Skip to content

Commit 48ffdb0

Browse files
committed
PYTHON-705 - Fix Bulk API legacy upsert _id compatibility
Versions of MongoDB previous to 2.6 only return the upserted field for an upsert operation if the _id value is an ObjectId. This patch works around that issue to ensure nUpserted counts are correct regardless of server version.
1 parent 7e90881 commit 48ffdb0

File tree

2 files changed

+34
-0
lines changed

2 files changed

+34
-0
lines changed

pymongo/bulk.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,25 @@ def _merge_legacy(run, full_result, result, index):
103103

104104
if run.op_type == _INSERT:
105105
full_result['nInserted'] += 1
106+
106107
elif run.op_type == _UPDATE:
107108
if "upserted" in result:
108109
doc = {"index": run.index(index), "_id": result["upserted"]}
109110
full_result["upserted"].append(doc)
110111
full_result['nUpserted'] += affected
112+
# Versions of MongoDB before 2.6 don't return the _id for an
113+
# upsert if _id is not an ObjectId.
114+
elif result.get("updatedExisting") == False and affected == 1:
115+
op = run.ops[index]
116+
# If _id is in both the update document *and* the query spec
117+
# the update document _id takes precedence.
118+
_id = op['u'].get('_id', op['q'].get('_id'))
119+
doc = {u"index": run.index(index), u"_id": _id}
120+
full_result["upserted"].append(doc)
121+
full_result['nUpserted'] += affected
111122
else:
112123
full_result['nMatched'] += affected
124+
113125
elif run.op_type == _DELETE:
114126
full_result['nRemoved'] += affected
115127

test/test_bulk.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,28 @@ def test_upsert_large(self):
596596

597597
self.assertEqual(1, self.coll.find({'x': 1}).count())
598598

599+
def test_client_generated_upsert_id(self):
600+
batch = self.coll.initialize_ordered_bulk_op()
601+
batch.find({'_id': 0}).upsert().update_one({'$set': {'a': 0}})
602+
batch.find({'a': 1}).upsert().replace_one({'_id': 1})
603+
if not client_context.version.at_least(2, 6, 0):
604+
# This case is only possible in MongoDB versions before 2.6.
605+
batch.find({'_id': 3}).upsert().replace_one({'_id': 2})
606+
else:
607+
# This is just here to make the counts right in all cases.
608+
batch.find({'_id': 2}).upsert().replace_one({'_id': 2})
609+
result = batch.execute()
610+
self.assertEqualResponse(
611+
{'nMatched': 0,
612+
'nModified': 0,
613+
'nUpserted': 3,
614+
'nInserted': 0,
615+
'nRemoved': 0,
616+
'upserted': [{'index': 0, '_id': 0},
617+
{'index': 1, '_id': 1},
618+
{'index': 2, '_id': 2}]},
619+
result)
620+
599621
def test_single_ordered_batch(self):
600622
batch = self.coll.initialize_ordered_bulk_op()
601623
batch.insert({'a': 1})

0 commit comments

Comments
 (0)