Skip to content

Commit 99c3b95

Browse files
committed
Rewrote updateDocumentIndexes() with more robust index comparison logic
Removed getAllIndexes() and getAllDocumentIndexes(), which were never called. Removed normalization of indexes returned by MongoCollection::getIndexInfo() and implemented a new method to compare such an index with one from class metadata. This method also supports geospatial indexes and won't drop a unique/dropDups index in Mongo just to create a new unique/non-dropDups index from class metadata.
1 parent 984dc7b commit 99c3b95

File tree

1 file changed

+75
-95
lines changed

1 file changed

+75
-95
lines changed

lib/Doctrine/ODM/MongoDB/SchemaManager.php

+75-95
Original file line numberDiff line numberDiff line change
@@ -87,62 +87,40 @@ public function updateDocumentIndexes($documentName)
8787

8888
if ($documentIndexes = $this->getDocumentIndexes($documentName)) {
8989

90-
$defaults = array(
91-
'safe' => true,
92-
'dropDups' => false,
93-
'background' => false,
94-
'unique' => false,
95-
'sparse' => false,
96-
);
97-
foreach ($documentIndexes as &$documentIndex) {
98-
$documentIndex['options'] = array_merge($defaults, $documentIndex['options']);
99-
}
100-
101-
if ($collection = $this->dm->getDocumentCollection($class->name)) {
102-
103-
$mongoIndexes = $collection->getIndexInfo();
104-
foreach ($mongoIndexes as $i => $mongoIndex) {
105-
if ($mongoIndex['name'] === '_id_') {
106-
unset($mongoIndexes[$i]);
107-
continue;
108-
}
109-
$mongoIndexes[$i] = $this->rawIndexToDocumentIndex($mongoIndex);
90+
$collection = $this->dm->getDocumentCollection($documentName);
91+
$mongoIndexes = $collection->getIndexInfo();
92+
93+
/* Determine which Mongo indexes should be deleted. Exclude the ID
94+
* index and those that are equivalent to any in the class metadata.
95+
*/
96+
$mongoIndexes = array_filter($mongoIndexes, function($mongoIndex) use ($documentIndexes) {
97+
if ('_id_' === $mongoIndex['name']) {
98+
return false;
11099
}
111100

112-
$update = false;
113-
foreach ($documentIndexes as $i => $documentIndex) {
114-
// Remove each index from array that exists already
115-
foreach ($mongoIndexes as $j => $mongoIndex) {
116-
$keyDiff = array_diff_assoc($mongoIndex['keys'], $documentIndex['keys']);
117-
$optDiff = array_diff_assoc($mongoIndex['options'], $documentIndex['options']);
118-
if (empty($keyDiff)) {
119-
if (empty($optDiff) || (count($optDiff) === 1 && isset($optDiff['name']))) {
120-
// Index exists exactly as document
121-
unset($mongoIndexes[$j]);
122-
continue;
123-
} else {
124-
// Only options differ, update
125-
unset($mongoIndexes[$j]);
126-
$update = true;
127-
}
128-
}
129-
}
130-
}
131-
132-
// The rest need to be deleted
133-
foreach ($mongoIndexes as $mongoIndex) {
134-
if (isset($mongoIndex['options']['name'])) {
135-
$collection->getDatabase()->command(array(
136-
'deleteIndexes' => $collection->getName(),
137-
'index' => $mongoIndex['options']['name']
138-
));
101+
foreach ($documentIndexes as $documentIndex) {
102+
if ($this->isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)) {
103+
return false;
139104
}
140105
}
141106

142-
if ($update) {
143-
$this->ensureDocumentIndexes($documentName);
107+
return true;
108+
});
109+
110+
// Delete indexes that do not exist in class metadata
111+
foreach ($mongoIndexes as $mongoIndex) {
112+
if (isset($mongoIndex['name'])) {
113+
/* Note: MongoCollection::deleteIndex() cannot delete
114+
* custom-named indexes, so use the deleteIndexes command.
115+
*/
116+
$collection->getDatabase()->command(array(
117+
'deleteIndexes' => $collection->getName(),
118+
'index' => $mongoIndex['name'],
119+
));
144120
}
145121
}
122+
123+
$this->ensureDocumentIndexes($documentName);
146124
}
147125
}
148126

@@ -179,37 +157,6 @@ public function getAllIndexes($raw = true)
179157
return $all;
180158
}
181159

182-
/**
183-
* Returns all document indexes - indexed by documentName
184-
*
185-
* @return array
186-
*/
187-
public function getAllDocumentIndexes()
188-
{
189-
$return = array();
190-
$defaults = array(
191-
'safe' => true,
192-
'dropDups' => false,
193-
'background' => false,
194-
'unique' => false,
195-
'sparse' => false,
196-
);
197-
198-
foreach ($this->metadataFactory->getAllMetadata() as $class) {
199-
if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
200-
continue;
201-
}
202-
if ($indexes = $this->getDocumentIndexes($class->name)) {
203-
foreach ($indexes as &$index) {
204-
$index['options'] = array_merge($defaults, $index['options']);
205-
}
206-
$return[$class->name] = $indexes;
207-
}
208-
}
209-
210-
return $return;
211-
}
212-
213160
public function getDocumentIndexes($documentName)
214161
{
215162
$visited = array();
@@ -451,23 +398,56 @@ public function createDocumentDatabase($documentName)
451398
}
452399

453400
/**
454-
* Convert an array from a raw MongoDB index to ODM style.
401+
* Determine if an index returned by MongoCollection::getIndexInfo() can be
402+
* considered equivalent to an index in class metadata.
455403
*
456-
* @param array $rawIndex
457-
* @return array
404+
* Indexes are considered different if:
405+
*
406+
* (a) Key/direction pairs differ or are not in the same order
407+
* (b) Sparse or unique options differ
408+
* (c) Mongo index is unique without dropDups and mapped index is unique
409+
* with dropDups
410+
* (d) Geospatial options differ (bits, max, min)
411+
*
412+
* Regarding (c), the inverse case is not a reason to delete and
413+
* recreate the index, since dropDups only affects creation of
414+
* the unique index. Additionally, the background option is only
415+
* relevant to index creation and is not considered.
458416
*/
459-
private function rawIndexToDocumentIndex($rawIndex)
417+
private function isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)
460418
{
461-
return array(
462-
'keys' => $rawIndex['key'],
463-
'options' => array(
464-
'name' => $rawIndex['name'],
465-
'safe' => isset($rawIndex['safe']) ? $rawIndex['safe'] : true,
466-
'dropDups' => isset($rawIndex['dropDups']) ? $rawIndex['dropDups'] : false,
467-
'background' => isset($rawIndex['background']) ? $rawIndex['background'] : false,
468-
'unique' => isset($rawIndex['unique']) ? $rawIndex['unique'] : false,
469-
'sparse' => isset($rawIndex['sparse']) ? $rawIndex['sparse'] : false,
470-
)
471-
);
419+
$documentIndexOptions = $documentIndex['options'];
420+
421+
if ($mongoIndex['key'] !== $documentIndex['keys']) {
422+
return false;
423+
}
424+
425+
if (empty($mongoIndex['sparse']) xor empty($documentIndexOptions['sparse'])) {
426+
return false;
427+
}
428+
429+
if (empty($mongoIndex['unique']) xor empty($documentIndexOptions['unique'])) {
430+
return false;
431+
}
432+
433+
if (!empty($mongoIndex['unique']) && empty($mongoIndex['dropDups']) &&
434+
!empty($documentIndexOptions['unique']) && !empty($documentIndexOptions)) {
435+
436+
return false;
437+
}
438+
439+
foreach (array('bits', 'max', 'min') as $option) {
440+
if (isset($mongoIndex[$option]) xor isset($documentIndexOptions[$option])) {
441+
return false;
442+
}
443+
444+
if (isset($mongoIndex[$option]) && isset($documentIndexOptions[$option]) &&
445+
$mongoIndex[$option] !== $documentIndexOptions[$option]) {
446+
447+
return false;
448+
}
449+
}
450+
451+
return true;
472452
}
473453
}

0 commit comments

Comments
 (0)