Skip to content

Commit

Permalink
Merge pull request #1966 from alcaeus/fix-duplicate-embedded-index-name
Browse files Browse the repository at this point in the history
Fix embedding documents containing named indexes
  • Loading branch information
alcaeus authored Mar 6, 2019
2 parents c7868c5 + 52d47d3 commit 0cd3c37
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 1 deletion.
12 changes: 11 additions & 1 deletion docs/en/reference/annotations-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,8 @@ Optional attributes:
respectively. Special index types (e.g. "2dsphere") should be specified as
strings. This is required when `@Index`_ is used at the class level.
-
options - Options for creating the index
options - Options for creating the index. Options are documented in the
:ref:`indexes chapter <indees>`.

The ``keys`` and ``options`` attributes correspond to the arguments for
`MongoCollection::createIndex() <http://php.net/manual/en/mongocollection.createindex.php>`_.
Expand Down Expand Up @@ -827,6 +828,15 @@ If you are creating a single-field index, you can simply specify an `@Index`_ or
/** @Field(type="string") @UniqueIndex */
private $username;
.. note::

If the ``name`` option is specified on an index in an embedded document, it
will be prefixed with the embedded field path before creating the index.
This is necessary to avoid index name conflict when the same document is
embedded multiple times in a single collection. Prefixing of the index name
can cause errors due to excessive index name length. In this case, try
shortening the index name or embedded field path.

@Indexes
--------

Expand Down
9 changes: 9 additions & 0 deletions docs/en/reference/indexes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,15 @@ Also, for your convenience you can create the indexes for your mapped documents
unless you specify a discriminator map for the :ref:`embed-one <embed_one>`
or :ref:`embed-many <embed_many>` relationship.

.. note::

If the ``name`` option is specified on an index in an embedded document, it
will be prefixed with the embedded field path before creating the index.
This is necessary to avoid index name conflict when the same document is
embedded multiple times in a single collection. Prefixing of the index name
can cause errors due to excessive index name length. In this case, try
shortening the index name or embedded field path.

Geospatial Indexing
-------------------

Expand Down
11 changes: 11 additions & 0 deletions lib/Doctrine/ODM/MongoDB/SchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
use function sprintf;

class SchemaManager
{
Expand Down Expand Up @@ -173,18 +174,25 @@ private function doGetDocumentIndexes($documentName, array &$visited)
} else {
continue;
}

foreach ($possibleEmbeds as $embed) {
if (isset($embeddedDocumentIndexes[$embed])) {
$embeddedIndexes = $embeddedDocumentIndexes[$embed];
} else {
$embeddedIndexes = $this->doGetDocumentIndexes($embed, $visited);
$embeddedDocumentIndexes[$embed] = $embeddedIndexes;
}

foreach ($embeddedIndexes as $embeddedIndex) {
foreach ($embeddedIndex['keys'] as $key => $value) {
$embeddedIndex['keys'][$fieldMapping['name'] . '.' . $key] = $value;
unset($embeddedIndex['keys'][$key]);
}

if (isset($embeddedIndex['options']['name'])) {
$embeddedIndex['options']['name'] = sprintf('%s_%s', $fieldMapping['name'], $embeddedIndex['options']['name']);
}

$indexes[] = $embeddedIndex;
}
}
Expand All @@ -195,12 +203,15 @@ private function doGetDocumentIndexes($documentName, array &$visited)
if ($key == $fieldMapping['name']) {
$key = ClassMetadataInfo::getReferenceFieldName($fieldMapping['storeAs'], $key);
}

$newKeys[$key] = $v;
}

$indexes[$idx]['keys'] = $newKeys;
}
}
}

return $indexes;
}

Expand Down
88 changes: 88 additions & 0 deletions tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH1344Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

namespace Doctrine\ODM\MongoDB\Tests\Functional\Ticket;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\ODM\MongoDB\Tests\BaseTest;
use MongoResultException;

class GH1344Test extends BaseTest
{
public function testGeneratingIndexesDoesNotThrowException()
{
$indexes = $this->dm->getSchemaManager()->getDocumentIndexes(GH1344Main::class);
self::assertCount(4, $indexes);
self::assertSame('embedded1_embedded', $indexes[0]['options']['name']);
self::assertSame('embedded1_embedded_nested', $indexes[1]['options']['name']);
self::assertSame('embedded2_embedded', $indexes[2]['options']['name']);
self::assertSame('embedded2_embedded_nested', $indexes[3]['options']['name']);

$this->dm->getSchemaManager()->ensureDocumentIndexes(GH1344Main::class);
}

public function testGeneratingIndexesWithTooLongIndexNameThrowsException()
{
// Ensure that at least the beginning of the index name is contained in
// the exception message. This can vary between driver/server versions.
$this->expectException(MongoResultException::class);
$this->expectExceptionMessageRegExp('#GH1344TooLongIndexName.\$embedded1_this_is_a_really_long_name_that#');

$this->dm->getSchemaManager()->ensureDocumentIndexes(GH1344TooLongIndexName::class);
}
}

/** @ODM\Document */
class GH1344Main
{
/** @ODM\Id */
public $id;

/** @ODM\EmbedOne(targetDocument=GH1344Embedded::class) */
public $embedded1;

/** @ODM\EmbedOne(targetDocument=GH1344Embedded::class) */
public $embedded2;
}

/**
* @ODM\EmbeddedDocument
* @ODM\Index(keys={"property"="asc"}, name="embedded")
*/
class GH1344Embedded
{
/** @ODM\Field */
public $property;

/** @ODM\EmbedOne(targetDocument=GH1344EmbeddedNested::class) */
public $embedded;
}

/**
* @ODM\EmbeddedDocument
* @ODM\Index(keys={"property"="asc"}, name="nested")
*/
class GH1344EmbeddedNested
{
/** @ODM\Field */
public $property;
}

/** @ODM\Document */
class GH1344TooLongIndexName
{
/** @ODM\Id */
public $id;

/** @ODM\EmbedOne(targetDocument=GH1344TooLongIndexNameEmbedded::class) */
public $embedded1;
}

/**
* @ODM\EmbeddedDocument
* @ODM\Index(keys={"property"="asc"}, name="this_is_a_really_long_name_that_will_cause_problems_for_whoever_tries_to_use_it_whether_in_an_embedded_field_or_not")
*/
class GH1344TooLongIndexNameEmbedded
{
/** @ODM\Field */
public $property;
}

0 comments on commit 0cd3c37

Please sign in to comment.