Commit c1c33531 authored by Steve Müller's avatar Steve Müller

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

Support for Partial Indexes for PostgreSql and Sqlite
parents f61840a6 8feb89be
# Upgrade to 2.5
## BC BREAK: Doctrine\DBAL\Schema\Table
The methods ``addIndex()`` and ``addUniqueIndex()`` in ``Doctrine\DBAL\Schema\Table``
hav an additional, optional parameter. If you override these methods, you should
add this new parameter to the declaration of your overridden methods.
## BC BREAK: Doctrine\DBAL\Connection
The visibility of the property ``$_platform`` in ``Doctrine\DBAL\Connection``
......
......@@ -1745,11 +1745,27 @@ 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)
{
if ($this->supportsPartialIndexes() && $index->hasOption('where')) {
return ' WHERE ' . $index->getOption('where');
}
return '';
}
/**
* Adds additional flags for index generation.
*
......@@ -2289,7 +2305,7 @@ abstract class AbstractPlatform
return 'CONSTRAINT ' . $name . ' UNIQUE ('
. $this->getIndexFieldDeclarationListSQL($columns)
. ')';
. ')' . $this->getPartialIndexSQL($index);
}
/**
......@@ -2312,8 +2328,8 @@ abstract class AbstractPlatform
}
return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ('
. $this->getIndexFieldDeclarationListSQL($columns)
. ')';
. $this->getIndexFieldDeclarationListSQL($columns)
. ')' . $this->getPartialIndexSQL($index);
}
/**
......@@ -2571,7 +2587,7 @@ abstract class AbstractPlatform
return $item;
}
/**
* Some platforms have boolean literals that needs to be correctly converted
*
......@@ -3007,6 +3023,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,14 @@ class PostgreSqlPlatform extends AbstractPlatform
return true;
}
/**
* {@inheritdoc}
*/
public function supportsPartialIndexes()
{
return true;
}
/**
* {@inheritdoc}
*/
......@@ -322,7 +330,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
......
......@@ -863,6 +863,7 @@ abstract class AbstractSchemaManager
'unique' => $tableIndex['non_unique'] ? false : true,
'primary' => $tableIndex['primary'],
'flags' => isset($tableIndex['flags']) ? $tableIndex['flags'] : array(),
'options' => isset($tableIndex['where']) ? array('where' => $tableIndex['where']) : array(),
);
} else {
$result[$keyName]['columns'][] = $tableIndex['column_name'];
......@@ -885,7 +886,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['options']);
}
if ($index) {
......
......@@ -49,20 +49,31 @@ class Index extends AbstractAsset implements Constraint
*/
protected $_flags = array();
/**
* Platform specific options
*
* @todo $_flags should eventually be refactored into options
*
* @var array
*/
private $options = array();
/**
* @param string $indexName
* @param string[] $columns
* @param boolean $isUnique
* @param boolean $isPrimary
* @param string[] $flags
* @param array $options
*/
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(), array $options = array())
{
$isUnique = $isUnique || $isPrimary;
$this->_setName($indexName);
$this->_isUnique = $isUnique;
$this->_isPrimary = $isPrimary;
$this->options = $options;
foreach ($columns as $column) {
$this->_addColumn($column);
......@@ -199,15 +210,23 @@ class Index extends AbstractAsset implements Constraint
$sameColumns = $this->spansColumns($other->getColumns());
if ($sameColumns) {
if ( ! $this->isUnique() && !$this->isPrimary()) {
if ( ! $this->samePartialIndex($other)) {
return false;
}
if ( ! $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
// index that has no constraints.
return true;
} elseif ($other->isPrimary() != $this->isPrimary()) {
}
if ($other->isPrimary() != $this->isPrimary()) {
return false;
} elseif ($other->isUnique() != $this->isUnique()) {
}
if ($other->isUnique() != $this->isUnique()) {
return false;
}
......@@ -232,7 +251,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->samePartialIndex($other)) {
return true;
}
......@@ -288,4 +307,51 @@ class Index extends AbstractAsset implements Constraint
{
unset($this->_flags[strtolower($flag)]);
}
/**
* @param string $name
*
* @return boolean
*/
public function hasOption($name)
{
return isset($this->options[strtolower($name)]);
}
/**
* @param string $name
*
* @return mixed
*/
public function getOption($name)
{
return $this->options[strtolower($name)];
}
/**
* @return array
*/
public function getOptions()
{
return $this->options;
}
/**
* Return whether the two indexes have the same partial index
* @param \Doctrine\DBAL\Schema\Index $other
* @return boolean
*/
private function samePartialIndex(Index $other)
{
if ($this->hasOption('where') && $other->hasOption('where') && $this->getOption('where') == $other->getOption('where')) {
return true;
}
if ( ! $this->hasOption('where') && ! $other->hasOption('where')) {
return true;
}
return false;
}
}
......@@ -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 array $options
*
* @return self
*/
public function addIndex(array $columnNames, $indexName = null, array $flags = array())
public function addIndex(array $columnNames, $indexName = null, array $flags = array(), array $options = array())
{
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, $options));
}
/**
......@@ -192,10 +193,11 @@ class Table extends AbstractAsset
/**
* @param array $columnNames
* @param string|null $indexName
* @param array $options
*
* @return self
*/
public function addUniqueIndex(array $columnNames, $indexName = null)
public function addUniqueIndex(array $columnNames, $indexName = null, array $options = array())
{
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(), $options));
}
/**
......@@ -277,12 +279,13 @@ class Table extends AbstractAsset
* @param boolean $isUnique
* @param boolean $isPrimary
* @param array $flags
* @param array $options
*
* @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(), array $options = array())
{
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, $options);
}
/**
......
......@@ -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(), array('where' => $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());
}
}
......@@ -822,6 +822,11 @@ class SQLAnywherePlatformTest extends AbstractPlatformTestCase
);
}
public function testGeneratesPartialIndexesSqlOnlyWhenSupportingPartialIndexes()
{
$this->markTestSkipped('Index declaration in statements like CREATE TABLE is not supported.');
}
/**
* {@inheritdoc}
*/
......
......@@ -8,9 +8,9 @@ use Doctrine\DBAL\Schema\Index;
class IndexTest extends \PHPUnit_Framework_TestCase
{
public function createIndex($unique=false, $primary=false)
public function createIndex($unique = false, $primary = false, $options = array())
{
return new Index("foo", array("bar", "baz"), $unique, $primary);
return new Index("foo", array("bar", "baz"), $unique, $primary, array(), $options);
}
public function testCreateIndex()
......@@ -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(), array());
$partial = new Index('partial', array('col1', 'col2'), true, false, array(), array('where' => 'col1 IS NULL'));
$another = new Index('another', array('col1', 'col2'), true, false, array(), array('where' => '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(), array());
$partial = new Index('partial', array('col1', 'col2'), true, false, array(), array('where' => 'col1 IS NULL'));
$another = new Index('another', array('col1', 'col2'), true, false, array(), array('where' => '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
*/
......@@ -112,4 +142,18 @@ class IndexTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($index->hasColumnAtPosition("bar", 1));
$this->assertFalse($index->hasColumnAtPosition("baz", 0));
}
public function testOptions()
{
$idx1 = $this->createIndex();
$this->assertFalse($idx1->hasOption('where'));
$this->assertEmpty($idx1->getOptions());
$idx2 = $this->createIndex(false, false, array('where' => 'name IS NULL'));
$this->assertTrue($idx2->hasOption('where'));
$this->assertTrue($idx2->hasOption('WHERE'));
$this->assertSame('name IS NULL', $idx2->getOption('where'));
$this->assertSame('name IS NULL', $idx2->getOption('WHERE'));
$this->assertSame(array('where' => 'name IS NULL'), $idx2->getOptions());
}
}
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