Commit e26b0444 authored by Adrien Crivelli's avatar Adrien Crivelli

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.
parent 0b51b818
...@@ -1745,11 +1745,25 @@ abstract class AbstractPlatform ...@@ -1745,11 +1745,25 @@ abstract class AbstractPlatform
} }
$query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table; $query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table;
$query .= ' (' . $this->getIndexFieldDeclarationListSQL($columns) . ')'; $query .= ' (' . $this->getIndexFieldDeclarationListSQL($columns) . ')' . $this->getPartialIndexSQL($index);
return $query; return $query;
} }
/**
* Adds condition for partial index.
*
* @param \Doctrine\DBAL\Schema\Index $index
*
* @return string
*/
protected function getPartialIndexSQL(Index $index)
{
$where = $index->getWhere();
return $this->supportsPartialIndexes() && $where ? ' WHERE ' . $where : '';
}
/** /**
* Adds additional flags for index generation. * Adds additional flags for index generation.
* *
...@@ -2302,7 +2316,7 @@ abstract class AbstractPlatform ...@@ -2302,7 +2316,7 @@ abstract class AbstractPlatform
return 'CONSTRAINT ' . $name . ' UNIQUE (' return 'CONSTRAINT ' . $name . ' UNIQUE ('
. $this->getIndexFieldDeclarationListSQL($columns) . $this->getIndexFieldDeclarationListSQL($columns)
. ')'; . ')' . $this->getPartialIndexSQL($index);
} }
/** /**
...@@ -2326,7 +2340,7 @@ abstract class AbstractPlatform ...@@ -2326,7 +2340,7 @@ abstract class AbstractPlatform
return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' (' return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ('
. $this->getIndexFieldDeclarationListSQL($columns) . $this->getIndexFieldDeclarationListSQL($columns)
. ')'; . ')' . $this->getPartialIndexSQL($index);
} }
/** /**
...@@ -3008,6 +3022,16 @@ abstract class AbstractPlatform ...@@ -3008,6 +3022,16 @@ abstract class AbstractPlatform
return true; return true;
} }
/**
* Whether the platform supports partial indexes.
*
* @return boolean
*/
public function supportsPartialIndexes()
{
return false;
}
/** /**
* Whether the platform supports altering tables. * Whether the platform supports altering tables.
* *
......
...@@ -174,6 +174,16 @@ class PostgreSqlPlatform extends AbstractPlatform ...@@ -174,6 +174,16 @@ class PostgreSqlPlatform extends AbstractPlatform
return true; return true;
} }
/**
* Whether the platform supports partial indexes.
*
* @return boolean
*/
public function supportsPartialIndexes()
{
return true;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
...@@ -314,7 +324,8 @@ class PostgreSqlPlatform extends AbstractPlatform ...@@ -314,7 +324,8 @@ class PostgreSqlPlatform extends AbstractPlatform
public function getListTableIndexesSQL($table, $currentDatabase = null) public function getListTableIndexesSQL($table, $currentDatabase = null)
{ {
return "SELECT quote_ident(relname) as relname, pg_index.indisunique, pg_index.indisprimary, return "SELECT quote_ident(relname) as relname, pg_index.indisunique, pg_index.indisprimary,
pg_index.indkey, pg_index.indrelid pg_index.indkey, pg_index.indrelid,
TRIM(BOTH '()' FROM pg_get_expr(indpred, indrelid)) AS where
FROM pg_class, pg_index FROM pg_class, pg_index
WHERE oid IN ( WHERE oid IN (
SELECT indexrelid SELECT indexrelid
......
...@@ -463,6 +463,16 @@ class SqlitePlatform extends AbstractPlatform ...@@ -463,6 +463,16 @@ class SqlitePlatform extends AbstractPlatform
return true; return true;
} }
/**
* Whether the platform supports partial indexes.
*
* @return boolean
*/
public function supportsPartialIndexes()
{
return true;
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
......
...@@ -819,6 +819,7 @@ abstract class AbstractSchemaManager ...@@ -819,6 +819,7 @@ abstract class AbstractSchemaManager
'unique' => $tableIndex['non_unique'] ? false : true, 'unique' => $tableIndex['non_unique'] ? false : true,
'primary' => $tableIndex['primary'], 'primary' => $tableIndex['primary'],
'flags' => isset($tableIndex['flags']) ? $tableIndex['flags'] : array(), 'flags' => isset($tableIndex['flags']) ? $tableIndex['flags'] : array(),
'where' => isset($tableIndex['where']) ? $tableIndex['where'] : null,
); );
} else { } else {
$result[$keyName]['columns'][] = $tableIndex['column_name']; $result[$keyName]['columns'][] = $tableIndex['column_name'];
...@@ -841,7 +842,7 @@ abstract class AbstractSchemaManager ...@@ -841,7 +842,7 @@ abstract class AbstractSchemaManager
} }
if ( ! $defaultPrevented) { if ( ! $defaultPrevented) {
$index = new Index($data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags']); $index = new Index($data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags'], $data['where']);
} }
if ($index) { if ($index) {
......
...@@ -49,20 +49,29 @@ class Index extends AbstractAsset implements Constraint ...@@ -49,20 +49,29 @@ class Index extends AbstractAsset implements Constraint
*/ */
protected $_flags = array(); protected $_flags = array();
/**
* Platform specific condition for partial indexes
*
* @var string|null
*/
protected $_where = null;
/** /**
* @param string $indexName * @param string $indexName
* @param string[] $columns * @param string[] $columns
* @param boolean $isUnique * @param boolean $isUnique
* @param boolean $isPrimary * @param boolean $isPrimary
* @param string[] $flags * @param string[] $flags
* @param string|null $where
*/ */
public function __construct($indexName, array $columns, $isUnique = false, $isPrimary = false, array $flags = array()) public function __construct($indexName, array $columns, $isUnique = false, $isPrimary = false, array $flags = array(), $where = null)
{ {
$isUnique = $isUnique || $isPrimary; $isUnique = $isUnique || $isPrimary;
$this->_setName($indexName); $this->_setName($indexName);
$this->_isUnique = $isUnique; $this->_isUnique = $isUnique;
$this->_isPrimary = $isPrimary; $this->_isPrimary = $isPrimary;
$this->_where = $where;
foreach ($columns as $column) { foreach ($columns as $column) {
$this->_addColumn($column); $this->_addColumn($column);
...@@ -199,7 +208,9 @@ class Index extends AbstractAsset implements Constraint ...@@ -199,7 +208,9 @@ class Index extends AbstractAsset implements Constraint
$sameColumns = $this->spansColumns($other->getColumns()); $sameColumns = $this->spansColumns($other->getColumns());
if ($sameColumns) { if ($sameColumns) {
if ( ! $this->isUnique() && !$this->isPrimary()) { if ($other->getWhere() != $this->getWhere()) {
return false;
} elseif ( ! $this->isUnique() && !$this->isPrimary()) {
// this is a special case: If the current key is neither primary or unique, any uniqe or // this is a special case: If the current key is neither primary or unique, any uniqe or
// primary key will always have the same effect for the index and there cannot be any constraint // primary key will always have the same effect for the index and there cannot be any constraint
// overlaps. This means a primary or unique index can always fulfill the requirements of just an // overlaps. This means a primary or unique index can always fulfill the requirements of just an
...@@ -232,7 +243,7 @@ class Index extends AbstractAsset implements Constraint ...@@ -232,7 +243,7 @@ class Index extends AbstractAsset implements Constraint
return false; return false;
} }
if ($this->spansColumns($other->getColumns()) && ($this->isPrimary() || $this->isUnique())) { if ($this->spansColumns($other->getColumns()) && ($this->isPrimary() || $this->isUnique()) && $this->getWhere() == $other->getWhere()) {
return true; return true;
} }
...@@ -288,4 +299,13 @@ class Index extends AbstractAsset implements Constraint ...@@ -288,4 +299,13 @@ class Index extends AbstractAsset implements Constraint
{ {
unset($this->_flags[strtolower($flag)]); unset($this->_flags[strtolower($flag)]);
} }
/**
* Returns the where condition for partial indexes, if any
* @return string|null
*/
public function getWhere()
{
return $this->_where;
}
} }
...@@ -239,7 +239,8 @@ class PostgreSqlSchemaManager extends AbstractSchemaManager ...@@ -239,7 +239,8 @@ class PostgreSqlSchemaManager extends AbstractSchemaManager
'key_name' => $row['relname'], 'key_name' => $row['relname'],
'column_name' => trim($colRow['attname']), 'column_name' => trim($colRow['attname']),
'non_unique' => !$row['indisunique'], 'non_unique' => !$row['indisunique'],
'primary' => $row['indisprimary'] 'primary' => $row['indisprimary'],
'where' => $row['where'],
); );
} }
} }
......
...@@ -146,10 +146,11 @@ class Table extends AbstractAsset ...@@ -146,10 +146,11 @@ class Table extends AbstractAsset
* @param array $columnNames * @param array $columnNames
* @param string|null $indexName * @param string|null $indexName
* @param array $flags * @param array $flags
* @param string|null $where
* *
* @return self * @return self
*/ */
public function addIndex(array $columnNames, $indexName = null, array $flags = array()) public function addIndex(array $columnNames, $indexName = null, array $flags = array(), $where = null)
{ {
if ($indexName == null) { if ($indexName == null) {
$indexName = $this->_generateIdentifierName( $indexName = $this->_generateIdentifierName(
...@@ -157,7 +158,7 @@ class Table extends AbstractAsset ...@@ -157,7 +158,7 @@ class Table extends AbstractAsset
); );
} }
return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags)); return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $where));
} }
/** /**
...@@ -192,10 +193,11 @@ class Table extends AbstractAsset ...@@ -192,10 +193,11 @@ class Table extends AbstractAsset
/** /**
* @param array $columnNames * @param array $columnNames
* @param string|null $indexName * @param string|null $indexName
* @param string|null $where
* *
* @return self * @return self
*/ */
public function addUniqueIndex(array $columnNames, $indexName = null) public function addUniqueIndex(array $columnNames, $indexName = null, $where = null)
{ {
if ($indexName === null) { if ($indexName === null) {
$indexName = $this->_generateIdentifierName( $indexName = $this->_generateIdentifierName(
...@@ -203,7 +205,7 @@ class Table extends AbstractAsset ...@@ -203,7 +205,7 @@ class Table extends AbstractAsset
); );
} }
return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false)); return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, array(), $where));
} }
/** /**
...@@ -277,12 +279,13 @@ class Table extends AbstractAsset ...@@ -277,12 +279,13 @@ class Table extends AbstractAsset
* @param boolean $isUnique * @param boolean $isUnique
* @param boolean $isPrimary * @param boolean $isPrimary
* @param array $flags * @param array $flags
* @param string|null $where
* *
* @return Index * @return Index
* *
* @throws SchemaException * @throws SchemaException
*/ */
private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = array()) private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = array(), $where = null)
{ {
if (preg_match('(([^a-zA-Z0-9_]+))', $indexName)) { if (preg_match('(([^a-zA-Z0-9_]+))', $indexName)) {
throw SchemaException::indexNameInvalid($indexName); throw SchemaException::indexNameInvalid($indexName);
...@@ -298,7 +301,7 @@ class Table extends AbstractAsset ...@@ -298,7 +301,7 @@ class Table extends AbstractAsset
} }
} }
return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags); return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $where);
} }
/** /**
......
...@@ -136,6 +136,27 @@ abstract class AbstractPlatformTestCase extends \Doctrine\Tests\DbalTestCase ...@@ -136,6 +136,27 @@ abstract class AbstractPlatformTestCase extends \Doctrine\Tests\DbalTestCase
abstract public function getGenerateUniqueIndexSql(); abstract public function getGenerateUniqueIndexSql();
public function testGeneratesPartialIndexesSqlOnlyWhenSupportingPartialIndexes()
{
$where = 'test IS NULL AND test2 IS NOT NULL';
$indexDef = new \Doctrine\DBAL\Schema\Index('name', array('test', 'test2'), false, false, array(), $where);
$expected = ' WHERE ' . $where;
$actuals = array();
$actuals []= $this->_platform->getIndexDeclarationSQL('name', $indexDef);
$actuals []= $this->_platform->getUniqueConstraintDeclarationSQL('name', $indexDef);
$actuals []= $this->_platform->getCreateIndexSQL($indexDef, 'table');
foreach ($actuals as $actual) {
if ($this->_platform->supportsPartialIndexes()) {
$this->assertStringEndsWith($expected, $actual, 'WHERE clause should be present');
} else {
$this->assertStringEndsNotWith($expected, $actual, 'WHERE clause should NOT be present');
}
}
}
public function testGeneratesForeignKeyCreationSql() public function testGeneratesForeignKeyCreationSql()
{ {
$fk = new \Doctrine\DBAL\Schema\ForeignKeyConstraint(array('fk_name_id'), 'other_table', array('id'), ''); $fk = new \Doctrine\DBAL\Schema\ForeignKeyConstraint(array('fk_name_id'), 'other_table', array('id'), '');
......
...@@ -10,4 +10,9 @@ class PostgreSqlPlatformTest extends AbstractPostgreSqlPlatformTestCase ...@@ -10,4 +10,9 @@ class PostgreSqlPlatformTest extends AbstractPostgreSqlPlatformTestCase
{ {
return new PostgreSqlPlatform; return new PostgreSqlPlatform;
} }
public function testSupportsPartialIndexes()
{
$this->assertTrue($this->_platform->supportsPartialIndexes());
}
} }
...@@ -845,6 +845,10 @@ class SQLAnywherePlatformTest extends AbstractPlatformTestCase ...@@ -845,6 +845,10 @@ class SQLAnywherePlatformTest extends AbstractPlatformTestCase
'ALTER TABLE mytable RENAME quoted3 TO "baz"', 'ALTER TABLE mytable RENAME quoted3 TO "baz"',
); );
} }
public function testGeneratesPartialIndexesSqlOnlyWhenSupportingPartialIndexes()
{
$this->markTestSkipped('Index declaration in statements like CREATE TABLE is not supported.');
}
/** /**
* @group DBAL-807 * @group DBAL-807
......
...@@ -497,6 +497,11 @@ class SqlitePlatformTest extends AbstractPlatformTestCase ...@@ -497,6 +497,11 @@ class SqlitePlatformTest extends AbstractPlatformTestCase
); );
} }
public function testSupportsPartialIndexes()
{
$this->assertTrue($this->_platform->supportsPartialIndexes());
}
/** /**
* @group DBAL-807 * @group DBAL-807
*/ */
......
...@@ -79,6 +79,36 @@ class IndexTest extends \PHPUnit_Framework_TestCase ...@@ -79,6 +79,36 @@ class IndexTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($idx1->isFullfilledBy($uniq)); $this->assertTrue($idx1->isFullfilledBy($uniq));
} }
public function testFullfilledWithPartial()
{
$without = new Index('without', array('col1', 'col2'), true, false, array(), null);
$partial = new Index('partial', array('col1', 'col2'), true, false, array(), 'col1 IS NULL');
$another = new Index('another', array('col1', 'col2'), true, false, array(), 'col1 IS NULL');
$this->assertFalse($partial->isFullfilledBy($without));
$this->assertFalse($without->isFullfilledBy($partial));
$this->assertTrue($partial->isFullfilledBy($partial));
$this->assertTrue($partial->isFullfilledBy($another));
$this->assertTrue($another->isFullfilledBy($partial));
}
public function testOverrulesWithPartial()
{
$without = new Index('without', array('col1', 'col2'), true, false, array(), null);
$partial = new Index('partial', array('col1', 'col2'), true, false, array(), 'col1 IS NULL');
$another = new Index('another', array('col1', 'col2'), true, false, array(), 'col1 IS NULL');
$this->assertFalse($partial->overrules($without));
$this->assertFalse($without->overrules($partial));
$this->assertTrue($partial->overrules($partial));
$this->assertTrue($partial->overrules($another));
$this->assertTrue($another->overrules($partial));
}
/** /**
* @group DBAL-220 * @group DBAL-220
*/ */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment