Skip to content

Commit

Permalink
Added support of functional indexes for MySQL and Postgres
Browse files Browse the repository at this point in the history
  • Loading branch information
prohalexey committed Jun 6, 2024
1 parent 0b77350 commit fb58217
Show file tree
Hide file tree
Showing 14 changed files with 352 additions and 56 deletions.
10 changes: 9 additions & 1 deletion src/Driver/AbstractMySQLDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Doctrine\DBAL\Platforms\MariaDB1052Platform;
use Doctrine\DBAL\Platforms\MariaDB1060Platform;
use Doctrine\DBAL\Platforms\MariaDBPlatform;
use Doctrine\DBAL\Platforms\MySQL8013Platform;
use Doctrine\DBAL\Platforms\MySQL80Platform;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\ServerVersionProvider;
Expand Down Expand Up @@ -46,7 +47,14 @@ public function getDatabasePlatform(ServerVersionProvider $versionProvider): Abs
return new MariaDBPlatform();
}

if (version_compare($version, '8.0.0', '>=')) {
if (version_compare($version, '8.0.13', '>=')) {
return new MySQL8013Platform();
}

if (
version_compare($version, '8.0.0', '>=')
&& version_compare($version, '8.0.13', '<')
) {
return new MySQL80Platform();
}

Expand Down
5 changes: 5 additions & 0 deletions src/Platforms/AbstractMySQLPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ abstract class AbstractMySQLPlatform extends AbstractPlatform
final public const LENGTH_LIMIT_BLOB = 65535;
final public const LENGTH_LIMIT_MEDIUMBLOB = 16777215;

public function getColumnNameForIndexFetch(): string

Check warning on line 46 in src/Platforms/AbstractMySQLPlatform.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/AbstractMySQLPlatform.php#L46

Added line #L46 was not covered by tests
{
return 'COLUMN_NAME';

Check warning on line 48 in src/Platforms/AbstractMySQLPlatform.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/AbstractMySQLPlatform.php#L48

Added line #L48 was not covered by tests
}

protected function doModifyLimitQuery(string $query, ?int $limit, int $offset): string
{
if ($limit !== null) {
Expand Down
39 changes: 39 additions & 0 deletions src/Platforms/AbstractPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@
use function str_contains;
use function str_replace;
use function strlen;
use function strrpos;
use function strtolower;
use function strtoupper;
use function substr;

/**
* Base class for all DatabasePlatforms. The DatabasePlatforms are the central
Expand Down Expand Up @@ -794,6 +796,16 @@ private function buildCreateTableSQL(Table $table, bool $createForeignKeys): arr
$options['primary'] = [];

foreach ($table->getIndexes() as $index) {
if ($index->isFunctional() && ! $this->supportsFunctionalIndex()) {
throw new InvalidArgumentException(sprintf(
'Index "%s" on table "%s" contains a functional part, ' .
'but platform "%s" does not support functional indexes.',
$index->getName(),
$table->getName(),
substr(static::class, (int) strrpos(static::class, '\\') + 1),
));
}

if (! $index->isPrimary()) {
$options['indexes'][$index->getQuotedName($this)] = $index;

Expand Down Expand Up @@ -1082,6 +1094,16 @@ public function getCreateIndexSQL(Index $index, string $table): string
));
}

if ($index->isFunctional() && ! $this->supportsFunctionalIndex()) {
throw new InvalidArgumentException(sprintf(
'Index "%s" on table "%s" contains a functional part, ' .
'but platform "%s" does not support functional indexes.',
$name,
$table,
substr(static::class, (int) strrpos(static::class, '\\') + 1),
));
}

if ($index->isPrimary()) {
return $this->getCreatePrimaryKeySQL($index, $table);
}
Expand Down Expand Up @@ -1534,6 +1556,15 @@ public function getIndexDeclarationSQL(Index $index): string
throw new InvalidArgumentException('Incomplete definition. "columns" required.');
}

if ($index->isFunctional() && ! $this->supportsFunctionalIndex()) {
throw new InvalidArgumentException(sprintf(
'Index "%s" contains a functional part, ' .
'but platform "%s" does not support functional indexes.',
$index->getName(),
substr(static::class, (int) strrpos(static::class, '\\') + 1),
));

Check warning on line 1565 in src/Platforms/AbstractPlatform.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/AbstractPlatform.php#L1560-L1565

Added lines #L1560 - L1565 were not covered by tests
}

return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $index->getQuotedName($this)
. ' (' . implode(', ', $index->getQuotedColumns($this)) . ')' . $this->getPartialIndexSQL($index);
}
Expand Down Expand Up @@ -1974,6 +2005,14 @@ public function supportsColumnCollation(): bool
return false;
}

/**
* A flag that indicates whether the platform supports functional indexes.
*/
public function supportsFunctionalIndex(): bool
{
return false;
}

/**
* Gets the format string, as accepted by the date() function, that describes
* the format of a stored datetime value of this platform.
Expand Down
23 changes: 23 additions & 0 deletions src/Platforms/MySQL8013Platform.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Platforms;

/**
* Provides features of the MySQL since 8.0.13 database platform.
*
* Note: Should not be used with versions prior to 8.0.13.
*/
class MySQL8013Platform extends MySQL80Platform
{
public function getColumnNameForIndexFetch(): string

Check warning on line 14 in src/Platforms/MySQL8013Platform.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/MySQL8013Platform.php#L14

Added line #L14 was not covered by tests
{
return "COALESCE(COLUMN_NAME, CONCAT('(', REPLACE(EXPRESSION, '\\\''', ''''), ')'))";

Check warning on line 16 in src/Platforms/MySQL8013Platform.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/MySQL8013Platform.php#L16

Added line #L16 was not covered by tests
}

public function supportsFunctionalIndex(): bool
{
return true;
}
}
5 changes: 5 additions & 0 deletions src/Platforms/PostgreSQLPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -781,4 +781,9 @@ public function createSchemaManager(Connection $connection): PostgreSQLSchemaMan
{
return new PostgreSQLSchemaManager($connection, $this);
}

public function supportsFunctionalIndex(): bool

Check warning on line 785 in src/Platforms/PostgreSQLPlatform.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/PostgreSQLPlatform.php#L785

Added line #L785 was not covered by tests
{
return true;

Check warning on line 787 in src/Platforms/PostgreSQLPlatform.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/PostgreSQLPlatform.php#L787

Added line #L787 was not covered by tests
}
}
11 changes: 11 additions & 0 deletions src/Platforms/SQLitePlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
use function sprintf;
use function str_replace;
use function strpos;
use function strrpos;
use function strtolower;
use function substr;
use function trim;
Expand Down Expand Up @@ -561,6 +562,16 @@ public function getCreateIndexSQL(Index $index, string $table): string
));
}

if ($index->isFunctional() && ! $this->supportsFunctionalIndex()) {
throw new InvalidArgumentException(sprintf(
'Index "%s" on table "%s" contains a functional part, ' .
'but platform "%s" does not support functional indexes.',
$name,
$table,
substr(static::class, (int) strrpos(static::class, '\\') + 1),
));

Check warning on line 572 in src/Platforms/SQLitePlatform.php

View check run for this annotation

Codecov / codecov/patch

src/Platforms/SQLitePlatform.php#L566-L572

Added lines #L566 - L572 were not covered by tests
}

if ($index->isPrimary()) {
return $this->getCreatePrimaryKeySQL($index, $table);
}
Expand Down
28 changes: 25 additions & 3 deletions src/Schema/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use function array_search;
use function array_shift;
use function count;
use function str_ends_with;
use function str_starts_with;
use function strtolower;

class Index extends AbstractAsset
Expand All @@ -27,6 +29,8 @@ class Index extends AbstractAsset

protected bool $_isPrimary = false;

protected bool $_isFunctional = false;

/**
* Platform specific flags for indexes.
*
Expand Down Expand Up @@ -58,6 +62,10 @@ public function __construct(

foreach ($columns as $column) {
$this->_addColumn($column);

$this->_isFunctional = $this->_isFunctional === true
? $this->_isFunctional

Check warning on line 67 in src/Schema/Index.php

View check run for this annotation

Codecov / codecov/patch

src/Schema/Index.php#L67

Added line #L67 was not covered by tests
: self::isFunctionalIndex($column);
}

foreach ($flags as $flag) {
Expand Down Expand Up @@ -101,10 +109,14 @@ public function getQuotedColumns(AbstractPlatform $platform): array
foreach ($this->_columns as $column) {
$length = array_shift($subParts);

$quotedColumn = $column->getQuotedName($platform);
if ($this->isFunctional()) {
$quotedColumn = $column->getName();
} else {
$quotedColumn = $column->getQuotedName($platform);

if ($length !== null) {
$quotedColumn .= '(' . $length . ')';
if ($length !== null) {
$quotedColumn .= '(' . $length . ')';

Check warning on line 118 in src/Schema/Index.php

View check run for this annotation

Codecov / codecov/patch

src/Schema/Index.php#L118

Added line #L118 was not covered by tests
}
}

$columns[] = $quotedColumn;
Expand Down Expand Up @@ -137,6 +149,11 @@ public function isPrimary(): bool
return $this->_isPrimary;
}

public function isFunctional(): bool
{
return $this->_isFunctional;
}

public function hasColumnAtPosition(string $name, int $pos = 0): bool
{
$name = $this->trimQuotes(strtolower($name));
Expand Down Expand Up @@ -283,6 +300,11 @@ public function getOptions(): array
return $this->options;
}

public static function isFunctionalIndex(string $name): bool
{
return str_starts_with($name, '(') && str_ends_with($name, ')');
}

/**
* Return whether the two indexes have the same partial index
*/
Expand Down
19 changes: 17 additions & 2 deletions src/Schema/MySQLSchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -390,10 +390,12 @@ protected function selectIndexColumns(string $databaseName, ?string $tableName =
$sql .= ' TABLE_NAME,';
}

$sql .= <<<'SQL'
$columnName = $this->getColumnNameForIndexFetch();

Check warning on line 393 in src/Schema/MySQLSchemaManager.php

View check run for this annotation

Codecov / codecov/patch

src/Schema/MySQLSchemaManager.php#L393

Added line #L393 was not covered by tests

$sql .= <<<SQL

Check warning on line 395 in src/Schema/MySQLSchemaManager.php

View check run for this annotation

Codecov / codecov/patch

src/Schema/MySQLSchemaManager.php#L395

Added line #L395 was not covered by tests
NON_UNIQUE AS Non_Unique,
INDEX_NAME AS Key_name,
COLUMN_NAME AS Column_Name,
{$columnName},

Check warning on line 398 in src/Schema/MySQLSchemaManager.php

View check run for this annotation

Codecov / codecov/patch

src/Schema/MySQLSchemaManager.php#L398

Added line #L398 was not covered by tests
SUB_PART AS Sub_Part,
INDEX_TYPE AS Index_Type
FROM information_schema.STATISTICS
Expand Down Expand Up @@ -539,4 +541,17 @@ private function getDefaultTableOptions(): DefaultTableOptions

return $this->defaultTableOptions;
}

/**
* EXPRESSION
*
* MySQL 8.0.13 and higher supports functional key parts (see Functional Key Parts), which affects both
* the COLUMN_NAME and EXPRESSION columns:
* For a nonfunctional key part, COLUMN_NAME indicates the column indexed by the key part and EXPRESSION is NULL.
* For a functional key part, COLUMN_NAME column is NULL and EXPRESSION indicates the expression for the key part.
*/
private function getColumnNameForIndexFetch(): string

Check warning on line 553 in src/Schema/MySQLSchemaManager.php

View check run for this annotation

Codecov / codecov/patch

src/Schema/MySQLSchemaManager.php#L553

Added line #L553 was not covered by tests
{
return $this->platform->getColumnNameForIndexFetch() . ' as Column_Name';

Check warning on line 555 in src/Schema/MySQLSchemaManager.php

View check run for this annotation

Codecov / codecov/patch

src/Schema/MySQLSchemaManager.php#L555

Added line #L555 was not covered by tests
}
}
Loading

0 comments on commit fb58217

Please sign in to comment.