Skip to content

Commit

Permalink
Add possibility to use fields instead of column for unique constraint…
Browse files Browse the repository at this point in the history
… and indexes (doctrine#8345)
  • Loading branch information
Lustmored committed Mar 9, 2021
1 parent 2685b65 commit 27913d3
Show file tree
Hide file tree
Showing 12 changed files with 185 additions and 17 deletions.
2 changes: 2 additions & 0 deletions doctrine-mapping.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
<xs:attribute name="columns" type="xs:string" use="required"/>
<xs:attribute name="fields" type="xs:string" use="optional"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>

Expand All @@ -351,6 +352,7 @@
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
<xs:attribute name="columns" type="xs:string" use="required"/>
<xs:attribute name="fields" type="xs:string" use="optional"/>
<xs:attribute name="flags" type="xs:string" use="optional"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
Expand Down
20 changes: 18 additions & 2 deletions lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,15 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)

if ($tableAnnot->indexes !== null) {
foreach ($tableAnnot->indexes as $indexAnnot) {
$index = ['columns' => $indexAnnot->columns];
$index = [];

if (! empty($indexAnnot->columns)) {
$index['columns'] = $indexAnnot->columns;
}

if (! empty($indexAnnot->fields)) {
$index['fields'] = $indexAnnot->fields;
}

if (! empty($indexAnnot->flags)) {
$index['flags'] = $indexAnnot->flags;
Expand All @@ -132,7 +140,15 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)

if ($tableAnnot->uniqueConstraints !== null) {
foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
$uniqueConstraint = ['columns' => $uniqueConstraintAnnot->columns];
$uniqueConstraint = [];

if (! empty($uniqueConstraintAnnot->columns)) {
$uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns;
}

if (! empty($uniqueConstraintAnnot->fields)) {
$uniqueConstraint['fields'] = $uniqueConstraintAnnot->fields;
}

if (! empty($uniqueConstraintAnnot->options)) {
$uniqueConstraint['options'] = $uniqueConstraintAnnot->options;
Expand Down
20 changes: 18 additions & 2 deletions lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,15 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
if (isset($xmlRoot->indexes)) {
$metadata->table['indexes'] = [];
foreach ($xmlRoot->indexes->index as $indexXml) {
$index = ['columns' => explode(',', (string) $indexXml['columns'])];
$index = [];

if (isset($indexXml['columns']) && ! empty($indexXml['columns'])) {
$index['columns'] = explode(',', (string) $indexXml['columns']);
}

if (isset($indexXml['fields'])) {
$index['fields'] = explode(',', (string) $indexXml['fields']);
}

if (isset($indexXml['flags'])) {
$index['flags'] = explode(',', (string) $indexXml['flags']);
Expand All @@ -236,7 +244,15 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
if (isset($xmlRoot->{'unique-constraints'})) {
$metadata->table['uniqueConstraints'] = [];
foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $uniqueXml) {
$unique = ['columns' => explode(',', (string) $uniqueXml['columns'])];
$unique = [];

if (isset($uniqueXml['columns']) && ! empty($uniqueXml['columns'])) {
$unique['columns'] = explode(',', (string) $uniqueXml['columns']);
}

if (isset($uniqueXml['fields'])) {
$unique['fields'] = explode(',', (string) $uniqueXml['fields']);
}

if (isset($uniqueXml->options)) {
$unique['options'] = $this->_parseOptions($uniqueXml->options->children());
Expand Down
40 changes: 32 additions & 8 deletions lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,22 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
$indexYml['name'] = $name;
}

if (is_string($indexYml['columns'])) {
$index = ['columns' => array_map('trim', explode(',', $indexYml['columns']))];
} else {
$index = ['columns' => $indexYml['columns']];
$index = [];

if (isset($indexYml['columns'])) {
if (is_string($indexYml['columns'])) {
$index['columns'] = array_map('trim', explode(',', $indexYml['columns']));
} else {
$index['columns'] = $indexYml['columns'];
}
}

if (isset($indexYml['fields'])) {
if (is_string($indexYml['fields'])) {
$index['fields'] = array_map('trim', explode(',', $indexYml['fields']));
} else {
$index['fields'] = $indexYml['fields'];
}
}

if (isset($indexYml['flags'])) {
Expand All @@ -259,10 +271,22 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
$uniqueYml['name'] = $name;
}

if (is_string($uniqueYml['columns'])) {
$unique = ['columns' => array_map('trim', explode(',', $uniqueYml['columns']))];
} else {
$unique = ['columns' => $uniqueYml['columns']];
$unique = [];

if (isset($uniqueYml['columns'])) {
if (is_string($uniqueYml['columns'])) {
$unique['columns'] = array_map('trim', explode(',', $uniqueYml['columns']));
} else {
$unique['columns'] = $uniqueYml['columns'];
}
}

if (isset($uniqueYml['fields'])) {
if (is_string($uniqueYml['fields'])) {
$unique['fields'] = array_map('trim', explode(',', $uniqueYml['fields']));
} else {
$unique['fields'] = $uniqueYml['fields'];
}
}

if (isset($uniqueYml['options'])) {
Expand Down
3 changes: 3 additions & 0 deletions lib/Doctrine/ORM/Mapping/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ final class Index implements Annotation
/** @var array<string> */
public $columns;

/** @var array<string> */
public $fields;

/** @var array<string> */
public $flags;

Expand Down
3 changes: 3 additions & 0 deletions lib/Doctrine/ORM/Mapping/UniqueConstraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ final class UniqueConstraint implements Annotation
/** @var array<string> */
public $columns;

/** @var array<string> */
public $fields;

/** @var array */
public $options;
}
37 changes: 34 additions & 3 deletions lib/Doctrine/ORM/Tools/SchemaTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,37 @@ private function processingNotRequired($class, array $processedClasses)
($class->isInheritanceTypeSingleTable() && $class->name !== $class->rootEntityName);
}

/**
* Resolves fields in index mapping to column names
*
* @param ClassMetadata $class
* @param array $indexData index or unique constraint data
*
* @return string[] Column names from combined fields and columns mappings
*/
private function getIndexColumns($class, array $indexData)
{
$columns = [];

if (isset($indexData['columns'])) {
$columns = $indexData['columns'];
}

if (isset($indexData['fields'])) {
foreach ($indexData['fields'] as $fieldName) {
if ($class->hasField($fieldName)) {
$columns[] = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
} elseif ($class->hasAssociation($fieldName)) {
foreach ($class->getAssociationMapping($fieldName)['joinColumns'] as $joinColumn) {
$columns[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
}
}
}
}

return $columns;
}

/**
* Creates a Schema instance from a given set of metadata classes.
*
Expand Down Expand Up @@ -311,13 +342,13 @@ static function (ClassMetadata $class) use ($idMapping): bool {
$indexData['flags'] = [];
}

$table->addIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName, (array) $indexData['flags'], $indexData['options'] ?? []);
$table->addIndex($this->getIndexColumns($class, $indexData), is_numeric($indexName) ? null : $indexName, (array) $indexData['flags'], $indexData['options'] ?? []);
}
}

if (isset($class->table['uniqueConstraints'])) {
foreach ($class->table['uniqueConstraints'] as $indexName => $indexData) {
$uniqIndex = new Index($indexName, $indexData['columns'], true, false, [], $indexData['options'] ?? []);
$uniqIndex = new Index($indexName, $this->getIndexColumns($class, $indexData), true, false, [], $indexData['options'] ?? []);

foreach ($table->getIndexes() as $tableIndexName => $tableIndex) {
if ($tableIndex->isFullfilledBy($uniqIndex)) {
Expand All @@ -326,7 +357,7 @@ static function (ClassMetadata $class) use ($idMapping): bool {
}
}

$table->addUniqueIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName, $indexData['options'] ?? []);
$table->addUniqueIndex($uniqIndex->getColumns(), is_numeric($indexName) ? null : $indexName, $indexData['options'] ?? []);
}
}

Expand Down
10 changes: 8 additions & 2 deletions tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ public function testEntityIndexes(ClassMetadata $class): ClassMetadata
[
'name_idx' => ['columns' => ['name']],
0 => ['columns' => ['user_email']],
'fields' => ['fields' => ['name', 'email']],
'column_fields' => ['columns' => ['name'], 'fields' => ['address']],
],
$class->table['indexes']
);
Expand Down Expand Up @@ -139,6 +141,7 @@ public function testEntityUniqueConstraints(ClassMetadata $class): ClassMetadata
$this->assertEquals(
[
'search_idx' => ['columns' => ['name', 'user_email'], 'options' => ['where' => 'name IS NOT NULL']],
'phone_idx' => ['fields' => ['name', 'phone']],
],
$class->table['uniqueConstraints']
);
Expand Down Expand Up @@ -1065,8 +1068,8 @@ public function testDiscriminatorColumnDefaultName(): void
* @HasLifecycleCallbacks
* @Table(
* name="cms_users",
* uniqueConstraints={@UniqueConstraint(name="search_idx", columns={"name", "user_email"}, options={"where": "name IS NOT NULL"})},
* indexes={@Index(name="name_idx", columns={"name"}), @Index(name="0", columns={"user_email"})},
* uniqueConstraints={@UniqueConstraint(name="search_idx", columns={"name", "user_email"}, options={"where": "name IS NOT NULL"}), @UniqueConstraint(name="phone_idx", fields={"name", "phone"})},
* indexes={@Index(name="name_idx", columns={"name"}), @Index(name="0", columns={"user_email"}), @index(name="fields", fields={"name", "email"}), @Index(name="column_fields", columns={"name"}, fields={"address"})},
* options={"foo": "bar", "baz": {"key": "val"}}
* )
* @NamedQueries({@NamedQuery(name="all", query="SELECT u FROM __CLASS__ u")})
Expand Down Expand Up @@ -1264,10 +1267,13 @@ public static function loadMetadata(ClassMetadataInfo $metadata): void
);
$metadata->table['uniqueConstraints'] = [
'search_idx' => ['columns' => ['name', 'user_email'], 'options' => ['where' => 'name IS NOT NULL']],
'phone_idx' => ['fields' => ['name', 'phone']],
];
$metadata->table['indexes'] = [
'name_idx' => ['columns' => ['name']],
0 => ['columns' => ['user_email']],
'fields' => ['fields' => ['name', 'email']],
'column_fields' => ['columns' => ['name'], 'fields' => ['address']],
];
$metadata->setSequenceGeneratorDefinition(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,13 @@
];
$metadata->table['uniqueConstraints'] = [
'search_idx' => ['columns' => ['name', 'user_email'], 'options' => ['where' => 'name IS NOT NULL']],
'phone_idx' => ['fields' => ['name', 'phone']],
];
$metadata->table['indexes'] = [
'name_idx' => ['columns' => ['name']],
0 => ['columns' => ['user_email']],
'fields' => ['fields' => ['name', 'email']],
'column_fields' => ['columns' => ['name'], 'fields' => ['address']],
];
$metadata->setSequenceGeneratorDefinition(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<indexes>
<index name="name_idx" columns="name"/>
<index columns="user_email"/>
<index name="fields" columns="" fields="name,email"/>
<index name="column_fields" columns="name" fields="address"/>
</indexes>

<unique-constraints>
Expand All @@ -24,6 +26,7 @@
<option name="where">name IS NOT NULL</option>
</options>
</unique-constraint>
<unique-constraint columns="" fields="name,phone" name="phone_idx"/>
</unique-constraints>

<lifecycle-callbacks>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,15 @@ Doctrine\Tests\ORM\Mapping\User:
columns: name,user_email
options:
where: name IS NOT NULL
phone_idx:
fields: name,phone
indexes:
name_idx:
columns: name
0:
columns: user_email
fields:
fields: name,email
column_fields:
columns: name
fields: address
54 changes: 54 additions & 0 deletions tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\UnderscoreNamingStrategy;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs;
use Doctrine\ORM\Tools\SchemaTool;
Expand Down Expand Up @@ -277,6 +278,21 @@ public function testDerivedCompositeKey(): void
self::assertSame($foreignColumns, $foreignKey->getForeignColumns());
}
}

public function testIndexesBasedOnFields(): void
{
$em = $this->getTestEntityManager();
$em->getConfiguration()->setNamingStrategy(new UnderscoreNamingStrategy());
$schemaTool = new SchemaTool($em);
$metadata = $em->getClassMetadata(IndexByFieldEntity::class);
$schema = $schemaTool->getSchemaFromMetadata([$metadata]);

$table = $schema->getTable('field_index');

$this->assertEquals(['index'], $table->getIndex('index')->getColumns());
$this->assertEquals(['index', 'field_name'], $table->getIndex('table')->getColumns());
$this->assertEquals(['table', 'index'], $table->getIndex('uniq')->getColumns());
}
}

/**
Expand Down Expand Up @@ -425,3 +441,41 @@ class GH6830Category
*/
public $boards;
}

/**
* @Entity
* @Table(
* name="field_index",
* indexes={
* @Index(name="index", fields={"index"}),
* @Index(name="table", columns={"index"}, fields={"fieldName"})
* },
* uniqueConstraints={
* @UniqueConstraint(name="uniq", columns={"table"}, fields={"index"})
* }
* )
*/
class IndexByFieldEntity
{
/**
* @var int
* @Id
* @Column(type="integer")
*/
public $id;

/**
* @Column
*/
public $index;

/**
* @Column
*/
public $table;

/**
* @Column
*/
public $fieldName;
}

0 comments on commit 27913d3

Please sign in to comment.