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
}
$query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table;
$query .= ' (' . $this->getIndexFieldDeclarationListSQL($columns) . ')';
$query .= ' (' . $this->getIndexFieldDeclarationListSQL($columns) . ')' . $this->getPartialIndexSQL($index);
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.
*
......@@ -2302,7 +2316,7 @@ abstract class AbstractPlatform
return 'CONSTRAINT ' . $name . ' UNIQUE ('
. $this->getIndexFieldDeclarationListSQL($columns)
. ')';
. ')' . $this->getPartialIndexSQL($index);
}
/**
......@@ -2326,7 +2340,7 @@ abstract class AbstractPlatform
return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ('
. $this->getIndexFieldDeclarationListSQL($columns)
. ')';
. ')' . $this->getPartialIndexSQL($index);
}
/**
......@@ -3008,6 +3022,16 @@ abstract class AbstractPlatform
return true;
}
/**
* Whether the platform supports partial indexes.
*
* @return boolean
*/
public function supportsPartialIndexes()
{
return false;
}
/**
* Whether the platform supports altering tables.
*
......
......@@ -174,6 +174,16 @@ class PostgreSqlPlatform extends AbstractPlatform
return true;
}
/**
* Whether the platform supports partial indexes.
*
* @return boolean
*/
public function supportsPartialIndexes()
{
return true;
}
/**
* {@inheritdoc}
*/
......@@ -314,7 +324,8 @@ class PostgreSqlPlatform extends AbstractPlatform
public function getListTableIndexesSQL($table, $currentDatabase = null)
{
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
WHERE oid IN (
SELECT indexrelid
......
......@@ -463,6 +463,16 @@ class SqlitePlatform extends AbstractPlatform
return true;
}
/**
* Whether the platform supports partial indexes.
*
* @return boolean
*/
public function supportsPartialIndexes()
{
return true;
}
/**
* {@inheritDoc}
*/
......
......@@ -819,6 +819,7 @@ abstract class AbstractSchemaManager
'unique' => $tableIndex['non_unique'] ? false : true,
'primary' => $tableIndex['primary'],
'flags' => isset($tableIndex['flags']) ? $tableIndex['flags'] : array(),
'where' => isset($tableIndex['where']) ? $tableIndex['where'] : null,
);
} else {
$result[$keyName]['columns'][] = $tableIndex['column_name'];
......@@ -841,7 +842,7 @@ abstract class AbstractSchemaManager
}
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) {
......
......@@ -49,20 +49,29 @@ class Index extends AbstractAsset implements Constraint
*/
protected $_flags = array();
/**
* Platform specific condition for partial indexes
*
* @var string|null
*/
protected $_where = null;
/**
* @param string $indexName
* @param string[] $columns
* @param boolean $isUnique
* @param boolean $isPrimary
* @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;
$this->_setName($indexName);
$this->_isUnique = $isUnique;
$this->_isPrimary = $isPrimary;
$this->_where = $where;
foreach ($columns as $column) {
$this->_addColumn($column);
......@@ -199,7 +208,9 @@ class Index extends AbstractAsset implements Constraint
$sameColumns = $this->spansColumns($other->getColumns());
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
// 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
......@@ -232,7 +243,7 @@ class Index extends AbstractAsset implements Constraint
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;
}
......@@ -288,4 +299,13 @@ class Index extends AbstractAsset implements Constraint
{
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
'key_name' => $row['relname'],
'column_name' => trim($colRow['attname']),
'non_unique' => !$row['indisunique'],
'primary' => $row['indisprimary']
'primary' => $row['indisprimary'],
'where' => $row['where'],
);
}
}
......
......@@ -146,10 +146,11 @@ class Table extends AbstractAsset
* @param array $columnNames
* @param string|null $indexName
* @param array $flags
* @param string|null $where
*
* @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) {
$indexName = $this->_generateIdentifierName(
......@@ -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
/**
* @param array $columnNames
* @param string|null $indexName
* @param string|null $where
*
* @return self
*/
public function addUniqueIndex(array $columnNames, $indexName = null)
public function addUniqueIndex(array $columnNames, $indexName = null, $where = null)
{
if ($indexName === null) {
$indexName = $this->_generateIdentifierName(
......@@ -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
* @param boolean $isUnique
* @param boolean $isPrimary
* @param array $flags
* @param string|null $where
*
* @return Index
*
* @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)) {
throw SchemaException::indexNameInvalid($indexName);
......@@ -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
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()
{
$fk = new \Doctrine\DBAL\Schema\ForeignKeyConstraint(array('fk_name_id'), 'other_table', array('id'), '');
......
......@@ -10,4 +10,9 @@ class PostgreSqlPlatformTest extends AbstractPostgreSqlPlatformTestCase
{
return new PostgreSqlPlatform;
}
public function testSupportsPartialIndexes()
{
$this->assertTrue($this->_platform->supportsPartialIndexes());
}
}
......@@ -845,6 +845,10 @@ class SQLAnywherePlatformTest extends AbstractPlatformTestCase
'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
......
......@@ -497,6 +497,11 @@ class SqlitePlatformTest extends AbstractPlatformTestCase
);
}
public function testSupportsPartialIndexes()
{
$this->assertTrue($this->_platform->supportsPartialIndexes());
}
/**
* @group DBAL-807
*/
......
......@@ -79,6 +79,36 @@ class IndexTest extends \PHPUnit_Framework_TestCase
$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
*/
......
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