1
0
mirror of synced 2025-01-18 22:41:43 +03:00

Merge pull request #1027 from PowerKiKi/feature-partial-indexes

Support for Partial Indexes for PostgreSql and Sqlite
This commit is contained in:
Marco Pivetta 2014-11-05 13:19:29 +01:00
commit 76e1a469ef
15 changed files with 140 additions and 83 deletions

View File

@ -421,6 +421,13 @@ 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
@ -1154,6 +1161,13 @@ 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:element name="options" type="orm:options" minOccurs="0" />
<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: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,9 +335,10 @@
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="index">
<xs:sequence>
<xs:element name="options" type="orm:options" minOccurs="0" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
@ -344,7 +346,7 @@
<xs:attribute name="flags" 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->options)) {
$index['options'] = $indexAnnot->options;
}
if ( ! empty($indexAnnot->name)) {
$primaryTable['indexes'][$indexAnnot->name] = $index;
} else {
@ -119,6 +123,10 @@ class AnnotationDriver extends AbstractAnnotationDriver
foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
$uniqueConstraint = array('columns' => $uniqueConstraintAnnot->columns);
if ( ! empty($uniqueConstraintAnnot->options)) {
$uniqueConstraint['options'] = $uniqueConstraintAnnot->options;
}
if ( ! empty($uniqueConstraintAnnot->name)) {
$primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint;
} else {

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->options)) {
$index['options'] = $this->_parseOptions($indexXml->options->children());
}
if (isset($indexXml['name'])) {
$metadata->table['indexes'][(string) $indexXml['name']] = $index;
} else {
@ -212,17 +216,18 @@ 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->options)) {
$unique['options'] = $this->_parseOptions($uniqueXml->options->children());
}
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['options'])) {
$index['options'] = $indexYml['options'];
}
$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['options'])) {
$unique['options'] = $uniqueYml['options'];
}
$metadata->table['uniqueConstraints'][$uniqueYml['name']] = $unique;
}
}

View File

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

View File

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

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['options']) ? $indexData['options'] : array());
}
}
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['options']) ? $indexData['options'] : array());
}
}

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'),
'options' => array('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"), 'options' => array('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"}, options={"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'), 'options'=> array('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"}, options={"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'), 'options' => array('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'), 'options'=> array('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'), 'options' => array('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,11 @@
<entity name="Doctrine\Tests\ORM\Mapping\Comment">
<indexes>
<index columns="content" flags="fulltext"/>
<index columns="content" flags="fulltext">
<options>
<option name="where">content IS NOT NULL</option>
</options>
</index>
</indexes>
<field name="content" type="text"/>

View File

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

View File

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

View File

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