diff --git a/src/Db/Adapter/PostgresAdapter.php b/src/Db/Adapter/PostgresAdapter.php index f2f19dc7..0ac51fdf 100644 --- a/src/Db/Adapter/PostgresAdapter.php +++ b/src/Db/Adapter/PostgresAdapter.php @@ -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( @@ -1302,7 +1306,8 @@ protected function getIndexSqlDefinition(Index $index, string $tableName): strin $this->quoteColumnName((string)$indexName), $this->quoteTableName($tableName), implode(',', $columnNames), - $includedColumns + $includedColumns, + $where, ); } diff --git a/src/Db/Adapter/SqliteAdapter.php b/src/Db/Adapter/SqliteAdapter.php index 09fa5c24..90db97a1 100644 --- a/src/Db/Adapter/SqliteAdapter.php +++ b/src/Db/Adapter/SqliteAdapter.php @@ -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]); diff --git a/src/Db/Adapter/SqlserverAdapter.php b/src/Db/Adapter/SqlserverAdapter.php index 77a77945..f6b990ce 100644 --- a/src/Db/Adapter/SqlserverAdapter.php +++ b/src/Db/Adapter/SqlserverAdapter.php @@ -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 ); } diff --git a/src/Db/Table/Index.php b/src/Db/Table/Index.php index a044e803..89cac034 100644 --- a/src/Db/Table/Index.php +++ b/src/Db/Table/Index.php @@ -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 { @@ -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. * @@ -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. * @@ -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)); diff --git a/tests/TestCase/Db/Adapter/PostgresAdapterTest.php b/tests/TestCase/Db/Adapter/PostgresAdapterTest.php index 7892912e..8eec4664 100644 --- a/tests/TestCase/Db/Adapter/PostgresAdapterTest.php +++ b/tests/TestCase/Db/Adapter/PostgresAdapterTest.php @@ -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); diff --git a/tests/TestCase/Db/Adapter/SqliteAdapterTest.php b/tests/TestCase/Db/Adapter/SqliteAdapterTest.php index f1212768..d6f04e68 100644 --- a/tests/TestCase/Db/Adapter/SqliteAdapterTest.php +++ b/tests/TestCase/Db/Adapter/SqliteAdapterTest.php @@ -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; @@ -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); diff --git a/tests/TestCase/Db/Adapter/SqlserverAdapterTest.php b/tests/TestCase/Db/Adapter/SqlserverAdapterTest.php index 5b9bfa8f..ff04773f 100644 --- a/tests/TestCase/Db/Adapter/SqlserverAdapterTest.php +++ b/tests/TestCase/Db/Adapter/SqlserverAdapterTest.php @@ -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; @@ -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);