TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Database\Schema\SchemaManager;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Schema\AbstractSchemaManager;
|
||||
|
||||
/**
|
||||
* Provides a couple of methods use-full to restore Doctrine DBAL 3 and earlier behaviour to
|
||||
* respect doctrine type override within column comment.
|
||||
*
|
||||
* Used within:
|
||||
*
|
||||
* - {@see MySQLSchemaManager::parentGetPortableTableColumnDefinition()}
|
||||
* - {@see PostgreSQLSchemaManager::parentGetPortableTableColumnDefinition()}
|
||||
* - {@see SQLiteSchemaManager::parentGetPortableTableColumnDefinition()}
|
||||
*
|
||||
* This trait contains code cloned or adopted from Doctrine DBAL 3 removed from {@see AbstractSchemaManager} hierarchy:
|
||||
*
|
||||
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/AbstractSchemaManager.php#L1730-L1752
|
||||
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/AbstractSchemaManager.php#L1730-L1752
|
||||
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/SqliteSchemaManager.php#L338-L344
|
||||
*
|
||||
* @internal for use in extended {@see AbstractSchemaManager} hierarchy and not part of public Core API.
|
||||
*/
|
||||
trait ColumnTypeCommentMethodsTrait
|
||||
{
|
||||
/**
|
||||
* Determine Doctrine Type based on column database type with respecting
|
||||
* comment definition as overrule type. That reflects the old behaviour
|
||||
* of Doctrine DBAL 3 and older.
|
||||
*
|
||||
* Code has been adopted from Doctrine DBAL v3 to be used and integrated within extended {@see AbstractSchemaManager}
|
||||
* hierarchy to restore removed behaviour, see:
|
||||
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/MySQLSchemaManager.php#L186-L192
|
||||
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/PostgreSQLSchemaManager.php#L427-L429
|
||||
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/SqliteSchemaManager.php#L338-L344
|
||||
*/
|
||||
private function determineColumnType(string $dbType, array &$tableColumn): string
|
||||
{
|
||||
$platform = ($this instanceof AbstractPlatform) ? $this : $this->platform;
|
||||
$type = $dbType !== '' ? $platform->getDoctrineTypeMapping($dbType) : '';
|
||||
if (isset($tableColumn['comment'])) {
|
||||
$type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
|
||||
$tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Doctrine DBAL 4 removed this from the {@see AbstractSchemaManager} hierarchy, and is here cloned, see:
|
||||
* https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/AbstractSchemaManager.php#L1730-L1752
|
||||
*
|
||||
* Given a table comment this method tries to extract a typehint for Doctrine Type, or returns
|
||||
* the type given as default.
|
||||
*
|
||||
* @param string|null $comment
|
||||
* @param string $currentType
|
||||
*
|
||||
* @return string
|
||||
*@internal This method should be only used from within the extended AbstractSchemaManager class hierarchy.
|
||||
*/
|
||||
private function extractDoctrineTypeFromComment(?string $comment, string $currentType): string
|
||||
{
|
||||
if ($comment !== null && preg_match('(\(DC2Type:(((?!\)).)+)\))', $comment, $match) === 1) {
|
||||
return $match[1];
|
||||
}
|
||||
|
||||
return $currentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Doctrine DBAL 4 removed this from the {@see AbstractSchemaManager} hierarchy, and is here cloned, see:
|
||||
* https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/AbstractSchemaManager.php#L1754-L1773
|
||||
*
|
||||
* @param string|null $comment
|
||||
* @param string|null $type
|
||||
*
|
||||
* @return string|null
|
||||
*@internal This method should be only used from within the extended AbstractSchemaManager class hierarchy.
|
||||
*/
|
||||
private function removeDoctrineTypeFromComment(?string $comment = null, ?string $type = null): ?string
|
||||
{
|
||||
if ($comment === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return str_replace('(DC2Type:' . $type . ')', '', $comment);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Database\Schema\SchemaManager;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform as DoctrineAbstractMySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\MariaDBPlatform as DoctrineMariaDBPlatform;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform as DoctrineMySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform as DoctrinePostgreSQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\SQLitePlatform as DoctrineSQLitePlatform;
|
||||
use Doctrine\DBAL\Schema\AbstractSchemaManager;
|
||||
use Doctrine\DBAL\Schema\SchemaManagerFactory;
|
||||
|
||||
/**
|
||||
* Custom SchemaManager factory to ensure that the extended SchemaManager
|
||||
* classes are used for supported platforms. Without this, custom schema
|
||||
* handling would be cut off.
|
||||
*
|
||||
* Note: This is the transition to mitigate the dropped doctrine event manager
|
||||
* since `doctrine/dbal ^4`.
|
||||
*
|
||||
* @see https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-not-setting-a-schema-manager-factory
|
||||
* @see https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-extension-via-schema-definition-events
|
||||
*
|
||||
* @internal for core internal usage and not part of public core API.
|
||||
*/
|
||||
final class CoreSchemaManagerFactory implements SchemaManagerFactory
|
||||
{
|
||||
public function createSchemaManager(Connection $connection): AbstractSchemaManager
|
||||
{
|
||||
$platform = $connection->getDatabasePlatform();
|
||||
// Platform specific SchemaManager are extended to manipulate the schema handling. TYPO3 needs to
|
||||
// do that to provide additional doctrine type handling and other workarounds or alignments. Long
|
||||
// time this have been done by using the `doctrine EventManager` to hook into several places, which
|
||||
// no longer exists.
|
||||
//
|
||||
// @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-not-setting-a-schema-manager-factory
|
||||
// @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-extension-via-doctrine-event-manager
|
||||
// @todo Consider make check on SchemaManager instance retrieved from $platform->createSchemaManager()
|
||||
return match (true) {
|
||||
$platform instanceof DoctrineSQLitePlatform => new SQLiteSchemaManager($connection, $platform),
|
||||
$platform instanceof DoctrinePostgreSQLPlatform => new PostgreSQLSchemaManager($connection, $platform),
|
||||
$platform instanceof DoctrineMariaDBPlatform,
|
||||
$platform instanceof DoctrineMySQLPlatform,
|
||||
$platform instanceof DoctrineAbstractMySQLPlatform => new MySQLSchemaManager($connection, $platform),
|
||||
default => $platform->createSchemaManager($connection),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Database\Schema\SchemaManager;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\MariaDBPlatform;
|
||||
use Doctrine\DBAL\Platforms\MariaDBPlatform as DoctrineMariaDBPlatform;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform as DoctrineMySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\MySQLSchemaManager as DoctrineMySQLSchemaManager;
|
||||
use Doctrine\DBAL\Types\BlobType;
|
||||
use Doctrine\DBAL\Types\JsonType;
|
||||
use Doctrine\DBAL\Types\TextType;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
/**
|
||||
* Extending the doctrine MySQLSchemaManager to integrate additional processing stuff
|
||||
* due to the dropped event system with `doctrine/dbal 4.x`.
|
||||
*
|
||||
* For example, this is used to process custom doctrine types.
|
||||
*
|
||||
* Platform specific SchemaManager are extended to manipulate the schema handling. TYPO3 needs to
|
||||
* do that to provide additional doctrine type handling and other workarounds or alignments. Long
|
||||
* time this have been done by using the `doctrine EventManager` to hook into several places, which
|
||||
* no longer exists.
|
||||
*
|
||||
* Note: MySQLSchemaManager is used for MySQL and MariaDB. Even doctrine/dbal 4.0 provides no dedicated
|
||||
* schema manager for doctrine/dbal 4.0. Keep this in mind.
|
||||
*
|
||||
* @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-not-setting-a-schema-manager-factory
|
||||
* @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-extension-via-doctrine-event-manager
|
||||
*
|
||||
* @internal not part of the public Core API.
|
||||
*/
|
||||
class MySQLSchemaManager extends DoctrineMySQLSchemaManager
|
||||
{
|
||||
use ColumnTypeCommentMethodsTrait;
|
||||
|
||||
/** @see https://mariadb.com/kb/en/library/string-literals/#escape-sequences */
|
||||
private const MARIADB_ESCAPE_SEQUENCES = [
|
||||
'\\0' => "\0",
|
||||
"\\'" => "'",
|
||||
'\\"' => '"',
|
||||
'\\b' => "\b",
|
||||
'\\n' => "\n",
|
||||
'\\r' => "\r",
|
||||
'\\t' => "\t",
|
||||
'\\Z' => "\x1a",
|
||||
'\\\\' => '\\',
|
||||
'\\%' => '%',
|
||||
'\\_' => '_',
|
||||
|
||||
// Internally, MariaDB escapes single quotes using the standard syntax
|
||||
"''" => "'",
|
||||
];
|
||||
|
||||
private const array MYSQL_ESCAPE_SEQUENCES = [
|
||||
'\\0' => "\0",
|
||||
"\\'" => "'",
|
||||
'\\"' => '"',
|
||||
'\\b' => "\b",
|
||||
'\\n' => "\n",
|
||||
'\\r' => "\r",
|
||||
'\\t' => "\t",
|
||||
'\\Z' => "\x1a",
|
||||
'\\\\' => '\\',
|
||||
'\\%' => '%',
|
||||
'\\_' => '_',
|
||||
|
||||
// internally
|
||||
"''" => "'",
|
||||
];
|
||||
|
||||
private const array MYSQL_UNQUOTE_SEQUENCES = [
|
||||
"\\'" => "'",
|
||||
'\\"' => '"',
|
||||
];
|
||||
|
||||
/**
|
||||
* Gets Table Column Definition.
|
||||
*
|
||||
* @param array<string, mixed> $tableColumn
|
||||
*/
|
||||
protected function _getPortableTableColumnDefinition(array $tableColumn): Column
|
||||
{
|
||||
/** @var DoctrineMariaDBPlatform|DoctrineMySQLPlatform $platform */
|
||||
$platform = $this->platform;
|
||||
$tableColumn = $this->normalizeTableColumnData($tableColumn, $platform);
|
||||
return $this->parentGetPortableTableColumnDefinition($tableColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $tableColumn
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function normalizeTableColumnData(array $tableColumn, DoctrineMariaDBPlatform|DoctrineMySQLPlatform $platform): array
|
||||
{
|
||||
if (!($platform instanceof DoctrineMySQLPlatform)) {
|
||||
return $tableColumn;
|
||||
}
|
||||
|
||||
$tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
|
||||
$dbType = strtolower($tableColumn['type']);
|
||||
|
||||
$columnDefault = $tableColumn['default'] ?? null;
|
||||
$type = Type::getType($platform->getDoctrineTypeMapping($dbType));
|
||||
if ($type instanceof TextType || $type instanceof BlobType || $type instanceof JsonType) {
|
||||
$tableColumn['default'] = $this->getMySQLTextAndBlobColumnDefault($columnDefault);
|
||||
}
|
||||
|
||||
return $tableColumn;
|
||||
}
|
||||
|
||||
protected function getMySQLTextAndBlobColumnDefault(?string $columnDefault): ?string
|
||||
{
|
||||
if ($columnDefault === null || $columnDefault === 'NULL') {
|
||||
return null;
|
||||
}
|
||||
if (str_starts_with($columnDefault, '_')) {
|
||||
$columnDefault = substr($columnDefault, (mb_strpos($columnDefault, '\'') - 1));
|
||||
}
|
||||
if ($columnDefault === "\'\'") {
|
||||
return '';
|
||||
}
|
||||
if (preg_match("/^\\\'(.*)\\\'$/", trim($columnDefault), $matches) === 1) {
|
||||
return strtr(
|
||||
strtr($matches[1], self::MYSQL_ESCAPE_SEQUENCES),
|
||||
// MySQL saves quoted single-quote as escaped single-quote in the INFORMATION SCHEMA table, even
|
||||
// if it has been provided with double-quote quoting and is inconsistent for itself and enforces
|
||||
// a additional unquoting after the un-escaping step
|
||||
self::MYSQL_UNQUOTE_SEQUENCES
|
||||
);
|
||||
}
|
||||
return $columnDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $tableIndexes
|
||||
* @param string $tableName
|
||||
*
|
||||
* @return array<string, Index>
|
||||
*/
|
||||
protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array
|
||||
{
|
||||
// Get doctrine generated list.
|
||||
$tableIndexesList = parent::_getPortableTableIndexesList(
|
||||
// tableIndexes
|
||||
$tableIndexes,
|
||||
// tableName
|
||||
$tableName,
|
||||
);
|
||||
|
||||
// Concatenate index prefix length to column name
|
||||
// @todo Adapt TYPO3 schema comparison to use Index::getOption('lengths')
|
||||
// instead of assuming that the length is concatenated to the column name.
|
||||
return array_map(
|
||||
static function (Index $index): Index {
|
||||
if (!$index->hasOption('lengths')) {
|
||||
return $index;
|
||||
}
|
||||
|
||||
$options = $index->getOptions();
|
||||
$lengths = $options['lengths'];
|
||||
unset($options['lengths']);
|
||||
|
||||
$columns = $index->getColumns();
|
||||
foreach ($columns as $id => $column) {
|
||||
if (!isset($lengths[$id])) {
|
||||
continue;
|
||||
}
|
||||
$columns[$id] = $column . '(' . $lengths[$id] . ')';
|
||||
}
|
||||
|
||||
return new Index(
|
||||
$index->getName(),
|
||||
$columns,
|
||||
$index->isUnique(),
|
||||
$index->isPrimary(),
|
||||
$index->getFlags(),
|
||||
$options
|
||||
);
|
||||
},
|
||||
$tableIndexesList
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Table Column Definition.
|
||||
*
|
||||
* This is a copy of {@see DoctrineMySQLSchemaManager::_getPortableTableColumnDefinition()} with a minor change
|
||||
* to respect column comments for Doctrine Type matching and thus restoring Doctrine DBAL behaviour before v4.x.
|
||||
*
|
||||
* @param array $tableColumn
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function parentGetPortableTableColumnDefinition(array $tableColumn): Column
|
||||
{
|
||||
$tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
|
||||
|
||||
$dbType = $tableColumn['type'];
|
||||
$length = null;
|
||||
$scale = 0;
|
||||
$precision = null;
|
||||
$fixed = false;
|
||||
$values = [];
|
||||
|
||||
// This is the change required for TYPO3 - rest of method is kept (cloned) from original.
|
||||
// Following line differs from \Doctrine\DBAL\Schema\MySQLSchemaManager::_getPortableTableColumnDefinition,
|
||||
// taken from:
|
||||
// - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/MySQLSchemaManager.php#L186-L192
|
||||
$type = $this->determineColumnType($dbType, $tableColumn);
|
||||
|
||||
switch ($dbType) {
|
||||
case 'char':
|
||||
case 'varchar':
|
||||
$length = $tableColumn['character_maximum_length'];
|
||||
break;
|
||||
|
||||
case 'binary':
|
||||
case 'varbinary':
|
||||
$length = $tableColumn['character_octet_length'];
|
||||
break;
|
||||
|
||||
case 'tinytext':
|
||||
$length = AbstractMySQLPlatform::LENGTH_LIMIT_TINYTEXT;
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
$length = AbstractMySQLPlatform::LENGTH_LIMIT_TEXT;
|
||||
break;
|
||||
|
||||
case 'mediumtext':
|
||||
$length = AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMTEXT;
|
||||
break;
|
||||
|
||||
case 'tinyblob':
|
||||
$length = AbstractMySQLPlatform::LENGTH_LIMIT_TINYBLOB;
|
||||
break;
|
||||
|
||||
case 'blob':
|
||||
$length = AbstractMySQLPlatform::LENGTH_LIMIT_BLOB;
|
||||
break;
|
||||
|
||||
case 'mediumblob':
|
||||
$length = AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMBLOB;
|
||||
break;
|
||||
|
||||
case 'float':
|
||||
case 'double':
|
||||
case 'real':
|
||||
case 'numeric':
|
||||
case 'decimal':
|
||||
$precision = $tableColumn['numeric_precision'];
|
||||
if (isset($tableColumn['numeric_scale'])) {
|
||||
$scale = $tableColumn['numeric_scale'];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($dbType) {
|
||||
case 'char':
|
||||
case 'binary':
|
||||
$fixed = true;
|
||||
break;
|
||||
|
||||
case 'enum':
|
||||
$values = $this->parseEnumExpression($tableColumn['column_type']);
|
||||
break;
|
||||
|
||||
case 'set':
|
||||
// --------------------------------------------------------------
|
||||
// `SET` handling and parsing is a custom TYPO3 implementation
|
||||
// --------------------------------------------------------------
|
||||
$values = $this->parseSetExpression($tableColumn['column_type']);
|
||||
// --------------------------------------------------------------
|
||||
}
|
||||
|
||||
if ($this->platform instanceof MariaDBPlatform) {
|
||||
$columnDefault = $this->getMariaDBColumnDefault($this->platform, $tableColumn['default']);
|
||||
} else {
|
||||
$columnDefault = $tableColumn['default'];
|
||||
}
|
||||
|
||||
$options = [
|
||||
'length' => $length,
|
||||
'unsigned' => str_contains($tableColumn['column_type'], 'unsigned'),
|
||||
'fixed' => $fixed,
|
||||
'default' => $columnDefault,
|
||||
'notnull' => $tableColumn['null'] !== 'YES',
|
||||
'scale' => $scale,
|
||||
'precision' => $precision,
|
||||
'autoincrement' => str_contains($tableColumn['extra'], 'auto_increment'),
|
||||
'values' => $values,
|
||||
];
|
||||
|
||||
if (isset($tableColumn['comment'])) {
|
||||
$options['comment'] = $tableColumn['comment'];
|
||||
}
|
||||
|
||||
$column = new Column($tableColumn['field'], Type::getType($type), $options);
|
||||
$column->setPlatformOption('charset', $tableColumn['characterset']);
|
||||
$column->setPlatformOption('collation', $tableColumn['collation']);
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Doctrine/Mysql-compatible column default values for MariaDB 10.2.7+ servers.
|
||||
*
|
||||
* - Since MariaDb 10.2.7 column defaults stored in information_schema are now quoted
|
||||
* to distinguish them from expressions (see MDEV-10134).
|
||||
* - CURRENT_TIMESTAMP, CURRENT_TIME, CURRENT_DATE are stored in information_schema
|
||||
* as current_timestamp(), currdate(), currtime()
|
||||
* - Quoted 'NULL' is not enforced by Maria, it is technically possible to have
|
||||
* null in some circumstances (see https://jira.mariadb.org/browse/MDEV-14053)
|
||||
* - \' is always stored as '' in information_schema (normalized)
|
||||
*
|
||||
* @link https://mariadb.com/kb/en/library/information-schema-columns-table/
|
||||
* @link https://jira.mariadb.org/browse/MDEV-13132
|
||||
*
|
||||
* Copy of {@see DoctrineMySQLSchemaManager::getMariaDBColumnDefault()}
|
||||
*
|
||||
* @param string|null $columnDefault default value as stored in information_schema for MariaDB >= 10.2.7
|
||||
*/
|
||||
private function getMariaDBColumnDefault(MariaDBPlatform $platform, ?string $columnDefault): ?string
|
||||
{
|
||||
if ($columnDefault === 'NULL' || $columnDefault === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preg_match('/^\'(.*)\'$/', $columnDefault, $matches) === 1) {
|
||||
return strtr($matches[1], self::MARIADB_ESCAPE_SEQUENCES);
|
||||
}
|
||||
|
||||
return match ($columnDefault) {
|
||||
'current_timestamp()' => $platform->getCurrentTimestampSQL(),
|
||||
'curdate()' => $platform->getCurrentDateSQL(),
|
||||
'curtime()' => $platform->getCurrentTimeSQL(),
|
||||
default => $columnDefault,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Cloned from {@see DoctrineMySQLSchemaManager::parseEnumExpression()} (4.3.x).
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
private function parseEnumExpression(string $expression): array
|
||||
{
|
||||
$result = preg_match_all("/'([^']*(?:''[^']*)*)'/", $expression, $matches);
|
||||
assert($result !== false);
|
||||
|
||||
return array_map(
|
||||
static fn(string $match): string => strtr($match, ["''" => "'"]),
|
||||
$matches[1],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adopted from {@see DoctrineMySQLSchemaManager::parseEnumExpression()} (4.3.x).
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
private function parseSetExpression(string $expression): array
|
||||
{
|
||||
$result = preg_match_all("/'([^']*(?:''[^']*)*)'/", $expression, $matches);
|
||||
assert($result !== false);
|
||||
|
||||
return array_map(
|
||||
static fn(string $match): string => strtr($match, ["''" => "'"]),
|
||||
$matches[1],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Database\Schema\SchemaManager;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\PostgreSQLSchemaManager as DoctrinePostgreSQLSchemaManager;
|
||||
use Doctrine\DBAL\Types\JsonType;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
/**
|
||||
* Extending the doctrine PostgreSQLSchemaManager to integrate additional processing stuff
|
||||
* due to the dropped event system with `doctrine/dbal 4.x`.
|
||||
*
|
||||
* For example, this is used to process custom doctrine types.
|
||||
*
|
||||
* Platform specific SchemaManager are extended to manipulate the schema handling. TYPO3 needs to
|
||||
* do that to provide additional doctrine type handling and other workarounds or alignments. Long
|
||||
* time this have been done by using the `doctrine EventManager` to hook into several places, which
|
||||
* no longer exists.
|
||||
*
|
||||
* @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-not-setting-a-schema-manager-factory
|
||||
* @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-extension-via-doctrine-event-manager
|
||||
*
|
||||
* @internal not part of the public Core API.
|
||||
*/
|
||||
class PostgreSQLSchemaManager extends DoctrinePostgreSQLSchemaManager
|
||||
{
|
||||
use ColumnTypeCommentMethodsTrait;
|
||||
|
||||
/**
|
||||
* Gets Table Column Definition.
|
||||
*
|
||||
* @param array<string, mixed> $tableColumn
|
||||
*/
|
||||
protected function _getPortableTableColumnDefinition(array $tableColumn): Column
|
||||
{
|
||||
return $this->parentGetPortableTableColumnDefinition($tableColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Table Column Definition.
|
||||
*
|
||||
* This is a copy of {@see DoctrinePostgreSQLSchemaManager::_getPortableTableColumnDefinition()} with a minor change
|
||||
* to respect column comments for Doctrine Type matching and thus restoring Doctrine DBAL behaviour before v4.x.
|
||||
*
|
||||
* @param array $tableColumn
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function parentGetPortableTableColumnDefinition(array $tableColumn): Column
|
||||
{
|
||||
$tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
|
||||
|
||||
$length = null;
|
||||
$precision = null;
|
||||
$scale = 0;
|
||||
$fixed = false;
|
||||
$jsonb = false;
|
||||
|
||||
$dbType = $tableColumn['type'];
|
||||
|
||||
if (
|
||||
$tableColumn['domain_type'] !== null
|
||||
&& ! $this->platform->hasDoctrineTypeMappingFor($dbType)
|
||||
) {
|
||||
$dbType = $tableColumn['domain_type'];
|
||||
$completeType = $tableColumn['domain_complete_type'];
|
||||
} else {
|
||||
$completeType = $tableColumn['complete_type'];
|
||||
}
|
||||
|
||||
// This is the change required for TYPO3 - rest of method is kept (cloned) from original.
|
||||
// Following line differs from \Doctrine\DBAL\Schema\MySQLSchemaManager::_getPortableTableColumnDefinition,
|
||||
// taken from:
|
||||
// - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/PostgreSQLSchemaManager.php#L427-L429
|
||||
$type = $this->determineColumnType($dbType, $tableColumn);
|
||||
|
||||
switch ($dbType) {
|
||||
case 'bpchar':
|
||||
case 'varchar':
|
||||
$parameters = $this->parseColumnTypeParameters($completeType);
|
||||
if (count($parameters) > 0) {
|
||||
$length = $parameters[0];
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'double':
|
||||
case 'decimal':
|
||||
case 'money':
|
||||
case 'numeric':
|
||||
$parameters = $this->parseColumnTypeParameters($completeType);
|
||||
if (count($parameters) > 0) {
|
||||
$precision = $parameters[0];
|
||||
}
|
||||
|
||||
if (count($parameters) > 1) {
|
||||
$scale = $parameters[1];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($dbType === 'bpchar') {
|
||||
$fixed = true;
|
||||
} elseif ($dbType === 'jsonb') {
|
||||
$jsonb = true;
|
||||
}
|
||||
|
||||
$options = [
|
||||
'length' => $length,
|
||||
'notnull' => (bool)$tableColumn['isnotnull'],
|
||||
'default' => $this->parseDefaultExpression($tableColumn['default']),
|
||||
'precision' => $precision,
|
||||
'scale' => $scale,
|
||||
'fixed' => $fixed,
|
||||
'autoincrement' => $tableColumn['attidentity'] === 'd',
|
||||
];
|
||||
|
||||
if ($tableColumn['comment'] !== null) {
|
||||
$options['comment'] = $tableColumn['comment'];
|
||||
}
|
||||
|
||||
$column = new Column($tableColumn['field'], Type::getType($type), $options);
|
||||
|
||||
if (! empty($tableColumn['collation'])) {
|
||||
$column->setPlatformOption('collation', $tableColumn['collation']);
|
||||
}
|
||||
|
||||
if ($column->getType() instanceof JsonType) {
|
||||
$column->setPlatformOption('jsonb', $jsonb);
|
||||
}
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a default value expression as given by PostgreSQL
|
||||
*
|
||||
* Copy of {@see DoctrinePostgreSQLSchemaManager::parseDefaultExpression()} (Doctrine DBAL 4.3.x)
|
||||
*/
|
||||
private function parseDefaultExpression(?string $expression): mixed
|
||||
{
|
||||
if ($expression === null || str_starts_with($expression, 'NULL::')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($expression === 'true') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($expression === 'false') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (preg_match("/^'(.*)'::/s", $expression, $matches) === 1) {
|
||||
return str_replace("''", "'", $matches[1]);
|
||||
}
|
||||
|
||||
return $expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the parameters between parenthesis in the data type.
|
||||
*
|
||||
* Copy of {@see DoctrinePostgreSQLSchemaManager::parseColumnTypeParameters()}
|
||||
*
|
||||
* @return list<int>
|
||||
*/
|
||||
private function parseColumnTypeParameters(string $type): array
|
||||
{
|
||||
if (preg_match('/\((\d+)(?:,(\d+))?\)/', $type, $matches) !== 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$parameters = [(int)$matches[1]];
|
||||
|
||||
if (isset($matches[2])) {
|
||||
$parameters[] = (int)$matches[2];
|
||||
}
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Database\Schema\SchemaManager;
|
||||
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\SQLiteSchemaManager as DoctrineSQLiteSchemaManager;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
/**
|
||||
* Extending the doctrine SQLiteSchemaManager to integrate additional processing stuff
|
||||
* due to the dropped event system with `doctrine/dbal 4.x`.
|
||||
*
|
||||
* For example, this is used to process custom doctrine types.
|
||||
*
|
||||
* Platform specific SchemaManager are extended to manipulate the schema handling. TYPO3 needs to
|
||||
* do that to provide additional doctrine type handling and other workarounds or alignments. Long
|
||||
* time this have been done by using the `doctrine EventManager` to hook into several places, which
|
||||
* no longer exists.
|
||||
*
|
||||
* @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-not-setting-a-schema-manager-factory
|
||||
* @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-extension-via-doctrine-event-manager
|
||||
*
|
||||
* @internal not part of the public Core API.
|
||||
*/
|
||||
class SQLiteSchemaManager extends DoctrineSQLiteSchemaManager
|
||||
{
|
||||
use ColumnTypeCommentMethodsTrait;
|
||||
|
||||
/**
|
||||
* Doctrine DBAL v4 dropped column comment based type api, which TYPO3 still needs. To mitigate this, this
|
||||
* method is overridden to reapply the type comment removal, adopted from:
|
||||
*
|
||||
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/SqliteSchemaManager.php#L338-L344
|
||||
*
|
||||
* by using {@see ColumnTypeCommentMethodsTrait::determineColumnType()} to reuse methods.
|
||||
*/
|
||||
protected function _getPortableTableColumnList(string $table, string $database, array $tableColumns): array
|
||||
{
|
||||
$list = parent::_getPortableTableColumnList($table, $database, $tableColumns);
|
||||
foreach ($list as $columnName => $column) {
|
||||
$fakeTableColumn = [
|
||||
'type' => $column->getType(),
|
||||
'comment' => $column->getComment(),
|
||||
];
|
||||
$type = $this->determineColumnType('', $fakeTableColumn);
|
||||
if ($type !== '') {
|
||||
$column->setType(Type::getType($type));
|
||||
}
|
||||
$column->setComment($fakeTableColumn['comment']);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Table Column Definition.
|
||||
*
|
||||
* @param array<string, mixed> $tableColumn
|
||||
*/
|
||||
protected function _getPortableTableColumnDefinition(array $tableColumn): Column
|
||||
{
|
||||
return parent::_getPortableTableColumnDefinition($tableColumn);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user