TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
<?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;
|
||||
|
||||
use Doctrine\DBAL\Schema\Comparator as DoctrineComparator;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\TableDiff as DoctrineTableDiff;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
|
||||
/**
|
||||
* Compares two Schemas and returns an instance of SchemaDiff.
|
||||
*
|
||||
* @internal not part of public core API.
|
||||
*/
|
||||
final readonly class Comparator
|
||||
{
|
||||
public function __construct(
|
||||
private DoctrineComparator $comparator
|
||||
) {}
|
||||
|
||||
public function compareSchemas(Schema $oldSchema, Schema $newSchema): SchemaDiff
|
||||
{
|
||||
$schemaDiff = $this->comparator->compareSchemas($oldSchema, $newSchema);
|
||||
|
||||
$alteredTables = $this->mapAlteredTablesToTypo3TableDiff($schemaDiff->getAlteredTables());
|
||||
$alteredTables = SchemaDiff::ensureCollection(...$alteredTables);
|
||||
$alteredTables = $this->compareTableOptions($oldSchema, $newSchema, $alteredTables);
|
||||
|
||||
return SchemaDiff::ensure(
|
||||
$schemaDiff,
|
||||
[
|
||||
'alteredTables' => $alteredTables,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<DoctrineTableDiff> $alteredTables
|
||||
* @return array<TableDiff>
|
||||
*/
|
||||
private function mapAlteredTablesToTypo3TableDiff(array $alteredTables): array
|
||||
{
|
||||
return array_map(
|
||||
static fn(DoctrineTableDiff $tableDiff): TableDiff => TableDiff::ensure($tableDiff),
|
||||
$alteredTables
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide change information about table options like the ENGINE (#77786)
|
||||
* which are not implemented by doctrine/dbal itself
|
||||
*
|
||||
* @param array<string, TableDiff> $alteredTables
|
||||
* @return array<string, TableDiff>
|
||||
*/
|
||||
private function compareTableOptions(Schema $oldSchema, Schema $newSchema, array $alteredTables): array
|
||||
{
|
||||
foreach ($newSchema->getTables() as $newTable) {
|
||||
$newTableName = $newTable->getShortestName($newSchema->getName());
|
||||
if (!$oldSchema->hasTable($newTableName)) {
|
||||
// new table, no ALTER TABLE needed
|
||||
continue;
|
||||
}
|
||||
|
||||
$oldTable = $oldSchema->getTable($newTableName);
|
||||
$newTableOptions = array_merge($oldTable->getOptions(), $newTable->getOptions());
|
||||
$optionDiff = ArrayUtility::arrayDiffAssocRecursive($newTableOptions, $oldTable->getOptions());
|
||||
if ($optionDiff === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = $newTable->getName();
|
||||
$tableDiff = $alteredTables[$key] ?? new TableDiff($newTable);
|
||||
$tableDiff->setTableOptions($optionDiff);
|
||||
$alteredTables[$key] = $tableDiff;
|
||||
}
|
||||
|
||||
return $alteredTables;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
<?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\Exception;
|
||||
|
||||
use TYPO3\CMS\Core\Exception;
|
||||
|
||||
/**
|
||||
* A detail exception thrown within DefaultTcaSchema.
|
||||
*
|
||||
* @internal not part of public core API.
|
||||
*/
|
||||
class DefaultTcaSchemaTablePositionException extends Exception {}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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\Exception;
|
||||
|
||||
/**
|
||||
* @internal not part of public core API.
|
||||
*/
|
||||
class StatementException extends \Exception
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function sqlError(string $sql): StatementException
|
||||
{
|
||||
return new self($sql, 1471504820);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function syntaxError(string $message, ?\Exception $previous = null): StatementException
|
||||
{
|
||||
return new self('[SQL Error] ' . $message, 1471504821, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function semanticError(string $message, ?\Exception $previous = null): StatementException
|
||||
{
|
||||
return new self('[Semantic Error] ' . $message, 1471504822, $previous);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?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\Information;
|
||||
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Types\Exception\TypesException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use TYPO3\CMS\Core\Database\Schema\SchemaInformation;
|
||||
|
||||
/**
|
||||
* Provides subset of column schema information compared to {@see Column} and intended to be cacheable.
|
||||
*
|
||||
* @internal This class is only for internal core usage and is not part of the public core API.
|
||||
*/
|
||||
final readonly class ColumnInfo
|
||||
{
|
||||
/**
|
||||
* @param string[] $values
|
||||
*/
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $typeName,
|
||||
public mixed $default,
|
||||
public bool $notNull,
|
||||
public ?int $length,
|
||||
public ?int $precision,
|
||||
public int $scale,
|
||||
public bool $fixed,
|
||||
public bool $unsigned,
|
||||
public bool $autoincrement,
|
||||
public array $values,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws TypesException
|
||||
*/
|
||||
public function getType(): Type
|
||||
{
|
||||
return Type::getType($this->typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in {@see SchemaInformation::buildTableInformation()} to transform doctrine Columns to ColumnInfo.
|
||||
*/
|
||||
public static function convertFromDoctrineColumn(Column $column): self
|
||||
{
|
||||
// `Column->getType()` is not passed here by intention to mitigate cache issues getting information from
|
||||
// persisted cache due to `sbl_object_id()` usage in the Doctrine DBAL TypesRegistry not matching the
|
||||
// type later on. Skipping it here and not having it as class property is part of the mitigation strategy
|
||||
// and resolves the cache issues with `Column` directly.
|
||||
return new self(
|
||||
name: $column->getName(),
|
||||
typeName: Type::lookupName($column->getType()),
|
||||
default: $column->getDefault(),
|
||||
notNull: $column->getNotnull(),
|
||||
length: $column->getLength(),
|
||||
precision: $column->getPrecision(),
|
||||
scale: $column->getScale(),
|
||||
fixed: $column->getFixed(),
|
||||
unsigned: $column->getUnsigned(),
|
||||
autoincrement: $column->getAutoincrement(),
|
||||
values: $column->getValues(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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\Information;
|
||||
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
|
||||
/**
|
||||
* Provides reduced table information compared to {@see Table} and intended to be cacheable.
|
||||
*
|
||||
* @internal This class is only for internal core usage and is not part of the public core API.
|
||||
*/
|
||||
final readonly class TableInfo
|
||||
{
|
||||
/**
|
||||
* @param array<string, ColumnInfo> $columnInfos
|
||||
*/
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private array $columnInfos,
|
||||
) {}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function hasColumnInfo(string $columnName): bool
|
||||
{
|
||||
return in_array($columnName, $this->getColumnNames(), true);
|
||||
}
|
||||
|
||||
public function getColumnInfo(string $columnName): ?ColumnInfo
|
||||
{
|
||||
return $this->columnInfos[$columnName] ?? null;
|
||||
}
|
||||
|
||||
public function getColumnNames(): array
|
||||
{
|
||||
return array_keys($this->columnInfos);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, ColumnInfo>
|
||||
*/
|
||||
public function getColumnInfos(): array
|
||||
{
|
||||
return $this->columnInfos;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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\Parser\AST;
|
||||
|
||||
/**
|
||||
* Base class for all definition items that can occur in the definition
|
||||
* of a table, namely fields, indexes and foreign keys.
|
||||
*/
|
||||
abstract class AbstractCreateDefinitionItem {}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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\Parser\AST;
|
||||
|
||||
/**
|
||||
* Base class for all create type statements like CREATE TABLE
|
||||
* or CREATE VIEW.
|
||||
*/
|
||||
abstract class AbstractCreateStatement {}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?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\Parser\AST;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\AbstractDataType;
|
||||
|
||||
/**
|
||||
* Syntax tree node for column definitions within "create table" statements.
|
||||
* Holds basic attributes common to all types of columns.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CreateColumnDefinitionItem extends AbstractCreateDefinitionItem
|
||||
{
|
||||
public bool $allowNull = true;
|
||||
// Has explicit default value?
|
||||
public bool $hasDefaultValue = false;
|
||||
// The explicit default value
|
||||
public mixed $defaultValue = null;
|
||||
public bool $autoIncrement = false;
|
||||
// Create non-unique index for column?
|
||||
public bool $index = false;
|
||||
// Create unique constraint for column?
|
||||
public bool $unique = false;
|
||||
// Use column as primary key for table?
|
||||
public bool $primary = false;
|
||||
public ?string $comment = null;
|
||||
// Column format: "dynamic" or "fixed"
|
||||
public ?string $columnFormat = null;
|
||||
// The storage type for the column (ignored unless MySQL Cluster with NDB Engine)
|
||||
public ?string $storage = null;
|
||||
public ?ReferenceDefinition $reference = null;
|
||||
|
||||
public function __construct(
|
||||
public readonly Identifier $columnName,
|
||||
public readonly AbstractDataType $dataType,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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\Parser\AST;
|
||||
|
||||
/**
|
||||
* Syntax node for the whole definition of a table/view. Collects
|
||||
* the nodes for fields, indexes and foreign keys.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final readonly class CreateDefinition
|
||||
{
|
||||
/**
|
||||
* @param AbstractCreateDefinitionItem[] $items
|
||||
*/
|
||||
public function __construct(
|
||||
public array $items
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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\Parser\AST;
|
||||
|
||||
/**
|
||||
* Syntax node to structure a foreign key definition.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CreateForeignKeyDefinitionItem extends AbstractCreateDefinitionItem
|
||||
{
|
||||
/**
|
||||
* @param IndexColumnName[] $columnNames
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly Identifier $indexName,
|
||||
public readonly array $columnNames,
|
||||
public readonly ReferenceDefinition $reference,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?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\Parser\AST;
|
||||
|
||||
/**
|
||||
* Syntax node to structure an index definition.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CreateIndexDefinitionItem extends AbstractCreateDefinitionItem
|
||||
{
|
||||
// Use a special index type (MySQL: BTREE | HASH)
|
||||
public string $indexType = '';
|
||||
// @var IndexColumnName[]
|
||||
public array $columnNames = [];
|
||||
// Index options KEY_BLOCK_SIZE, USING, WITH PARSER or COMMENT
|
||||
public array $options = [];
|
||||
|
||||
public function __construct(
|
||||
public readonly ?Identifier $indexName = null,
|
||||
public readonly bool $isPrimary = false,
|
||||
public readonly bool $isUnique = false,
|
||||
public readonly bool $isSpatial = false,
|
||||
public readonly bool $isFulltext = false
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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\Parser\AST;
|
||||
|
||||
/**
|
||||
* Syntax node to represent the initial CREATE TABLE statement in the
|
||||
* syntax tree. Represents everything up to the start of the definition
|
||||
* of fields/indexes/foreign keys.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CreateTableClause
|
||||
{
|
||||
public function __construct(
|
||||
public readonly Identifier $tableName,
|
||||
public bool $isTemporary = false
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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\Parser\AST;
|
||||
|
||||
/**
|
||||
* Root node for a CREATE TABLE statement in the syntax tree.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CreateTableStatement extends AbstractCreateStatement
|
||||
{
|
||||
public Identifier $tableName;
|
||||
public bool $isTemporary = false;
|
||||
public array $tableOptions = [];
|
||||
|
||||
public function __construct(
|
||||
CreateTableClause $createTableClause,
|
||||
public readonly CreateDefinition $createDefinition
|
||||
) {
|
||||
$this->tableName = $createTableClause->tableName;
|
||||
$this->isTemporary = $createTableClause->isTemporary;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Base class for all data types that contains properties
|
||||
* common to all data types.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractDataType
|
||||
{
|
||||
/** Used by most field types for length/precision information */
|
||||
protected int $length = 0;
|
||||
/** Used for floating point type columns. -1 is used to indicate no value has been set. */
|
||||
protected int $precision = -1;
|
||||
/** Used for floating point type columns. -1 is used to indicate that no value has been set. */
|
||||
protected int $scale = -1;
|
||||
/** Differentiate between CHAR/VARCHAR and BINARY/VARBINARY */
|
||||
protected bool $fixed = false;
|
||||
/** Unsigned flag for numeric columns */
|
||||
protected bool $unsigned = false;
|
||||
/** Extra options for a column that control specific features/flags */
|
||||
protected array $options = [];
|
||||
/** Options for ENUM/SET data types */
|
||||
protected array $values = [];
|
||||
|
||||
public function getLength(): int
|
||||
{
|
||||
return $this->length;
|
||||
}
|
||||
|
||||
public function setLength(int $length): void
|
||||
{
|
||||
$this->length = $length;
|
||||
}
|
||||
|
||||
public function getPrecision(): int
|
||||
{
|
||||
return $this->precision;
|
||||
}
|
||||
|
||||
public function setPrecision(int $precision): void
|
||||
{
|
||||
$this->precision = $precision;
|
||||
}
|
||||
|
||||
public function getScale(): int
|
||||
{
|
||||
return $this->scale;
|
||||
}
|
||||
|
||||
public function setScale(int $scale): void
|
||||
{
|
||||
$this->scale = $scale;
|
||||
}
|
||||
|
||||
public function isFixed(): bool
|
||||
{
|
||||
return $this->fixed;
|
||||
}
|
||||
|
||||
public function setFixed(bool $fixed): void
|
||||
{
|
||||
$this->fixed = $fixed;
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
public function setOptions(array $options): void
|
||||
{
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
public function isUnsigned(): bool
|
||||
{
|
||||
return $this->unsigned;
|
||||
}
|
||||
|
||||
public function setUnsigned(bool $unsigned): void
|
||||
{
|
||||
$this->unsigned = $unsigned;
|
||||
}
|
||||
|
||||
public function getValues(): array
|
||||
{
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
public function setValues(array $values): void
|
||||
{
|
||||
$this->values = $values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the BIGINT SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class BigIntDataType extends IntegerDataType {}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the BINARY SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class BinaryDataType extends AbstractDataType
|
||||
{
|
||||
public function __construct(int $length)
|
||||
{
|
||||
/**
|
||||
* BINARY is "fixed type". Setting it here instructs Doctrine DBAL to use this type instead of
|
||||
* the "variable type" when being transformed within the {@see TableBuilder::addColumn()} method.
|
||||
*/
|
||||
$this->fixed = true;
|
||||
$this->length = $length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the BIT SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class BitDataType extends AbstractDataType
|
||||
{
|
||||
public function __construct(int $length)
|
||||
{
|
||||
$this->length = $length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the BLOB SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BlobDataType extends AbstractDataType
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// MySQL BLOB can store 64KB
|
||||
$this->length = 65535;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the CHAR SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CharDataType extends AbstractDataType
|
||||
{
|
||||
public function __construct(int $length, array $options)
|
||||
{
|
||||
/**
|
||||
* CHAR is "fixed type". Setting it here instructs Doctrine DBAL to use this type instead of
|
||||
* the "variable type" when being transformed within the {@see TableBuilder::addColumn()} method.
|
||||
*/
|
||||
$this->fixed = true;
|
||||
$this->length = $length;
|
||||
$this->options = $options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the DATE SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class DateDataType extends AbstractDataType {}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the DATETIME SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class DateTimeDataType extends AbstractDataType
|
||||
{
|
||||
public function __construct(int $length)
|
||||
{
|
||||
$this->length = $length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the DECIMAL SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class DecimalDataType extends AbstractDataType
|
||||
{
|
||||
public function __construct(array $dataTypeDecimals, array $dataTypeOptions)
|
||||
{
|
||||
$this->precision = $dataTypeDecimals['length'] ?? -1;
|
||||
$this->scale = $dataTypeDecimals['decimals'] ?? -1;
|
||||
$this->options = $dataTypeOptions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the DOUBLE SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class DoubleDataType extends FloatDataType {}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the ENUM SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class EnumDataType extends AbstractDataType
|
||||
{
|
||||
public function __construct(array $values, array $options)
|
||||
{
|
||||
$this->values = $values;
|
||||
$this->options = $options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the FLOAT SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class FloatDataType extends AbstractDataType
|
||||
{
|
||||
public function __construct(array $dataTypeDecimals, array $dataTypeOptions)
|
||||
{
|
||||
// -1 is used to indicate that no value has been provided
|
||||
$this->precision = $dataTypeDecimals['length'] ?? -1;
|
||||
$this->scale = $dataTypeDecimals['decimals'] ?? -1;
|
||||
$this->options = $dataTypeOptions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the INT SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class IntegerDataType extends AbstractDataType
|
||||
{
|
||||
public function __construct(int $length, array $options)
|
||||
{
|
||||
$this->length = $length;
|
||||
$this->options = $options;
|
||||
if (array_key_exists('unsigned', $options) && $options['unsigned']) {
|
||||
$this->setUnsigned(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the JSON SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class JsonDataType extends AbstractDataType
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// JSON is not yet supported by Doctrine 2.5 and will be remapped
|
||||
// to a TEXT type. Setting the length here will ensure a LONGTEXT
|
||||
// column type is selected.
|
||||
$this->length = 2147483647;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the LONGBLOB SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class LongBlobDataType extends BlobDataType
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
// MySQL LONGBLOB can store 4GB of data, to be 32bit safe only claim 2GB
|
||||
$this->length = 2147483647;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the LONGTEXT SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class LongTextDataType extends TextDataType
|
||||
{
|
||||
public function __construct(array $options)
|
||||
{
|
||||
parent::__construct($options);
|
||||
// MySQL LONGTEXT can store 4GB of data, to be 32bit safe only claim 2GB
|
||||
$this->length = 2147483647;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the MEDIUMBLOB SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class MediumBlobDataType extends BlobDataType
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
// MySQL MEDIUMBLOB can store 16MB
|
||||
$this->length = 16777215;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the MEDIUMINT SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class MediumIntDataType extends IntegerDataType {}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the MEDIUMTEXT SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class MediumTextDataType extends TextDataType
|
||||
{
|
||||
public function __construct(array $options)
|
||||
{
|
||||
parent::__construct($options);
|
||||
// MySQL MEDIUMTEXT can store 16MB
|
||||
$this->length = 16777215;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the NUMERIC SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class NumericDataType extends DecimalDataType {}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the REAL SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class RealDataType extends FloatDataType {}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the SET SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class SetDataType extends AbstractDataType
|
||||
{
|
||||
public function __construct(array $values, array $options)
|
||||
{
|
||||
$this->values = $values;
|
||||
$this->options = $options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the SMALLINT SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class SmallIntDataType extends IntegerDataType {}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the TEXT SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class TextDataType extends AbstractDataType
|
||||
{
|
||||
public function __construct(array $options)
|
||||
{
|
||||
// MySQL TEXT can store 64KB
|
||||
$this->length = 65535;
|
||||
$this->options = $options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the TIME SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class TimeDataType extends AbstractDataType
|
||||
{
|
||||
public function __construct(int $length)
|
||||
{
|
||||
$this->length = $length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the TIMESTAMP SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class TimestampDataType extends AbstractDataType
|
||||
{
|
||||
public function __construct(int $length)
|
||||
{
|
||||
$this->length = $length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the TINYBLOB SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class TinyBlobDataType extends BlobDataType
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
// MySQL TINYBLOB can store 255 bytes
|
||||
$this->length = 255;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the TINYINT SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class TinyIntDataType extends IntegerDataType {}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the TINYTEXT SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class TinyTextDataType extends TextDataType
|
||||
{
|
||||
public function __construct(array $options)
|
||||
{
|
||||
parent::__construct($options);
|
||||
// MySQL TINYTEXT can store 255 characters
|
||||
$this->length = 255;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* @internal for `EXT:core` internal usage and not part of public API.
|
||||
*/
|
||||
final class UuidDataType extends AbstractDataType {}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the VARBINARY SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class VarBinaryDataType extends AbstractDataType
|
||||
{
|
||||
public function __construct(int $length)
|
||||
{
|
||||
$this->length = $length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the VARCHAR SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class VarCharDataType extends AbstractDataType
|
||||
{
|
||||
public function __construct(int $length, array $options)
|
||||
{
|
||||
$this->length = $length;
|
||||
$this->options = $options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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\Parser\AST\DataType;
|
||||
|
||||
/**
|
||||
* Node representing the YEAR SQL column type
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class YearDataType extends AbstractDataType {}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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\Parser\AST;
|
||||
|
||||
/**
|
||||
* Syntax node to represent identifiers used in various parts of a
|
||||
* SQL statements like table, field or index names.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Identifier
|
||||
{
|
||||
private string $quoteChar = '`';
|
||||
|
||||
public function __construct(
|
||||
public readonly string $schemaObjectName
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Quotes the schema object name.
|
||||
*/
|
||||
public function getQuotedName(): string
|
||||
{
|
||||
$c = $this->quoteChar;
|
||||
return $c . str_replace($c, $c . $c, $this->schemaObjectName) . $c;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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\Parser\AST;
|
||||
|
||||
/**
|
||||
* Syntax node to represent a column within an index, which can in MySQL
|
||||
* context consist of the actual column name, length information for a partial
|
||||
* index and a direction which influences default sorting and access patterns.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final readonly class IndexColumnName
|
||||
{
|
||||
public function __construct(
|
||||
public Identifier $columnName,
|
||||
public int $length,
|
||||
public ?string $direction = null
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?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\Parser\AST;
|
||||
|
||||
/**
|
||||
* Syntax node to represent the REFERENCES part of a foreign key
|
||||
* definition, encapsulating ON UPDATE/ON DELETE actions as well
|
||||
* as the foreign table name and columns.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ReferenceDefinition
|
||||
{
|
||||
// Match type if given: FULL, PARTIAL or SIMPLE
|
||||
public ?string $match = null;
|
||||
// Reference option if given: RESTRICT | CASCADE | SET NULL | NO ACTION
|
||||
public ?string $onDelete = null;
|
||||
// Reference option if given: RESTRICT | CASCADE | SET NULL | NO ACTION
|
||||
public ?string $onUpdate = null;
|
||||
|
||||
/**
|
||||
* @param IndexColumnName[] $columnNames
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly Identifier $tableName,
|
||||
public readonly array $columnNames,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
<?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\Parser;
|
||||
|
||||
use Doctrine\Common\Lexer\AbstractLexer;
|
||||
|
||||
/**
|
||||
* Scans a MySQL CREATE TABLE statement for tokens.
|
||||
*/
|
||||
class Lexer extends AbstractLexer
|
||||
{
|
||||
// All tokens that are not valid identifiers must be < 100
|
||||
public const T_NONE = 1;
|
||||
public const T_STRING = 2;
|
||||
public const T_INPUT_PARAMETER = 3;
|
||||
public const T_CLOSE_PARENTHESIS = 4;
|
||||
public const T_OPEN_PARENTHESIS = 5;
|
||||
public const T_COMMA = 6;
|
||||
public const T_DIVIDE = 7;
|
||||
public const T_DOT = 8;
|
||||
public const T_EQUALS = 9;
|
||||
public const T_GREATER_THAN = 10;
|
||||
public const T_LOWER_THAN = 11;
|
||||
public const T_MINUS = 12;
|
||||
public const T_MULTIPLY = 13;
|
||||
public const T_NEGATE = 14;
|
||||
public const T_PLUS = 15;
|
||||
public const T_OPEN_CURLY_BRACE = 16;
|
||||
public const T_CLOSE_CURLY_BRACE = 17;
|
||||
public const T_SEMICOLON = 18;
|
||||
|
||||
// All tokens that are identifiers or keywords that could be considered as identifiers should be >= 100
|
||||
public const T_IDENTIFIER = 100;
|
||||
|
||||
// All tokens that could be considered as a data type should be >= 200
|
||||
public const T_BIT = 201;
|
||||
public const T_TINYINT = 202;
|
||||
public const T_SMALLINT = 203;
|
||||
public const T_MEDIUMINT = 204;
|
||||
public const T_INT = 205;
|
||||
public const T_INTEGER = 206;
|
||||
public const T_BIGINT = 207;
|
||||
public const T_REAL = 208;
|
||||
public const T_DOUBLE = 209;
|
||||
public const T_FLOAT = 210;
|
||||
public const T_DECIMAL = 211;
|
||||
public const T_NUMERIC = 212;
|
||||
public const T_DATE = 213;
|
||||
public const T_TIME = 214;
|
||||
public const T_TIMESTAMP = 215;
|
||||
public const T_DATETIME = 216;
|
||||
public const T_YEAR = 217;
|
||||
public const T_CHAR = 218;
|
||||
public const T_VARCHAR = 219;
|
||||
public const T_BINARY = 220;
|
||||
public const T_VARBINARY = 221;
|
||||
public const T_TINYBLOB = 222;
|
||||
public const T_BLOB = 223;
|
||||
public const T_MEDIUMBLOB = 224;
|
||||
public const T_LONGBLOB = 225;
|
||||
public const T_TINYTEXT = 226;
|
||||
public const T_TEXT = 227;
|
||||
public const T_MEDIUMTEXT = 228;
|
||||
public const T_LONGTEXT = 229;
|
||||
public const T_ENUM = 230;
|
||||
public const T_SET = 231;
|
||||
public const T_JSON = 232;
|
||||
public const T_UUID = 233;
|
||||
|
||||
// All keyword tokens should be >= 300
|
||||
public const T_CREATE = 300;
|
||||
public const T_TEMPORARY = 301;
|
||||
public const T_TABLE = 302;
|
||||
public const T_IF = 303;
|
||||
public const T_NOT = 304;
|
||||
public const T_EXISTS = 305;
|
||||
public const T_CONSTRAINT = 306;
|
||||
public const T_INDEX = 307;
|
||||
public const T_KEY = 308;
|
||||
public const T_FULLTEXT = 309;
|
||||
public const T_SPATIAL = 310;
|
||||
public const T_PRIMARY = 311;
|
||||
public const T_UNIQUE = 312;
|
||||
public const T_CHECK = 313;
|
||||
public const T_DEFAULT = 314;
|
||||
public const T_AUTO_INCREMENT = 315;
|
||||
public const T_COMMENT = 316;
|
||||
public const T_COLUMN_FORMAT = 317;
|
||||
public const T_STORAGE = 318;
|
||||
public const T_REFERENCES = 319;
|
||||
public const T_NULL = 320;
|
||||
public const T_FIXED = 321;
|
||||
public const T_DYNAMIC = 322;
|
||||
public const T_MEMORY = 323;
|
||||
public const T_DISK = 324;
|
||||
public const T_UNSIGNED = 325;
|
||||
public const T_ZEROFILL = 326;
|
||||
public const T_CURRENT_TIMESTAMP = 327;
|
||||
public const T_CHARACTER = 328;
|
||||
public const T_COLLATE = 329;
|
||||
public const T_ASC = 330;
|
||||
public const T_DESC = 331;
|
||||
public const T_MATCH = 332;
|
||||
public const T_FULL = 333;
|
||||
public const T_PARTIAL = 334;
|
||||
public const T_SIMPLE = 335;
|
||||
public const T_ON = 336;
|
||||
public const T_UPDATE = 337;
|
||||
public const T_DELETE = 338;
|
||||
public const T_RESTRICT = 339;
|
||||
public const T_CASCADE = 340;
|
||||
public const T_NO = 341;
|
||||
public const T_ACTION = 342;
|
||||
public const T_USING = 343;
|
||||
public const T_BTREE = 344;
|
||||
public const T_HASH = 345;
|
||||
public const T_KEY_BLOCK_SIZE = 346;
|
||||
public const T_WITH = 347;
|
||||
public const T_PARSER = 348;
|
||||
public const T_FOREIGN = 349;
|
||||
public const T_ENGINE = 350;
|
||||
public const T_AVG_ROW_LENGTH = 351;
|
||||
public const T_CHECKSUM = 352;
|
||||
public const T_COMPRESSION = 353;
|
||||
public const T_CONNECTION = 354;
|
||||
public const T_DATA = 355;
|
||||
public const T_DIRECTORY = 356;
|
||||
public const T_DELAY_KEY_WRITE = 357;
|
||||
public const T_ENCRYPTION = 358;
|
||||
public const T_INSERT_METHOD = 359;
|
||||
public const T_MAX_ROWS = 360;
|
||||
public const T_MIN_ROWS = 361;
|
||||
public const T_PACK_KEYS = 362;
|
||||
public const T_PASSWORD = 363;
|
||||
public const T_ROW_FORMAT = 364;
|
||||
public const T_STATS_AUTO_RECALC = 365;
|
||||
public const T_STATS_PERSISTENT = 366;
|
||||
public const T_STATS_SAMPLE_PAGES = 367;
|
||||
public const T_TABLESPACE = 368;
|
||||
public const T_UNION = 369;
|
||||
public const T_PRECISION = 370;
|
||||
|
||||
/**
|
||||
* Lexical catchable patterns.
|
||||
*/
|
||||
protected function getCatchablePatterns(): array
|
||||
{
|
||||
return [
|
||||
'(?:-?[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?', // numbers
|
||||
'`(?:[^`]|``)*`', // quoted identifiers
|
||||
"'(?:[^']|'')*'", // quoted strings
|
||||
'\)', // closing parenthesis
|
||||
'[a-z0-9$_][\w$]*', // unquoted identifiers
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Lexical non-catchable patterns.
|
||||
*/
|
||||
protected function getNonCatchablePatterns(): array
|
||||
{
|
||||
return ['\s+'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve token type. Also processes the token value if necessary.
|
||||
*
|
||||
* @param string $value
|
||||
*/
|
||||
protected function getType(&$value): int
|
||||
{
|
||||
$type = self::T_NONE;
|
||||
|
||||
// Recognize numeric values
|
||||
if (is_numeric($value)) {
|
||||
if (str_contains($value, '.') || stripos($value, 'e') !== false) {
|
||||
return self::T_FLOAT;
|
||||
}
|
||||
|
||||
return self::T_INTEGER;
|
||||
}
|
||||
|
||||
// Recognize quoted strings
|
||||
if ($value[0] === "'") {
|
||||
$value = str_replace("''", "'", substr($value, 1, -1));
|
||||
|
||||
return self::T_STRING;
|
||||
}
|
||||
|
||||
// Recognize quoted strings
|
||||
if ($value[0] === '`') {
|
||||
$value = str_replace('``', '`', substr($value, 1, -1));
|
||||
|
||||
return self::T_IDENTIFIER;
|
||||
}
|
||||
|
||||
// Recognize identifiers, aliased or qualified names
|
||||
if (ctype_alpha($value[0])) {
|
||||
$name = 'TYPO3\\CMS\\Core\\Database\\Schema\\Parser\\Lexer::T_' . strtoupper($value);
|
||||
|
||||
if (defined($name)) {
|
||||
$type = constant($name);
|
||||
|
||||
if ($type > 100) {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
return self::T_STRING;
|
||||
}
|
||||
|
||||
switch ($value) {
|
||||
// Recognize symbols
|
||||
case '.':
|
||||
return self::T_DOT;
|
||||
case ';':
|
||||
return self::T_SEMICOLON;
|
||||
case ',':
|
||||
return self::T_COMMA;
|
||||
case '(':
|
||||
return self::T_OPEN_PARENTHESIS;
|
||||
case ')':
|
||||
return self::T_CLOSE_PARENTHESIS;
|
||||
case '=':
|
||||
return self::T_EQUALS;
|
||||
case '>':
|
||||
return self::T_GREATER_THAN;
|
||||
case '<':
|
||||
return self::T_LOWER_THAN;
|
||||
case '+':
|
||||
return self::T_PLUS;
|
||||
case '-':
|
||||
return self::T_MINUS;
|
||||
case '*':
|
||||
return self::T_MULTIPLY;
|
||||
case '/':
|
||||
return self::T_DIVIDE;
|
||||
case '!':
|
||||
return self::T_NEGATE;
|
||||
case '{':
|
||||
return self::T_OPEN_CURLY_BRACE;
|
||||
case '}':
|
||||
return self::T_CLOSE_CURLY_BRACE;
|
||||
// Default
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,451 @@
|
||||
<?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\Parser;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\CreateColumnDefinitionItem;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\CreateForeignKeyDefinitionItem;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\CreateIndexDefinitionItem;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\CreateTableStatement;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\AbstractDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\BigIntDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\BinaryDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\BlobDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\CharDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\DateDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\DateTimeDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\DecimalDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\DoubleDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\EnumDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\FloatDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\IntegerDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\JsonDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\LongBlobDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\LongTextDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\MediumBlobDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\MediumIntDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\MediumTextDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\NumericDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\RealDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\SetDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\SmallIntDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\TextDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\TimeDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\TimestampDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\TinyBlobDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\TinyIntDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\TinyTextDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\UuidDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\VarBinaryDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\VarCharDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\DataType\YearDataType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\IndexColumnName;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\AST\ReferenceDefinition;
|
||||
use TYPO3\CMS\Core\Database\Schema\Types\SetType;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Converts a CreateTableStatement syntax node into a Doctrine Table
|
||||
* object that represents the table defined in the original SQL statement.
|
||||
*/
|
||||
class TableBuilder
|
||||
{
|
||||
/**
|
||||
* @var Table
|
||||
*/
|
||||
protected $table;
|
||||
|
||||
/**
|
||||
* @var AbstractPlatform
|
||||
*/
|
||||
protected $platform;
|
||||
|
||||
/**
|
||||
* TableBuilder constructor.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
*/
|
||||
public function __construct(?AbstractPlatform $platform = null)
|
||||
{
|
||||
// Register custom data types as no connection might have
|
||||
// been established yet so the types would not be available
|
||||
// when building tables/columns.
|
||||
ConnectionPool::registerDoctrineTypes();
|
||||
$this->platform = $platform ?: GeneralUtility::makeInstance(MySQLPlatform::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Doctrine Table object based on the parsed MySQL SQL command.
|
||||
*
|
||||
* @throws \Doctrine\DBAL\Schema\SchemaException
|
||||
* @throws \RuntimeException
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function create(CreateTableStatement $tableStatement): Table
|
||||
{
|
||||
$this->table = GeneralUtility::makeInstance(
|
||||
Table::class,
|
||||
$tableStatement->tableName->getQuotedName(),
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
$this->buildTableOptions($tableStatement->tableOptions)
|
||||
);
|
||||
|
||||
foreach ($tableStatement->createDefinition->items as $item) {
|
||||
switch (get_class($item)) {
|
||||
case CreateColumnDefinitionItem::class:
|
||||
$this->addColumn($item);
|
||||
break;
|
||||
case CreateIndexDefinitionItem::class:
|
||||
$this->addIndex($item);
|
||||
break;
|
||||
case CreateForeignKeyDefinitionItem::class:
|
||||
$this->addForeignKey($item);
|
||||
break;
|
||||
default:
|
||||
throw new \RuntimeException(
|
||||
'Unknown item definition of type "' . get_class($item) . '" encountered.',
|
||||
1472044085
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->table;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Doctrine\DBAL\Schema\SchemaException
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function addColumn(CreateColumnDefinitionItem $item): Column
|
||||
{
|
||||
$column = $this->table->addColumn(
|
||||
$item->columnName->getQuotedName(),
|
||||
$this->getDoctrineColumnTypeName($item->dataType)
|
||||
);
|
||||
|
||||
$column->setNotnull($item->allowNull === false);
|
||||
$column->setAutoincrement($item->autoIncrement);
|
||||
$column->setComment((string)$item->comment);
|
||||
|
||||
// Set default value (unless it's an auto increment column)
|
||||
if ($item->hasDefaultValue && !$column->getAutoincrement()) {
|
||||
$column->setDefault($item->defaultValue);
|
||||
}
|
||||
|
||||
if ($item->dataType->getLength()) {
|
||||
$column->setLength($item->dataType->getLength());
|
||||
}
|
||||
|
||||
if ($item->dataType->getPrecision() >= 0) {
|
||||
$column->setPrecision($item->dataType->getPrecision());
|
||||
}
|
||||
|
||||
if ($item->dataType->getScale() >= 0) {
|
||||
$column->setScale($item->dataType->getScale());
|
||||
}
|
||||
|
||||
if ($item->dataType->isUnsigned()) {
|
||||
$column->setUnsigned(true);
|
||||
}
|
||||
|
||||
// Select CHAR/VARCHAR or BINARY/VARBINARY
|
||||
if ($item->dataType->isFixed()) {
|
||||
$column->setFixed(true);
|
||||
}
|
||||
|
||||
if ($item->dataType instanceof SetDataType) {
|
||||
$column->setValues($item->dataType->getValues());
|
||||
}
|
||||
if ($item->dataType instanceof EnumDataType) {
|
||||
$column->setValues($item->dataType->getValues());
|
||||
}
|
||||
|
||||
$dataTypeSupportsCharsetAndCollation = (
|
||||
$item->dataType instanceof CharDataType
|
||||
|| $item->dataType instanceof VarCharDataType
|
||||
|| $item->dataType instanceof TextDataType
|
||||
);
|
||||
$options = $item->dataType->getOptions();
|
||||
if ($dataTypeSupportsCharsetAndCollation && ($options['charset'] ?? null)) {
|
||||
$column->setPlatformOption('charset', $options['charset']);
|
||||
}
|
||||
if ($dataTypeSupportsCharsetAndCollation && ($options['charset'] ?? null)) {
|
||||
$column->setPlatformOption('collation', $options['collation']);
|
||||
}
|
||||
|
||||
if ($item->index) {
|
||||
$this->table->addIndex([$item->columnName->getQuotedName()]);
|
||||
}
|
||||
|
||||
if ($item->unique) {
|
||||
$this->table->addUniqueIndex([$item->columnName->getQuotedName()]);
|
||||
}
|
||||
|
||||
if ($item->primary) {
|
||||
$this->table->setPrimaryKey([$item->columnName->getQuotedName()]);
|
||||
}
|
||||
|
||||
if ($item->reference !== null) {
|
||||
$this->addForeignKeyConstraint(
|
||||
[$item->columnName->getQuotedName()],
|
||||
$item->reference
|
||||
);
|
||||
}
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Doctrine\DBAL\Schema\SchemaException
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function addIndex(CreateIndexDefinitionItem $item): Index
|
||||
{
|
||||
$indexName = $item->indexName->getQuotedName();
|
||||
|
||||
$columnNames = array_map(
|
||||
static function (IndexColumnName $columnName): string {
|
||||
if ($columnName->length) {
|
||||
return $columnName->columnName->getQuotedName() . '(' . $columnName->length . ')';
|
||||
}
|
||||
return $columnName->columnName->getQuotedName();
|
||||
},
|
||||
$item->columnNames
|
||||
);
|
||||
|
||||
if ($item->isPrimary) {
|
||||
$this->table->setPrimaryKey($columnNames);
|
||||
$index = $this->table->getPrimaryKey();
|
||||
} else {
|
||||
$index = GeneralUtility::makeInstance(
|
||||
Index::class,
|
||||
$indexName,
|
||||
$columnNames,
|
||||
$item->isUnique,
|
||||
$item->isPrimary
|
||||
);
|
||||
|
||||
if ($item->isFulltext) {
|
||||
$index->addFlag('fulltext');
|
||||
} elseif ($item->isSpatial) {
|
||||
$index->addFlag('spatial');
|
||||
}
|
||||
|
||||
// Doctrine keys the indexes by a normalized name of its own. Do not rely on that key
|
||||
// here, but build the list explicitly from the index names, so that re-defining an
|
||||
// index replaces the previously added one - independent of how Doctrine keys them.
|
||||
$indexes = [];
|
||||
foreach ($this->table->getIndexes() as $existingIndex) {
|
||||
$indexes[strtolower($existingIndex->getName())] = $existingIndex;
|
||||
}
|
||||
$indexes[strtolower($index->getName())] = $index;
|
||||
|
||||
$this->table = new Table(
|
||||
$this->table->getQuotedName($this->platform),
|
||||
$this->table->getColumns(),
|
||||
array_values($indexes),
|
||||
[],
|
||||
$this->table->getForeignKeys(),
|
||||
$this->table->getOptions()
|
||||
);
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare an explicit foreign key definition item to be added to the table being built.
|
||||
*/
|
||||
protected function addForeignKey(CreateForeignKeyDefinitionItem $item)
|
||||
{
|
||||
$indexName = $item->indexName->getQuotedName() ?: null;
|
||||
$localColumnNames = array_map(
|
||||
static function (IndexColumnName $columnName): string {
|
||||
return $columnName->columnName->getQuotedName();
|
||||
},
|
||||
$item->columnNames
|
||||
);
|
||||
$this->addForeignKeyConstraint($localColumnNames, $item->reference, $indexName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a foreign key constraint to the table being built.
|
||||
*
|
||||
* @param string[] $localColumnNames
|
||||
*/
|
||||
protected function addForeignKeyConstraint(
|
||||
array $localColumnNames,
|
||||
ReferenceDefinition $referenceDefinition,
|
||||
?string $indexName = null
|
||||
) {
|
||||
$foreignTableName = $referenceDefinition->tableName->getQuotedName();
|
||||
$foreignColumnNames = array_map(
|
||||
static function (IndexColumnName $columnName): string {
|
||||
return $columnName->columnName->getQuotedName();
|
||||
},
|
||||
$referenceDefinition->columnNames
|
||||
);
|
||||
|
||||
$options = [
|
||||
'onDelete' => $referenceDefinition->onDelete,
|
||||
'onUpdate' => $referenceDefinition->onUpdate,
|
||||
];
|
||||
|
||||
$this->table->addForeignKeyConstraint(
|
||||
$foreignTableName,
|
||||
$localColumnNames,
|
||||
$foreignColumnNames,
|
||||
$options,
|
||||
$indexName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function getDoctrineColumnTypeName(AbstractDataType $dataType): string
|
||||
{
|
||||
switch (get_class($dataType)) {
|
||||
case TinyIntDataType::class:
|
||||
// TINYINT is MySQL specific and mapped to a standard SMALLINT
|
||||
case SmallIntDataType::class:
|
||||
$doctrineType = Types::SMALLINT;
|
||||
break;
|
||||
case MediumIntDataType::class:
|
||||
// MEDIUMINT is MySQL specific and mapped to a standard INT
|
||||
case IntegerDataType::class:
|
||||
$doctrineType = Types::INTEGER;
|
||||
break;
|
||||
case BigIntDataType::class:
|
||||
$doctrineType = Types::BIGINT;
|
||||
break;
|
||||
case BinaryDataType::class:
|
||||
case VarBinaryDataType::class:
|
||||
// CHAR/VARCHAR is determined by "fixed" column property
|
||||
$doctrineType = Types::BINARY;
|
||||
break;
|
||||
case TinyBlobDataType::class:
|
||||
case MediumBlobDataType::class:
|
||||
case BlobDataType::class:
|
||||
case LongBlobDataType::class:
|
||||
// Actual field type is determined by field length
|
||||
$doctrineType = Types::BLOB;
|
||||
break;
|
||||
case DateDataType::class:
|
||||
$doctrineType = Types::DATE_MUTABLE;
|
||||
break;
|
||||
case TimestampDataType::class:
|
||||
case DateTimeDataType::class:
|
||||
// TIMESTAMP or DATETIME are determined by "version" column property
|
||||
$doctrineType = Types::DATETIME_MUTABLE;
|
||||
break;
|
||||
case NumericDataType::class:
|
||||
case DecimalDataType::class:
|
||||
$doctrineType = Types::DECIMAL;
|
||||
break;
|
||||
case RealDataType::class:
|
||||
case FloatDataType::class:
|
||||
case DoubleDataType::class:
|
||||
$doctrineType = Types::FLOAT;
|
||||
break;
|
||||
case TimeDataType::class:
|
||||
$doctrineType = Types::TIME_MUTABLE;
|
||||
break;
|
||||
case TinyTextDataType::class:
|
||||
case MediumTextDataType::class:
|
||||
case TextDataType::class:
|
||||
case LongTextDataType::class:
|
||||
$doctrineType = Types::TEXT;
|
||||
break;
|
||||
case CharDataType::class:
|
||||
case VarCharDataType::class:
|
||||
$doctrineType = Types::STRING;
|
||||
break;
|
||||
case EnumDataType::class:
|
||||
$doctrineType = Types::ENUM;
|
||||
break;
|
||||
case SetDataType::class:
|
||||
$doctrineType = SetType::TYPE;
|
||||
break;
|
||||
case JsonDataType::class:
|
||||
$doctrineType = Types::JSON;
|
||||
break;
|
||||
case YearDataType::class:
|
||||
// The YEAR data type is MySQL specific and offers little to no benefit.
|
||||
// The two-digit year logic implemented in this data type (1-69 mapped to
|
||||
// 2001-2069, 70-99 mapped to 1970-1999) can be easily implemented in the
|
||||
// application and for all other accounts it's an integer with a valid
|
||||
// range of 1901 to 2155.
|
||||
// Using a SMALLINT covers the value range and ensures database compatibility.
|
||||
$doctrineType = Types::SMALLINT;
|
||||
break;
|
||||
case UuidDataType::class:
|
||||
// UUID/GUID is only supported by PostgreSQL for now, but Doctrine DBAL implemented a fallback
|
||||
// for other platforms and we can safely use `Types::GUID` here in case `UUID` has been set in
|
||||
// `ext_tables.sql` for a table column.
|
||||
$doctrineType = Types::GUID;
|
||||
break;
|
||||
default:
|
||||
throw new \RuntimeException(
|
||||
'Unsupported data type: ' . get_class($dataType) . '!',
|
||||
1472046376
|
||||
);
|
||||
}
|
||||
|
||||
return $doctrineType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the table specific options as far as they are supported by Doctrine.
|
||||
*/
|
||||
protected function buildTableOptions(array $tableOptions): array
|
||||
{
|
||||
$options = [];
|
||||
|
||||
if (!empty($tableOptions['engine'])) {
|
||||
$options['engine'] = (string)$tableOptions['engine'];
|
||||
}
|
||||
if (!empty($tableOptions['character_set'])) {
|
||||
$options['charset'] = (string)$tableOptions['character_set'];
|
||||
}
|
||||
if (!empty($tableOptions['collation'])) {
|
||||
$options['collate'] = (string)$tableOptions['collation'];
|
||||
}
|
||||
if (!empty($tableOptions['auto_increment'])) {
|
||||
$options['auto_increment'] = (string)$tableOptions['auto_increment'];
|
||||
}
|
||||
if (!empty($tableOptions['comment'])) {
|
||||
$options['comment'] = (string)$tableOptions['comment'];
|
||||
}
|
||||
if (!empty($tableOptions['row_format'])) {
|
||||
$options['row_format'] = (string)$tableOptions['row_format'];
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<?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;
|
||||
|
||||
use Doctrine\DBAL\Schema\SchemaDiff as DoctrineSchemaDiff;
|
||||
use Doctrine\DBAL\Schema\Sequence;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Schema\TableDiff as DoctrineTableDiff;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Based on the doctrine/dbal implementation restoring direct property access
|
||||
* and adding further helper methods.
|
||||
*
|
||||
* @internal not part of public Core API.
|
||||
*/
|
||||
class SchemaDiff extends DoctrineSchemaDiff
|
||||
{
|
||||
/**
|
||||
* Constructs an SchemaDiff object.
|
||||
*
|
||||
* @internal The diff can be only instantiated by a {@see Comparator}.
|
||||
*
|
||||
* @param array<string> $createdSchemas
|
||||
* @param array<string> $droppedSchemas
|
||||
* @param array<string, Table> $createdTables
|
||||
* @param array<string, TableDiff> $alteredTables
|
||||
* @param array<string, Table> $droppedTables
|
||||
* @param array<Sequence> $createdSequences
|
||||
* @param array<Sequence> $alteredSequences
|
||||
* @param array<Sequence> $droppedSequences
|
||||
*/
|
||||
public function __construct(
|
||||
public array $createdSchemas,
|
||||
public array $droppedSchemas,
|
||||
public array $createdTables,
|
||||
public array $alteredTables,
|
||||
public array $droppedTables,
|
||||
public array $createdSequences,
|
||||
public array $alteredSequences,
|
||||
public array $droppedSequences,
|
||||
) {
|
||||
$this->alteredTables = array_filter($alteredTables, static function (TableDiff $diff): bool {
|
||||
return !$diff->isEmpty();
|
||||
});
|
||||
// NOTE: parent::__construct() not called by intention.
|
||||
}
|
||||
|
||||
/** @return array<string> */
|
||||
public function getCreatedSchemas(): array
|
||||
{
|
||||
return $this->createdSchemas;
|
||||
}
|
||||
|
||||
/** @return array<string> */
|
||||
public function getDroppedSchemas(): array
|
||||
{
|
||||
return $this->droppedSchemas;
|
||||
}
|
||||
|
||||
/** @return array<string, Table> */
|
||||
public function getCreatedTables(): array
|
||||
{
|
||||
return $this->createdTables;
|
||||
}
|
||||
|
||||
/** @return array<string, TableDiff> */
|
||||
public function getAlteredTables(): array
|
||||
{
|
||||
return $this->alteredTables;
|
||||
}
|
||||
|
||||
/** @return array<string, Table> */
|
||||
public function getDroppedTables(): array
|
||||
{
|
||||
return $this->droppedTables;
|
||||
}
|
||||
|
||||
/** @return array<Sequence> */
|
||||
public function getCreatedSequences(): array
|
||||
{
|
||||
return $this->createdSequences;
|
||||
}
|
||||
|
||||
/** @return array<Sequence> */
|
||||
public function getAlteredSequences(): array
|
||||
{
|
||||
return $this->alteredSequences;
|
||||
}
|
||||
|
||||
/** @return array<Sequence> */
|
||||
public function getDroppedSequences(): array
|
||||
{
|
||||
return $this->droppedSequences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the diff is empty (contains no changes).
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return count($this->createdSchemas) === 0
|
||||
&& count($this->droppedSchemas) === 0
|
||||
&& count($this->createdTables) === 0
|
||||
&& count($this->alteredTables) === 0
|
||||
&& count($this->droppedTables) === 0
|
||||
&& count($this->createdSequences) === 0
|
||||
&& count($this->alteredSequences) === 0
|
||||
&& count($this->droppedSequences) === 0;
|
||||
}
|
||||
|
||||
public static function ensure(SchemaDiff|DoctrineSchemaDiff $schemaDiff, array $additionalArguments = []): self
|
||||
{
|
||||
return new self(...[
|
||||
'createdSchemas' => $schemaDiff->getCreatedSchemas(),
|
||||
'droppedSchemas' => $schemaDiff->getDroppedSchemas(),
|
||||
'createdTables' => self::ensureCollection(...$schemaDiff->getCreatedTables()),
|
||||
'alteredTables' => self::ensureCollection(...$schemaDiff->getAlteredTables()),
|
||||
'droppedTables' => self::ensureCollection(...$schemaDiff->getDroppedTables()),
|
||||
'createdSequences' => $schemaDiff->getCreatedSequences(),
|
||||
'alteredSequences' => $schemaDiff->getAlteredSequences(),
|
||||
'droppedSequences' => $schemaDiff->getDroppedSequences(),
|
||||
...$additionalArguments,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DoctrineTableDiff|TableDiff|Table ...$tableDiffs
|
||||
* @return TableDiff[]|Table[]
|
||||
*/
|
||||
public static function ensureCollection(DoctrineTableDiff|TableDiff|Table ...$tableDiffs): array
|
||||
{
|
||||
$collection = [];
|
||||
foreach ($tableDiffs as $key => $tableDiff) {
|
||||
if ($tableDiff instanceof DoctrineTableDiff) {
|
||||
$tableDiff = TableDiff::ensure($tableDiff);
|
||||
}
|
||||
if (is_int($key) || MathUtility::canBeInterpretedAsInteger($key)) {
|
||||
$key = $tableDiff instanceof Table
|
||||
? $tableDiff->getName()
|
||||
: $tableDiff->getOldTable()->getName();
|
||||
}
|
||||
$collection[$key] = $tableDiff;
|
||||
}
|
||||
return $collection;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?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;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
|
||||
use TYPO3\CMS\Core\Database\Schema\Information\ColumnInfo;
|
||||
use TYPO3\CMS\Core\Database\Schema\Information\TableInfo;
|
||||
use TYPO3\CMS\Core\Package\Cache\PackageDependentCacheIdentifier;
|
||||
|
||||
/**
|
||||
* This wrapper of SchemaManager contains some internal caches to avoid performance issues for recurring calls to
|
||||
* specific schema related information. This should only be used in context where no changes are expected to happen.
|
||||
*
|
||||
* @internal This class is only for internal core usage and is not part of the public core API.
|
||||
*/
|
||||
final class SchemaInformation
|
||||
{
|
||||
private string $connectionIdentifier;
|
||||
|
||||
public function __construct(
|
||||
private readonly Connection $connection,
|
||||
private readonly FrontendInterface $runtime,
|
||||
private readonly FrontendInterface $cache,
|
||||
private readonly PackageDependentCacheIdentifier $packageDependentCacheIdentifier,
|
||||
) {
|
||||
$this->connectionIdentifier = $this->packageDependentCacheIdentifier
|
||||
->withPrefix(str_replace(
|
||||
['.', ':', '/', '\\', '!', '?'],
|
||||
'_',
|
||||
(string)($connection->getParams()['dbname'] ?? 'generic')
|
||||
))
|
||||
// hash connection params, which holds various information like host,
|
||||
// port etc. to get a descriptive hash for this connection.
|
||||
->withAdditionalHashedIdentifier(serialize($connection->getParams()))
|
||||
->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to doctrine DBAL/AbstractSchemaManager, but with a cache-layer.
|
||||
* This is used core internally to auto-add types, for instance in Connection::insert().
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function listTableNames(): array
|
||||
{
|
||||
$identifier = $this->connectionIdentifier . '-tablenames';
|
||||
// Level 1 cache
|
||||
$tableNames = $this->runtime->get($identifier);
|
||||
if (is_array($tableNames)) {
|
||||
return $tableNames;
|
||||
}
|
||||
// Level 2 cache
|
||||
$tableNames = $this->cache->get($identifier);
|
||||
if (is_array($tableNames)) {
|
||||
// Retrieved from level 2, set to level 1 cache.
|
||||
$this->runtime->set($identifier, $tableNames);
|
||||
return $tableNames;
|
||||
}
|
||||
return $this->buildTableNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tableName
|
||||
* @return array<string, ColumnInfo>
|
||||
*/
|
||||
public function listTableColumnInfos(string $tableName): array
|
||||
{
|
||||
return $this->getTableInfo($tableName)->getColumnInfos();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tableName
|
||||
* @return string[]
|
||||
*/
|
||||
public function listTableColumnNames(string $tableName): array
|
||||
{
|
||||
return $this->getTableInfo($tableName)->getColumnNames();
|
||||
}
|
||||
|
||||
public function getTableInfo(string $tableName): TableInfo
|
||||
{
|
||||
$identifier = $this->connectionIdentifier . '-tableinfo-' . $tableName;
|
||||
$tableInfo = $this->runtime->get($identifier);
|
||||
// Level 1 cache
|
||||
if ($tableInfo instanceof TableInfo) {
|
||||
return $tableInfo;
|
||||
}
|
||||
// Level 2 cache
|
||||
$tableInfo = $this->cache->get($identifier);
|
||||
if ($tableInfo instanceof TableInfo) {
|
||||
// Retrieved from level 2, set to level 1 cache.
|
||||
$this->runtime->set($identifier, $tableInfo);
|
||||
return $tableInfo;
|
||||
}
|
||||
return $this->buildTableInformation($tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function buildTableNames(): array
|
||||
{
|
||||
$identifier = $this->connectionIdentifier . '-tablenames';
|
||||
$names = array_values($this->connection->createSchemaManager()->listTableNames());
|
||||
// Level 1 cache
|
||||
$this->runtime->set($identifier, $names);
|
||||
// Level 2 cache
|
||||
$this->cache->set($identifier, $names);
|
||||
return $names;
|
||||
}
|
||||
|
||||
private function buildTableInformation(string $tableName): TableInfo
|
||||
{
|
||||
$identifier = $this->connectionIdentifier . '-tableinfo-' . $tableName;
|
||||
// Transform doctrine columns into ColumnInfo and add to new associative array using column name with
|
||||
// unmodified casing as array keys and not the lowercased from doctrine dbal associative array, which
|
||||
// leads to comparison issues in the core using the names. We need the untouched casing.
|
||||
$columns = $this->connection->createSchemaManager()->listTableColumns($tableName);
|
||||
$columnInfos = [];
|
||||
foreach ($columns as $column) {
|
||||
$columnInfo = ColumnInfo::convertFromDoctrineColumn($column);
|
||||
$columnInfos[$columnInfo->name] = $columnInfo;
|
||||
}
|
||||
$tableInfo = new TableInfo(
|
||||
name: $tableName,
|
||||
columnInfos: $columnInfos,
|
||||
);
|
||||
// Level 1 cache
|
||||
$this->runtime->set($identifier, $tableInfo);
|
||||
// Level 2 cache
|
||||
$this->cache->set($identifier, $tableInfo);
|
||||
return $tableInfo;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,515 @@
|
||||
<?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;
|
||||
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\SchemaDiff;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
|
||||
use TYPO3\CMS\Core\Core\Bootstrap;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Schema\Exception\StatementException;
|
||||
use TYPO3\CMS\Core\Database\Schema\Parser\Parser;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
|
||||
/**
|
||||
* Helper methods to handle SQL files and transform them into individual statements
|
||||
* for further processing.
|
||||
*
|
||||
* @internal not part of public core API.
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
readonly class SchemaMigrator
|
||||
{
|
||||
public function __construct(
|
||||
private ConnectionPool $connectionPool,
|
||||
private Parser $parser,
|
||||
private DefaultTcaSchema $defaultTcaSchema,
|
||||
private TcaSchemaFactory $tcaSchemaFactory,
|
||||
#[Autowire(service: 'cache.runtime')]
|
||||
private FrontendInterface $runtime,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Compare current and expected schema definitions and provide updates suggestions in the form
|
||||
* of SQL statements.
|
||||
*
|
||||
* @param string[] $statements The CREATE TABLE statements
|
||||
* @param bool $remove TRUE for RENAME/DROP table and column suggestions, FALSE for ADD/CHANGE suggestions
|
||||
* @return array<string, array> SQL statements to migrate the database to the expected schema, indexed by performed operation
|
||||
* @throws DBALException
|
||||
* @throws SchemaException
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException
|
||||
* @throws StatementException
|
||||
*/
|
||||
public function getUpdateSuggestions(array $statements, bool $remove = false): array
|
||||
{
|
||||
$tables = $this->parseCreateTableStatements($statements);
|
||||
$updateSuggestions = [];
|
||||
foreach ($this->connectionPool->getConnectionNames() as $connectionName) {
|
||||
$connection = $this->connectionPool->getConnectionByName($connectionName);
|
||||
$connectionMigrator = new ConnectionMigrator($connectionName, $connection, $this->connectionPool, $tables);
|
||||
$updateSuggestions[$connectionName] = $connectionMigrator->getUpdateSuggestions($remove);
|
||||
}
|
||||
return $updateSuggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the raw Doctrine SchemaDiff objects for each connection. This diff contains
|
||||
* all changes without any pre-processing.
|
||||
*
|
||||
* @return array<string, SchemaDiff>
|
||||
* @throws DBALException
|
||||
* @throws SchemaException
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException
|
||||
* @throws StatementException
|
||||
*/
|
||||
public function getSchemaDiffs(array $statements): array
|
||||
{
|
||||
$tables = $this->parseCreateTableStatements($statements);
|
||||
$schemaDiffs = [];
|
||||
foreach ($this->connectionPool->getConnectionNames() as $connectionName) {
|
||||
$connection = $this->connectionPool->getConnectionByName($connectionName);
|
||||
$connectionMigrator = new ConnectionMigrator($connectionName, $connection, $this->connectionPool, $tables);
|
||||
$schemaDiffs[$connectionName] = $connectionMigrator->getSchemaDiff();
|
||||
}
|
||||
return $schemaDiffs;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method executes statements from the update suggestions, or a subset of them
|
||||
* filtered by the statements hashes, one by one.
|
||||
*
|
||||
* @param string[] $statements The CREATE TABLE statements
|
||||
* @param string[] $selectedStatements The hashes of the update suggestions to execute
|
||||
* @throws DBALException
|
||||
* @throws SchemaException
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws StatementException
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function migrate(array $statements, array $selectedStatements): array
|
||||
{
|
||||
$result = [];
|
||||
$updateSuggestionsPerConnection = array_replace_recursive(
|
||||
$this->getUpdateSuggestions($statements),
|
||||
$this->getUpdateSuggestions($statements, true)
|
||||
);
|
||||
|
||||
foreach ($updateSuggestionsPerConnection as $connectionName => $updateSuggestions) {
|
||||
unset($updateSuggestions['tables_count'], $updateSuggestions['change_currentValue']);
|
||||
$updateSuggestions = array_merge(...array_values($updateSuggestions));
|
||||
$statementsToExecute = array_intersect_key($updateSuggestions, $selectedStatements);
|
||||
if (count($statementsToExecute) === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$connection = $this->connectionPool->getConnectionByName($connectionName);
|
||||
foreach ($statementsToExecute as $hash => $statement) {
|
||||
try {
|
||||
$connection->executeStatement($statement);
|
||||
} catch (DBALException $e) {
|
||||
$result[$hash] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->flushDatabaseSchemaCache();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform add/change/create operations on tables and fields in an optimized, non-interactive, mode.
|
||||
*
|
||||
* @param string[] $statements The CREATE TABLE statements
|
||||
* @param bool $createOnly Only perform changes that add fields or create tables
|
||||
* @return array<string, string> Error messages for statements that occurred during the installation procedure.
|
||||
* @throws DBALException
|
||||
* @throws SchemaException
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException
|
||||
* @throws StatementException
|
||||
*/
|
||||
public function install(array $statements, bool $createOnly = false): array
|
||||
{
|
||||
$tables = $this->parseCreateTableStatements($statements);
|
||||
$result = [];
|
||||
foreach ($this->connectionPool->getConnectionNames() as $connectionName) {
|
||||
$connection = $this->connectionPool->getConnectionByName($connectionName);
|
||||
$connectionMigrator = new ConnectionMigrator($connectionName, $connection, $this->connectionPool, $tables);
|
||||
$lastResult = $connectionMigrator->install($createOnly);
|
||||
$result = array_merge($result, $lastResult);
|
||||
}
|
||||
$this->flushDatabaseSchemaCache();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import static data (INSERT statements)
|
||||
*/
|
||||
public function importStaticData(array $statements, bool $truncate = false): array
|
||||
{
|
||||
$result = [];
|
||||
$insertStatements = [];
|
||||
|
||||
foreach ($statements as $statement) {
|
||||
// Only handle insert statements and extract the table at the same time. Extracting
|
||||
// the table name is required to perform the inserts on the right connection.
|
||||
if (preg_match('/^INSERT\s+INTO\s+`?(\w+)`?(.*)/i', $statement, $matches)) {
|
||||
[, $tableName, $sqlFragment] = $matches;
|
||||
$insertStatements[$tableName][] = sprintf(
|
||||
'INSERT INTO %s %s',
|
||||
$this->connectionPool->getConnectionForTable($tableName)->quoteIdentifier($tableName),
|
||||
rtrim($sqlFragment, ';')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($insertStatements as $tableName => $perTableStatements) {
|
||||
$connection = $this->connectionPool->getConnectionForTable($tableName);
|
||||
|
||||
if ($truncate) {
|
||||
$connection->truncate($tableName);
|
||||
}
|
||||
|
||||
foreach ((array)$perTableStatements as $statement) {
|
||||
try {
|
||||
$connection->executeStatement($statement);
|
||||
$result[$statement] = '';
|
||||
} catch (DBALException $e) {
|
||||
$result[$statement] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse CREATE TABLE statements into Doctrine Table objects.
|
||||
*
|
||||
* @param string[] $statements The SQL CREATE TABLE statements
|
||||
* @return array<non-empty-string, Table>
|
||||
* @throws SchemaException
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException
|
||||
* @throws StatementException
|
||||
*/
|
||||
protected function parseCreateTableStatements(array $statements): array
|
||||
{
|
||||
$tables = $this->prepareTablesFromStatements($statements);
|
||||
$tables = $this->ensureTableDefinitionForAllTCAManagedTables($tables);
|
||||
$tables = $this->mergeTableDefinitions($tables);
|
||||
$tables = $this->enrichTablesFromDefaultTCASchema($tables);
|
||||
$tables = $this->ensureDefaultTCAFieldsAreOrdered($tables);
|
||||
return $tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Have fields triggered by 'ctrl' settings first in the list. This is done for cosmetic
|
||||
* reasons to improve readability of db schema when opening tables in a database browser.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getPrioritizedFieldNames(string $tableName): array
|
||||
{
|
||||
if (!$this->tcaSchemaFactory->has($tableName)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$prioritizedFieldNames = [
|
||||
'uid',
|
||||
'pid',
|
||||
];
|
||||
$tableSchema = $this->tcaSchemaFactory->get($tableName);
|
||||
|
||||
if ($tableSchema->hasCapability(TcaSchemaCapability::CreatedAt)) {
|
||||
$prioritizedFieldNames[] = $tableSchema->getCapability(TcaSchemaCapability::CreatedAt)->getFieldName();
|
||||
}
|
||||
if ($tableSchema->hasCapability(TcaSchemaCapability::UpdatedAt)) {
|
||||
$prioritizedFieldNames[] = $tableSchema->getCapability(TcaSchemaCapability::UpdatedAt)->getFieldName();
|
||||
}
|
||||
if ($tableSchema->hasCapability(TcaSchemaCapability::SoftDelete)) {
|
||||
$prioritizedFieldNames[] = $tableSchema->getCapability(TcaSchemaCapability::SoftDelete)->getFieldName();
|
||||
}
|
||||
if ($tableSchema->hasCapability(TcaSchemaCapability::RestrictionDisabledField)) {
|
||||
$prioritizedFieldNames[] = $tableSchema->getCapability(TcaSchemaCapability::RestrictionDisabledField)->getFieldName();
|
||||
}
|
||||
if ($tableSchema->hasCapability(TcaSchemaCapability::RestrictionStartTime)) {
|
||||
$prioritizedFieldNames[] = $tableSchema->getCapability(TcaSchemaCapability::RestrictionStartTime)->getFieldName();
|
||||
}
|
||||
if ($tableSchema->hasCapability(TcaSchemaCapability::RestrictionEndTime)) {
|
||||
$prioritizedFieldNames[] = $tableSchema->getCapability(TcaSchemaCapability::RestrictionEndTime)->getFieldName();
|
||||
}
|
||||
if ($tableSchema->hasCapability(TcaSchemaCapability::RestrictionUserGroup)) {
|
||||
$prioritizedFieldNames[] = $tableSchema->getCapability(TcaSchemaCapability::RestrictionUserGroup)->getFieldName();
|
||||
}
|
||||
if ($tableSchema->isLanguageAware()) {
|
||||
$languageField = $tableSchema->getCapability(TcaSchemaCapability::Language);
|
||||
$prioritizedFieldNames[] = $languageField->getLanguageField()->getName();
|
||||
$prioritizedFieldNames[] = $languageField->getTranslationOriginPointerField()->getName();
|
||||
// @todo `l10n_state` is automatically added in `DefaultTcaSchema->enrichSingleTableFieldsFromTcaCtrl()`
|
||||
// if `ctrl->languageField` and `ctrl->transOrigPointerField` are configured, and not provided
|
||||
// by extension `ext_tables.sql`. This field has no representation in TcaSchema language field
|
||||
// handling yet, nor is this covered within TcaEnrichment thus adding it here directly for now.
|
||||
$prioritizedFieldNames[] = 'l10n_state';
|
||||
if (!empty($languageField->hasTranslationSourceField())) {
|
||||
$prioritizedFieldNames[] = $languageField->getTranslationSourceField()->getName();
|
||||
}
|
||||
if (!empty($languageField->hasDiffSourceField())) {
|
||||
$prioritizedFieldNames[] = $languageField->getDiffSourceField()->getName();
|
||||
}
|
||||
}
|
||||
if ($tableSchema->hasCapability(TcaSchemaCapability::SortByField)) {
|
||||
$prioritizedFieldNames[] = $tableSchema->getCapability(TcaSchemaCapability::SortByField)->getFieldName();
|
||||
}
|
||||
if ($tableSchema->hasCapability(TcaSchemaCapability::InternalDescription)) {
|
||||
$prioritizedFieldNames[] = $tableSchema->getCapability(TcaSchemaCapability::InternalDescription)->getFieldName();
|
||||
}
|
||||
if ($tableSchema->hasCapability(TcaSchemaCapability::EditLock)) {
|
||||
$prioritizedFieldNames[] = $tableSchema->getCapability(TcaSchemaCapability::EditLock)->getFieldName();
|
||||
}
|
||||
if ($tableSchema->hasCapability(TcaSchemaCapability::AncestorReferenceField)) {
|
||||
$prioritizedFieldNames[] = $tableSchema->getCapability(TcaSchemaCapability::AncestorReferenceField)->getFieldName();
|
||||
}
|
||||
if ($tableSchema->isWorkspaceAware()) {
|
||||
// @todo Adding hardcoded field names directly thus not having a representation within the TcaSchema. These
|
||||
// fields do not get proper TCA either within `TcaEnrichment` albeit ensured to be created within
|
||||
// `DefaultTcaSchema->enrichSingleTableFieldsFromTcaCtrl()` as soon as `ctr->versioningWS` is true.
|
||||
$prioritizedFieldNames[] = 't3ver_wsid';
|
||||
$prioritizedFieldNames[] = 't3ver_oid';
|
||||
$prioritizedFieldNames[] = 't3ver_state';
|
||||
$prioritizedFieldNames[] = 't3ver_stage';
|
||||
}
|
||||
|
||||
return $prioritizedFieldNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* To give extensions the ability to extend or modify the database schema for core or other extension tables, a
|
||||
* collection of DDL statement parts are parsed into partial table classes. This method merges the table definition
|
||||
* parts to end up with a single table representation to ease further handling.
|
||||
*
|
||||
* @param Table[] $tables
|
||||
* @return array<non-empty-string, Table>
|
||||
*/
|
||||
private function mergeTableDefinitions(array $tables): array
|
||||
{
|
||||
$return = [];
|
||||
foreach ($tables as $table) {
|
||||
$tableName = $this->trimIdentifierQuotes($table->getName());
|
||||
if (!array_key_exists($tableName, $return)) {
|
||||
$return[$tableName] = $table;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Merge multiple table definitions. Later definitions overrule identical
|
||||
// columns, indexes and foreign_keys. Order of definitions is based on
|
||||
// extension load order.
|
||||
$currentTableDefinition = $return[$tableName];
|
||||
$return[$tableName] = new Table(
|
||||
$tableName,
|
||||
$this->mergeColumns(...$currentTableDefinition->getColumns(), ...$table->getColumns()),
|
||||
$this->mergeIndexes(...array_values($currentTableDefinition->getIndexes()), ...array_values($table->getIndexes())),
|
||||
[],
|
||||
$this->mergeForeignKeys(...array_values($currentTableDefinition->getForeignKeys()), ...array_values($table->getForeignKeys())),
|
||||
array_merge($currentTableDefinition->getOptions(), $table->getOptions())
|
||||
);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Column ...$columns
|
||||
* @return Column[]
|
||||
*/
|
||||
private function mergeColumns(Column ...$columns): array
|
||||
{
|
||||
$mergedColumns = [];
|
||||
foreach ($columns as $column) {
|
||||
$mergedColumns[$column->getName()] = $column;
|
||||
}
|
||||
return array_values($mergedColumns);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Index ...$indexes
|
||||
* @return Index[]
|
||||
*/
|
||||
private function mergeIndexes(Index ...$indexes): array
|
||||
{
|
||||
$mergedIndexes = [];
|
||||
foreach ($indexes as $index) {
|
||||
$mergedIndexes[$index->getName()] = $index;
|
||||
}
|
||||
return array_values($mergedIndexes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unnamed foreign key constraints cannot be identified by name and are therefore kept as they are.
|
||||
* Doctrine generates a name for them, but only as the array key - not on the constraint itself.
|
||||
*
|
||||
* @param ForeignKeyConstraint ...$foreignKeys
|
||||
* @return ForeignKeyConstraint[]
|
||||
*/
|
||||
private function mergeForeignKeys(ForeignKeyConstraint ...$foreignKeys): array
|
||||
{
|
||||
$mergedForeignKeys = [];
|
||||
foreach ($foreignKeys as $foreignKey) {
|
||||
$foreignKeyName = $foreignKey->getName();
|
||||
if ($foreignKeyName === '') {
|
||||
$mergedForeignKeys[] = $foreignKey;
|
||||
continue;
|
||||
}
|
||||
$mergedForeignKeys[$foreignKeyName] = $foreignKey;
|
||||
}
|
||||
return array_values($mergedForeignKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim all possible identifier quotes from identifier. This method has been cloned from Doctrine DBAL.
|
||||
*
|
||||
* @see \Doctrine\DBAL\Schema\AbstractAsset::trimQuotes()
|
||||
*/
|
||||
private function trimIdentifierQuotes(string $identifier): string
|
||||
{
|
||||
return str_replace(['`', '"', '[', ']'], '', $identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $statements
|
||||
* @return Table[]
|
||||
* @throws SchemaException
|
||||
* @throws StatementException
|
||||
*/
|
||||
protected function prepareTablesFromStatements(array $statements): array
|
||||
{
|
||||
$tables = [];
|
||||
foreach ($statements as $statement) {
|
||||
// We need to keep multiple table definitions at this point so
|
||||
// that Extensions can modify existing tables.
|
||||
try {
|
||||
$tables[] = $this->parser->parse($statement);
|
||||
} catch (StatementException $statementException) {
|
||||
// Enrich the error message with the full invalid statement
|
||||
throw new StatementException(
|
||||
$statementException->getMessage() . ' in statement: ' . LF . $statement,
|
||||
1476171315,
|
||||
$statementException
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Flatten the array of arrays by one level
|
||||
$tables = array_merge(...$tables);
|
||||
|
||||
return $tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure we have a table definition for all tables within TCA, add missing ones
|
||||
* as "empty" tables without columns. This is needed for DefaultTcaSchema: It goes
|
||||
* through TCA to add columns automatically, but needs a table definition of all
|
||||
* TCA tables. We're not doing this in DefaultTcaSchema to not introduce a dependency
|
||||
* to the Parser class in there, which we have here so conveniently already.
|
||||
*
|
||||
* @param Table[] $tables
|
||||
* @return Table[]
|
||||
* @throws SchemaException
|
||||
* @throws StatementException
|
||||
*/
|
||||
protected function ensureTableDefinitionForAllTCAManagedTables(array $tables): array
|
||||
{
|
||||
$tableNamesFromTca = $this->tcaSchemaFactory->all()->getNames();
|
||||
$tableNamesFromExtTables = [];
|
||||
foreach ($tables as $table) {
|
||||
$tableNamesFromExtTables[] = $table->getName();
|
||||
}
|
||||
$tableNamesFromExtTables = array_unique($tableNamesFromExtTables);
|
||||
$missingTableNames = array_diff($tableNamesFromTca, $tableNamesFromExtTables);
|
||||
foreach ($missingTableNames as $tableName) {
|
||||
$createTableSql = 'CREATE TABLE ' . $tableName . '();';
|
||||
$tables[] = $this->parser->parse($createTableSql)[0];
|
||||
}
|
||||
return $tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<non-empty-string, Table> $tables
|
||||
* @return array<non-empty-string, Table>
|
||||
*/
|
||||
protected function enrichTablesFromDefaultTCASchema(array $tables): array
|
||||
{
|
||||
return $this->defaultTcaSchema->enrich($tables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the default TCA fields are ordered.
|
||||
*
|
||||
* @param array<non-empty-string, Table> $tables
|
||||
* @return array<non-empty-string, Table>
|
||||
*/
|
||||
protected function ensureDefaultTCAFieldsAreOrdered(array $tables): array
|
||||
{
|
||||
foreach ($tables as $k => $table) {
|
||||
$prioritizedColumnNames = $this->getPrioritizedFieldNames($table->getName());
|
||||
// no TCA table
|
||||
if (empty($prioritizedColumnNames)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prioritizedColumns = [];
|
||||
$nonPrioritizedColumns = [];
|
||||
|
||||
foreach ($table->getColumns() as $columnObject) {
|
||||
if (in_array($columnObject->getName(), $prioritizedColumnNames, true)) {
|
||||
$prioritizedColumns[] = $columnObject;
|
||||
} else {
|
||||
$nonPrioritizedColumns[] = $columnObject;
|
||||
}
|
||||
}
|
||||
|
||||
$tables[$k] = new Table(
|
||||
$table->getName(),
|
||||
array_merge($prioritizedColumns, $nonPrioritizedColumns),
|
||||
$table->getIndexes(),
|
||||
[],
|
||||
$table->getForeignKeys(),
|
||||
$table->getOptions()
|
||||
);
|
||||
}
|
||||
return $tables;
|
||||
}
|
||||
|
||||
protected function flushDatabaseSchemaCache(): void
|
||||
{
|
||||
Bootstrap::createCache('database_schema')->flush();
|
||||
$this->runtime->flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?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;
|
||||
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use TYPO3\CMS\Core\Database\Event\AlterTableDefinitionStatementsEvent;
|
||||
use TYPO3\CMS\Core\Package\PackageManager;
|
||||
|
||||
/**
|
||||
* Helper methods to handle raw SQL input and transform it into individual statements
|
||||
* for further processing.
|
||||
*
|
||||
* @internal not part of public core API.
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
class SqlReader
|
||||
{
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
/**
|
||||
* @var PackageManager
|
||||
*/
|
||||
protected $packageManager;
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(EventDispatcherInterface $eventDispatcher, PackageManager $packageManager)
|
||||
{
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->packageManager = $packageManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycle through all loaded extensions and get full table definitions as concatenated string
|
||||
*
|
||||
* @param bool $withStatic TRUE if sql from ext_tables_static+adt.sql should be loaded, too.
|
||||
* @return string Concatenated SQL of loaded extensions ext_tables.sql
|
||||
*/
|
||||
public function getTablesDefinitionString(bool $withStatic = false): string
|
||||
{
|
||||
$sqlString = [];
|
||||
|
||||
// Find all ext_tables.sql of loaded extensions
|
||||
foreach ($this->packageManager->getActivePackages() as $package) {
|
||||
$packagePath = $package->getPackagePath();
|
||||
if (@file_exists($packagePath . 'ext_tables.sql')) {
|
||||
$sqlString[] = (string)file_get_contents($packagePath . 'ext_tables.sql');
|
||||
}
|
||||
if ($withStatic && @file_exists($packagePath . 'ext_tables_static+adt.sql')) {
|
||||
$sqlString[] = (string)file_get_contents($packagePath . 'ext_tables_static+adt.sql');
|
||||
}
|
||||
}
|
||||
|
||||
$event = $this->eventDispatcher->dispatch(new AlterTableDefinitionStatementsEvent($sqlString));
|
||||
$sqlString = $event->getSqlData();
|
||||
|
||||
return implode(LF . LF, $sqlString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array where every entry is a single SQL-statement.
|
||||
* Input must be formatted like an ordinary MySQL dump file. Every statements needs to be terminated by a ';'
|
||||
* and there may only be one statement (or partial statement) per line.
|
||||
*
|
||||
* @param string $dumpContent The SQL dump content.
|
||||
* @param string|null $queryRegex Regex to select which statements to return.
|
||||
* @return array Array of SQL statements
|
||||
*/
|
||||
public function getStatementArray(string $dumpContent, ?string $queryRegex = null): array
|
||||
{
|
||||
$statementArray = [];
|
||||
$statementArrayPointer = 0;
|
||||
$isInMultilineComment = false;
|
||||
foreach (explode(LF, $dumpContent) as $lineContent) {
|
||||
$lineContent = trim($lineContent);
|
||||
|
||||
// Skip empty lines and comments
|
||||
if ($lineContent === ''
|
||||
|| $lineContent[0] === '#'
|
||||
|| str_starts_with($lineContent, '--')
|
||||
|| str_starts_with($lineContent, '/*')
|
||||
|| str_ends_with($lineContent, '*/')
|
||||
|| $isInMultilineComment
|
||||
) {
|
||||
// skip c style multiline comments
|
||||
if (str_starts_with($lineContent, '/*') && !str_ends_with($lineContent, '*/')) {
|
||||
$isInMultilineComment = true;
|
||||
}
|
||||
if (str_ends_with($lineContent, '*/')) {
|
||||
$isInMultilineComment = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$statementArray[$statementArrayPointer] = ($statementArray[$statementArrayPointer] ?? '') . $lineContent;
|
||||
|
||||
if (str_ends_with($lineContent, ';')) {
|
||||
$statement = trim($statementArray[$statementArrayPointer]);
|
||||
if (!$statement || ($queryRegex && !preg_match('/' . $queryRegex . '/i', $statement))) {
|
||||
unset($statementArray[$statementArrayPointer]);
|
||||
}
|
||||
$statementArrayPointer++;
|
||||
} else {
|
||||
$statementArray[$statementArrayPointer] .= ' ';
|
||||
}
|
||||
}
|
||||
|
||||
return $statementArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract only INSERT statements from SQL dump
|
||||
*/
|
||||
public function getInsertStatementArray(string $dumpContent): array
|
||||
{
|
||||
return $this->getStatementArray($dumpContent, '^INSERT');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract only CREATE TABLE statements from SQL dump
|
||||
*/
|
||||
public function getCreateTableStatementArray(string $dumpContent): array
|
||||
{
|
||||
return $this->getStatementArray($dumpContent, '^CREATE TABLE');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
<?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;
|
||||
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\ColumnDiff;
|
||||
use Doctrine\DBAL\Schema\Exception\InvalidState;
|
||||
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Schema\TableDiff as DoctrineTableDiff;
|
||||
|
||||
/**
|
||||
* Based on the doctrine/dbal implementation restoring direct property access
|
||||
* and adding further helper methods.
|
||||
*
|
||||
* @internal not part of public core API.
|
||||
*/
|
||||
class TableDiff extends DoctrineTableDiff
|
||||
{
|
||||
/**
|
||||
* $newTableName is a TYPO3 internal addition to handle renames at a later point.
|
||||
*/
|
||||
public ?string $newName = null;
|
||||
|
||||
/**
|
||||
* Constructs a TableDiff object.
|
||||
*
|
||||
* @param array<string, Column> $addedColumns
|
||||
* @param array<string, ColumnDiff> $changedColumns
|
||||
* @param array<string, Column> $droppedColumns
|
||||
* @param array<string, Index> $addedIndexes
|
||||
* @param array<string, Index> $modifiedIndexes
|
||||
* @param array<string, Index> $droppedIndexes
|
||||
* @param array<string, Index> $renamedIndexes
|
||||
* @param array<ForeignKeyConstraint> $addedForeignKeys
|
||||
* @param array<ForeignKeyConstraint> $modifiedForeignKeys
|
||||
* @param array<ForeignKeyConstraint> $droppedForeignKeys
|
||||
* @param array<int|string, mixed> $tableOptions
|
||||
*
|
||||
* @internal The diff can be only instantiated by a {@see Comparator}.
|
||||
*
|
||||
* @todo Consider to change from array to typed collections with array access support.
|
||||
*/
|
||||
public function __construct(
|
||||
public Table $oldTable,
|
||||
public array $addedColumns = [],
|
||||
public array $changedColumns = [],
|
||||
public array $droppedColumns = [],
|
||||
public array $addedIndexes = [],
|
||||
public array $modifiedIndexes = [],
|
||||
public array $droppedIndexes = [],
|
||||
public array $renamedIndexes = [],
|
||||
public array $addedForeignKeys = [],
|
||||
public array $modifiedForeignKeys = [],
|
||||
public array $droppedForeignKeys = [],
|
||||
public array $tableOptions = [],
|
||||
) {
|
||||
// NOTE: parent::__construct() not called by intention.
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for table options.
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
public function getTableOptions(): array
|
||||
{
|
||||
return $this->tableOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for table options
|
||||
*
|
||||
* @param array<int|string, mixed> $tableOptions
|
||||
*/
|
||||
public function setTableOptions(array $tableOptions): self
|
||||
{
|
||||
$this->tableOptions = $tableOptions;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a table options has been set.
|
||||
*/
|
||||
public function hasTableOption(string $optionName): bool
|
||||
{
|
||||
return array_key_exists($optionName, $this->tableOptions);
|
||||
}
|
||||
|
||||
public function getTableOption(string $optionName): string
|
||||
{
|
||||
if ($this->hasTableOption($optionName)) {
|
||||
return (string)$this->tableOptions[$optionName];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getOldTable(): Table
|
||||
{
|
||||
return $this->oldTable;
|
||||
}
|
||||
|
||||
/** @return array<string, Column> */
|
||||
public function getAddedColumns(): array
|
||||
{
|
||||
return $this->addedColumns;
|
||||
}
|
||||
|
||||
/** @return array<string, ColumnDiff> */
|
||||
public function getChangedColumns(): array
|
||||
{
|
||||
return $this->changedColumns;
|
||||
}
|
||||
|
||||
/** @return array<string, Column> */
|
||||
public function getDroppedColumns(): array
|
||||
{
|
||||
return $this->droppedColumns;
|
||||
}
|
||||
|
||||
/** @return array<string, Index> */
|
||||
public function getAddedIndexes(): array
|
||||
{
|
||||
return $this->addedIndexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@see getAddedIndexes()} and {@see getDroppedIndexes()} instead.
|
||||
*
|
||||
* @return array<string, Index>
|
||||
*/
|
||||
public function getModifiedIndexes(): array
|
||||
{
|
||||
return $this->modifiedIndexes;
|
||||
}
|
||||
|
||||
/** @return array<string, Index> */
|
||||
public function getDroppedIndexes(): array
|
||||
{
|
||||
return $this->droppedIndexes;
|
||||
}
|
||||
|
||||
/** @return array<string,Index> */
|
||||
public function getRenamedIndexes(): array
|
||||
{
|
||||
return $this->renamedIndexes;
|
||||
}
|
||||
|
||||
/** @return array<ForeignKeyConstraint> */
|
||||
public function getAddedForeignKeys(): array
|
||||
{
|
||||
return $this->addedForeignKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@see getAddedForeignKeys()} and {@see getDroppedForeignKeys()} instead.
|
||||
*
|
||||
* @return array<ForeignKeyConstraint>
|
||||
*/
|
||||
public function getModifiedForeignKeys(): array
|
||||
{
|
||||
return $this->modifiedForeignKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@see getDroppedForeignKeyConstraintNames()}.
|
||||
*
|
||||
* @return array<ForeignKeyConstraint>
|
||||
*/
|
||||
public function getDroppedForeignKeys(): array
|
||||
{
|
||||
return $this->droppedForeignKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden, because the parent implementation reads the parent property directly. As the parent
|
||||
* constructor is not called by intention, that property is never initialized, making the inherited
|
||||
* method raise an `Error`. Reads the redeclared property here instead, keeping the parent behaviour.
|
||||
*
|
||||
* @return array<UnqualifiedName>
|
||||
*/
|
||||
public function getDroppedForeignKeyConstraintNames(): array
|
||||
{
|
||||
$names = [];
|
||||
foreach ($this->droppedForeignKeys as $droppedForeignKey) {
|
||||
$name = $droppedForeignKey->getObjectName();
|
||||
if ($name === null) {
|
||||
throw InvalidState::tableDiffContainsUnnamedDroppedForeignKeyConstraints();
|
||||
}
|
||||
$names[] = $name;
|
||||
}
|
||||
return $names;
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return count($this->getAddedColumns()) === 0
|
||||
&& count($this->getChangedColumns()) === 0
|
||||
&& count($this->getDroppedColumns()) === 0
|
||||
&& count($this->getAddedIndexes()) === 0
|
||||
&& count($this->getModifiedIndexes()) === 0
|
||||
&& count($this->getDroppedIndexes()) === 0
|
||||
&& count($this->getRenamedIndexes()) === 0
|
||||
&& count($this->getAddedForeignKeys()) === 0
|
||||
&& count($this->getModifiedForeignKeys()) === 0
|
||||
&& count($this->getDroppedForeignKeys()) === 0
|
||||
// doctrine/dbal 4.x removed the newName. TYPO3 needs that to provide a rename to prefix logic before
|
||||
// really dropping tables instead. Therefore, we need to add here an empty check for the reintroduced
|
||||
// property.See for example: ConnectionMigrator->migrateUnprefixedRemovedTablesToRenames
|
||||
&& $this->getNewName() !== null && $this->getNewName() !== ''
|
||||
&& $this->getTableOptions() === [];
|
||||
}
|
||||
|
||||
public function getNewName(): ?string
|
||||
{
|
||||
return $this->newName;
|
||||
}
|
||||
|
||||
public static function ensure(DoctrineTableDiff|TableDiff $tableDiff): self
|
||||
{
|
||||
$diff = new self(
|
||||
// oldTable
|
||||
$tableDiff->getOldTable(),
|
||||
// addedColumns
|
||||
$tableDiff->getAddedColumns(),
|
||||
// changedColumns
|
||||
[],
|
||||
// droppedColumns
|
||||
$tableDiff->getDroppedColumns(),
|
||||
// addedIndexes
|
||||
$tableDiff->getAddedIndexes(),
|
||||
// modifiedIndexes
|
||||
[],
|
||||
// droppedIndexes
|
||||
$tableDiff->getDroppedIndexes(),
|
||||
// renamedIndexes
|
||||
$tableDiff->getRenamedIndexes(),
|
||||
// addedForeignKeys
|
||||
$tableDiff->getAddedForeignKeys(),
|
||||
// modifiedForeignKeys
|
||||
$tableDiff->getModifiedForeignKeys(),
|
||||
// droppedForeignKeys
|
||||
$tableDiff->getDroppedForeignKeys(),
|
||||
// tableOptions
|
||||
($tableDiff instanceof TableDiff ? $tableDiff->tableOptions : []),
|
||||
);
|
||||
|
||||
// doctrine/dbal 4+ removed the column name as array index for modified column definitions,
|
||||
// but we rely on it. Restore it !
|
||||
// Ensure to use custom ColumnDiff instance with more data and
|
||||
foreach ($tableDiff->getChangedColumns() as $changedColumn) {
|
||||
$diff->changedColumns[$changedColumn->getOldColumn()->getName()] = new ColumnDiff(
|
||||
// oldColumn
|
||||
$changedColumn->getOldColumn(),
|
||||
// newColumn
|
||||
$changedColumn->getNewColumn(),
|
||||
);
|
||||
}
|
||||
|
||||
// doctrine/dbal 4+ removed the index name as array index for modified index definitions,
|
||||
// but we rely on it. Restore it !.
|
||||
foreach ($tableDiff->getModifiedIndexes() as $modifiedIndex) {
|
||||
$diff->modifiedIndexes[$modifiedIndex->getName()] = $modifiedIndex;
|
||||
}
|
||||
|
||||
// Accumulate modified index separated into added and dropped information to modifiedIndexes again,
|
||||
// otherwise required drop action may not be executed before trying to add an existing index first.
|
||||
// Required for planned doctrine/dbal 4.3.0 change (deprecation) and currently breaking with an open
|
||||
// discussion to mitigate that before dbal release. We still prepare for this case to be on the safer
|
||||
// side here.
|
||||
// Needs to be done in a two-step strategy to avoid changing array while iterating over it.
|
||||
// - https://github.com/doctrine/dbal/pull/6831
|
||||
// - https://github.com/doctrine/dbal/issues/6880
|
||||
/**
|
||||
* @var array<int, array{added: Index, dropped: Index}> $transformIndexOperations
|
||||
*/
|
||||
$transformIndexOperations = [];
|
||||
foreach ($diff->getAddedIndexes() as $addedIndex) {
|
||||
foreach ($diff->getDroppedIndexes() as $droppedIndex) {
|
||||
if ($droppedIndex->getName() === $addedIndex->getName()) {
|
||||
$transformIndexOperations[] = [
|
||||
'added' => $addedIndex,
|
||||
'dropped' => $droppedIndex,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($transformIndexOperations as $data) {
|
||||
$diff->unsetAddedIndex($data['added']);
|
||||
$diff->unsetDroppedIndex($data['dropped']);
|
||||
$diff->modifiedIndexes[$data['added']->getName()] = $data['added'];
|
||||
}
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal This method exists only for compatibility with the current implementation of schema managers
|
||||
* that modify the diff while processing it.
|
||||
*/
|
||||
public function unsetAddedIndex(Index $index): void
|
||||
{
|
||||
$this->addedIndexes = array_filter(
|
||||
$this->addedIndexes,
|
||||
static function (Index $addedIndex) use ($index): bool {
|
||||
return $addedIndex !== $index;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal This method exists only for compatibility with the current implementation of schema managers
|
||||
* that modify the diff while processing it.
|
||||
*/
|
||||
public function unsetDroppedIndex(Index $index): void
|
||||
{
|
||||
$this->droppedIndexes = array_filter(
|
||||
$this->droppedIndexes,
|
||||
static function (Index $droppedIndex) use ($index): bool {
|
||||
return $droppedIndex !== $index;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?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\Types;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform as DoctrineAbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Exception\InvalidType;
|
||||
|
||||
/**
|
||||
* This custom type extends doctrine native DateTimeType to allow a
|
||||
* formatted string (in "Y-m-d H:i:s") directly, in addition to a DateTimeInterface.
|
||||
*
|
||||
* @internal not part of public core API.
|
||||
*/
|
||||
class DateTimeType extends \Doctrine\DBAL\Types\DateTimeType
|
||||
{
|
||||
public function convertToDatabaseValue($value, DoctrineAbstractPlatform $platform): ?string
|
||||
{
|
||||
if ($value === null || (is_string($value) && $value !== '')) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($value instanceof \DateTimeInterface) {
|
||||
return $value->format($platform->getDateTimeFormatString());
|
||||
}
|
||||
|
||||
throw InvalidType::new($value, self::getTypeRegistry()->lookupName($this), ['null', 'string', 'DateTime']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?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\Types;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform as DoctrineAbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Exception\InvalidType;
|
||||
|
||||
/**
|
||||
* This custom type extends doctrine native DateType to allow a
|
||||
* formatted string (in "Y-m-d") directly, in addition to a DateTimeInterface.
|
||||
*
|
||||
* @internal not part of public core API.
|
||||
*/
|
||||
class DateType extends \Doctrine\DBAL\Types\DateType
|
||||
{
|
||||
public function convertToDatabaseValue($value, DoctrineAbstractPlatform $platform): mixed
|
||||
{
|
||||
if ($value === null || (is_string($value) && $value !== '')) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($value instanceof \DateTimeInterface) {
|
||||
return $value->format($platform->getDateFormatString());
|
||||
}
|
||||
|
||||
throw InvalidType::new($value, self::getTypeRegistry()->lookupName($this), ['null', 'string', 'DateTime']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?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\Types;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
/**
|
||||
* Type that maps a TYPE field.
|
||||
*
|
||||
* @internal not part of public core API.
|
||||
*
|
||||
* @todo SetType does not work for SQLite and PostgresSQL. SQLite supports it with a slightly other syntax and
|
||||
* PostgreSQL needs to create a custom type with a human-readable name, which is not reasonable either. Consider
|
||||
* to deprecate and drop ENUM support due not having compatibility for all supported database systems.
|
||||
*/
|
||||
class SetType extends Type
|
||||
{
|
||||
public const TYPE = 'set';
|
||||
|
||||
/**
|
||||
* Gets the SQL declaration snippet for a field of this type.
|
||||
*
|
||||
* @param array $fieldDeclaration The field declaration.
|
||||
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The currently used database platform.
|
||||
*/
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
|
||||
{
|
||||
if (method_exists($platform, 'getSetDeclarationSQL')) {
|
||||
return $platform->getSetDeclarationSQL($fieldDeclaration);
|
||||
}
|
||||
$quotedValues = array_map($platform->quoteStringLiteral(...), $fieldDeclaration['values']);
|
||||
return sprintf('SET(%s)', implode(', ', $quotedValues));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?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\Types;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform as DoctrineAbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Exception\InvalidType;
|
||||
|
||||
/**
|
||||
* This custom type extends doctrine native TimeType to allow a
|
||||
* formatted string (in "H:i:s") directly, in addition to a DateTimeInterface.
|
||||
*
|
||||
* @internal not part of public core API.
|
||||
*/
|
||||
class TimeType extends \Doctrine\DBAL\Types\TimeType
|
||||
{
|
||||
public function convertToDatabaseValue($value, DoctrineAbstractPlatform $platform): ?string
|
||||
{
|
||||
if ($value === null || (is_string($value) && $value !== '')) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($value instanceof \DateTimeInterface) {
|
||||
return $value->format($platform->getTimeFormatString());
|
||||
}
|
||||
|
||||
throw InvalidType::new($value, self::getTypeRegistry()->lookupName($this), ['null', 'DateTime']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user