diff --git a/UPGRADE.md b/UPGRADE.md index d3a9b6a8c96..6a29c5519bb 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -8,6 +8,34 @@ awareness about deprecated code. # Upgrade to 4.3 +## Deprecated `Index` methods, properties and behavior + +The following `Index` methods and properties have been deprecated: + +- `Index::getColumns()`, `Index::getQuotedColumns()`, `Index::getUnquotedColumns()`, + `Index::$_columns` – use `Index::getIndexedColumns()` instead. +- `Index::isSimpleIndex()`, `Index::isUnique()`, `Index::$_isUnique` – use `Index::getType()` and compare with + `IndexType::REGULAR` or `IndexType::UNIQUE` instead. +- `Index::addFlag()`, `Index::removeFlag()`, `Index::getFlags()`, `Index::hasFlag()`, `Index::$_flags` – use + `IndexEditor::setType()`, `Index::getType()`, `IndexEditor::setIsClustered()` and `Index::isClustered()` instead. +- `Index::getOption()`, `Index::hasOption()` and `Index::getOptions()` – use `Index::getIndexedColumns()` and + `Index::getPredicate()` instead. +- `Index::overrules()`, `Index::hasColumnAtPosition()` – no replacement provided. +- `AbstractPlatform::supportsColumnLengthIndexes()` – no replacement provided. + +Additionally, +1. Instantiation of an index without columns is deprecated. +2. The `Index::spansColumns()` method has been marked as internal. +3. Passing an empty string as partial index predicate has been deprecated. +4. The `Index` constructor has been marked as internal. Use `Index::editor()` to instantiate an editor and + `IndexEditor::create()` to create an index. + +The following conflicting index configurations have been deprecated: +1. Spatial index with column lengths specified. +2. Clustered fulltext or spatial index. +3. Partial fulltext or spatial index. +4. Clustered partial index. + ## Deprecated features related to primary key constraints 1. The `AbstractPlatform::getCreatePrimaryKeySQL()` method has been deprecated. Use the schema manager to create and @@ -129,7 +157,7 @@ the `Schema` class itself. ## Deprecated `ForeignKeyConstraint` methods, properties and behavior -The following `ForeignKeyConstraint` methods and property have been deprecated: +The following `ForeignKeyConstraint` methods and properties have been deprecated: - `ForeignKeyConstraint::getForeignTableName()`, `ForeignKeyConstraint::getQuotedForeignTableName()`, `ForeignKeyConstraint::getUnqualifiedForeignTableName()`, `ForeignKeyConstraint::$_foreignTableName` – use diff --git a/src/Platforms/AbstractMySQLPlatform.php b/src/Platforms/AbstractMySQLPlatform.php index 556e58f7244..2638d7a7d47 100644 --- a/src/Platforms/AbstractMySQLPlatform.php +++ b/src/Platforms/AbstractMySQLPlatform.php @@ -846,8 +846,16 @@ public function getDefaultTransactionIsolationLevel(): TransactionIsolationLevel return TransactionIsolationLevel::REPEATABLE_READ; } + /** @deprecated */ public function supportsColumnLengthIndexes(): bool { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + '%s is deprecated.', + __METHOD__, + ); + return true; } diff --git a/src/Platforms/AbstractPlatform.php b/src/Platforms/AbstractPlatform.php index 761152a32bd..f4fcc6d1df3 100644 --- a/src/Platforms/AbstractPlatform.php +++ b/src/Platforms/AbstractPlatform.php @@ -2053,9 +2053,18 @@ public function supportsPartialIndexes(): bool /** * Whether the platform supports indexes with column length definitions. + * + * @deprecated */ public function supportsColumnLengthIndexes(): bool { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + '%s is deprecated.', + __METHOD__, + ); + return false; } diff --git a/src/Schema/Exception/InvalidIndexDefinition.php b/src/Schema/Exception/InvalidIndexDefinition.php new file mode 100644 index 00000000000..b4533baf4fb --- /dev/null +++ b/src/Schema/Exception/InvalidIndexDefinition.php @@ -0,0 +1,28 @@ + */ protected array $_columns = []; + /** @deprecated Use {@see getType()} and compare with {@see IndexType::UNIQUE} instead. */ protected bool $_isUnique = false; /** @deprecated Use {@see PrimaryKeyConstraint()} instead. */ @@ -45,6 +51,8 @@ class Index extends AbstractNamedObject /** * Platform specific flags for indexes. * + * @deprecated + * * @var array */ protected array $_flags = []; @@ -59,6 +67,20 @@ class Index extends AbstractNamedObject private readonly array $columns; /** + * Index type. + * + * A null value indicates that an attempt to parse the index type failed. + */ + private ?IndexType $type = null; + + private ?string $predicate = null; + + private bool $failedToParsePredicate = false; + + /** + * @internal Use {@link Index::editor()} to instantiate an editor and {@link IndexEditor::create()} to create an + * index. + * * @param non-empty-list $columns * @param array $flags * @param array $options @@ -96,10 +118,30 @@ public function __construct( $this->_addColumn($column); } + if (isset($options['where'])) { + $predicate = $options['where']; + + if (strlen($predicate) === 0) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + 'Passing an empty string as index predicate is deprecated.', + ); + + $this->failedToParsePredicate = true; + } else { + $this->predicate = $predicate; + } + } + foreach ($flags as $flag) { $this->addFlag($flag); } + if (count($flags) === 0) { + $this->type = $this->inferType(); + } + $this->columns = $this->parseColumns($isPrimary, $columns, $options['lengths'] ?? []); } @@ -108,6 +150,15 @@ protected function getNameParser(): UnqualifiedNameParser return Parsers::getUnqualifiedNameParser(); } + public function getType(): IndexType + { + if ($this->type === null) { + throw InvalidState::indexHasInvalidType($this->getName()); + } + + return $this->type; + } + /** * Returns the indexed columns. * @@ -122,6 +173,30 @@ public function getIndexedColumns(): array return $this->columns; } + /** + * Returns whether the index is clustered. + */ + public function isClustered(): bool + { + return $this->hasFlag('clustered'); + } + + /** + * Returns the index predicate. + * + * @return ?non-empty-string + */ + public function getPredicate(): ?string + { + if ($this->failedToParsePredicate) { + throw InvalidState::indexHasInvalidPredicate($this->getName()); + } + + return $this->hasOption('where') + ? $this->getOption('where') + : null; + } + protected function _addColumn(string $column): void { $this->_columns[$column] = new Identifier($column); @@ -130,10 +205,19 @@ protected function _addColumn(string $column): void /** * Returns the names of the referencing table columns the constraint is associated with. * + * @deprecated Use {@see getIndexedColumns()} instead. + * * @return non-empty-list */ public function getColumns(): array { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + '%s is deprecated. Use Index::getIndexedColumns() instead.', + __METHOD__, + ); + /** @phpstan-ignore return.type */ return array_keys($this->_columns); } @@ -145,12 +229,21 @@ public function getColumns(): array * is a keyword reserved by the platform. * Otherwise, the plain unquoted value as inserted is returned. * + * @deprecated Use {@see getIndexedColumns()} instead. + * * @param AbstractPlatform $platform The platform to use for quotation. * * @return list */ public function getQuotedColumns(AbstractPlatform $platform): array { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + '%s is deprecated. Use Index::getIndexedColumns() instead.', + __METHOD__, + ); + $subParts = $platform->supportsColumnLengthIndexes() && $this->hasOption('lengths') ? $this->getOption('lengths') : []; @@ -171,22 +264,50 @@ public function getQuotedColumns(AbstractPlatform $platform): array return $columns; } - /** @return non-empty-list */ + /** + * @deprecated Use {@see getIndexedColumns()} instead. + * + * @return non-empty-list + */ public function getUnquotedColumns(): array { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + '%s is deprecated. Use Index::getIndexedColumns() instead.', + __METHOD__, + ); + return array_map($this->trimQuotes(...), $this->getColumns()); } /** * Is the index neither unique nor primary key? + * + * @deprecated Use {@see getType()} and compare with {@see IndexType::REGULAR} instead. */ public function isSimpleIndex(): bool { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + '%s is deprecated. Use Index::getType() and compare with IndexType::REGULAR instead.', + __METHOD__, + ); + return ! $this->_isPrimary && ! $this->_isUnique; } + /** @deprecated Use {@see getType()} and compare with {@see IndexType::UNIQUE} instead. */ public function isUnique(): bool { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + '%s is deprecated. Use Index::getType() and compare with IndexType::UNIQUE instead.', + __METHOD__, + ); + return $this->_isUnique; } @@ -202,8 +323,16 @@ public function isPrimary(): bool return $this->_isPrimary; } + /** @deprecated Use {@see getIndexedColumns()} instead. */ public function hasColumnAtPosition(string $name, int $pos = 0): bool { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + '%s is deprecated. Use Index::getIndexedColumns() instead.', + __METHOD__, + ); + $name = $this->trimQuotes(strtolower($name)); $indexColumns = array_map('strtolower', $this->getUnquotedColumns()); @@ -213,6 +342,8 @@ public function hasColumnAtPosition(string $name, int $pos = 0): bool /** * Checks if this index exactly spans the given column names in the correct order. * + * @internal + * * @param array $columnNames */ public function spansColumns(array $columnNames): bool @@ -278,9 +409,18 @@ public function isFulfilledBy(Index $other): bool /** * Detects if the other index is a non-unique, non primary index that can be overwritten by this one. + * + * @deprecated */ public function overrules(Index $other): bool { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + '%s is deprecated.', + __METHOD__, + ); + if ($other->isPrimary()) { return false; } @@ -297,57 +437,210 @@ public function overrules(Index $other): bool /** * Returns platform specific flags for indexes. * + * @deprecated Use {@see getType()} and {@see isClustered()} instead. + * * @return array */ public function getFlags(): array { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + '%s is deprecated. Use Index::getType() and Index::isClustered() instead.', + __METHOD__, + ); + return array_keys($this->_flags); } /** * Adds Flag for an index that translates to platform specific handling. * + * @deprecated Use {@see edit()}, {@see IndexEditor::setType()} and {@see IndexEditor::setIsClustered()} instead. + * * @example $index->addFlag('CLUSTERED') */ public function addFlag(string $flag): self { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + '%s is deprecated. Use Index::edit(), IndexEditor::setType() and IndexEditor::setIsClustered()' + . ' instead.', + __METHOD__, + ); + $this->_flags[strtolower($flag)] = true; + $this->validateFlags(); + + $this->type = $this->inferType(); + return $this; } /** * Does this index have a specific flag? + * + * @deprecated Use {@see getType()} and {@see isClustered()} instead. */ public function hasFlag(string $flag): bool { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + '%s is deprecated. Use Index::getType() and Index::isClustered() instead.', + __METHOD__, + ); + return isset($this->_flags[strtolower($flag)]); } /** - * Removes a flag. + * @deprecated Use {@see edit()}, {@see IndexEditor::setType()} and {@see IndexEditor::setIsClustered()} + * instead. */ public function removeFlag(string $flag): void { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + '%s is deprecated. Use Index::edit(), IndexEditor::setType() and IndexEditor::setIsClustered()' + . ' instead.', + __METHOD__, + ); + unset($this->_flags[strtolower($flag)]); + + $this->type = $this->inferType(); } + /** @deprecated Use {@see getIndexedColumns()} and {@see getPredicate()} instead. */ public function hasOption(string $name): bool { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + '%s is deprecated. Use Index::getIndexedColumns() and Index::getPredicate() instead.', + __METHOD__, + ); + return isset($this->options[strtolower($name)]); } + /** @deprecated Use {@see getIndexedColumns()} and {@see getPredicate()} instead. */ public function getOption(string $name): mixed { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + '%s is deprecated. Use Index::getIndexedColumns() and Index::getPredicate() instead.', + __METHOD__, + ); + return $this->options[strtolower($name)]; } - /** @return array */ + /** + * @deprecated Use {@see getIndexedColumns()} and {@see getPredicate()} instead. + * + * @return array + */ public function getOptions(): array { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + '%s is deprecated. Use Index::getIndexedColumns() and Index::getPredicate() instead.', + __METHOD__, + ); + return $this->options; } + private function validateFlags(): void + { + $unsupportedFlags = $this->_flags; + unset( + $unsupportedFlags['fulltext'], + $unsupportedFlags['spatial'], + $unsupportedFlags['clustered'], + $unsupportedFlags['nonclustered'], + ); + + if (count($unsupportedFlags) > 0) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + 'Configuring an index with non-standard flags is deprecated: %s', + implode(', ', array_keys($unsupportedFlags)), + ); + } + + if ( + $this->hasFlag('clustered') && ( + $this->hasFlag('nonclustered') + || $this->hasFlag('fulltext') + || $this->hasFlag('spatial') + ) + ) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + 'A fulltext, spatial or non-clustered index cannot be clustered.', + ); + } + + if ( + $this->predicate === null + || (! $this->hasFlag('fulltext') + && ! $this->hasFlag('spatial') + && ! $this->hasFlag('clustered')) + ) { + return; + } + + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + 'A fulltext, spatial or clustered index cannot be partial.', + ); + } + + private function inferType(): ?IndexType + { + $type = IndexType::REGULAR; + $matches = []; + + if ($this->_isUnique) { + $type = IndexType::UNIQUE; + $matches[] = 'unique'; + } + + if ($this->hasFlag('fulltext')) { + $type = IndexType::FULLTEXT; + $matches[] = 'fulltext'; + } + + if ($this->hasFlag('spatial')) { + $type = IndexType::SPATIAL; + $matches[] = 'spatial'; + } + + if (count($matches) > 1) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6886', + 'Configuring an index with mutually exclusive properties is deprecated: %s', + implode(', ', $matches), + ); + + return null; + } + + return $type; + } + /** * @param non-empty-array $columnNames * @param array $lengths @@ -444,4 +737,25 @@ private function hasSameColumnLengths(self $other): bool return array_filter($this->options['lengths'] ?? [], $filter) === array_filter($other->options['lengths'] ?? [], $filter); } + + /** + * Instantiates a new index editor. + */ + public static function editor(): IndexEditor + { + return new IndexEditor(); + } + + /** + * Instantiates a new index editor and initializes it with the properties of the current index. + */ + public function edit(): IndexEditor + { + return self::editor() + ->setName($this->getObjectName()) + ->setType($this->getType()) + ->setColumns(...$this->getIndexedColumns()) + ->setIsClustered($this->isClustered()) + ->setPredicate($this->getPredicate()); + } } diff --git a/src/Schema/Index/IndexType.php b/src/Schema/Index/IndexType.php new file mode 100644 index 00000000000..646668853f2 --- /dev/null +++ b/src/Schema/Index/IndexType.php @@ -0,0 +1,13 @@ +columnName; } + /** @return ?positive-int */ public function getLength(): ?int { return $this->length; diff --git a/src/Schema/IndexEditor.php b/src/Schema/IndexEditor.php new file mode 100644 index 00000000000..191ca11ece7 --- /dev/null +++ b/src/Schema/IndexEditor.php @@ -0,0 +1,123 @@ + */ + private array $columns = []; + + private bool $isClustered = false; + + /** @var ?non-empty-string */ + private ?string $predicate = null; + + /** @internal Use {@link Index::editor()} or {@link Index::edit()} to create an instance */ + public function __construct() + { + } + + public function setName(?UnqualifiedName $name): self + { + $this->name = $name; + + return $this; + } + + public function setType(IndexType $type): self + { + $this->type = $type; + + return $this; + } + + public function setColumns(IndexedColumn $firstColumn, IndexedColumn ...$otherColumns): self + { + $this->columns = array_merge([$firstColumn], array_values($otherColumns)); + + return $this; + } + + public function setColumnNames(UnqualifiedName $firstColumnName, UnqualifiedName ...$otherColumnNames): self + { + $this->columns = array_map( + static fn (UnqualifiedName $name) => new IndexedColumn($name, null), + array_merge([$firstColumnName], array_values($otherColumnNames)), + ); + + return $this; + } + + public function setIsClustered(bool $isClustered): self + { + $this->isClustered = $isClustered; + + return $this; + } + + /** @param ?non-empty-string $predicate */ + public function setPredicate(?string $predicate): self + { + $this->predicate = $predicate; + + return $this; + } + + public function create(): Index + { + if ($this->name === null) { + throw InvalidIndexDefinition::nameNotSet(); + } + + if (count($this->columns) < 1) { + throw InvalidIndexDefinition::columnsNotSet(); + } + + $columnNames = $lengths = $flags = []; + foreach ($this->columns as $column) { + $columnNames[] = $column->getColumnName()->toString(); + $lengths[] = $column->getLength(); + } + + $options = ['lengths' => $lengths]; + + if ($this->type === IndexType::FULLTEXT) { + $flags[] = 'fulltext'; + } elseif ($this->type === IndexType::SPATIAL) { + $flags[] = 'spatial'; + } + + if ($this->isClustered) { + $flags[] = 'clustered'; + } + + if ($this->predicate !== null) { + $options['where'] = $this->predicate; + } + + return new Index( + $this->name->toString(), + $columnNames, + $this->type === IndexType::UNIQUE, + false, + $flags, + $options, + ); + } +} diff --git a/tests/Schema/Index/IndexedColumnTest.php b/tests/Schema/Index/IndexedColumnTest.php new file mode 100644 index 00000000000..ffbbeb9d32c --- /dev/null +++ b/tests/Schema/Index/IndexedColumnTest.php @@ -0,0 +1,21 @@ +expectException(InvalidIndexDefinition::class); + + // @phpstan-ignore argument.type + new IndexedColumn(UnqualifiedName::unquoted('id'), -1); + } +} diff --git a/tests/Schema/IndexEditorTest.php b/tests/Schema/IndexEditorTest.php new file mode 100644 index 00000000000..d6ac15885ea --- /dev/null +++ b/tests/Schema/IndexEditorTest.php @@ -0,0 +1,82 @@ +setColumnNames(UnqualifiedName::unquoted('id')); + + $this->expectException(InvalidIndexDefinition::class); + + $editor->create(); + } + + public function testColumnsNotSet(): void + { + $editor = Index::editor() + ->setName(UnqualifiedName::unquoted('idx_user_id')); + + $this->expectException(InvalidIndexDefinition::class); + + $editor->create(); + } + + public function testPreservesRegularIndexProperties(): void + { + $index1 = new Index( + 'idx_user_name', + ['user_name'], + false, + false, + [], + [ + 'lengths' => [32], + 'where' => 'is_active = 1', + ], + ); + + $index2 = $index1->edit() + ->create(); + + self::assertSame('idx_user_name', $index2->getName()); + self::assertSame(['user_name'], $index2->getColumns()); + self::assertFalse($index2->isUnique()); + self::assertFalse($index2->isPrimary()); + self::assertSame([], $index2->getFlags()); + self::assertSame([ + 'lengths' => [32], + 'where' => 'is_active = 1', + ], $index2->getOptions()); + } + + /** @param array $flags */ + #[TestWith([['fulltext']])] + #[TestWith([['spatial']])] + #[TestWith([['clustered']])] + public function testPreservesIndexFlags(array $flags): void + { + $index1 = new Index( + 'idx_test', + ['test'], + false, + false, + $flags, + ); + + $index2 = $index1->edit() + ->create(); + + self::assertSame($flags, $index2->getFlags()); + } +} diff --git a/tests/Schema/IndexTest.php b/tests/Schema/IndexTest.php index 65582676a98..82d1f5d2437 100644 --- a/tests/Schema/IndexTest.php +++ b/tests/Schema/IndexTest.php @@ -7,10 +7,12 @@ use Doctrine\DBAL\Schema\Exception\InvalidState; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Index\IndexedColumn; +use Doctrine\DBAL\Schema\Index\IndexType; use Doctrine\DBAL\Schema\Name\Identifier; use Doctrine\DBAL\Schema\Name\UnqualifiedName; use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; class IndexTest extends TestCase @@ -147,10 +149,12 @@ public function testFlags(): void self::assertTrue($idx1->hasFlag('clustered')); self::assertTrue($idx1->hasFlag('CLUSTERED')); self::assertSame(['clustered'], $idx1->getFlags()); + self::assertTrue($idx1->isClustered()); $idx1->removeFlag('clustered'); self::assertFalse($idx1->hasFlag('clustered')); self::assertEmpty($idx1->getFlags()); + self::assertFalse($idx1->isClustered()); } public function testIndexQuotes(): void @@ -277,4 +281,76 @@ public function testGetIndexedColumns(): void self::assertEquals(UnqualifiedName::unquoted('last_name'), $indexedColumns[1]->getColumnName()); self::assertNull($indexedColumns[1]->getLength()); } + + public function testUnsupportedFlag(): void + { + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/dbal/pull/6886'); + new Index('idx_user_name', ['name'], false, false, ['banana']); + } + + /** @param list $flags */ + #[TestWith([true, ['fulltext']])] + #[TestWith([true, ['spatial']])] + #[TestWith([false, ['fulltext', 'spatial']])] + public function testConflictInFlagsSignificantForTypeInference(bool $isUnique, array $flags): void + { + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/dbal/pull/6886'); + $index = new Index('idx_user_name', ['name'], $isUnique, false, $flags); + + $this->expectException(InvalidState::class); + $index->getType(); + } + + /** @param list $flags */ + #[TestWith([['fulltext', 'clustered'], IndexType::FULLTEXT])] + #[TestWith([['spatial', 'clustered'], IndexType::SPATIAL])] + #[TestWith([['nonclustered', 'clustered'], IndexType::REGULAR])] + public function testConflictInFlagsInsignificantForTypeInference(array $flags, IndexType $expectedType): void + { + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/dbal/pull/6886'); + $index = new Index('idx_user_name', ['name'], false, false, $flags); + + self::assertEquals($expectedType, $index->getType()); + } + + /** @param list $flags */ + #[TestWith([false, [], IndexType::REGULAR])] + #[TestWith([false, ['fulltext'], IndexType::FULLTEXT])] + #[TestWith([false, ['spatial'], IndexType::SPATIAL])] + #[TestWith([true, [], IndexType::UNIQUE])] + public function testParseType(bool $isUnique, array $flags, IndexType $expectedType): void + { + $this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/dbal/pull/6886'); + $index = new Index('idx_user_name', ['user_id'], $isUnique, false, $flags); + + self::assertEquals($expectedType, $index->getType()); + } + + #[TestWith([null])] + #[TestWith(['is_active = 1'])] + public function testGetPredicate(?string $predicate): void + { + $this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/dbal/pull/6886'); + $index = new Index('idx_user_name', ['user_id'], false, false, [], ['where' => $predicate]); + + self::assertEquals($predicate, $index->getPredicate()); + } + + public function testEmptyPredicate(): void + { + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/dbal/pull/6886'); + $index = new Index('idx_user_name', ['user_id'], false, false, [], ['where' => '']); + + $this->expectException(InvalidState::class); + $index->getPredicate(); + } + + #[TestWith(['fulltext'])] + #[TestWith(['spatial'])] + #[TestWith(['clustered'])] + public function testPartialIndexWithConflictingFlags(string $flag): void + { + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/dbal/pull/6886'); + new Index('idx_user_name', ['user_id'], false, false, [$flag], ['where' => 'is_active = 1']); + } }