Skip to content

Commit

Permalink
Add Index::setWhere()
Browse files Browse the repository at this point in the history
implement partial indexes in postgres, sqlite and sqlserver
  • Loading branch information
markstory committed Dec 26, 2024
1 parent 67f09ae commit 019f9ae
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 12 deletions.
11 changes: 8 additions & 3 deletions src/Db/Adapter/PostgresAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -1286,13 +1286,17 @@ protected function getIndexSqlDefinition(Index $index, string $tableName): strin
}, $columnNames);

$include = $index->getInclude();
$includedColumns = $include ? sprintf('INCLUDE ("%s")', implode('","', $include)) : '';
$includedColumns = $include ? sprintf(' INCLUDE ("%s")', implode('","', $include)) : '';

$createIndexSentence = 'CREATE %sINDEX%s %s ON %s ';
if ($index->getType() === self::GIN_INDEX_TYPE) {
$createIndexSentence .= ' USING ' . $index->getType() . '(%s) %s;';
} else {
$createIndexSentence .= '(%s) %s;';
$createIndexSentence .= '(%s)%s%s;';
}
$where = (string)$index->getWhere();
if ($where) {
$where = ' WHERE ' . $where;
}

return sprintf(
Expand All @@ -1302,7 +1306,8 @@ protected function getIndexSqlDefinition(Index $index, string $tableName): strin
$this->quoteColumnName((string)$indexName),
$this->quoteTableName($tableName),
implode(',', $columnNames),
$includedColumns
$includedColumns,
$where,
);
}

Expand Down
9 changes: 7 additions & 2 deletions src/Db/Adapter/SqliteAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -1378,11 +1378,16 @@ protected function getAddIndexInstructions(Table $table, Index $index): AlterIns
$indexColumnArray[] = sprintf('`%s` ASC', $column);
}
$indexColumns = implode(',', $indexColumnArray);
$where = (string)$index->getWhere();
if ($where) {
$where = ' WHERE ' . $where;
}
$sql = sprintf(
'CREATE %s ON %s (%s)',
'CREATE %s ON %s (%s)%s',
$this->getIndexSqlDefinition($table, $index),
$this->quoteTableName($table->getName()),
$indexColumns
$indexColumns,
$where
);

return new AlterInstructions([], [$sql]);
Expand Down
9 changes: 7 additions & 2 deletions src/Db/Adapter/SqlserverAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -1200,14 +1200,19 @@ protected function getIndexSqlDefinition(Index $index, string $tableName): strin

$include = $index->getInclude();
$includedColumns = $include ? sprintf('INCLUDE ([%s])', implode('],[', $include)) : '';
$where = (string)$index->getWhere();
if ($where) {
$where = ' WHERE ' . $where;
}

return sprintf(
'CREATE %s INDEX %s ON %s (%s) %s;',
'CREATE %s INDEX %s ON %s (%s) %s%s;',
($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
$indexName,
$this->quoteTableName($tableName),
implode(',', $columnNames),
$includedColumns
$includedColumns,
$where
);
}

Expand Down
34 changes: 29 additions & 5 deletions src/Db/Table/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@
*
* @see \Migrations\BaseMigration::index()
* @see \Migrations\Db\Table::addIndex()
*
* TODO expand functionality of Index:
* - Add ifnotexists
* - Add where for partial indexes
*/
class Index
{
Expand Down Expand Up @@ -74,6 +70,11 @@ class Index
*/
protected bool $concurrent = false;

/**
* @var string|null The where clause for partial indexes.
*/
protected ?string $where = null;

/**
* Sets the index columns.
*
Expand Down Expand Up @@ -245,6 +246,29 @@ public function getConcurrently(): bool
return $this->concurrent;
}

/**
* Set the where clause for partial indexes.
*
* @param ?string $where The where clause for partial indexes.
* @return $this
*/
public function setWhere(?string $where)
{
$this->where = $where;

return $this;
}

/**
* Get the where clause for partial indexes.
*
* @return ?string
*/
public function getWhere(): ?string
{
return $this->where;
}

/**
* Utility method that maps an array of index options to this objects methods.
*
Expand All @@ -255,7 +279,7 @@ public function getConcurrently(): bool
public function setOptions(array $options)
{
// Valid Options
$validOptions = ['type', 'unique', 'name', 'limit', 'order', 'include'];
$validOptions = ['concurrently', 'type', 'unique', 'name', 'limit', 'order', 'include', 'where'];
foreach ($options as $option => $value) {
if (!in_array($option, $validOptions, true)) {
throw new RuntimeException(sprintf('"%s" is not a valid index option.', $option));
Expand Down
22 changes: 22 additions & 0 deletions tests/TestCase/Db/Adapter/PostgresAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,28 @@ public function testCreateTableWithIndexConcurrently(): void
$this->assertStringContainsString('CREATE UNIQUE INDEX CONCURRENTLY "table1_email"', $indexQuery);
}

public function testCreateTableIndexWithWhere(): void
{
$options = $this->adapter->getOptions();
$options['dryrun'] = true;
$this->adapter->setOptions($options);

$index = new Index();
$index->setColumns('email')
->setType(Index::UNIQUE)
->setWhere('is_verified = true');

$table = new Table('table1', [], $this->adapter);
$table->addColumn('email', 'string')
->addColumn('is_verified', 'boolean')
->addIndex($index)
->save();
$queries = $this->out->messages();
$indexQuery = $queries[3];
$this->assertStringContainsString('CREATE UNIQUE INDEX "table1_email"', $indexQuery);
$this->assertStringContainsString('("email") WHERE is_verified = true', $indexQuery);
}

public function testAddPrimaryKey()
{
$table = new Table('table1', ['id' => false], $this->adapter);
Expand Down
23 changes: 23 additions & 0 deletions tests/TestCase/Db/Adapter/SqliteAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Migrations\Db\Table;
use Migrations\Db\Table\Column;
use Migrations\Db\Table\ForeignKey;
use Migrations\Db\Table\Index;
use PDOException;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -358,6 +359,28 @@ public function testCreateTableWithoutAutoIncrementingPrimaryKeyAndWithForeignKe
);
}

public function testCreateTableIndexWithWhere(): void
{
$options = $this->adapter->getOptions();
$options['dryrun'] = true;
$this->adapter->setOptions($options);

$index = new Index();
$index->setColumns('email')
->setType(Index::UNIQUE)
->setWhere('is_verified = true');

$table = new Table('table1', [], $this->adapter);
$table->addColumn('email', 'string')
->addColumn('is_verified', 'boolean')
->addIndex($index)
->save();
$queries = $this->out->messages();
$indexQuery = $queries[2];
$this->assertStringContainsString('CREATE UNIQUE INDEX `table1_email_index`', $indexQuery);
$this->assertStringContainsString('(`email` ASC) WHERE is_verified = true', $indexQuery);
}

public function testAddPrimaryKey()
{
$table = new Table('table1', ['id' => false], $this->adapter);
Expand Down
24 changes: 24 additions & 0 deletions tests/TestCase/Db/Adapter/SqlserverAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Migrations\Db\Table;
use Migrations\Db\Table\Column;
use Migrations\Db\Table\ForeignKey;
use Migrations\Db\Table\Index;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Depends;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -305,6 +306,29 @@ public function testCreateTableWithNamedIndexes()
$this->assertTrue($this->adapter->hasIndexByName('table1', 'myemailindex'));
}

public function testCreateTableIndexWithWhere(): void
{
$options = $this->adapter->getOptions();
$options['dryrun'] = true;
$this->adapter->setOptions($options);

$index = new Index();
$index->setColumns('email')
->setName('active_email_index')
->setType(Index::UNIQUE)
->setWhere('is_verified = true');

$table = new Table('table1', [], $this->adapter);
$table->addColumn('email', 'string')
->addColumn('is_verified', 'boolean')
->addIndex($index)
->save();
$queries = $this->out->messages();
$indexQuery = $queries[2];
$this->assertStringContainsString('CREATE UNIQUE INDEX [active_email_index]', $indexQuery);
$this->assertStringContainsString('([email]) WHERE is_verified = true', $indexQuery);
}

public function testAddPrimaryKey()
{
$table = new Table('table1', ['id' => false], $this->adapter);
Expand Down

0 comments on commit 019f9ae

Please sign in to comment.