1
0
mirror of synced 2024-12-05 03:06:05 +03:00

Support for Partial Indexes for PostgreSql and Sqlite

Support for Partial Indexes was available in Doctrine 1 following
http://www.doctrine-project.org/jira/browse/DC-82. This commit
reintroduce support for Doctrine 2. We use the same syntax with an
optionnal "where" attribute for Index and UniqueConstraint.
This commit is contained in:
Adrien Crivelli 2014-05-07 23:36:40 +09:00
parent 499f09fc99
commit eeb7ff4a6d
15 changed files with 127 additions and 83 deletions

View File

@ -418,6 +418,12 @@ Required attributes:
- **name**: Name of the Index
- **columns**: Array of columns.
Optional attributes:
- **options**: Array of platform specific options
- **where**: SQL WHERE condition to be used for partial indexes. It will only have effect on supported platforms.
Example:
.. code-block:: php
@ -1151,6 +1157,12 @@ Required attributes:
- **name**: Name of the Index
- **columns**: Array of columns.
Optional attributes:
- **options**: Array of platform specific options
- **where**: SQL WHERE condition to be used for partial indexes. It will only have effect on supported platforms.
Example:
.. code-block:: php

View File

@ -5,9 +5,9 @@
xmlns:orm="http://doctrine-project.org/schemas/orm/doctrine-mapping"
elementFormDefault="qualified">
<xs:annotation>
<xs:documentation><![CDATA[
This is the XML Schema for the object/relational
<xs:annotation>
<xs:documentation><![CDATA[
This is the XML Schema for the object/relational
mapping file used by the Doctrine ORM.
]]></xs:documentation>
</xs:annotation>
@ -23,27 +23,27 @@
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
</xs:element>
<xs:complexType name="emptyType">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="cascade-type">
<xs:sequence>
<xs:element name="cascade-all" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-persist" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-merge" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-remove" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-refresh" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-all" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-persist" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-merge" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-remove" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-refresh" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-detach" type="orm:emptyType" minOccurs="0"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:simpleType name="lifecycle-callback-type">
<xs:restriction base="xs:token">
<xs:enumeration value="prePersist"/>
@ -64,7 +64,7 @@
<xs:enumeration value="NONSTRICT_READ_WRITE"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="lifecycle-callback">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
@ -73,7 +73,7 @@
<xs:attribute name="method" type="xs:NMTOKEN" use="required" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="lifecycle-callbacks">
<xs:sequence>
<xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="1" maxOccurs="unbounded" />
@ -199,7 +199,7 @@
<xs:attribute name="read-only" type="xs:boolean" default="false" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="option" mixed="true">
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="option" type="orm:option"/>
@ -245,7 +245,7 @@
<xs:enumeration value="NOTIFY"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="inheritance-type">
<xs:restriction base="xs:token">
<xs:enumeration value="SINGLE_TABLE"/>
@ -253,33 +253,33 @@
<xs:enumeration value="TABLE_PER_CLASS"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="generator-strategy">
<xs:restriction base="xs:token">
<xs:simpleType name="generator-strategy">
<xs:restriction base="xs:token">
<xs:enumeration value="NONE"/>
<xs:enumeration value="TABLE"/>
<xs:enumeration value="SEQUENCE"/>
<xs:enumeration value="IDENTITY"/>
<xs:enumeration value="AUTO"/>
<xs:enumeration value="UUID"/>
<xs:enumeration value="TABLE"/>
<xs:enumeration value="SEQUENCE"/>
<xs:enumeration value="IDENTITY"/>
<xs:enumeration value="AUTO"/>
<xs:enumeration value="UUID"/>
<xs:enumeration value="CUSTOM" />
</xs:restriction>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="fk-action">
<xs:restriction base="xs:token">
<xs:enumeration value="CASCADE"/>
<xs:enumeration value="RESTRICT"/>
<xs:simpleType name="fk-action">
<xs:restriction base="xs:token">
<xs:enumeration value="CASCADE"/>
<xs:enumeration value="RESTRICT"/>
<xs:enumeration value="SET NULL"/>
</xs:restriction>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="fetch-type">
<xs:restriction base="xs:token">
<xs:enumeration value="EAGER"/>
<xs:simpleType name="fetch-type">
<xs:restriction base="xs:token">
<xs:enumeration value="EAGER"/>
<xs:enumeration value="LAZY"/>
<xs:enumeration value="EXTRA_LAZY"/>
</xs:restriction>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="field">
@ -299,13 +299,13 @@
<xs:attribute name="scale" type="xs:integer" use="optional" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="embedded">
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="class" type="xs:string" use="required" />
<xs:attribute name="column-prefix" type="xs:string" use="optional" />
</xs:complexType>
<xs:complexType name="discriminator-column">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
@ -317,16 +317,17 @@
<xs:attribute name="column-definition" type="xs:string" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="unique-constraint">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
<xs:attribute name="columns" type="xs:string" use="required"/>
<xs:attribute name="where" type="xs:string" use="optional"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="unique-constraints">
<xs:sequence>
<xs:element name="unique-constraint" type="orm:unique-constraint" minOccurs="1" maxOccurs="unbounded"/>
@ -334,7 +335,7 @@
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="index">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
@ -342,9 +343,10 @@
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
<xs:attribute name="columns" type="xs:string" use="required"/>
<xs:attribute name="flags" type="xs:string" use="optional"/>
<xs:attribute name="where" type="xs:string" use="optional"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="indexes">
<xs:sequence>
<xs:element name="index" type="orm:index" minOccurs="1" maxOccurs="unbounded"/>
@ -352,7 +354,7 @@
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="discriminator-mapping">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
@ -361,7 +363,7 @@
<xs:attribute name="class" type="xs:string" use="required"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="discriminator-map">
<xs:sequence>
<xs:element name="discriminator-mapping" type="orm:discriminator-mapping" minOccurs="1" maxOccurs="unbounded"/>
@ -509,7 +511,7 @@
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="many-to-one">
<xs:sequence>
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
@ -528,7 +530,7 @@
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="one-to-one">
<xs:sequence>
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>

View File

@ -102,11 +102,15 @@ class AnnotationDriver extends AbstractAnnotationDriver
if ($tableAnnot->indexes !== null) {
foreach ($tableAnnot->indexes as $indexAnnot) {
$index = array('columns' => $indexAnnot->columns);
if ( ! empty($indexAnnot->flags)) {
$index['flags'] = $indexAnnot->flags;
}
if (! empty($indexAnnot->where)) {
$index['where'] = $indexAnnot->where;
}
if ( ! empty($indexAnnot->name)) {
$primaryTable['indexes'][$indexAnnot->name] = $index;
} else {
@ -118,6 +122,10 @@ class AnnotationDriver extends AbstractAnnotationDriver
if ($tableAnnot->uniqueConstraints !== null) {
foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
$uniqueConstraint = array('columns' => $uniqueConstraintAnnot->columns);
if ( ! empty($uniqueConstraintAnnot->where)) {
$uniqueConstraint['where'] = $uniqueConstraintAnnot->where;
}
if ( ! empty($uniqueConstraintAnnot->name)) {
$primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint;

View File

@ -196,11 +196,15 @@ class XmlDriver extends FileDriver
$metadata->table['indexes'] = array();
foreach ($xmlRoot->indexes->index as $indexXml) {
$index = array('columns' => explode(',', (string) $indexXml['columns']));
if (isset($indexXml['flags'])) {
$index['flags'] = explode(',', (string) $indexXml['flags']);
}
if (isset($indexXml['where'])) {
$index['where'] = $indexXml['where'];
}
if (isset($indexXml['name'])) {
$metadata->table['indexes'][(string) $indexXml['name']] = $index;
} else {
@ -212,17 +216,17 @@ class XmlDriver extends FileDriver
// Evaluate <unique-constraints..>
if (isset($xmlRoot->{'unique-constraints'})) {
$metadata->table['uniqueConstraints'] = array();
foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $unique) {
$columns = explode(',', (string)$unique['columns']);
foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $uniqueXml) {
$unique = array('columns' => explode(',', (string) $uniqueXml['columns']));
if (isset($unique['name'])) {
$metadata->table['uniqueConstraints'][(string)$unique['name']] = array(
'columns' => $columns
);
if (isset($uniqueXml['where'])) {
$unique['where'] = $uniqueXml['where'];
}
if (isset($uniqueXml['name'])) {
$metadata->table['uniqueConstraints'][(string)$uniqueXml['name']] = $unique;
} else {
$metadata->table['uniqueConstraints'][] = array(
'columns' => $columns
);
$metadata->table['uniqueConstraints'][] = $unique;
}
}
}

View File

@ -220,27 +220,32 @@ class YamlDriver extends FileDriver
}
}
if (isset($indexYml['where'])) {
$index['where'] = $indexYml['where'];
}
$metadata->table['indexes'][$indexYml['name']] = $index;
}
}
// Evaluate uniqueConstraints
if (isset($element['uniqueConstraints'])) {
foreach ($element['uniqueConstraints'] as $name => $unique) {
if ( ! isset($unique['name'])) {
$unique['name'] = $name;
foreach ($element['uniqueConstraints'] as $name => $uniqueYml) {
if ( ! isset($uniqueYml['name'])) {
$uniqueYml['name'] = $name;
}
if (is_string($unique['columns'])) {
$columns = explode(',', $unique['columns']);
$columns = array_map('trim', $columns);
if (is_string($uniqueYml['columns'])) {
$unique = array('columns' => array_map('trim', explode(',', $uniqueYml['columns'])));
} else {
$columns = $unique['columns'];
$unique = array('columns' => $uniqueYml['columns']);
}
$metadata->table['uniqueConstraints'][$unique['name']] = array(
'columns' => $columns
);
if (isset($uniqueYml['where'])) {
$unique['where'] = $uniqueYml['where'];
}
$metadata->table['uniqueConstraints'][$uniqueYml['name']] = $unique;
}
}

View File

@ -39,4 +39,9 @@ final class Index implements Annotation
* @var array<string>
*/
public $flags;
/**
* @var string
*/
public $where;
}

View File

@ -34,4 +34,9 @@ final class UniqueConstraint implements Annotation
* @var array<string>
*/
public $columns;
/**
* @var string
*/
public $where;
}

View File

@ -267,14 +267,14 @@ class SchemaTool
if( ! isset($indexData['flags'])) {
$indexData['flags'] = array();
}
$table->addIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName, (array)$indexData['flags']);
$table->addIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName, (array)$indexData['flags'], isset($indexData['where']) ? $indexData['where'] : null);
}
}
if (isset($class->table['uniqueConstraints'])) {
foreach ($class->table['uniqueConstraints'] as $indexName => $indexData) {
$table->addUniqueIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName);
$table->addUniqueIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName, isset($indexData['where']) ? $indexData['where'] : null);
}
}

View File

@ -73,14 +73,15 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase
return $class;
}
public function testEntityIndexFlags()
public function testEntityIndexFlagsAndPartialIndexes()
{
$class = $this->createClassMetadata('Doctrine\Tests\ORM\Mapping\Comment');
$this->assertEquals(array(
0 => array(
'columns' => array('content'),
'flags' => array('fulltext')
'flags' => array('fulltext'),
'where' => 'content IS NOT NULL',
)
), $class->table['indexes']);
}
@ -95,7 +96,7 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase
'ClassMetadata should have uniqueConstraints key in table property when Unique Constraints are set.');
$this->assertEquals(array(
"search_idx" => array("columns" => array("name", "user_email"))
"search_idx" => array("columns" => array("name", "user_email"), 'where' => 'name IS NOT NULL')
), $class->table['uniqueConstraints']);
return $class;
@ -938,7 +939,7 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase
* @HasLifecycleCallbacks
* @Table(
* name="cms_users",
* uniqueConstraints={@UniqueConstraint(name="search_idx", columns={"name", "user_email"})},
* uniqueConstraints={@UniqueConstraint(name="search_idx", columns={"name", "user_email"}, where="name IS NOT NULL")},
* indexes={@Index(name="name_idx", columns={"name"}), @Index(name="0", columns={"user_email"})},
* options={"foo": "bar", "baz": {"key": "val"}}
* )
@ -1122,7 +1123,7 @@ class User
'orderBy' => NULL,
));
$metadata->table['uniqueConstraints'] = array(
'search_idx' => array('columns' => array('name', 'user_email')),
'search_idx' => array('columns' => array('name', 'user_email'), 'where' => 'name IS NOT NULL'),
);
$metadata->table['indexes'] = array(
'name_idx' => array('columns' => array('name')), 0 => array('columns' => array('user_email'))
@ -1281,7 +1282,7 @@ class Group {}
/**
* @Entity
* @Table(indexes={@Index(columns={"content"}, flags={"fulltext"})})
* @Table(indexes={@Index(columns={"content"}, flags={"fulltext"}, where="content IS NOT NULL")})
*/
class Comment
{
@ -1295,7 +1296,7 @@ class Comment
$metadata->setInheritanceType(ClassMetadataInfo::INHERITANCE_TYPE_NONE);
$metadata->setPrimaryTable(array(
'indexes' => array(
array('columns' => array('content'), 'flags' => array('fulltext'))
array('columns' => array('content'), 'flags' => array('fulltext'), 'where' => 'content IS NOT NULL')
)
));

View File

@ -5,7 +5,7 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo;
$metadata->setInheritanceType(ClassMetadataInfo::INHERITANCE_TYPE_NONE);
$metadata->setPrimaryTable(array(
'indexes' => array(
array('columns' => array('content'), 'flags' => array('fulltext'))
array('columns' => array('content'), 'flags' => array('fulltext'), 'where' => 'content IS NOT NULL')
)
));

View File

@ -112,11 +112,11 @@ $metadata->mapManyToMany(array(
'orderBy' => NULL,
));
$metadata->table['options'] = array(
'foo' => 'bar',
'foo' => 'bar',
'baz' => array('key' => 'val')
);
$metadata->table['uniqueConstraints'] = array(
'search_idx' => array('columns' => array('name', 'user_email')),
'search_idx' => array('columns' => array('name', 'user_email'), 'where' => 'name IS NOT NULL'),
);
$metadata->table['indexes'] = array(
'name_idx' => array('columns' => array('name')), 0 => array('columns' => array('user_email'))

View File

@ -8,7 +8,7 @@
<entity name="Doctrine\Tests\ORM\Mapping\Comment">
<indexes>
<index columns="content" flags="fulltext"/>
<index columns="content" flags="fulltext" where="content IS NOT NULL"/>
</indexes>
<field name="content" type="text"/>

View File

@ -19,7 +19,7 @@
</indexes>
<unique-constraints>
<unique-constraint columns="name,user_email" name="search_idx" />
<unique-constraint columns="name,user_email" name="search_idx" where="name IS NOT NULL" />
</unique-constraints>
<lifecycle-callbacks>

View File

@ -7,3 +7,4 @@ Doctrine\Tests\ORM\Mapping\Comment:
0:
columns: content
flags: fulltext
where: "content IS NOT NULL"

View File

@ -69,11 +69,12 @@ Doctrine\Tests\ORM\Mapping\User:
cascade:
- all
lifecycleCallbacks:
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ]
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ]
postPersist: [ doStuffOnPostPersist ]
uniqueConstraints:
search_idx:
columns: name,user_email
where: name IS NOT NULL
indexes:
name_idx:
columns: name