Permalink
Browse files

Merge pull request #750 from doctrine/mongodb26-upsert

Use replacement document for upserts with no modifiers (#741)
  • Loading branch information...
2 parents 60a4f3f + 67fa0c5 commit 63978994ae75feb8d8b68c5f0b2022bbf924fdae @jmikola jmikola committed Feb 6, 2014
@@ -289,11 +289,40 @@ private function executeUpsert(array $data, array $options)
$options['upsert'] = true;
$criteria = array('_id' => $data['$set']['_id']);
unset($data['$set']['_id']);
- // stupid php
+
+ // Do not send an empty $set modifier
if (empty($data['$set'])) {
- $data['$set'] = new \stdClass;
+ unset($data['$set']);
+ }
+
+ /* If there are no modifiers remaining, we're upserting a document with
+ * an identifier as its only field. Since a document with the identifier
+ * may already exist, the desired behavior is "insert if not exists" and
+ * NOOP otherwise. MongoDB 2.6+ does not allow empty modifiers, so $set
+ * the identifier to the same value in our criteria.
+ *
+ * This will fail for versions before MongoDB 2.6, which require an
+ * empty $set modifier. The best we can do (without attempting to check
+ * server versions in advance) is attempt the 2.6+ behavior and retry
+ * after the relevant exception.
+ *
+ * See: https://jira.mongodb.org/browse/SERVER-12266
+ */
+ if (empty($data)) {
+ $retry = true;
+ $data = array('$set' => array('_id' => $criteria['_id']));
}
- $this->collection->update($criteria, $data, $options);
+
+ try {
+ $this->collection->update($criteria, $data, $options);
+ return;
+ } catch (\MongoCursorException $e) {
+ if (empty($retry) || strpos($e->getMessage(), 'Mod on _id not allowed') === false) {
+ throw $e;
+ }
+ }
+
+ $this->collection->update($criteria, array('$set' => new \stdClass), $options);
}
/**
@@ -26,6 +26,21 @@ public function setUp()
$this->documentPersister = $this->uow->getDocumentPersister($this->class);
}
+ public function testExecuteUpsertShouldNeverReplaceDocuments()
+ {
+ $originalData = $this->dm->getDocumentCollection($this->class)->findOne();
+
+ $document = new DocumentPersisterTestDocument();
+ $document->id = $originalData['_id'];
+
+ $this->dm->persist($document);
+ $this->dm->flush();
+
+ $updatedData = $this->dm->getDocumentCollection($this->class)->findOne(array('_id' => $originalData['_id']));
+
+ $this->assertEquals($originalData, $updatedData);
+ }
+
public function testLoadPreparesCriteriaAndSort()
{
$criteria = array('name' => array('$in' => array('a', 'b')));

0 comments on commit 6397899

Please sign in to comment.