TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -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;
|
||||
|
||||
use Doctrine\DBAL\Configuration as DoctrineConfiguration;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
final class Configuration extends DoctrineConfiguration
|
||||
{
|
||||
private ?ContainerInterface $container = null;
|
||||
|
||||
public function getContainer(): ContainerInterface
|
||||
{
|
||||
return $this->container ?? throw new \LogicException('Doctrine database configuration requires a container to be set via `setContainer()`', 1782369693);
|
||||
}
|
||||
|
||||
public function setContainer(ContainerInterface $container): self
|
||||
{
|
||||
$this->container = $container;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,505 @@
|
||||
<?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;
|
||||
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\Configuration as DoctrineConfiguration;
|
||||
use Doctrine\DBAL\Connection as DoctrineConnection;
|
||||
use Doctrine\DBAL\Connection\StaticServerVersionProvider;
|
||||
use Doctrine\DBAL\Driver;
|
||||
use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\DBAL\Platforms\MariaDBPlatform as DoctrineMariaDBPlatform;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform as DoctrineMySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform as DoctrinePostgreSQLPlatform;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\DBAL\ServerVersionProvider;
|
||||
use TYPO3\CMS\Core\Cache\CacheManager;
|
||||
use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
|
||||
use TYPO3\CMS\Core\Database\Query\BulkInsertQuery;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
|
||||
use TYPO3\CMS\Core\Database\Schema\SchemaInformation;
|
||||
use TYPO3\CMS\Core\Package\Cache\PackageDependentCacheIdentifier;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Note: This is a fragile base class. It owns no contract of its own and specializes Doctrine's
|
||||
* concrete Connection by overriding quoting, insert/update/delete and connect behavior.
|
||||
* Subclassing is forced on us though: Doctrine's DriverManager only accepts a `wrapperClass` that
|
||||
* is a Doctrine\DBAL\Connection, and DBAL exposes no high-level connection interface to compose
|
||||
* against, so a real composition decorator is not possible.
|
||||
*
|
||||
* Integrators may in turn point the `wrapperClass` connection option at a subclass of this
|
||||
* class, but because of the fragility described above such subclasses are not covered by the
|
||||
* backward-compatibility promise.
|
||||
*/
|
||||
class Connection extends DoctrineConnection
|
||||
{
|
||||
/**
|
||||
* Represents a SQL NULL data type.
|
||||
*/
|
||||
public const PARAM_NULL = ParameterType::NULL;
|
||||
|
||||
/**
|
||||
* Represents a SQL INTEGER data type.
|
||||
*/
|
||||
public const PARAM_INT = ParameterType::INTEGER;
|
||||
|
||||
/**
|
||||
* Represents a SQL CHAR, VARCHAR data type.
|
||||
*/
|
||||
public const PARAM_STR = ParameterType::STRING;
|
||||
|
||||
/**
|
||||
* Represents a SQL large object data type.
|
||||
*/
|
||||
public const PARAM_LOB = ParameterType::LARGE_OBJECT;
|
||||
|
||||
/**
|
||||
* Represents a boolean data type.
|
||||
*/
|
||||
public const PARAM_BOOL = ParameterType::BOOLEAN;
|
||||
|
||||
/**
|
||||
* Represents an array of integer values.
|
||||
*/
|
||||
public const PARAM_INT_ARRAY = ArrayParameterType::INTEGER;
|
||||
|
||||
/**
|
||||
* Represents an array of string values.
|
||||
*/
|
||||
public const PARAM_STR_ARRAY = ArrayParameterType::STRING;
|
||||
|
||||
private ExpressionBuilder $expressionBuilder;
|
||||
private array $prepareConnectionCommands = [];
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the Connection class.
|
||||
*
|
||||
* @param array $params The connection parameters.
|
||||
* @param Driver $driver The driver to use.
|
||||
* @param DoctrineConfiguration|null $config The configuration, optional.
|
||||
*/
|
||||
public function __construct(#[\SensitiveParameter] array $params, Driver $driver, ?DoctrineConfiguration $config = null)
|
||||
{
|
||||
parent::__construct($params, $driver, $config);
|
||||
if (!$config instanceof Configuration) {
|
||||
throw new \InvalidArgumentException('TYPO3 Connection expects the custom TYPO3 Configuration object to be given', 1782369775);
|
||||
}
|
||||
$this->expressionBuilder = GeneralUtility::makeInstance(ExpressionBuilder::class, $this, $config->getContainer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the DatabasePlatform for the connection and initializes custom types and event listeners.
|
||||
*/
|
||||
protected function connect(): ConnectionInterface
|
||||
{
|
||||
if ($this->_conn !== null) {
|
||||
return $this->_conn;
|
||||
}
|
||||
// Early return if the connection is already open and custom setup has been done.
|
||||
$connection = parent::connect();
|
||||
foreach ($this->prepareConnectionCommands as $command) {
|
||||
$this->executeStatement($command);
|
||||
}
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of a SQL query builder.
|
||||
*/
|
||||
public function createQueryBuilder(): QueryBuilder
|
||||
{
|
||||
return GeneralUtility::makeInstance(QueryBuilder::class, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quotes a string so it can be safely used as a table or column name, even if
|
||||
* it is a reserved name.
|
||||
* EXAMPLE: tableName.fieldName => "tableName"."fieldName"
|
||||
*
|
||||
* Delimiting style depends on the underlying database platform that is being used.
|
||||
*
|
||||
* Note that this does not call the parent implementation, because both
|
||||
* Doctrine DBAL `Connection::quoteIdentifier()` and `AbstractPlatform::quoteIdentifier()`
|
||||
* are deprecated and will be removed with Doctrine DBAL 5.0. The quoting is done
|
||||
* here instead, identical to the removed implementation.
|
||||
*
|
||||
* @param string $identifier The name to be quoted.
|
||||
* @return string The quoted name.
|
||||
*/
|
||||
public function quoteIdentifier(string $identifier): string
|
||||
{
|
||||
if ($identifier === '*') {
|
||||
return $identifier;
|
||||
}
|
||||
$platform = $this->getDatabasePlatform();
|
||||
if (!str_contains($identifier, '.')) {
|
||||
return $platform->quoteSingleIdentifier($identifier);
|
||||
}
|
||||
return implode('.', array_map($platform->quoteSingleIdentifier(...), explode('.', $identifier)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Quotes an array of column names, so it can be safely used, even if the name is a reserved name.
|
||||
* Delimiting style depends on the underlying database platform that is being used.
|
||||
*/
|
||||
public function quoteIdentifiers(array $input): array
|
||||
{
|
||||
return array_map($this->quoteIdentifier(...), $input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quotes an associative array of column-value so the column names can be safely used, even
|
||||
* if the name is a reserved name.
|
||||
* Delimiting style depends on the underlying database platform that is being used.
|
||||
*/
|
||||
public function quoteColumnValuePairs(array $input): array
|
||||
{
|
||||
return array_combine($this->quoteIdentifiers(array_keys($input)), array_values($input));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if the column types are specified by column name or using
|
||||
* positional information. In the first case quote the field names
|
||||
* accordingly.
|
||||
*/
|
||||
protected function quoteColumnTypes(array $input): array
|
||||
{
|
||||
if (!is_string(key($input))) {
|
||||
return $input;
|
||||
}
|
||||
return $this->quoteColumnValuePairs($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quotes like wildcards for given string value.
|
||||
*
|
||||
* @param string $value The value to be quoted.
|
||||
* @return string The quoted value.
|
||||
*/
|
||||
public function escapeLikeWildcards(string $value): string
|
||||
{
|
||||
return addcslashes($value, '_%');
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a table row with specified data.
|
||||
*
|
||||
* All SQL identifiers are expected to be unquoted and will be quoted when building the query.
|
||||
*
|
||||
* @param string $tableName The name of the table to insert data into.
|
||||
* @param array $data An associative array containing column-value pairs.
|
||||
* @param array $types Types of the inserted data.
|
||||
* @return int The number of affected rows.
|
||||
* @throws Exception
|
||||
*/
|
||||
public function insert(string $tableName, array $data, array $types = []): int
|
||||
{
|
||||
$this->ensureDatabaseValueTypes($tableName, $data, $types);
|
||||
return parent::insert(
|
||||
$this->quoteIdentifier($tableName),
|
||||
$this->quoteColumnValuePairs($data),
|
||||
$this->quoteColumnTypes($types)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk inserts table rows with specified data.
|
||||
* All SQL identifiers are expected to be unquoted and will be quoted when building the query.
|
||||
*
|
||||
* @param string $tableName The name of the table to insert data into.
|
||||
* @param array $data An array containing associative arrays of column-value pairs or just the values to be inserted.
|
||||
* @param array $columns An array containing the column names of the data which should be inserted.
|
||||
* @param array $types Types of the inserted data.
|
||||
* @return int The number of affected rows.
|
||||
*/
|
||||
public function bulkInsert(string $tableName, array $data, array $columns = [], array $types = []): int
|
||||
{
|
||||
$totalAffectedRows = 0;
|
||||
$columnLength = $columns !== [] ? count($columns) : 1000;
|
||||
$maxBindParameters = PlatformInformation::getMaxBindParameters($this->getDatabasePlatform());
|
||||
$maxChunkSize = (int)(($maxBindParameters / $columnLength) / 2);
|
||||
$chunks = array_chunk($data, $maxChunkSize);
|
||||
foreach ($chunks as $chunk) {
|
||||
$query = GeneralUtility::makeInstance(BulkInsertQuery::class, $this, $tableName, $columns);
|
||||
foreach ($chunk as $values) {
|
||||
$this->ensureDatabaseValueTypes($tableName, $values, $types);
|
||||
$query->addValues($values, $types);
|
||||
}
|
||||
$totalAffectedRows += $query->execute();
|
||||
}
|
||||
return $totalAffectedRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an SQL SELECT statement on a table.
|
||||
* All SQL identifiers are expected to be unquoted and will be quoted when building the query.
|
||||
*
|
||||
* @param string[] $columns The columns of the table which to select.
|
||||
* @param string $tableName The name of the table on which to select.
|
||||
* @param array $identifiers The selection criteria. An associative array containing column-value pairs.
|
||||
* @param string[] $groupBy The columns to group the results by.
|
||||
* @param array $orderBy Associative array of column name/sort directions pairs.
|
||||
* @param int $limit The maximum number of rows to return.
|
||||
* @param int $offset The first result row to select (when used with limit)
|
||||
* @return Result The executed statement.
|
||||
*/
|
||||
public function select(
|
||||
array $columns,
|
||||
string $tableName,
|
||||
array $identifiers = [],
|
||||
array $groupBy = [],
|
||||
array $orderBy = [],
|
||||
int $limit = 0,
|
||||
int $offset = 0
|
||||
) {
|
||||
$query = $this->createQueryBuilder();
|
||||
$query->select(...$columns)->from($tableName);
|
||||
foreach ($identifiers as $identifier => $value) {
|
||||
$query->andWhere($query->expr()->eq($identifier, $query->createNamedParameter($value)));
|
||||
}
|
||||
foreach ($orderBy as $fieldName => $order) {
|
||||
$query->addOrderBy($fieldName, $order);
|
||||
}
|
||||
if (!empty($groupBy)) {
|
||||
$query->groupBy(...$groupBy);
|
||||
}
|
||||
if ($limit > 0) {
|
||||
$query->setMaxResults($limit);
|
||||
$query->setFirstResult($offset);
|
||||
}
|
||||
return $query->executeQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an SQL UPDATE statement on a table.
|
||||
* All SQL identifiers are expected to be unquoted and will be quoted when building the query.
|
||||
*
|
||||
* @param string $tableName The name of the table to update.
|
||||
* @param array $data An associative array containing column-value pairs.
|
||||
* @param array $identifier The update criteria. An associative array containing column-value pairs.
|
||||
* @param array $types Types of the merged $data and $identifier arrays in that order.
|
||||
* @return int The number of affected rows.
|
||||
* @throws Exception
|
||||
*/
|
||||
public function update(string $tableName, array $data, array $identifier = [], array $types = []): int
|
||||
{
|
||||
$this->ensureDatabaseValueTypes($tableName, $data, $types);
|
||||
return parent::update(
|
||||
$this->quoteIdentifier($tableName),
|
||||
$this->quoteColumnValuePairs($data),
|
||||
$this->quoteColumnValuePairs($identifier),
|
||||
$this->quoteColumnTypes($types)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an SQL DELETE statement on a table.
|
||||
* All SQL identifiers are expected to be unquoted and will be quoted when building the query.
|
||||
*
|
||||
* @param string $tableName The name of the table on which to delete.
|
||||
* @param array $identifier The deletion criteria. An associative array containing column-value pairs.
|
||||
* @param array $types The types of identifiers.
|
||||
* @return int The number of affected rows.
|
||||
*/
|
||||
public function delete(string $tableName, array $identifier = [], array $types = []): int
|
||||
{
|
||||
return parent::delete(
|
||||
$this->quoteIdentifier($tableName),
|
||||
$this->quoteColumnValuePairs($identifier),
|
||||
$this->quoteColumnTypes($types)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an SQL TRUNCATE statement on a table.
|
||||
* All SQL identifiers are expected to be unquoted and will be quoted when building the query.
|
||||
*
|
||||
* @param string $tableName The name of the table to truncate.
|
||||
* @param bool $cascade Not supported on many platforms but would cascade the truncate by following foreign keys.
|
||||
* @return int The number of affected rows. For a truncate this is unreliable as there is no meaningful information.
|
||||
*/
|
||||
public function truncate(string $tableName, bool $cascade = false): int
|
||||
{
|
||||
return $this->executeStatement(
|
||||
$this->getDatabasePlatform()->getTruncateTableSQL(
|
||||
$this->quoteIdentifier($tableName),
|
||||
$cascade
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an SQL SELECT COUNT() statement on a table and returns the count result.
|
||||
*
|
||||
* @param string $item The column/expression of the table which to count
|
||||
* @param string $tableName The name of the table on which to count.
|
||||
* @param array $identifiers The selection criteria. An associative array containing column-value pairs.
|
||||
* @return int The number of rows counted
|
||||
*/
|
||||
public function count(string $item, string $tableName, array $identifiers): int
|
||||
{
|
||||
$query = $this->createQueryBuilder();
|
||||
$query->count($item)->from($tableName);
|
||||
foreach ($identifiers as $identifier => $value) {
|
||||
$query->andWhere($query->expr()->eq($identifier, $query->createNamedParameter($value)));
|
||||
}
|
||||
return (int)$query->executeQuery()->fetchOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of the current platform if applicable, containing the platform as prefix.
|
||||
*
|
||||
* If no version information is available only the platform name will be shown.
|
||||
* If the platform name is unknown or unsupported the driver name will be shown.
|
||||
*
|
||||
* @internal only and not part of public API.
|
||||
*/
|
||||
public function getPlatformServerVersion(): string
|
||||
{
|
||||
$platform = $this->getDatabasePlatform();
|
||||
$version = trim($this->typo3_getServerVersionProvider()->getServerVersion());
|
||||
if ($version !== '') {
|
||||
$version = ' ' . $version;
|
||||
}
|
||||
return match (true) {
|
||||
// @todo Check if we should use 'MariaDB' now directly instead of MySQL as an alias.
|
||||
$platform instanceof DoctrineMariaDBPlatform => 'MySQL' . $version,
|
||||
$platform instanceof DoctrineMySQLPlatform => 'MySQL' . $version,
|
||||
$platform instanceof DoctrinePostgreSQLPlatform => 'PostgreSQL' . $version,
|
||||
default => (str_replace('Platform', '', array_reverse(explode('\\', $platform::class))[0])) . $version,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute commands after initializing a new connection.
|
||||
*/
|
||||
public function prepareConnection(string $commands): void
|
||||
{
|
||||
if (empty($commands)) {
|
||||
return;
|
||||
}
|
||||
$this->prepareConnectionCommands = GeneralUtility::trimExplode(
|
||||
LF,
|
||||
str_replace(
|
||||
'\' . LF . \'',
|
||||
LF,
|
||||
$commands
|
||||
),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the last inserted row.
|
||||
* If the underlying driver does not support identity columns, an exception is thrown.
|
||||
*
|
||||
* @return numeric-string
|
||||
*/
|
||||
public function lastInsertId(): string
|
||||
{
|
||||
return (string)parent::lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ExpressionBuilder for the connection.
|
||||
*/
|
||||
public function getExpressionBuilder(): ExpressionBuilder
|
||||
{
|
||||
return $this->expressionBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method ensures that data values a properly converted to their database equivalent.
|
||||
* Additionally, it adds the proper types to the type-array, if this has no manual preset types.
|
||||
* Note: Types are *only* added if not given externally.
|
||||
*
|
||||
* @internal Should be private, but mocked in tests currently.
|
||||
*/
|
||||
protected function ensureDatabaseValueTypes(string $tableName, array &$data, array &$types): void
|
||||
{
|
||||
$tableInfo = $this->getSchemaInformation()->getTableInfo($tableName);
|
||||
array_walk($data, function (mixed &$value, string $key) use ($tableInfo, &$types): void {
|
||||
// Use database schema field type in case no Type or ParameterType has been provided manually
|
||||
// for field `$key`, falling back to ParameterType::STRING in case field does not exists in
|
||||
// the schema, which is the default ParameterType used by doctrine anyway.
|
||||
if (!isset($types[$key]) && $tableInfo->hasColumnInfo($key)) {
|
||||
$types[$key] = $tableInfo->getColumnInfo($key)->getType();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal May vanish anytime, currently used core-internal at some places.
|
||||
*/
|
||||
public function getSchemaInformation(): SchemaInformation
|
||||
{
|
||||
return new SchemaInformation(
|
||||
$this,
|
||||
GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime'),
|
||||
GeneralUtility::makeInstance(CacheManager::class)->getCache('database_schema'),
|
||||
GeneralUtility::makeInstance(PackageDependentCacheIdentifier::class),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a function in a transaction.
|
||||
*
|
||||
* The function gets passed this Connection instance as an (optional) parameter.
|
||||
*
|
||||
* If an exception occurs during execution of the function or transaction commit,
|
||||
* the transaction is rolled back and the exception re-thrown.
|
||||
*
|
||||
* @param \Closure(self):T $func The function to execute transactionally.
|
||||
* @return T The value returned by $func
|
||||
* @throws \Throwable
|
||||
* @template T
|
||||
*/
|
||||
public function transactional(\Closure $func): mixed
|
||||
{
|
||||
/** @var \Closure(DoctrineConnection):T $func Required to satisfy PHPStan. */
|
||||
return parent::transactional($func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the suitable `ServerVersionProvider`, which could be the connection itself or
|
||||
* a `StaticServerVersionProvider` based on either of following configuration values:
|
||||
*
|
||||
* - $params['serverVersion']
|
||||
* - $params['primary']['serverVersion']
|
||||
*
|
||||
* This is an extract from {@see \Doctrine\DBAL\Connection::getDatabasePlatform()} and handled as internal for
|
||||
* now and will be tried to provide upstream making it API and is the reason why it is a prefixed method.
|
||||
*
|
||||
* It's currently only used in internal {@see self::getPlatformServerVersion()}.
|
||||
*
|
||||
* @internal only and not part of public API.
|
||||
*/
|
||||
protected function typo3_getServerVersionProvider(): ServerVersionProvider
|
||||
{
|
||||
$params = $this->getParams();
|
||||
return match (true) {
|
||||
isset($params['serverVersion']) => new StaticServerVersionProvider($params['serverVersion']),
|
||||
isset($params['primary']['serverVersion']) => new StaticServerVersionProvider($params['primary']['serverVersion']),
|
||||
default => $this,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
<?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;
|
||||
|
||||
use Doctrine\DBAL\Driver\Middleware as DriverMiddleware;
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\DBAL\Exception\MalformedDsnException;
|
||||
use Doctrine\DBAL\Tools\DsnParser;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use TYPO3\CMS\Core\Database\Middleware\UsableForConnectionInterface;
|
||||
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
|
||||
use TYPO3\CMS\Core\Database\Schema\SchemaManager\CoreSchemaManagerFactory;
|
||||
use TYPO3\CMS\Core\Database\Schema\Types\DateTimeType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Types\DateType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Types\SetType;
|
||||
use TYPO3\CMS\Core\Database\Schema\Types\TimeType;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Manager that handles opening/retrieving database connections.
|
||||
*
|
||||
* It's a facade to the actual Doctrine DBAL DriverManager that implements TYPO3
|
||||
* specific functionality like mapping individual tables to different database
|
||||
* connections.
|
||||
*
|
||||
* getConnectionForTable() is the only supported way to get a connection that
|
||||
* honors the table mapping configuration.
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
class ConnectionPool
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const DEFAULT_CONNECTION_NAME = 'Default';
|
||||
|
||||
/**
|
||||
* @var Connection[]
|
||||
*/
|
||||
protected array $connections = [];
|
||||
|
||||
/**
|
||||
* @var array<non-empty-string,class-string>
|
||||
* @todo Needs to be refactored. Only MySQL and MariaDB support this type, using this to register the type AND
|
||||
* add mappings to all connections, even unsupported connections for SQLite or PostgreSQL is not correct,
|
||||
* and needs to be respected. Or the type needs to provide working fallbacks for unsupported platforms.
|
||||
*/
|
||||
protected static array $customDoctrineTypes = [
|
||||
SetType::TYPE => SetType::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<non-empty-string,class-string>
|
||||
* @todo Needs to be refactored to differentiate between type registration and platform specific type mapping.
|
||||
*/
|
||||
protected static array $overrideDoctrineTypes = [
|
||||
Types::DATE_MUTABLE => DateType::class,
|
||||
Types::DATETIME_MUTABLE => DateTimeType::class,
|
||||
Types::DATETIME_IMMUTABLE => DateTimeType::class,
|
||||
Types::TIME_MUTABLE => TimeType::class,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
protected readonly ContainerInterface $container,
|
||||
protected readonly CoreSchemaManagerFactory $coreSchemaManagerFactory,
|
||||
protected readonly DriverMiddlewareService $driverMiddlewareService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a connection object based on the specified table name.
|
||||
*
|
||||
* This is the official entry point to get a database connection to ensure
|
||||
* that the mapping of table names to database connections is honored.
|
||||
*
|
||||
* @param string $tableName
|
||||
*/
|
||||
public function getConnectionForTable(string $tableName): Connection
|
||||
{
|
||||
if (empty($tableName)) {
|
||||
throw new \UnexpectedValueException(
|
||||
'ConnectionPool->getConnectionForTable() requires a table name to be provided.',
|
||||
1459421719
|
||||
);
|
||||
}
|
||||
|
||||
$connectionName = self::DEFAULT_CONNECTION_NAME;
|
||||
if (!empty($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName])) {
|
||||
$connectionName = (string)$GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName];
|
||||
}
|
||||
|
||||
return $this->getConnectionByName($connectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a connection object based on the specified identifier.
|
||||
*
|
||||
* This method should only be used in edge cases. Use getConnectionForTable() so
|
||||
* that the tablename<>databaseConnection mapping will be taken into account.
|
||||
*
|
||||
* @param string $connectionName
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
*/
|
||||
public function getConnectionByName(string $connectionName): Connection
|
||||
{
|
||||
if (empty($connectionName)) {
|
||||
throw new \UnexpectedValueException(
|
||||
'ConnectionPool->getConnectionByName() requires a connection name to be provided.',
|
||||
1459422125
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($this->connections[$connectionName])) {
|
||||
return $this->connections[$connectionName];
|
||||
}
|
||||
|
||||
$this->connections[$connectionName] = $this->getDatabaseConnection(
|
||||
$connectionName,
|
||||
$this->getConnectionParams($connectionName),
|
||||
);
|
||||
|
||||
return $this->connections[$connectionName];
|
||||
}
|
||||
|
||||
protected function getConnectionParams(string $connectionName): array
|
||||
{
|
||||
$connectionParams = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][$connectionName] ?? [];
|
||||
if (empty($connectionParams)) {
|
||||
throw new \RuntimeException(
|
||||
'The requested database connection named "' . $connectionName . '" has not been configured.',
|
||||
1459422492
|
||||
);
|
||||
}
|
||||
if (!empty($connectionParams['url'])) {
|
||||
$dsnUrl = $connectionParams['url'];
|
||||
unset($connectionParams['url']);
|
||||
try {
|
||||
$parsedParams = (new DsnParser())->parse($dsnUrl);
|
||||
} catch (MalformedDsnException $e) {
|
||||
throw new \UnexpectedValueException('Malformed connection parameter "url".', 1750964898, $e);
|
||||
}
|
||||
$connectionParams = [...$connectionParams, ...$parsedParams];
|
||||
}
|
||||
if (empty($connectionParams['wrapperClass'])) {
|
||||
$connectionParams['wrapperClass'] = Connection::class;
|
||||
}
|
||||
if (!is_a($connectionParams['wrapperClass'], Connection::class, true)) {
|
||||
throw new \UnexpectedValueException(
|
||||
'The "wrapperClass" for the connection name "' . $connectionName
|
||||
. '" needs to be a subclass of "' . Connection::class . '".',
|
||||
1459422968
|
||||
);
|
||||
}
|
||||
// Ensure integer value for port.
|
||||
if (array_key_exists('port', $connectionParams)) {
|
||||
$connectionParams['port'] = (int)($connectionParams['port'] ?? 0);
|
||||
}
|
||||
return $this->migrateConnectionParams($connectionName, $connectionParams);
|
||||
}
|
||||
|
||||
private function migrateConnectionParams(string $connectionName, #[\SensitiveParameter] array $params): array
|
||||
{
|
||||
$params['defaultTableOptions'] ??= [];
|
||||
$params = $this->removeInvalidConnectionParams($params);
|
||||
return $this->ensureDefaultConnectionCharset($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up invalid connection parameters.
|
||||
*/
|
||||
private function removeInvalidConnectionParams(#[\SensitiveParameter] array $params): array
|
||||
{
|
||||
// Remove defaultTableOptions for unsupported databases
|
||||
unset($params['tableoptions']);
|
||||
// Ensure to remove `defaultTableOptions` for drivers not supporting it.
|
||||
if (!in_array((string)($params['driver'] ?? ''), ['mysqli', 'pdo_mysql'], true)) {
|
||||
unset($params['defaultTableOptions']);
|
||||
return $params;
|
||||
}
|
||||
// ENGINE is a TYPO3 custom option not handled by doctrine/dbal by a custom implementation,
|
||||
// see `MySQLCompatibleAlterTablePlatformAwareTrait`
|
||||
$allowedDefaultTableOptions = ['charset', 'collation', 'engine'];
|
||||
$currentDefaultTableOptionsArrayKeys = array_keys($params['defaultTableOptions']);
|
||||
foreach ($currentDefaultTableOptionsArrayKeys as $optionIdentifier) {
|
||||
if (!in_array($optionIdentifier, $allowedDefaultTableOptions, true)) {
|
||||
unset($params['defaultTableOptions'][$optionIdentifier]);
|
||||
}
|
||||
}
|
||||
// Remove if empty.
|
||||
if ($params['defaultTableOptions'] === []) {
|
||||
unset($params['defaultTableOptions']);
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a suiting UTF-8 connection charset when nothing is set in connection configuration for `charset`.
|
||||
*
|
||||
* @todo Investigate how to deal with missing defaultTableOptions for MariaDB and MySQL connections,
|
||||
* which may be already partially set even when charset is missing.
|
||||
*/
|
||||
private function ensureDefaultConnectionCharset(#[\SensitiveParameter] array $params): array
|
||||
{
|
||||
if (!array_key_exists('charset', $params) || !is_string($params['charset']) || $params['charset'] === '') {
|
||||
$params['charset'] = 'utf8';
|
||||
// @todo Add `charset = utf8mb4` for MySQL/MariaDB as default connection charset in 14.0 as breaking change.
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return any doctrine driver middlewares, that may have been set up in:
|
||||
* - for all configured connections
|
||||
* - $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['driverMiddlewares'] for a specific connection
|
||||
*/
|
||||
protected function getDriverMiddlewares(string $connectionName, #[\SensitiveParameter] array $connectionParams): array
|
||||
{
|
||||
$driverMiddlewares = $this->getOrderedConnectionDriverMiddlewareConfiguration($connectionName, $connectionParams);
|
||||
$middlewares = [];
|
||||
foreach ($driverMiddlewares as $middlewareConfiguration) {
|
||||
$className = $middlewareConfiguration['target'];
|
||||
$disabled = $middlewareConfiguration['disabled'];
|
||||
if ($disabled === true) {
|
||||
// Middleware disabled, skip to next middleware.
|
||||
continue;
|
||||
}
|
||||
|
||||
$middlewares[] = GeneralUtility::makeInstance($className);
|
||||
}
|
||||
|
||||
return $middlewares;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal only for `ext:lowlevel` usage to retrieve configuration overview. *
|
||||
* @return array
|
||||
*/
|
||||
public function getConnectionMiddlewareConfigurationArrayForLowLevelConfiguration(): array
|
||||
{
|
||||
$configurationArray = [
|
||||
'Raw' => [
|
||||
'GlobalDriverMiddlewares' => $GLOBALS['TYPO3_CONF_VARS']['DB']['globalDriverMiddlewares'] ?? [],
|
||||
'Connections' => [],
|
||||
],
|
||||
'Connections' => [],
|
||||
];
|
||||
foreach (array_keys($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']) as $connectionName) {
|
||||
$connectionParams = $this->getConnectionParams($connectionName);
|
||||
$configurationArray['Raw']['Connections'][$connectionName] = $connectionParams;
|
||||
$configurationArray['Connections'][$connectionName] = $this->getOrderedConnectionDriverMiddlewareConfiguration($connectionName, $connectionParams);
|
||||
}
|
||||
return $configurationArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $connectionParams
|
||||
* @return array<non-empty-string, array{target: class-string, disabled: bool, after: string[], before: string[], type: string}>
|
||||
*/
|
||||
protected function getOrderedConnectionDriverMiddlewareConfiguration(string $connectionName, #[\SensitiveParameter] array $connectionParams): array
|
||||
{
|
||||
/** @var array<non-empty-string, array{target: class-string, disabled: bool, after: string[], before: string[], type: string}> $driverMiddlewares */
|
||||
$driverMiddlewares = [];
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['DB']['globalDriverMiddlewares'] ?? [] as $identifier => $middleware) {
|
||||
$identifier = (string)$identifier;
|
||||
$driverMiddlewares[$identifier] = $this->driverMiddlewareService->ensureCompleteMiddlewareConfiguration($middleware);
|
||||
$driverMiddlewares[$identifier]['type'] = 'global';
|
||||
}
|
||||
foreach ($connectionParams['driverMiddlewares'] ?? [] as $identifier => $middleware) {
|
||||
$identifier = (string)$identifier;
|
||||
// Merge driverMiddlewares over globalDriverMiddlewares
|
||||
$middleware = array_replace($driverMiddlewares[$identifier] ?? [], $middleware);
|
||||
$middleware = $this->driverMiddlewareService->ensureCompleteMiddlewareConfiguration($middleware);
|
||||
$driverMiddlewares[$identifier] = $middleware;
|
||||
$driverMiddlewares[$identifier]['type'] = $driverMiddlewares[$identifier]['type']
|
||||
? 'global-with-connection-override'
|
||||
: 'connection';
|
||||
}
|
||||
$driverMiddlewares = array_filter($driverMiddlewares, static function (array $middleware) use ($connectionName, $connectionParams): bool {
|
||||
$className = $middleware['target'];
|
||||
$classImplements = class_exists($className) ? (class_implements($className) ?: []) : [];
|
||||
if (!in_array(DriverMiddleware::class, $classImplements, true)) {
|
||||
throw new \UnexpectedValueException(
|
||||
sprintf(
|
||||
'Doctrine Driver Middleware "%s" must implement \Doctrine\DBAL\Driver\Middleware',
|
||||
$className
|
||||
),
|
||||
1677958727
|
||||
);
|
||||
}
|
||||
if (in_array(UsableForConnectionInterface::class, $classImplements, true)) {
|
||||
return GeneralUtility::makeInstance($middleware['target'])->canBeUsedForConnection($connectionName, $connectionParams);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return $this->driverMiddlewareService->order($driverMiddlewares);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a connection object based on the specified parameters
|
||||
*/
|
||||
protected function getDatabaseConnection(string $connectionName, #[\SensitiveParameter] array $connectionParams): Connection
|
||||
{
|
||||
self::registerDoctrineTypes();
|
||||
|
||||
$middlewares = $this->getDriverMiddlewares($connectionName, $connectionParams);
|
||||
$configuration = (new Configuration())
|
||||
->setContainer($this->container)
|
||||
->setMiddlewares($middlewares)
|
||||
// @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-not-setting-a-schema-manager-factory
|
||||
->setSchemaManagerFactory($this->coreSchemaManagerFactory);
|
||||
|
||||
/** @var Connection $conn */
|
||||
$conn = DriverManager::getConnection($connectionParams, $configuration);
|
||||
$conn->prepareConnection($connectionParams['initCommands'] ?? '');
|
||||
|
||||
// Register all custom data types in the type mapping
|
||||
foreach (self::$customDoctrineTypes as $type => $className) {
|
||||
$conn->getDatabasePlatform()->registerDoctrineTypeMapping($type, $type);
|
||||
}
|
||||
|
||||
// Register all override data types in the type mapping
|
||||
foreach (self::$overrideDoctrineTypes as $type => $className) {
|
||||
$conn->getDatabasePlatform()->registerDoctrineTypeMapping($type, $type);
|
||||
}
|
||||
|
||||
return $conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection specific query builder object that can be used to build
|
||||
* complex SQL queries using and object-oriented approach.
|
||||
*/
|
||||
public function getQueryBuilderForTable(string $tableName): QueryBuilder
|
||||
{
|
||||
if (empty($tableName)) {
|
||||
throw new \UnexpectedValueException(
|
||||
'ConnectionPool->getQueryBuilderForTable() requires a connection name to be provided.',
|
||||
1459423448
|
||||
);
|
||||
}
|
||||
|
||||
return $this->getConnectionForTable($tableName)->createQueryBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the names of all currently configured connections.
|
||||
*
|
||||
* This method should only be used in edge cases. Use getConnectionForTable() so
|
||||
* that the tablename<>databaseConnection mapping will be taken into account.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getConnectionNames(): array
|
||||
{
|
||||
return array_keys($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom and override Doctrine data types implemented by TYPO3.
|
||||
* This method is needed by Schema parser to register the types as it does
|
||||
* not require a database connection and thus the types don't get registered
|
||||
* automatically.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function registerDoctrineTypes(): void
|
||||
{
|
||||
// Register custom data types
|
||||
foreach (self::$customDoctrineTypes as $type => $className) {
|
||||
if (!Type::hasType($type)) {
|
||||
Type::addType($type, $className);
|
||||
}
|
||||
}
|
||||
// Override data types
|
||||
foreach (self::$overrideDoctrineTypes as $type => $className) {
|
||||
if (!Type::hasType($type)) {
|
||||
Type::addType($type, $className);
|
||||
continue;
|
||||
}
|
||||
Type::overrideType($type, $className);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to be used by functional tests
|
||||
* to close statically stored connections, in order
|
||||
* to use new connection in between single tests.
|
||||
*
|
||||
* This is a no-op nowadays since `$this->connections`
|
||||
* is no longer static and can be removed without replacement,
|
||||
* once testing framework is adapted to avoid calling this method.
|
||||
*/
|
||||
public function resetConnections(): void {}
|
||||
}
|
||||
@@ -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\Driver;
|
||||
|
||||
use Doctrine\DBAL\Driver\Connection as DriverConnection;
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
|
||||
use TYPO3\CMS\Core\Database\Driver\DriverConnection as Typo3PdoDriverConnection;
|
||||
|
||||
/**
|
||||
* @internal this implementation is not part of TYPO3's Public API.
|
||||
*/
|
||||
final class CustomPdoResultDriverDecorator extends AbstractDriverMiddleware
|
||||
{
|
||||
public function connect(#[\SensitiveParameter] array $params): DriverConnection
|
||||
{
|
||||
return new Typo3PdoDriverConnection(parent::connect($params));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?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\Driver;
|
||||
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Platforms\MariaDB1010Platform as DoctrineMariaDB1010Platform;
|
||||
use Doctrine\DBAL\Platforms\MariaDB1052Platform as DoctrineMariaDB1052Platform;
|
||||
use Doctrine\DBAL\Platforms\MariaDB1060Platform as DoctrineMariaDB1060Platform;
|
||||
use Doctrine\DBAL\Platforms\MariaDB110700Platform as DoctrineMariaDB110700Platform;
|
||||
use Doctrine\DBAL\Platforms\MariaDBPlatform as DoctrineMariaDBPlatform;
|
||||
use Doctrine\DBAL\Platforms\MySQL80Platform as DoctrineMySQL80Platform;
|
||||
use Doctrine\DBAL\Platforms\MySQL84Platform as DoctrineMySQL84Platform;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform as DoctrineMySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQL120Platform as DoctrinePostgreSQL120Platform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform as DoctrinePostgreSQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\SQLitePlatform as DoctrineSQLitePlatform;
|
||||
use Doctrine\DBAL\ServerVersionProvider;
|
||||
use TYPO3\CMS\Core\Database\Platform\MariaDB1010Platform as Typo3MariaDB1010Platform;
|
||||
use TYPO3\CMS\Core\Database\Platform\MariaDB1052Platform as Typo3MariaDB1052Platform;
|
||||
use TYPO3\CMS\Core\Database\Platform\MariaDB1060Platform as Typo3MariaDB1060Platform;
|
||||
use TYPO3\CMS\Core\Database\Platform\MariaDB110700Platform as Typo3MariaDB110700Platform;
|
||||
use TYPO3\CMS\Core\Database\Platform\MariaDBPlatform as Typo3MariaDBPlatform;
|
||||
use TYPO3\CMS\Core\Database\Platform\MySQL80Platform as Typo3MySQL80Platform;
|
||||
use TYPO3\CMS\Core\Database\Platform\MySQL84Platform as Typo3MySQL84Platform;
|
||||
use TYPO3\CMS\Core\Database\Platform\MySQLPlatform as Typo3MySQLPlatform;
|
||||
use TYPO3\CMS\Core\Database\Platform\PostgreSQL120Platform as Typo3PostgreSQL120Platform;
|
||||
use TYPO3\CMS\Core\Database\Platform\PostgreSQLPlatform as Typo3PostgreSQLPlatform;
|
||||
use TYPO3\CMS\Core\Database\Platform\SQLitePlatform as Typo3SQLitePlatform;
|
||||
|
||||
/**
|
||||
* @internal this implementation is not part of TYPO3's Public API.
|
||||
*/
|
||||
final class CustomPlatformDriverDecorator extends AbstractDriverMiddleware
|
||||
{
|
||||
public function getDatabasePlatform(ServerVersionProvider $versionProvider): AbstractPlatform
|
||||
{
|
||||
return $this->elevatePlatform(parent::getDatabasePlatform($versionProvider));
|
||||
}
|
||||
|
||||
/**
|
||||
* Due to the deprecation doctrine/event-manager usage in doctrine/dbal the platform classes needs to be extended
|
||||
* to still provide the same behaviour as before. Therefore, we replace the doctrine platform instances with our
|
||||
* extended classes.
|
||||
*
|
||||
* @param AbstractPlatform $platform
|
||||
* @return AbstractPlatform
|
||||
*/
|
||||
private function elevatePlatform(AbstractPlatform $platform): AbstractPlatform
|
||||
{
|
||||
return match ($platform::class) {
|
||||
DoctrineMySQLPlatform::class => new Typo3MySQLPlatform(),
|
||||
DoctrineMySQL80Platform::class => new Typo3MySQL80Platform(),
|
||||
DoctrineMySQL84Platform::class => new Typo3MySQL84Platform(),
|
||||
DoctrineMariaDB110700Platform::class => new Typo3MariaDB110700Platform(),
|
||||
DoctrineMariaDB1010Platform::class => new Typo3MariaDB1010Platform(),
|
||||
DoctrineMariaDB1060Platform::class => new Typo3MariaDB1060Platform(),
|
||||
DoctrineMariaDB1052Platform::class => new Typo3MariaDB1052Platform(),
|
||||
DoctrineMariaDBPlatform::class => new Typo3MariaDBPlatform(),
|
||||
DoctrineSQLitePlatform::class => new Typo3SQLitePlatform(),
|
||||
DoctrinePostgreSQL120Platform::class => new Typo3PostgreSQL120Platform(),
|
||||
DoctrinePostgreSQLPlatform::class => new Typo3PostgreSQLPlatform(),
|
||||
default => $platform,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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\Driver;
|
||||
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware;
|
||||
use Doctrine\DBAL\Driver\Result as ResultInterface;
|
||||
use Doctrine\DBAL\Driver\Statement as StatementInterface;
|
||||
|
||||
/**
|
||||
* DriverConnection decorator to replace the DriverResult with a custom class directly
|
||||
* in the connection and the driver statement class.
|
||||
*
|
||||
* @internal this implementation is not part of TYPO3's Public API.
|
||||
*/
|
||||
class DriverConnection extends AbstractConnectionMiddleware
|
||||
{
|
||||
public function prepare(string $sql): StatementInterface
|
||||
{
|
||||
return new DriverStatement(parent::prepare($sql));
|
||||
}
|
||||
|
||||
public function query(string $sql): ResultInterface
|
||||
{
|
||||
return new DriverResult(parent::query($sql));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?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\Driver;
|
||||
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractResultMiddleware;
|
||||
|
||||
/**
|
||||
* TYPO3's custom Result object for Database statements based on Doctrine DBAL.
|
||||
*
|
||||
* This is a lowlevel wrapper around PDO for TYPO3 based drivers to ensure mapResourceToString()
|
||||
* is called when retrieving data. This isn't the actual Result object (Doctrine\DBAL\Result) which
|
||||
* is used in user-land code.
|
||||
*
|
||||
* @internal this implementation is not part of TYPO3's Public API.
|
||||
*/
|
||||
class DriverResult extends AbstractResultMiddleware
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetchNumeric(): array|false
|
||||
{
|
||||
return $this->mapResourceToString(parent::fetchNumeric());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetchAssociative(): array|false
|
||||
{
|
||||
return $this->mapResourceToString(parent::fetchAssociative());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetchOne(): mixed
|
||||
{
|
||||
return $this->mapResourceToString(parent::fetchOne());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetchAllNumeric(): array
|
||||
{
|
||||
$data = $this->mapResourceToString(parent::fetchAllNumeric());
|
||||
assert(is_array($data));
|
||||
return array_map($this->mapResourceToString(...), $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetchAllAssociative(): array
|
||||
{
|
||||
$data = $this->mapResourceToString(parent::fetchAllAssociative());
|
||||
assert(is_array($data));
|
||||
return array_map($this->mapResourceToString(...), $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetchFirstColumn(): array
|
||||
{
|
||||
$data = $this->mapResourceToString(parent::fetchFirstColumn());
|
||||
assert(is_array($data));
|
||||
return array_map($this->mapResourceToString(...), $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map resources to string like is done for e.g. in mysqli driver
|
||||
*
|
||||
* @param mixed $record
|
||||
* @return mixed
|
||||
*/
|
||||
protected function mapResourceToString($record)
|
||||
{
|
||||
if (is_array($record)) {
|
||||
foreach ($record as $k => $value) {
|
||||
if (is_resource($value)) {
|
||||
$record[$k] = stream_get_contents($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $record;
|
||||
}
|
||||
}
|
||||
@@ -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\Driver;
|
||||
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware;
|
||||
use Doctrine\DBAL\Driver\Result as ResultInterface;
|
||||
|
||||
/**
|
||||
* TYPO3's custom Statement decorator object for Database statements based on Doctrine DBAL in TYPO3's drivers.
|
||||
*
|
||||
* This is a low-level wrapper around PDOStatement for TYPO3 based drivers to ensure the PDOStatement is put into
|
||||
* TYPO3's DriverResult object, and not in Doctrine's Result object. If Doctrine DBAL had a factory
|
||||
* for DriverResults this class could be removed.
|
||||
*
|
||||
* @internal this implementation is not part of TYPO3's Public API.
|
||||
*/
|
||||
class DriverStatement extends AbstractStatementMiddleware
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute(): ResultInterface
|
||||
{
|
||||
return new DriverResult(parent::execute());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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;
|
||||
|
||||
use TYPO3\CMS\Core\Service\DependencyOrderingService;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
readonly class DriverMiddlewareService
|
||||
{
|
||||
public function __construct(
|
||||
private DependencyOrderingService $dependencyOrderingService
|
||||
) {}
|
||||
|
||||
public function order(array $middlewares): array
|
||||
{
|
||||
return $this->dependencyOrderingService->orderByDependencies($middlewares);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $middleware
|
||||
* @return array{target: class-string, disabled: bool, after: string[], before: string[], type: string}
|
||||
*/
|
||||
public function ensureCompleteMiddlewareConfiguration(array $middleware): array
|
||||
{
|
||||
$target = (string)($middleware['target'] ?? '');
|
||||
if ($target === '' || !class_exists($target)) {
|
||||
throw new \RuntimeException(
|
||||
'Doctrine DBAL driver middleware registration requires a valid class-name as "target".',
|
||||
1701546655
|
||||
);
|
||||
}
|
||||
return [
|
||||
'target' => $target,
|
||||
'disabled' => (bool)($middleware['disabled'] ?? false),
|
||||
'after' => (array)($middleware['after'] ?? []),
|
||||
'before' => (array)($middleware['before'] ?? []),
|
||||
'type' => '',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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\Event;
|
||||
|
||||
/**
|
||||
* Event to intercept the "CREATE TABLE" statement from all loaded extensions.
|
||||
* The $sqlData variable holds a RAW Array of definitions from each file found.
|
||||
*/
|
||||
final class AlterTableDefinitionStatementsEvent
|
||||
{
|
||||
public function __construct(private array $sqlData) {}
|
||||
|
||||
public function addSqlData($data): void
|
||||
{
|
||||
$this->sqlData[] = $data;
|
||||
}
|
||||
|
||||
public function getSqlData(): array
|
||||
{
|
||||
return $this->sqlData;
|
||||
}
|
||||
|
||||
public function setSqlData(array $sqlData): void
|
||||
{
|
||||
$this->sqlData = $sqlData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?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\Middleware;
|
||||
|
||||
use Doctrine\DBAL\Driver as DoctrineDriver;
|
||||
use Doctrine\DBAL\Driver\Middleware as DoctrineDriverMiddleware;
|
||||
use TYPO3\CMS\Core\Database\Driver\CustomPdoResultDriverDecorator;
|
||||
|
||||
/**
|
||||
* The `php-ext PDO` based pdo_* driver can return column data in query result sets as type `resource`, where the
|
||||
* `mysqli` based driver returns type `string` instead. Dealing with data of type `resources`, for special driver,
|
||||
* is not a well known technical detail in the broader php and TYPO3 developer community. Therefore, the TYPO3 core
|
||||
* provided custom pdo_* driver implementation to provide a specific `DriverResult` class, which resolves this issue
|
||||
* by converting type `resource` column data directly to string in `\TYPO3\CMS\Core\Database\Driver\DriverResult`
|
||||
* within the method `mapResourceToString()` and uses it in the related methods.
|
||||
*
|
||||
* With the Doctrine DBAL Driver Middleware features the custom drivers could be reduced and the required DriverResult
|
||||
* set added in a cleaner way. As this comes with minor performance impact, the custom DriverResult set needs to be
|
||||
* plumbed only to the absolutely required drivers - and the reason for the conditional usage restricted with method
|
||||
* `canBeUsedForConnection()`.
|
||||
*
|
||||
* @see \TYPO3\CMS\Core\Database\Driver\DriverResult::mapResourceToString()
|
||||
*
|
||||
* @internal this implementation is not part of TYPO3's Public API.
|
||||
*/
|
||||
final class CustomPdoDriverResultMiddleware implements DoctrineDriverMiddleware, UsableForConnectionInterface
|
||||
{
|
||||
public function wrap(DoctrineDriver $driver): DoctrineDriver
|
||||
{
|
||||
return new CustomPdoResultDriverDecorator($driver);
|
||||
}
|
||||
|
||||
public function canBeUsedForConnection(string $identifier, array $connectionParams): bool
|
||||
{
|
||||
return match ($connectionParams['driver'] ?? '') {
|
||||
'pdo_sqlite', 'pdo_pgsql', 'pdo_mysql' => true,
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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\Middleware;
|
||||
|
||||
use Doctrine\DBAL\Driver as DoctrineDriver;
|
||||
use Doctrine\DBAL\Driver\Middleware as DoctrineDriverMiddleware;
|
||||
use TYPO3\CMS\Core\Database\Driver\CustomPlatformDriverDecorator;
|
||||
|
||||
/**
|
||||
* Wraps the driver to ensure extended *Platform classes are used for connections.
|
||||
* @internal only and not part of public core API.
|
||||
*/
|
||||
final class CustomPlatformDriverMiddleware implements DoctrineDriverMiddleware
|
||||
{
|
||||
public function wrap(DoctrineDriver $driver): DoctrineDriver
|
||||
{
|
||||
return new CustomPlatformDriverDecorator($driver);
|
||||
}
|
||||
}
|
||||
@@ -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\Middleware;
|
||||
|
||||
use Doctrine\DBAL\Driver\Middleware as DoctrineDriverMiddleware;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
|
||||
/**
|
||||
* Custom driver middleware can implement this interface to decide per connection and
|
||||
* connection configuration if it should be used or not. For example, registering a
|
||||
* global driver middleware which only takes affect on connections using a specific
|
||||
* driver like `pdo_sqlite`.
|
||||
*
|
||||
* Usually this should be a rare case and mostly a driver middleware can be simply
|
||||
* configured as a connection middleware directly, which leaves this more or less
|
||||
* a special implementation detail for the TYPO3 core.
|
||||
*/
|
||||
interface UsableForConnectionInterface extends DoctrineDriverMiddleware
|
||||
{
|
||||
/**
|
||||
* Return true if the driver middleware should be used for the concrete connection.
|
||||
*
|
||||
* @see ConnectionPool::getDriverMiddlewares()
|
||||
*/
|
||||
public function canBeUsedForConnection(string $identifier, array $connectionParams): bool;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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\Platform;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MariaDB1010Platform as DoctrineMariaDB1010Platform;
|
||||
use Doctrine\DBAL\Schema\TableDiff as DoctrineTableDiff;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\MySQLCompatibleAlterTablePlatformAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\Schema\TableDiff;
|
||||
|
||||
/**
|
||||
* doctrine/dbal 4+ removed the old doctrine event system. The new way is to extend the platform
|
||||
* class(es) and directly override the methods instead of consuming events. Therefore, we need to
|
||||
* extend the platform classes to provide some changes for TYPO3 database schema operations.
|
||||
*
|
||||
* @internal not part of Public Core API.
|
||||
*/
|
||||
class MariaDB1010Platform extends DoctrineMariaDB1010Platform
|
||||
{
|
||||
use GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
use MySQLCompatibleAlterTablePlatformAwareTrait;
|
||||
|
||||
/**
|
||||
* Gets the SQL statements for altering an existing table.
|
||||
*
|
||||
* This method returns an array of SQL statements, since some platforms need several statements.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getAlterTableSQL(TableDiff|DoctrineTableDiff $diff): array
|
||||
{
|
||||
return $this->getCustomAlterTableSQLEngineOptions($this, $diff, parent::getAlterTableSQL($diff));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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\Platform;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MariaDB1052Platform as DoctrineMariaDB1052Platform;
|
||||
use Doctrine\DBAL\Schema\TableDiff as DoctrineTableDiff;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\MySQLCompatibleAlterTablePlatformAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\Schema\TableDiff;
|
||||
|
||||
/**
|
||||
* doctrine/dbal 4+ removed the old doctrine event system. The new way is to extend the platform
|
||||
* class(es) and directly override the methods instead of consuming events. Therefore, we need to
|
||||
* extend the platform classes to provide some changes for TYPO3 database schema operations.
|
||||
*
|
||||
* @internal not part of Public Core API.
|
||||
*/
|
||||
class MariaDB1052Platform extends DoctrineMariaDB1052Platform
|
||||
{
|
||||
use GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
use MySQLCompatibleAlterTablePlatformAwareTrait;
|
||||
|
||||
/**
|
||||
* Gets the SQL statements for altering an existing table.
|
||||
*
|
||||
* This method returns an array of SQL statements, since some platforms need several statements.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getAlterTableSQL(TableDiff|DoctrineTableDiff $diff): array
|
||||
{
|
||||
return $this->getCustomAlterTableSQLEngineOptions($this, $diff, parent::getAlterTableSQL($diff));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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\Platform;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MariaDB1060Platform as DoctrineMariaDB1060Platform;
|
||||
use Doctrine\DBAL\Schema\TableDiff as DoctrineTableDiff;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\MySQLCompatibleAlterTablePlatformAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\Schema\TableDiff;
|
||||
|
||||
/**
|
||||
* doctrine/dbal 4+ removed the old doctrine event system. The new way is to extend the platform
|
||||
* class(es) and directly override the methods instead of consuming events. Therefore, we need to
|
||||
* extend the platform classes to provide some changes for TYPO3 database schema operations.
|
||||
*
|
||||
* @internal not part of Public Core API.
|
||||
*/
|
||||
class MariaDB1060Platform extends DoctrineMariaDB1060Platform
|
||||
{
|
||||
use GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
use MySQLCompatibleAlterTablePlatformAwareTrait;
|
||||
|
||||
/**
|
||||
* Gets the SQL statements for altering an existing table.
|
||||
*
|
||||
* This method returns an array of SQL statements, since some platforms need several statements.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getAlterTableSQL(TableDiff|DoctrineTableDiff $diff): array
|
||||
{
|
||||
return $this->getCustomAlterTableSQLEngineOptions($this, $diff, parent::getAlterTableSQL($diff));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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\Platform;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MariaDB110700Platform as DoctrineMariaDB110700Platform;
|
||||
use Doctrine\DBAL\Schema\TableDiff as DoctrineTableDiff;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\MySQLCompatibleAlterTablePlatformAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\Schema\TableDiff;
|
||||
|
||||
/**
|
||||
* doctrine/dbal 4+ removed the old doctrine event system. The new way is to extend the platform
|
||||
* class(es) and directly override the methods instead of consuming events. Therefore, we need to
|
||||
* extend the platform classes to provide some changes for TYPO3 database schema operations.
|
||||
*
|
||||
* @internal not part of Public Core API.
|
||||
*/
|
||||
class MariaDB110700Platform extends DoctrineMariaDB110700Platform
|
||||
{
|
||||
use GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
use MySQLCompatibleAlterTablePlatformAwareTrait;
|
||||
|
||||
/**
|
||||
* Gets the SQL statements for altering an existing table.
|
||||
*
|
||||
* This method returns an array of SQL statements, since some platforms need several statements.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getAlterTableSQL(TableDiff|DoctrineTableDiff $diff): array
|
||||
{
|
||||
return $this->getCustomAlterTableSQLEngineOptions($this, $diff, parent::getAlterTableSQL($diff));
|
||||
}
|
||||
}
|
||||
@@ -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\Platform;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MariaDBPlatform as DoctrineMariaDBPlatform;
|
||||
use Doctrine\DBAL\Schema\TableDiff as DoctrineTableDiff;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\MySQLCompatibleAlterTablePlatformAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\Schema\TableDiff;
|
||||
|
||||
/**
|
||||
* doctrine/dbal 4+ removed the old doctrine event system. The new way is to extend the platform
|
||||
* class(es) and directly override the methods instead of consuming events. Therefore, we need to
|
||||
* extend the platform classes to provide some changes for TYPO3 database schema operations.
|
||||
*
|
||||
* Note: `doctrine/dbal 4` raised minimal supported MariaDB version to 10.4.3 which the MariaDBPlatform reflects now.
|
||||
*
|
||||
* @internal not part of Public Core API.
|
||||
*/
|
||||
class MariaDBPlatform extends DoctrineMariaDBPlatform
|
||||
{
|
||||
use GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
use MySQLCompatibleAlterTablePlatformAwareTrait;
|
||||
|
||||
/**
|
||||
* Gets the SQL statements for altering an existing table.
|
||||
*
|
||||
* This method returns an array of SQL statements, since some platforms need several statements.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getAlterTableSQL(TableDiff|DoctrineTableDiff $diff): array
|
||||
{
|
||||
return $this->getCustomAlterTableSQLEngineOptions($this, $diff, parent::getAlterTableSQL($diff));
|
||||
}
|
||||
}
|
||||
@@ -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\Platform;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQL80Platform as DoctrineMySQL80Platform;
|
||||
use Doctrine\DBAL\Schema\TableDiff as DoctrineTableDiff;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\MySQLCompatibleAlterTablePlatformAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\MySQLDefaultValueDeclarationSQLOverrideTrait;
|
||||
use TYPO3\CMS\Core\Database\Schema\TableDiff;
|
||||
|
||||
/**
|
||||
* doctrine/dbal 4+ removed the old doctrine event system. The new way is to extend the platform
|
||||
* class(es) and directly override the methods instead of consuming events. Therefore, we need to
|
||||
* extend the platform classes to provide some changes for TYPO3 database schema operations.
|
||||
*
|
||||
* @internal not part of Public Core API.
|
||||
*/
|
||||
class MySQL80Platform extends DoctrineMySQL80Platform
|
||||
{
|
||||
use GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
use MySQLCompatibleAlterTablePlatformAwareTrait;
|
||||
use MySQLDefaultValueDeclarationSQLOverrideTrait;
|
||||
|
||||
/**
|
||||
* Gets the SQL statements for altering an existing table.
|
||||
*
|
||||
* This method returns an array of SQL statements, since some platforms need several statements.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getAlterTableSQL(TableDiff|DoctrineTableDiff $diff): array
|
||||
{
|
||||
return $this->getCustomAlterTableSQLEngineOptions($this, $diff, parent::getAlterTableSQL($diff));
|
||||
}
|
||||
}
|
||||
@@ -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\Platform;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQL84Platform as DoctrineMySQL84Platform;
|
||||
use Doctrine\DBAL\Schema\TableDiff as DoctrineTableDiff;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\MySQLCompatibleAlterTablePlatformAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\MySQLDefaultValueDeclarationSQLOverrideTrait;
|
||||
use TYPO3\CMS\Core\Database\Schema\TableDiff;
|
||||
|
||||
/**
|
||||
* doctrine/dbal 4+ removed the old doctrine event system. The new way is to extend the platform
|
||||
* class(es) and directly override the methods instead of consuming events. Therefore, we need to
|
||||
* extend the platform classes to provide some changes for TYPO3 database schema operations.
|
||||
*
|
||||
* @internal not part of Public Core API.
|
||||
*/
|
||||
class MySQL84Platform extends DoctrineMySQL84Platform
|
||||
{
|
||||
use GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
use MySQLCompatibleAlterTablePlatformAwareTrait;
|
||||
use MySQLDefaultValueDeclarationSQLOverrideTrait;
|
||||
|
||||
/**
|
||||
* Gets the SQL statements for altering an existing table.
|
||||
*
|
||||
* This method returns an array of SQL statements, since some platforms need several statements.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getAlterTableSQL(TableDiff|DoctrineTableDiff $diff): array
|
||||
{
|
||||
return $this->getCustomAlterTableSQLEngineOptions($this, $diff, parent::getAlterTableSQL($diff));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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\Platform;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform as DoctrineMySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\TableDiff as DoctrineTableDiff;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\MySQLCompatibleAlterTablePlatformAwareTrait;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\MySQLDefaultValueDeclarationSQLOverrideTrait;
|
||||
use TYPO3\CMS\Core\Database\Schema\TableDiff;
|
||||
|
||||
/**
|
||||
* doctrine/dbal 4+ removed the old doctrine event system. The new way is to extend the platform
|
||||
* class(es) and directly override the methods instead of consuming events. Therefore, we need to
|
||||
* extend the platform classes to provide some changes for TYPO3 database schema operations.
|
||||
*
|
||||
* Normally, this platform should not be used anymore since TYPO3 v12 due to the minimal MySQL requirement of 8.0.
|
||||
* However, keep this at least as a default during migration phases.
|
||||
*
|
||||
* @internal not part of Public Core API.
|
||||
*/
|
||||
class MySQLPlatform extends DoctrineMySQLPlatform
|
||||
{
|
||||
use GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
use MySQLCompatibleAlterTablePlatformAwareTrait;
|
||||
use MySQLDefaultValueDeclarationSQLOverrideTrait;
|
||||
|
||||
/**
|
||||
* Gets the SQL statements for altering an existing table.
|
||||
*
|
||||
* This method returns an array of SQL statements, since some platforms need several statements.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getAlterTableSQL(TableDiff|DoctrineTableDiff $diff): array
|
||||
{
|
||||
return $this->getCustomAlterTableSQLEngineOptions($this, $diff, parent::getAlterTableSQL($diff));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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\Platform;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform as DoctrineAbstractPlatform;
|
||||
|
||||
/**
|
||||
* @internal not part of public core API.
|
||||
*/
|
||||
final class PlatformHelper
|
||||
{
|
||||
/**
|
||||
* Doctrine DBAL 4 removed the `getIdentifierQuoteCharacter()` method from the platform classes and suggest to
|
||||
* use `Platform::quoteIdentifier()` instead. As this invoke the need to provide a fake identifier and extract
|
||||
* the character, this helper method is used throughout the core.
|
||||
*
|
||||
* @see https://github.com/doctrine/dbal/blob/4.0.x/UPGRADE.md#bc-break-removed-abstractplatform-methods-exposing-quote-characters
|
||||
* @see https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-abstractplatform-methods-exposing-quote-characters
|
||||
*
|
||||
* @param DoctrineAbstractPlatform $platform
|
||||
* @return string
|
||||
*
|
||||
* @internal Used in capsuled code and should not be needed to called by extension code. Not part of public API.
|
||||
*/
|
||||
public function getIdentifierQuoteCharacter(DoctrineAbstractPlatform $platform): string
|
||||
{
|
||||
// Note: Albeit not used yet, $platform is handed over from usages to allow easier adjustments if required.
|
||||
return $platform->quoteSingleIdentifier('fake')[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?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\Platform;
|
||||
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform as DoctrineAbstractPlatform;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Helper to handle platform specific details
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PlatformInformation
|
||||
{
|
||||
protected static array $identifierLimits = [
|
||||
'mysql' => 63,
|
||||
'postgresql' => 63,
|
||||
'sqlite' => 1024, // arbitrary limit, SQLite is only limited by the total statement length
|
||||
];
|
||||
|
||||
protected static array $bindParameterLimits = [
|
||||
'mysql' => 65535,
|
||||
'postgresql' => 34464,
|
||||
'sqlite' => 999,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected static array $charSetMap = [
|
||||
'mysql' => 'utf8mb4',
|
||||
'postgresql' => 'UTF8',
|
||||
'sqlite' => 'utf8',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected static array $databaseCreateWithCharsetMap = [
|
||||
'mysql' => 'CHARACTER SET %s',
|
||||
'postgresql' => "ENCODING '%s'",
|
||||
];
|
||||
|
||||
/**
|
||||
* Return the encoding of the given platform
|
||||
*/
|
||||
public static function getCharset(DoctrineAbstractPlatform $platform): string
|
||||
{
|
||||
$platformName = static::getPlatformIdentifier($platform);
|
||||
return static::$charSetMap[$platformName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the statement to create a database with the desired encoding for the given platform
|
||||
*/
|
||||
public static function getDatabaseCreateStatementWithCharset(DoctrineAbstractPlatform $platform, string $databaseName): string
|
||||
{
|
||||
try {
|
||||
$createStatement = $platform->getCreateDatabaseSQL($databaseName);
|
||||
} catch (DBALException $exception) {
|
||||
// just silently ignore that error as the selected database does not support any creation of a database
|
||||
return '';
|
||||
}
|
||||
|
||||
$platformName = static::getPlatformIdentifier($platform);
|
||||
$charset = static::getCharset($platform);
|
||||
|
||||
return $createStatement . ' ' . sprintf(static::$databaseCreateWithCharsetMap[$platformName], $charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information about the maximum supported length for a SQL identifier.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function getMaxIdentifierLength(DoctrineAbstractPlatform $platform): int
|
||||
{
|
||||
$platformName = static::getPlatformIdentifier($platform);
|
||||
return self::$identifierLimits[$platformName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information about the maximum number of bound parameters supported on this platform
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function getMaxBindParameters(DoctrineAbstractPlatform $platform): int
|
||||
{
|
||||
$platformName = static::getPlatformIdentifier($platform);
|
||||
return self::$bindParameterLimits[$platformName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the platform shortname to use as a lookup key
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
protected static function getPlatformIdentifier(DoctrineAbstractPlatform $platform): string
|
||||
{
|
||||
// @todo: In doctrine/dbal 3 MariaDBPlatform extended from MySQLPlatform, since doctrine/dbal 4+ from
|
||||
// AbstractMySQLPlatform. Consider to returning directly 'mariadb' here if consuming code is
|
||||
// prepared for the change.
|
||||
if ($platform instanceof DoctrineMariaDBPlatform) {
|
||||
return 'mysql';
|
||||
}
|
||||
if ($platform instanceof DoctrineMySQLPlatform) {
|
||||
return 'mysql';
|
||||
}
|
||||
if ($platform instanceof DoctrinePostgreSqlPlatform) {
|
||||
return 'postgresql';
|
||||
}
|
||||
if ($platform instanceof DoctrineSQLitePlatform) {
|
||||
return 'sqlite';
|
||||
}
|
||||
throw new \RuntimeException(
|
||||
'Unsupported Databaseplatform "' . get_class($platform) . '" detected in PlatformInformation',
|
||||
1500958070
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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\Platform;
|
||||
|
||||
use Doctrine\DBAL\Platforms\PostgreSQL120Platform as DoctrinePostgreSQL120Platform;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
|
||||
/**
|
||||
* doctrine/dbal 4+ removed the old doctrine event system. The new way is to extend the platform
|
||||
* class(es) and directly override the methods instead of consuming events. Therefore, we need to
|
||||
* extend the platform classes to provide some changes for TYPO3 database schema operations.
|
||||
*
|
||||
* Note: Albeit empty, we keep it now. Future refactoring may add stuff here, for example columnEquals() modifications.
|
||||
*
|
||||
* @internal not part of Public Core API.
|
||||
*/
|
||||
class PostgreSQL120Platform extends DoctrinePostgreSQL120Platform
|
||||
{
|
||||
use GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
}
|
||||
@@ -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\Platform;
|
||||
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform as DoctrinePostgreSQLPlatform;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
|
||||
/**
|
||||
* doctrine/dbal 4+ removed the old doctrine event system. The new way is to extend the platform
|
||||
* class(es) and directly override the methods instead of consuming events. Therefore, we need to
|
||||
* extend the platform classes to provide some changes for TYPO3 database schema operations.
|
||||
*
|
||||
* Note: Albeit empty, we keep it now. Future refactoring may add stuff here, for example columnEquals() modifications.
|
||||
*
|
||||
* @internal not part of Public Core API.
|
||||
*/
|
||||
class PostgreSQLPlatform extends DoctrinePostgreSQLPlatform
|
||||
{
|
||||
use GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
}
|
||||
@@ -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\Platform;
|
||||
|
||||
use Doctrine\DBAL\Platforms\SQLitePlatform as DoctrineSQLitePlatform;
|
||||
use TYPO3\CMS\Core\Database\Platform\Traits\GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
|
||||
/**
|
||||
* doctrine/dbal 4+ removed the old doctrine event system. The new way is to extend the platform
|
||||
* class(es) and directly override the methods instead of consuming events. Therefore, we need to
|
||||
* extend the platform classes to provide some changes for TYPO3 database schema operations.
|
||||
*
|
||||
* Note: Albeit empty, we keep it now. Future refactoring may add stuff here, for example columnEquals() modifications.
|
||||
*
|
||||
* @internal not part of Public Core API.
|
||||
*/
|
||||
class SQLitePlatform extends DoctrineSQLitePlatform
|
||||
{
|
||||
use GetColumnDeclarationSQLCommentTypeAwareTrait;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?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\Platform\Traits;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MariaDBPlatform;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\SQLitePlatform;
|
||||
use Doctrine\DBAL\Types\GuidType;
|
||||
use Doctrine\DBAL\Types\JsonType;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
/**
|
||||
* This trait provides some methods to restore removed behaviour of Doctrine DBAL within the extended
|
||||
* {@see AbstractPlatform} hierarchy.
|
||||
*
|
||||
* Related code places code has been taken from or adopted for it:
|
||||
*
|
||||
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Platforms/AbstractPlatform.php#L555-L572
|
||||
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Platforms/AbstractPlatform.php#L574-L597
|
||||
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Types/Type.php#L275-L295
|
||||
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Types/JsonType.php#L80-L95
|
||||
*/
|
||||
trait GetColumnDeclarationSQLCommentTypeAwareTrait
|
||||
{
|
||||
/**
|
||||
* Note that this provides a method override to combine type based comments with the column comment on platforms.
|
||||
*
|
||||
* Obtains DBMS specific SQL code portion needed to declare a generic type
|
||||
* column to be used in statements like CREATE TABLE.
|
||||
*
|
||||
* @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy.
|
||||
*
|
||||
* @param string $name The name the column to be declared.
|
||||
* @param mixed[] $column An associative array with the name of the properties
|
||||
* of the column being declared as array indexes. Currently, the types
|
||||
* of supported column properties are as follows:
|
||||
*
|
||||
* length
|
||||
* Integer value that determines the maximum length of the text
|
||||
* column. If this argument is missing the column should be
|
||||
* declared to have the longest length allowed by the DBMS.
|
||||
* default
|
||||
* Text value to be used as default for this column.
|
||||
* notnull
|
||||
* Boolean flag that indicates whether this column is constrained
|
||||
* to not be set to null.
|
||||
* charset
|
||||
* Text value with the default CHARACTER SET for this column.
|
||||
* collation
|
||||
* Text value with the default COLLATION for this column.
|
||||
* columnDefinition
|
||||
* a string that defines the complete column
|
||||
*
|
||||
* @return string DBMS specific SQL code portion that should be used to declare the column.
|
||||
*/
|
||||
public function getColumnDeclarationSQL(string $name, array $column): string
|
||||
{
|
||||
return parent::getColumnDeclarationSQL($name, $this->addTypeCommentIfNeeded($column));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add type comment (`DC2Type:<TypeName>`) to column comment if required.
|
||||
*
|
||||
* Adopted from Doctrine DBAL 3.9:
|
||||
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Platforms/AbstractPlatform.php#L555-L572
|
||||
* returning comment addition for column comment.
|
||||
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Platforms/AbstractPlatform.php#L574-L597
|
||||
* adding the type comment to column comment, if overall type comments has not been disabled (not the case for TYPO3)
|
||||
* and the column type method `requiresSQLCommentHint()` returned true for that type and platform, which no longer
|
||||
* exits and are now simplified processed with {@see self::typeRequiresCommentHint()}.
|
||||
*/
|
||||
private function addTypeCommentIfNeeded(array $column): array
|
||||
{
|
||||
if ($this->typeRequiresCommentHint($column['type'])) {
|
||||
$column['comment'] .= '(DC2Type:' . Type::lookupName($column['type']) . ')';
|
||||
}
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform specific type selection requiring column comment type specification.
|
||||
*
|
||||
* Up to Doctrine DBAL v3, this has been handled throughout various places, for example using the removed
|
||||
* `requiresSQLCommentHint()` method on DoctrineType implementations.
|
||||
* https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Types/Type.php#L275-L295 for
|
||||
* the basic implementation, where each type could override that method.
|
||||
*
|
||||
* Instead of extending all types to restore that behaviour, a mapping logic is now added with that method,
|
||||
* for example:
|
||||
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Types/JsonType.php#L80-L95
|
||||
*/
|
||||
private function typeRequiresCommentHint(Type $type): bool
|
||||
{
|
||||
$map = [
|
||||
SQLitePlatform::class => [
|
||||
JsonType::class,
|
||||
GuidType::class,
|
||||
],
|
||||
MariaDBPlatform::class => [
|
||||
GuidType::class,
|
||||
],
|
||||
MySQLPlatform::class => [
|
||||
GuidType::class,
|
||||
],
|
||||
];
|
||||
foreach ($map as $platformClass => $platformTypes) {
|
||||
if (!$this instanceof $platformClass) {
|
||||
continue;
|
||||
}
|
||||
foreach ($platformTypes as $platformType) {
|
||||
if ($type instanceof $platformType) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?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\Platform\Traits;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MariaDBPlatform as DoctrineMariaDBPlatform;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform as DoctrineMySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\TableDiff as DoctrineTableDiff;
|
||||
use TYPO3\CMS\Core\Database\Schema\TableDiff;
|
||||
|
||||
/**
|
||||
* `doctrine/dbal` does not support handling engine options directly. This trait in combination with extended
|
||||
* platform classes substitutes the deprecated `doctrine/event-manager` approach to influence database schema
|
||||
* related comparison and DDL handling.
|
||||
*
|
||||
* @internal shared code for extended MySQL and MariDB platform doctrine classes.
|
||||
*/
|
||||
trait MySQLCompatibleAlterTablePlatformAwareTrait
|
||||
{
|
||||
/**
|
||||
* @param TableDiff|DoctrineTableDiff $tableDiff
|
||||
* @param list<string> $result
|
||||
* @return list<string>
|
||||
*/
|
||||
protected function getCustomAlterTableSQLEngineOptions(DoctrineMariaDBPlatform|DoctrineMySQLPlatform $platform, TableDiff|DoctrineTableDiff $tableDiff, array $result): array
|
||||
{
|
||||
// Original Doctrine TableDiff without table options, continue default processing
|
||||
if (!$tableDiff instanceof TableDiff) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// No changes in table options, continue default processing
|
||||
if (count($tableDiff->getTableOptions()) === 0) {
|
||||
return $result;
|
||||
}
|
||||
$options = '';
|
||||
|
||||
if ($tableDiff->hasTableOption('engine')) {
|
||||
$options .= ' ENGINE = ' . $tableDiff->getTableOption('engine');
|
||||
}
|
||||
|
||||
if ($tableDiff->hasTableOption('row_format')) {
|
||||
$options .= ' ROW_FORMAT = ' . $tableDiff->getTableOption('row_format');
|
||||
} elseif ($tableDiff->hasTableOption('engine') && $tableDiff->getOldTable()->hasOption('row_format')) {
|
||||
// Ensure ROW_FORMAT is always explicitly applied if ENGINE is changed,
|
||||
// as "old" CREATE TABLE ROW_FORMAT options are cached and are re-applied if ENGINE is changed
|
||||
// (which would result in a "Wrong create options" error if MyISAM/FIXED is tried to be changed to InnoDB(+implicit FIXED)
|
||||
//
|
||||
// See https://bugs.mysql.com/bug.php?id=26214#c104034 into account:
|
||||
// > Row_format column in SHOW TABLE STATUS shows the actual row format of the table.
|
||||
// > Create_options in SHOW TABLE STATUS and SHOW CREATE TABLE show the options (including row format) that you specified at CREATE TABLE time.
|
||||
// >
|
||||
// > The original options are preserved because you may do ALTER TABLE ... ENGINE= and change the storage engine of the table, and a new storage
|
||||
// > engine may support the row format that you specified back then during CREATE TABLE.
|
||||
$options .= ' ROW_FORMAT = ' . $tableDiff->getOldTable()->getOption('row_format');
|
||||
}
|
||||
|
||||
if ($tableDiff->hasTableOption('charset')) {
|
||||
$options .= ' DEFAULT CHARACTER SET = ' . $tableDiff->getTableOption('charset');
|
||||
}
|
||||
if ($tableDiff->hasTableOption('collation')) {
|
||||
$options .= ' COLLATE = ' . $tableDiff->getTableOption('collation');
|
||||
}
|
||||
|
||||
// Add an ALTER TABLE statement to change the table engine to the list of statements.
|
||||
if ($options !== '') {
|
||||
$quotedTableName = $tableDiff->getOldTable()->getQuotedName($platform);
|
||||
$result[] = 'ALTER TABLE ' . $quotedTableName . $options;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?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\Platform\Traits;
|
||||
|
||||
use Doctrine\DBAL\Types;
|
||||
|
||||
/**
|
||||
* @internal not part of Public Core API.
|
||||
*/
|
||||
trait MySQLDefaultValueDeclarationSQLOverrideTrait
|
||||
{
|
||||
/**
|
||||
* Obtains DBMS specific SQL code portion needed to set a default value
|
||||
* declaration to be used in statements like CREATE TABLE.
|
||||
*
|
||||
* Oracle MySQL does not support default values on TEXT/BLOB columns until 8.0.13. Doctrine DBAL 4.x supports
|
||||
* earlier version of MySQL and decided to unset the column default value for TextType and BlobType generally
|
||||
* in the MySQL platform variants. This trait reintroduces the AbstractPlatform implementation to be used in
|
||||
* the TYPO3 platform overrides for MySQL to remove this limitation and allow the use of default value as
|
||||
* expressions.
|
||||
*
|
||||
* @see \Doctrine\DBAL\Platforms\MySQLPlatform::getDefaultValueDeclarationSQL()
|
||||
*
|
||||
* @param mixed[] $column The column definition array.
|
||||
*
|
||||
* @return string DBMS specific SQL code portion needed to set a default value.
|
||||
*/
|
||||
public function getDefaultValueDeclarationSQL(array $column): string
|
||||
{
|
||||
$type = $column['type'] ?? null;
|
||||
|
||||
// MySQL 8.0.13+ supports default for TEXT and BLOB fields only as expression, so we need to handle this
|
||||
// here properly for valid default value types.
|
||||
if ($type instanceof Types\TextType || $type instanceof Types\JsonType || $type instanceof Types\BlobType) {
|
||||
if (! isset($column['default'])) {
|
||||
return empty($column['notnull']) ? ' DEFAULT (NULL)' : '';
|
||||
}
|
||||
$default = $column['default'];
|
||||
if (is_int($default) || is_float($default)) {
|
||||
return ' DEFAULT (' . $default . ')';
|
||||
}
|
||||
return ' DEFAULT (' . $this->quoteStringLiteral($default) . ')';
|
||||
}
|
||||
|
||||
if (! isset($column['default'])) {
|
||||
return empty($column['notnull']) ? ' DEFAULT NULL' : '';
|
||||
}
|
||||
|
||||
$default = $column['default'];
|
||||
|
||||
if (! isset($column['type'])) {
|
||||
return " DEFAULT '" . $default . "'";
|
||||
}
|
||||
|
||||
if ($type instanceof Types\PhpIntegerMappingType) {
|
||||
return ' DEFAULT ' . $default;
|
||||
}
|
||||
|
||||
if ($type instanceof Types\PhpDateTimeMappingType && $default === $this->getCurrentTimestampSQL()) {
|
||||
return ' DEFAULT ' . $this->getCurrentTimestampSQL();
|
||||
}
|
||||
|
||||
if ($type instanceof Types\PhpTimeMappingType && $default === $this->getCurrentTimeSQL()) {
|
||||
return ' DEFAULT ' . $this->getCurrentTimeSQL();
|
||||
}
|
||||
|
||||
if ($type instanceof Types\PhpDateMappingType && $default === $this->getCurrentDateSQL()) {
|
||||
return ' DEFAULT ' . $this->getCurrentDateSQL();
|
||||
}
|
||||
|
||||
if ($type instanceof Types\BooleanType) {
|
||||
return ' DEFAULT ' . $this->convertBooleans($default);
|
||||
}
|
||||
|
||||
if (is_int($default) || is_float($default)) {
|
||||
return ' DEFAULT ' . $default;
|
||||
}
|
||||
|
||||
return ' DEFAULT ' . $this->quoteStringLiteral($default);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
<?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\Query;
|
||||
|
||||
use Doctrine\DBAL\Connection as DoctrineConnection;
|
||||
use TYPO3\CMS\Core\Database\Connection as Typo3Connection;
|
||||
|
||||
/**
|
||||
* Provides functionality to generate and execute row based bulk INSERT statements.
|
||||
*
|
||||
* Based on work by Steve Müller <st.mueller@dzh-online.de> for the Doctrine project,
|
||||
* licensed under the MIT license.
|
||||
*
|
||||
* This class will be removed from core and the functionality will be provided by
|
||||
* the upstream implementation once the pull request has been merged into Doctrine DBAL.
|
||||
*
|
||||
* @see https://github.com/doctrine/dbal/pull/682
|
||||
* @internal
|
||||
*/
|
||||
class BulkInsertQuery
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $columns;
|
||||
|
||||
/**
|
||||
* @var DoctrineConnection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $parameters = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $types = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $values = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DoctrineConnection $connection The connection to use for query execution.
|
||||
* @param string $table The name of the table to insert rows into.
|
||||
* @param string[] $columns The names of the columns to insert values into.
|
||||
* Can be left empty to allow arbitrary row inserts based on the table's column order.
|
||||
*/
|
||||
public function __construct(DoctrineConnection $connection, string $table, array $columns = [])
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->table = $connection->quoteIdentifier($table);
|
||||
$this->columns = $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the bulk insert statement as string.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getSQL();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a set of values to the bulk insert query to be inserted as a row into the specified table.
|
||||
*
|
||||
* @param array $values The set of values to be inserted as a row into the table.
|
||||
* If no columns have been specified for insertion, this can be
|
||||
* an arbitrary list of values to be inserted into the table.
|
||||
* Otherwise the values' keys have to match either one of the
|
||||
* specified column names or indexes.
|
||||
* @param array $types The types for the given values to bind to the query.
|
||||
* If no columns have been specified for insertion, the types'
|
||||
* keys will be matched against the given values' keys.
|
||||
* Otherwise the types' keys will be matched against the
|
||||
* specified column names and indexes.
|
||||
* Non-matching keys will be discarded, missing keys will not
|
||||
* be bound to a specific type.
|
||||
*
|
||||
* @throws \InvalidArgumentException if columns were specified for this query
|
||||
* and either no value for one of the specified
|
||||
* columns is given or multiple values are given
|
||||
* for a single column (named and indexed) or
|
||||
* multiple types are given for a single column
|
||||
* (named and indexed).
|
||||
*/
|
||||
public function addValues(array $values, array $types = [])
|
||||
{
|
||||
$valueSet = [];
|
||||
|
||||
if (empty($this->columns)) {
|
||||
foreach ($values as $index => $value) {
|
||||
$this->parameters[] = $value;
|
||||
$this->types[] = $types[$index] ?? null;
|
||||
$valueSet[] = '?';
|
||||
}
|
||||
|
||||
$this->values[] = $valueSet;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->columns as $index => $column) {
|
||||
$namedValue = isset($values[$column]) || array_key_exists($column, $values);
|
||||
$positionalValue = isset($values[$index]) || array_key_exists($index, $values);
|
||||
|
||||
if (!$namedValue && !$positionalValue) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('No value specified for column %s (index %d).', $column, $index),
|
||||
1476049651
|
||||
);
|
||||
}
|
||||
|
||||
if ($namedValue && $positionalValue && $values[$column] !== $values[$index]) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('Multiple values specified for column %s (index %d).', $column, $index),
|
||||
1476049652
|
||||
);
|
||||
}
|
||||
|
||||
$this->parameters[] = $namedValue ? $values[$column] : $values[$index];
|
||||
$valueSet[] = '?';
|
||||
|
||||
$namedType = isset($types[$column]);
|
||||
$positionalType = isset($types[$index]);
|
||||
|
||||
if ($namedType && $positionalType && $types[$column] !== $types[$index]) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('Multiple types specified for column %s (index %d).', $column, $index),
|
||||
1476049653
|
||||
);
|
||||
}
|
||||
|
||||
if ($namedType) {
|
||||
$this->types[] = $types[$column];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($positionalType) {
|
||||
$this->types[] = $types[$index];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->types[] = Typo3Connection::PARAM_STR;
|
||||
}
|
||||
|
||||
$this->values[] = $valueSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes this INSERT query using the bound parameters and their types.
|
||||
*
|
||||
* @return int The number of affected rows.
|
||||
*
|
||||
* @throws \LogicException if this query contains more rows than acceptable
|
||||
* for a single INSERT statement by the underlying platform.
|
||||
*/
|
||||
public function execute(): int
|
||||
{
|
||||
return $this->connection->executeStatement($this->getSQL(), $this->parameters, $this->types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameters for this INSERT query being constructed indexed by parameter index.
|
||||
*/
|
||||
public function getParameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameter types for this INSERT query being constructed indexed by parameter index.
|
||||
*/
|
||||
public function getParameterTypes(): array
|
||||
{
|
||||
return $this->types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SQL formed by the current specifications of this INSERT query.
|
||||
*
|
||||
*
|
||||
* @throws \LogicException if no values have been specified yet.
|
||||
*/
|
||||
public function getSQL(): string
|
||||
{
|
||||
if (empty($this->values)) {
|
||||
throw new \LogicException(
|
||||
'You need to add at least one set of values before generating the SQL.',
|
||||
1476049702
|
||||
);
|
||||
}
|
||||
|
||||
$connection = $this->connection;
|
||||
$columnList = '';
|
||||
|
||||
if (!empty($this->columns)) {
|
||||
$columnList = sprintf(
|
||||
' (%s)',
|
||||
implode(
|
||||
', ',
|
||||
array_map(
|
||||
static function (string $column) use ($connection): string {
|
||||
return $connection->quoteIdentifier($column);
|
||||
},
|
||||
$this->columns
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'INSERT INTO %s%s VALUES (%s)',
|
||||
$this->table,
|
||||
$columnList,
|
||||
implode(
|
||||
'), (',
|
||||
array_map(
|
||||
static function (array $valueSet): string {
|
||||
return implode(', ', $valueSet);
|
||||
},
|
||||
$this->values
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,924 @@
|
||||
<?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\Query;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MariaDBPlatform as DoctrineMariaDBPlatform;
|
||||
use Doctrine\DBAL\Platforms\MySQL80Platform as DoctrineMySQL80Platform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform as DoctrinePostgreSQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\SQLitePlatform as DoctrineSQLitePlatform;
|
||||
use Doctrine\DBAL\Query\Expression\CompositeExpression;
|
||||
use Doctrine\DBAL\Query\ForUpdate;
|
||||
use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode;
|
||||
use Doctrine\DBAL\Query\From;
|
||||
use Doctrine\DBAL\Query\Join;
|
||||
use Doctrine\DBAL\Query\QueryBuilder as DoctrineQueryBuilder;
|
||||
use Doctrine\DBAL\Query\QueryException;
|
||||
use Doctrine\DBAL\Query\QueryType;
|
||||
use Doctrine\DBAL\Query\Union;
|
||||
use Doctrine\DBAL\Query\UnionType;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
|
||||
/**
|
||||
* QueryBuilder class is responsible to dynamically create SQL queries.
|
||||
*
|
||||
* Important: Verify that every feature you use will work with your database vendor.
|
||||
* SQL Query Builder does not attempt to validate the generated SQL at all.
|
||||
*
|
||||
* The query builder does no validation whatsoever if certain features even work with the
|
||||
* underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements
|
||||
* even if some vendors such as MySQL support it.
|
||||
*
|
||||
* @internal not part of public core API. Uses as intermediate decorator wrapper to keep track of state, which is
|
||||
* considered internal and therefore by Doctrine DBAL but TYPO3 requires internal access.
|
||||
*/
|
||||
class ConcreteQueryBuilder extends DoctrineQueryBuilder
|
||||
{
|
||||
/**
|
||||
* The complete SQL string for this query.
|
||||
*/
|
||||
protected ?string $sql = null;
|
||||
|
||||
/**
|
||||
* The type of query this is. Can be select, update or delete.
|
||||
*/
|
||||
protected QueryType $type = QueryType::SELECT;
|
||||
|
||||
/**
|
||||
* The SELECT parts of the query.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $select = [];
|
||||
|
||||
/**
|
||||
* Whether this is a SELECT DISTINCT query.
|
||||
*/
|
||||
protected bool $distinct = false;
|
||||
|
||||
/**
|
||||
* The FROM parts of a SELECT query.
|
||||
*
|
||||
* @var From[]
|
||||
*/
|
||||
protected array $from = [];
|
||||
|
||||
protected ?ForUpdate $forUpdate = null;
|
||||
|
||||
/**
|
||||
* The list of joins, indexed by from alias.
|
||||
*
|
||||
* @var array<string, Join[]>
|
||||
*/
|
||||
protected array $join = [];
|
||||
|
||||
/**
|
||||
* The WHERE part of a SELECT, UPDATE or DELETE query.
|
||||
*/
|
||||
protected string|CompositeExpression|null $where = null;
|
||||
|
||||
/**
|
||||
* The GROUP BY part of a SELECT query.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $groupBy = [];
|
||||
|
||||
/**
|
||||
* The HAVING part of a SELECT query.
|
||||
*/
|
||||
protected string|CompositeExpression|null $having = null;
|
||||
|
||||
/**
|
||||
* The ORDER BY parts of a SELECT query.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $orderBy = [];
|
||||
|
||||
/**
|
||||
* The WITH query parts.
|
||||
*/
|
||||
protected WithCollection $typo3_with;
|
||||
|
||||
/**
|
||||
* The QueryBuilder for the union parts.
|
||||
*
|
||||
* @var Union[]
|
||||
*/
|
||||
protected array $typo3_unionParts = [];
|
||||
|
||||
/**
|
||||
* Initializes a new <tt>QueryBuilder</tt>.
|
||||
*
|
||||
* @param Connection $connection The DBAL Connection.
|
||||
*/
|
||||
public function __construct(protected readonly Connection $connection)
|
||||
{
|
||||
parent::__construct($this->connection);
|
||||
$this->typo3_with = new WithCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep clone of all expression objects in the SQL parts.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
parent::__clone();
|
||||
foreach ($this->from as $key => $from) {
|
||||
$this->from[$key] = clone $from;
|
||||
}
|
||||
foreach ($this->join as $fromAlias => $joins) {
|
||||
foreach ($joins as $key => $join) {
|
||||
$this->join[$fromAlias][$key] = clone $join;
|
||||
}
|
||||
}
|
||||
if (is_object($this->where)) {
|
||||
$this->where = clone $this->where;
|
||||
}
|
||||
if (is_object($this->having)) {
|
||||
$this->having = clone $this->having;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies union parts to be used to build a UNION query.
|
||||
* Replaces any previously specified parts.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $conn->createQueryBuilder()
|
||||
* ->union('SELECT 1 AS field1', 'SELECT 2 AS field1');
|
||||
* </code>
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function union(string|ConcreteQueryBuilder|DoctrineQueryBuilder $part): self
|
||||
{
|
||||
parent::union($part);
|
||||
$this->type = QueryType::UNION;
|
||||
$this->typo3_unionParts = [new Union($part)];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add parts to be used to build a UNION query.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $conn->createQueryBuilder()
|
||||
* ->union('SELECT 1 AS field1')
|
||||
* ->addUnion('SELECT 2 AS field1', 'SELECT 3 AS field1')
|
||||
* </code>
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addUnion(string|ConcreteQueryBuilder|DoctrineQueryBuilder $part, UnionType $type = UnionType::DISTINCT): self
|
||||
{
|
||||
parent::addUnion($part, $type);
|
||||
$this->type = QueryType::UNION;
|
||||
$this->typo3_unionParts[] = new Union($part, $type);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies an item that is to be returned in the query result.
|
||||
* Replaces any previously specified selections, if any.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $conn->createQueryBuilder()
|
||||
* ->select('u.id', 'p.id')
|
||||
* ->from('users', 'u')
|
||||
* ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
|
||||
* </code>
|
||||
*
|
||||
* @param string ...$expressions The selection expressions.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function select(string ...$expressions): self
|
||||
{
|
||||
parent::select(...$expressions);
|
||||
$this->type = QueryType::SELECT;
|
||||
if (count($expressions) < 1) {
|
||||
return $this;
|
||||
}
|
||||
$this->select = $expressions;
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or removes DISTINCT to/from the query.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $conn->createQueryBuilder()
|
||||
* ->select('u.id')
|
||||
* ->distinct()
|
||||
* ->from('users', 'u')
|
||||
* </code>
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function distinct(bool $distinct = true): self
|
||||
{
|
||||
parent::distinct($distinct);
|
||||
$this->distinct = $distinct;
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item that is to be returned in the query result.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $conn->createQueryBuilder()
|
||||
* ->select('u.id')
|
||||
* ->addSelect('p.id')
|
||||
* ->from('users', 'u')
|
||||
* ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id');
|
||||
* </code>
|
||||
*
|
||||
* @param string $expression The selection expression.
|
||||
* @param string ...$expressions Additional selection expressions.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function addSelect(string $expression, string ...$expressions): self
|
||||
{
|
||||
parent::addSelect($expression, ...$expressions);
|
||||
$this->type = QueryType::SELECT;
|
||||
$this->select = array_merge($this->select, [$expression], $expressions);
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the query being built into a bulk delete query that ranges over
|
||||
* a certain table.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $conn->createQueryBuilder()
|
||||
* ->delete('users', 'u')
|
||||
* ->where('u.id = :user_id')
|
||||
* ->setParameter(':user_id', 1);
|
||||
* </code>
|
||||
*
|
||||
* @param string $table The table whose rows are subject to the deletion.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function delete(string $table): self
|
||||
{
|
||||
parent::delete($table);
|
||||
$this->type = QueryType::DELETE;
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the query being built into a bulk update query that ranges over
|
||||
* a certain table
|
||||
*
|
||||
* <code>
|
||||
* $qb = $conn->createQueryBuilder()
|
||||
* ->update('counters', 'c')
|
||||
* ->set('c.value', 'c.value + 1')
|
||||
* ->where('c.id = ?');
|
||||
* </code>
|
||||
*
|
||||
* @param string $table The table whose rows are subject to the update.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function update(string $table): self
|
||||
{
|
||||
parent::update($table);
|
||||
$this->type = QueryType::UPDATE;
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the query being built into an insert query that inserts into
|
||||
* a certain table
|
||||
*
|
||||
* <code>
|
||||
* $qb = $conn->createQueryBuilder()
|
||||
* ->insert('users')
|
||||
* ->values(
|
||||
* array(
|
||||
* 'name' => '?',
|
||||
* 'password' => '?'
|
||||
* )
|
||||
* );
|
||||
* </code>
|
||||
*
|
||||
* @param string $table The table into which the rows should be inserted.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function insert(string $table): self
|
||||
{
|
||||
parent::insert($table);
|
||||
$this->type = QueryType::INSERT;
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and adds a query root corresponding to the table identified by the
|
||||
* given alias, forming a cartesian product with any existing query roots.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $conn->createQueryBuilder()
|
||||
* ->select('u.id')
|
||||
* ->from('users', 'u')
|
||||
* </code>
|
||||
*
|
||||
* @param string $table The table.
|
||||
* @param string|null $alias The alias of the table.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function from(string $table, ?string $alias = null): self
|
||||
{
|
||||
parent::from($table, $alias);
|
||||
$this->from[] = new From($table, $alias);
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and adds a join to the query.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $conn->createQueryBuilder()
|
||||
* ->select('u.name')
|
||||
* ->from('users', 'u')
|
||||
* ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
|
||||
* </code>
|
||||
*
|
||||
* @param string $fromAlias The alias that points to a from clause.
|
||||
* @param string $join The table name to join.
|
||||
* @param string $alias The alias of the join table.
|
||||
* @param string $condition The condition for the join.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function innerJoin(string $fromAlias, string $join, string $alias, ?string $condition = null): self
|
||||
{
|
||||
parent::innerJoin($fromAlias, $join, $alias, $condition);
|
||||
$this->join[$fromAlias][] = Join::inner($join, $alias, $condition);
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and adds a left join to the query.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $conn->createQueryBuilder()
|
||||
* ->select('u.name')
|
||||
* ->from('users', 'u')
|
||||
* ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
|
||||
* </code>
|
||||
*
|
||||
* @param string $fromAlias The alias that points to a from clause.
|
||||
* @param string $join The table name to join.
|
||||
* @param string $alias The alias of the join table.
|
||||
* @param string $condition The condition for the join.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function leftJoin(string $fromAlias, string $join, string $alias, ?string $condition = null): self
|
||||
{
|
||||
parent::leftJoin($fromAlias, $join, $alias, $condition);
|
||||
$this->join[$fromAlias][] = Join::left($join, $alias, $condition);
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and adds a right join to the query.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $conn->createQueryBuilder()
|
||||
* ->select('u.name')
|
||||
* ->from('users', 'u')
|
||||
* ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
|
||||
* </code>
|
||||
*
|
||||
* @param string $fromAlias The alias that points to a from clause.
|
||||
* @param string $join The table name to join.
|
||||
* @param string $alias The alias of the join table.
|
||||
* @param string $condition The condition for the join.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function rightJoin(string $fromAlias, string $join, string $alias, ?string $condition = null): self
|
||||
{
|
||||
parent::rightJoin($fromAlias, $join, $alias, $condition);
|
||||
$this->join[$fromAlias][] = Join::right($join, $alias, $condition);
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies one or more restrictions to the query result.
|
||||
* Replaces any previously specified restrictions, if any.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $conn->createQueryBuilder()
|
||||
* ->select('c.value')
|
||||
* ->from('counters', 'c')
|
||||
* ->where('c.id = ?');
|
||||
*
|
||||
* // You can optionally programmatically build and/or expressions
|
||||
* $qb = $conn->createQueryBuilder();
|
||||
*
|
||||
* $or = $qb->expr()->orx();
|
||||
* $or->add($qb->expr()->eq('c.id', 1));
|
||||
* $or->add($qb->expr()->eq('c.id', 2));
|
||||
*
|
||||
* $qb->update('counters', 'c')
|
||||
* ->set('c.value', 'c.value + 1')
|
||||
* ->where($or);
|
||||
* </code>
|
||||
*
|
||||
* @param string|CompositeExpression $predicate The WHERE clause predicate.
|
||||
* @param string|CompositeExpression ...$predicates Additional WHERE clause predicates.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function where(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self
|
||||
{
|
||||
$where = $this->where = $this->createPredicate($predicate, ...$predicates);
|
||||
parent::where($where);
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one or more restrictions to the query results, forming a logical
|
||||
* conjunction with any previously specified restrictions.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $conn->createQueryBuilder()
|
||||
* ->select('u')
|
||||
* ->from('users', 'u')
|
||||
* ->where('u.username LIKE ?')
|
||||
* ->andWhere('u.is_active = 1');
|
||||
* </code>
|
||||
*
|
||||
* @see where()
|
||||
*
|
||||
* @param string|CompositeExpression $predicate The predicate to append.
|
||||
* @param string|CompositeExpression ...$predicates Additional predicates to append.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function andWhere(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self
|
||||
{
|
||||
$where = $this->where = $this->appendToPredicate(
|
||||
$this->where,
|
||||
CompositeExpression::TYPE_AND,
|
||||
$predicate,
|
||||
...$predicates,
|
||||
);
|
||||
$this->where($where);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one or more restrictions to the query results, forming a logical
|
||||
* disjunction with any previously specified restrictions.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $em->createQueryBuilder()
|
||||
* ->select('u.name')
|
||||
* ->from('users', 'u')
|
||||
* ->where('u.id = 1')
|
||||
* ->orWhere('u.id = 2');
|
||||
* </code>
|
||||
*
|
||||
* @see where()
|
||||
*
|
||||
* @param string|CompositeExpression $predicate The predicate to append.
|
||||
* @param string|CompositeExpression ...$predicates Additional predicates to append.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function orWhere(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self
|
||||
{
|
||||
$where = $this->where = $this->appendToPredicate($this->where, CompositeExpression::TYPE_OR, $predicate, ...$predicates);
|
||||
$this->where($where);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies one or more grouping expressions over the results of the query.
|
||||
* Replaces any previously specified groupings, if any.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $conn->createQueryBuilder()
|
||||
* ->select('u.name')
|
||||
* ->from('users', 'u')
|
||||
* ->groupBy('u.id');
|
||||
* </code>
|
||||
*
|
||||
* @param string $expression The grouping expression
|
||||
* @param string ...$expressions Additional grouping expressions
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function groupBy(string $expression, string ...$expressions): self
|
||||
{
|
||||
$groupBy = $this->groupBy = array_merge([$expression], $expressions);
|
||||
parent::groupBy(...$groupBy);
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one or more grouping expressions to the query.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $conn->createQueryBuilder()
|
||||
* ->select('u.name')
|
||||
* ->from('users', 'u')
|
||||
* ->groupBy('u.lastLogin')
|
||||
* ->addGroupBy('u.createdAt');
|
||||
* </code>
|
||||
*
|
||||
* @param string $expression The grouping expression
|
||||
* @param string ...$expressions Additional grouping expressions
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function addGroupBy(string $expression, string ...$expressions): self
|
||||
{
|
||||
$groupBy = $this->groupBy = array_merge($this->groupBy, [$expression], $expressions);
|
||||
$this->groupBy(...$groupBy);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a restriction over the groups of the query.
|
||||
* Replaces any previous having restrictions, if any.
|
||||
*
|
||||
* @param string|CompositeExpression $predicate The HAVING clause predicate.
|
||||
* @param string|CompositeExpression ...$predicates Additional HAVING clause predicates.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function having(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self
|
||||
{
|
||||
$having = $this->having = $this->createPredicate($predicate, ...$predicates);
|
||||
parent::having($having);
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a restriction over the groups of the query, forming a logical
|
||||
* conjunction with any existing having restrictions.
|
||||
*
|
||||
* @param string|CompositeExpression $predicate The predicate to append.
|
||||
* @param string|CompositeExpression ...$predicates Additional predicates to append.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function andHaving(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self
|
||||
{
|
||||
$having = $this->having = $this->appendToPredicate(
|
||||
$this->having,
|
||||
CompositeExpression::TYPE_AND,
|
||||
$predicate,
|
||||
...$predicates,
|
||||
);
|
||||
$this->having($having);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a restriction over the groups of the query, forming a logical
|
||||
* disjunction with any existing having restrictions.
|
||||
*
|
||||
* @param string|CompositeExpression $predicate The predicate to append.
|
||||
* @param string|CompositeExpression ...$predicates Additional predicates to append.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function orHaving(string|CompositeExpression $predicate, string|CompositeExpression ...$predicates): self
|
||||
{
|
||||
$having = $this->having = $this->appendToPredicate(
|
||||
$this->having,
|
||||
CompositeExpression::TYPE_OR,
|
||||
$predicate,
|
||||
...$predicates,
|
||||
);
|
||||
$this->having($having);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a CompositeExpression from one or more predicates combined by the AND logic.
|
||||
*/
|
||||
private function createPredicate(
|
||||
string|CompositeExpression $predicate,
|
||||
string|CompositeExpression ...$predicates,
|
||||
): string|CompositeExpression {
|
||||
if (count($predicates) === 0) {
|
||||
return $predicate;
|
||||
}
|
||||
$predicates = array_filter($predicates, static fn(CompositeExpression|string|null $value): bool => !self::isEmptyPart($value));
|
||||
return new CompositeExpression(CompositeExpression::TYPE_AND, $predicate, ...$predicates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given predicates combined by the given type of logic to the current predicate.
|
||||
*/
|
||||
private function appendToPredicate(
|
||||
string|CompositeExpression|null $currentPredicate,
|
||||
string $type,
|
||||
string|CompositeExpression ...$predicates,
|
||||
): string|CompositeExpression {
|
||||
$predicates = array_filter($predicates, static fn(CompositeExpression|string|null $value): bool => !self::isEmptyPart($value));
|
||||
if ($currentPredicate instanceof CompositeExpression && $currentPredicate->getType() === $type) {
|
||||
return $currentPredicate->with(...$predicates);
|
||||
}
|
||||
if ($currentPredicate !== null) {
|
||||
array_unshift($predicates, $currentPredicate);
|
||||
} elseif (count($predicates) === 1) {
|
||||
return $predicates[0];
|
||||
}
|
||||
return new CompositeExpression($type, ...$predicates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies an ordering for the query results.
|
||||
* Replaces any previously specified orderings, if any.
|
||||
*
|
||||
* @param string $sort The ordering expression.
|
||||
* @param string $order The ordering direction.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function orderBy(string $sort, ?string $order = null): self
|
||||
{
|
||||
parent::orderBy($sort, $order);
|
||||
$orderBy = $sort;
|
||||
if ($order !== null) {
|
||||
$orderBy .= ' ' . $order;
|
||||
}
|
||||
$this->orderBy = [$orderBy];
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an ordering to the query results.
|
||||
*
|
||||
* @param string $sort The ordering expression.
|
||||
* @param string $order The ordering direction.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function addOrderBy(string $sort, ?string $order = null): self
|
||||
{
|
||||
parent::addOrderBy($sort, $order);
|
||||
$orderBy = $sort;
|
||||
if ($order !== null) {
|
||||
$orderBy .= ' ' . $order;
|
||||
}
|
||||
$this->orderBy[] = $orderBy;
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the WHERE conditions for the query.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function resetWhere(): self
|
||||
{
|
||||
parent::resetWhere();
|
||||
$this->where = null;
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the grouping for the query.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function resetGroupBy(): self
|
||||
{
|
||||
parent::resetGroupBy();
|
||||
$this->groupBy = [];
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the HAVING conditions for the query.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function resetHaving(): self
|
||||
{
|
||||
parent::resetHaving();
|
||||
$this->having = null;
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the ordering for the query.
|
||||
*
|
||||
* @return $this This QueryBuilder instance.
|
||||
*/
|
||||
public function resetOrderBy(): self
|
||||
{
|
||||
parent::resetOrderBy();
|
||||
$this->orderBy = [];
|
||||
$this->sql = '';
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMaxResults(?int $maxResults): ConcreteQueryBuilder
|
||||
{
|
||||
parent::setMaxResults($maxResults);
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setFirstResult(int $firstResult): ConcreteQueryBuilder
|
||||
{
|
||||
parent::setFirstResult($firstResult);
|
||||
$this->sql = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function forUpdate(ConflictResolutionMode $conflictResolutionMode = ConflictResolutionMode::ORDINARY): DoctrineQueryBuilder
|
||||
{
|
||||
parent::forUpdate($conflictResolutionMode);
|
||||
$this->forUpdate = new ForUpdate($conflictResolutionMode);
|
||||
|
||||
$this->sql = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSQL(): string
|
||||
{
|
||||
if ($this->typo3_with->isEmpty()) {
|
||||
return parent::getSQL();
|
||||
}
|
||||
return $this->sql ??= $this->prependWith(parent::getSQL());
|
||||
}
|
||||
|
||||
//##################################################################################################################
|
||||
// Below are added methods not originated from Doctrine DBAL QueryBuilder
|
||||
//##################################################################################################################
|
||||
|
||||
/**
|
||||
* @param string[] $fields
|
||||
* @param string[] $dependsOn
|
||||
*
|
||||
* @internal not part of public API, experimental and may change at any given time.
|
||||
*/
|
||||
public function typo3_with(
|
||||
string $name,
|
||||
string|QueryBuilder $expression,
|
||||
array $fields = [],
|
||||
array $dependsOn = [],
|
||||
): self {
|
||||
$this->typo3_with->set(new With($name, $fields, $dependsOn, $expression, false));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $fields
|
||||
* @param string[] $dependsOn
|
||||
*
|
||||
* @internal not part of public API, experimental and may change at any given time.
|
||||
*/
|
||||
public function typo3_addWith(
|
||||
string $name,
|
||||
string|QueryBuilder $expression,
|
||||
array $fields = [],
|
||||
array $dependsOn = [],
|
||||
): self {
|
||||
$this->typo3_with->add(new With($name, $fields, $dependsOn, $expression, false));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $fields
|
||||
* @param string[] $dependsOn
|
||||
*
|
||||
* @internal not part of public API, experimental and may change at any given time.
|
||||
*/
|
||||
public function typo3_withRecursive(
|
||||
string $name,
|
||||
bool $uniqueRows,
|
||||
string|QueryBuilder $initialExpression,
|
||||
string|QueryBuilder $recursiveExpression,
|
||||
array $fields = [],
|
||||
array $dependsOn = [],
|
||||
): self {
|
||||
$unionExpression = $this->connection->createQueryBuilder()
|
||||
->union($initialExpression)
|
||||
->addUnion($recursiveExpression, $uniqueRows ? UnionType::DISTINCT : UnionType::ALL);
|
||||
$this->typo3_with->set(new With($name, $fields, $dependsOn, $unionExpression, true));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $fields
|
||||
* @param string[] $dependsOn
|
||||
*
|
||||
* @internal not part of public API, experimental and may change at any given time.
|
||||
*/
|
||||
public function typo3_addWithRecursive(
|
||||
string $name,
|
||||
bool $uniqueRows,
|
||||
string|QueryBuilder $initialExpression,
|
||||
string|QueryBuilder $recursiveExpression,
|
||||
array $fields = [],
|
||||
array $dependsOn = [],
|
||||
): self {
|
||||
$unionExpression = $this->connection->createQueryBuilder()
|
||||
->union($initialExpression)
|
||||
->addUnion($recursiveExpression, $uniqueRows ? UnionType::DISTINCT : UnionType::ALL);
|
||||
$this->typo3_with->add(new With($name, $fields, $dependsOn, $unionExpression, true));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a query part used for where or having is empty. Used as array_filter in ConcreteQueryBuilder
|
||||
* methods. This is needed to avoid invalid sql syntax by empty parts, which can happen to relaxed custom
|
||||
* CompositeExpression handling.
|
||||
*
|
||||
* For example used to avoid : (uid = 1) and () and (pid = 2).
|
||||
*
|
||||
* @see ConcreteQueryBuilder::createPredicate()
|
||||
* @see ConcreteQueryBuilder::appendToPredicate()
|
||||
* @see \TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression::isEmptyPart()
|
||||
*
|
||||
* @param CompositeExpression|string|null $value
|
||||
* @return bool
|
||||
*/
|
||||
protected static function isEmptyPart(CompositeExpression|string|null $value): bool
|
||||
{
|
||||
return $value === null
|
||||
|| ($value instanceof CompositeExpression && $value->count() === 0)
|
||||
|| trim((string)$value, '() ') === ''
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Should be handled in {@see AbstractPlatform} class hierarchy directly in doctrine directly if support gets
|
||||
* accepted or handled internally here to avoid the force to extend and replace platform classes.
|
||||
*/
|
||||
private function supportsCommonTableExpressions(): bool
|
||||
{
|
||||
$platform = $this->connection->getDatabasePlatform();
|
||||
return $platform instanceof DoctrineMariaDBPlatform
|
||||
|| $platform instanceof DoctrineMySQL80Platform
|
||||
|| $platform instanceof DoctrineSQLitePlatform
|
||||
|| $platform instanceof DoctrinePostgreSQLPlatform
|
||||
;
|
||||
}
|
||||
|
||||
private function prependWith(string $sql): string
|
||||
{
|
||||
if (!$this->typo3_with->isEmpty() && !$this->supportsCommonTableExpressions()) {
|
||||
throw new QueryException(
|
||||
'WITH not supported for current connection.',
|
||||
1717762530,
|
||||
);
|
||||
}
|
||||
return $this->typo3_with . $sql;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?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\Query\Expression;
|
||||
|
||||
use Doctrine\DBAL\Query\Expression\CompositeExpression as DoctrineCompositeExpression;
|
||||
|
||||
/**
|
||||
* Facade of the Doctrine DBAL CompositeExpression to have
|
||||
* all Query related classes with in TYPO3\CMS namespace.
|
||||
*/
|
||||
class CompositeExpression extends DoctrineCompositeExpression
|
||||
{
|
||||
/**
|
||||
* Each expression part of the composite expression.
|
||||
*
|
||||
* @var self[]|string[]
|
||||
*/
|
||||
private array $parts;
|
||||
|
||||
/**
|
||||
* The instance type of composite expression.
|
||||
*/
|
||||
private string $type;
|
||||
|
||||
private bool $isOuter;
|
||||
|
||||
/**
|
||||
* @param list<self|DoctrineCompositeExpression|string|null> $parts
|
||||
* @internal Use factory methods `and()` or `or()` methods instead. Signature will change along with doctrine/dbal 4.
|
||||
*/
|
||||
public function __construct(string $type, array $parts = [], bool $isOuter = false)
|
||||
{
|
||||
$this->isOuter = $isOuter;
|
||||
// parent::__construct() call is left out by intention. doctrine/dbal works with private properties, which
|
||||
// make it otherwise impossible to keep compat method signature and providing the features needed.
|
||||
$this->type = $type;
|
||||
if ($parts !== []) {
|
||||
// doctrine/dbal solved the issue to avoid empty parts by making it mandatory to avoid instantiating this
|
||||
// class without a part. As we allow this and handle empty parts later on, we apply the empty check here.
|
||||
// @see https://github.com/doctrine/dbal/issues/2388
|
||||
$parts = array_filter($parts, static fn(CompositeExpression|DoctrineCompositeExpression|string|null $value): bool => !self::isEmptyPart($value));
|
||||
}
|
||||
$this->parts = $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the string representation of this composite expression.
|
||||
* If expression is empty, just return an empty string.
|
||||
* Native Doctrine expression would return () instead.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
$count = $this->count();
|
||||
if ($count === 0) {
|
||||
return '';
|
||||
}
|
||||
if ($count === 1) {
|
||||
return (string)$this->parts[0];
|
||||
}
|
||||
if ($this->isOuter) {
|
||||
return '(' . implode(') ' . $this->type . ' (', $this->parts) . ')';
|
||||
}
|
||||
return '((' . implode(') ' . $this->type . ' (', $this->parts) . '))';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param self|string|null $part
|
||||
* @param self|string|null ...$parts
|
||||
*/
|
||||
public static function and($part = null, ...$parts): self
|
||||
{
|
||||
return (new self(self::TYPE_AND, []))->with($part, ...$parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param self|string|null $part
|
||||
* @param self|string|null ...$parts
|
||||
*/
|
||||
public static function or($part = null, ...$parts): self
|
||||
{
|
||||
return (new self(self::TYPE_OR, []))->with($part, ...$parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new CompositeExpression with the given parts added.
|
||||
*
|
||||
* @param self|string|null $part
|
||||
* @param self|string|null ...$parts
|
||||
*/
|
||||
public function with($part = null, ...$parts): self
|
||||
{
|
||||
$mergedParts = array_merge([$part], $parts);
|
||||
$mergedParts = array_filter($mergedParts, static fn(CompositeExpression|DoctrineCompositeExpression|string|null $value): bool => !self::isEmptyPart($value));
|
||||
$that = clone $this;
|
||||
foreach ($mergedParts as $singlePart) {
|
||||
$that->parts[] = $singlePart;
|
||||
}
|
||||
|
||||
return $that;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the amount of expressions on composite expression.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of this composite expression (AND/OR).
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a part is considerable empty.
|
||||
*
|
||||
* doctrine/dbal solved the issue to avoid empty parts by making it mandatory to avoid instantiating this
|
||||
* class without a part. As we allow this and handle empty parts later on, we apply the empty check here.
|
||||
* @see https://github.com/doctrine/dbal/issues/2388
|
||||
*/
|
||||
private static function isEmptyPart(CompositeExpression|DoctrineCompositeExpression|string|null $value): bool
|
||||
{
|
||||
if ($value === null) {
|
||||
return true;
|
||||
}
|
||||
if (is_string($value)) {
|
||||
return trim($value, '() ') === '';
|
||||
}
|
||||
if ($value instanceof CompositeExpression) {
|
||||
// TYPO3 implementation filters empty parts on setting and count is reliable in that case.
|
||||
return $value->parts === [];
|
||||
}
|
||||
// We need to use the count method, because the property is private in Doctrine and cannot be checked
|
||||
// against an empty array like it can be done for the own instance. Using Reflection would negate the
|
||||
// benefit. That's life.
|
||||
if ($value->count() === 0) {
|
||||
// Note that this should not be possible with plain Doctrine DBAL
|
||||
// composite expression, still lets ensure a fallback here.
|
||||
return true;
|
||||
}
|
||||
// Doctrine DBAL CompositeExpression does not filter empty parts, so we need to build the string to
|
||||
// evaluate if it is empty or not, which comes with some performance impact hitting only when TYPO3
|
||||
// extension authors are using the Doctrine Composite Expression instead of the TYPO3 variant.
|
||||
return trim((string)$value, '() ') === '';
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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\Query;
|
||||
|
||||
class NamedParameterNotSupportedForPreparedStatementException extends \InvalidArgumentException
|
||||
{
|
||||
public static function new(string $placeholderName): NamedParameterNotSupportedForPreparedStatementException
|
||||
{
|
||||
return new self(
|
||||
sprintf(
|
||||
"Cannot prepare statement for QueryBuilder because unsupported named placeholder '%s'",
|
||||
$placeholderName
|
||||
),
|
||||
1639249867
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,290 @@
|
||||
<?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\Query;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Contains misc helper methods to build syntactically valid SQL queries.
|
||||
* Most helper functions are required to deal with legacy data where the
|
||||
* format of the input is not strict enough to reliably use the SQL parts
|
||||
* in queries directly.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
readonly class QueryHelper
|
||||
{
|
||||
/**
|
||||
* Takes an input, possibly prefixed with ORDER BY, and explodes it into
|
||||
* and array of arrays where each item consists of a fieldName and an order
|
||||
* direction.
|
||||
*
|
||||
* Each of the resulting fieldName/direction pairs can be used passed into
|
||||
* QueryBuilder::orderBy() so sort a query result set.
|
||||
*
|
||||
* @param string $input eg . "ORDER BY title, uid
|
||||
* @return array|array[] Array of arrays containing fieldName/direction pairs
|
||||
*/
|
||||
public static function parseOrderBy(string $input): array
|
||||
{
|
||||
if ($input === '') {
|
||||
return [];
|
||||
}
|
||||
$input = preg_replace('/^(?:ORDER[[:space:]]*BY[[:space:]]*)+/i', '', trim($input)) ?: '';
|
||||
$orderExpressions = GeneralUtility::trimExplode(',', $input, true);
|
||||
|
||||
return array_map(
|
||||
static function (string $expression): array {
|
||||
$fieldNameOrderArray = GeneralUtility::trimExplode(' ', $expression, true);
|
||||
$fieldName = $fieldNameOrderArray[0] ?? null;
|
||||
$order = $fieldNameOrderArray[1] ?? null;
|
||||
|
||||
return [$fieldName, $order];
|
||||
},
|
||||
$orderExpressions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an input, possibly prefixed with FROM, and explodes it into
|
||||
* and array of arrays where each item consists of a tableName and an
|
||||
* optional alias name.
|
||||
*
|
||||
* Each of the resulting pairs can be used with QueryBuilder::from()
|
||||
* to select from one or more tables.
|
||||
*
|
||||
* @param string $input eg . "FROM aTable, anotherTable AS b, aThirdTable c"
|
||||
* @return array|array[] Array of arrays containing tableName/alias pairs
|
||||
*/
|
||||
public static function parseTableList(string $input): array
|
||||
{
|
||||
if ($input === '') {
|
||||
return [];
|
||||
}
|
||||
$input = preg_replace('/^(?:FROM[[:space:]]+)+/i', '', trim($input)) ?: '';
|
||||
$tableExpressions = GeneralUtility::trimExplode(',', $input, true);
|
||||
|
||||
return array_map(
|
||||
static function (string $expression): array {
|
||||
[$tableName, $as, $alias] = array_pad(GeneralUtility::trimExplode(' ', $expression, true), 3, null);
|
||||
|
||||
if (!empty($as) && strtolower($as) === 'as' && !empty($alias)) {
|
||||
return [$tableName, $alias];
|
||||
}
|
||||
if (!empty($as) && empty($alias)) {
|
||||
return [$tableName, $as];
|
||||
}
|
||||
return [$tableName, null];
|
||||
},
|
||||
$tableExpressions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the prefix "GROUP BY" from the input string.
|
||||
*
|
||||
* This function should be used when you can't guarantee that the string
|
||||
* that you want to use as a GROUP BY fragment is not prefixed.
|
||||
*
|
||||
* @param string $input eg. "GROUP BY title, uid
|
||||
* @return array|string[] column names to group by
|
||||
*/
|
||||
public static function parseGroupBy(string $input): array
|
||||
{
|
||||
if ($input === '') {
|
||||
return [];
|
||||
}
|
||||
$input = preg_replace('/^(?:GROUP[[:space:]]*BY[[:space:]]*)+/i', '', trim($input)) ?: '';
|
||||
|
||||
return GeneralUtility::trimExplode(',', $input, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a JOIN SQL fragment into table name, alias and join conditions.
|
||||
*
|
||||
* @param string $input eg. "JOIN tableName AS a ON a.uid = anotherTable.uid_foreign"
|
||||
* @return array assoc array consisting of the keys tableName, tableAlias and joinCondition
|
||||
*/
|
||||
public static function parseJoin(string $input): array
|
||||
{
|
||||
$input = trim($input);
|
||||
$quoteCharacter = ' ';
|
||||
$matchQuotingStartCharacters = [
|
||||
'`' => '`',
|
||||
'"' => '"',
|
||||
'[' => '[]',
|
||||
];
|
||||
|
||||
// Check if the tableName is quoted
|
||||
$firstCharOfInputValue = $input[0] ?? '';
|
||||
if ($matchQuotingStartCharacters[$firstCharOfInputValue] ?? false) {
|
||||
$quoteCharacter .= $matchQuotingStartCharacters[$firstCharOfInputValue];
|
||||
$input = substr($input, 1);
|
||||
$tableName = strtok($input, $quoteCharacter);
|
||||
} else {
|
||||
$tableName = strtok($input, $quoteCharacter);
|
||||
}
|
||||
|
||||
$tableAlias = (string)strtok($quoteCharacter);
|
||||
if (strtolower($tableAlias) === 'as') {
|
||||
$tableAlias = (string)strtok($quoteCharacter);
|
||||
// Skip the next token which must be ON
|
||||
strtok(' ');
|
||||
$joinCondition = strtok('');
|
||||
} elseif (strtolower($tableAlias) === 'on') {
|
||||
$tableAlias = null;
|
||||
$joinCondition = strtok('');
|
||||
} else {
|
||||
// Skip the next token which must be ON
|
||||
strtok(' ');
|
||||
$joinCondition = strtok('');
|
||||
}
|
||||
|
||||
// Catch the edge case that the table name is unquoted and the
|
||||
// table alias is actually quoted. This will not work in the case
|
||||
// that the quoted table alias contains whitespace.
|
||||
$firstCharacterOfTableAlias = $tableAlias[0] ?? '';
|
||||
if ($matchQuotingStartCharacters[$firstCharacterOfTableAlias] ?? false) {
|
||||
$tableAlias = substr((string)$tableAlias, 1, -1);
|
||||
}
|
||||
|
||||
$tableAlias = $tableAlias ?: $tableName;
|
||||
|
||||
return ['tableName' => $tableName, 'tableAlias' => $tableAlias, 'joinCondition' => $joinCondition];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the prefixes AND/OR from the input string.
|
||||
*
|
||||
* This function should be used when you can't guarantee that the string
|
||||
* that you want to use as a WHERE fragment is not prefixed.
|
||||
*
|
||||
* @param string $constraint The where part fragment with a possible leading AND or OR operator
|
||||
* @return string The modified where part without leading operator
|
||||
*/
|
||||
public static function stripLogicalOperatorPrefix(string $constraint): string
|
||||
{
|
||||
return preg_replace('/^(?:(AND|OR)[[:space:]]*)+/i', '', trim($constraint)) ?: '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date and time formats compatible with the given database.
|
||||
* This simple method should probably be deprecated and removed later.
|
||||
*/
|
||||
public static function getDateTimeFormats(): array
|
||||
{
|
||||
return [
|
||||
'date' => [
|
||||
'empty' => '0000-00-00',
|
||||
'format' => 'Y-m-d',
|
||||
],
|
||||
'datetime' => [
|
||||
'empty' => '0000-00-00 00:00:00',
|
||||
'format' => 'Y-m-d H:i:s',
|
||||
],
|
||||
'time' => [
|
||||
'empty' => '00:00:00',
|
||||
'format' => 'H:i:s',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date and time types compatible with the given database.
|
||||
* This simple method should probably be deprecated and removed later.
|
||||
*/
|
||||
public static function getDateTimeTypes(): array
|
||||
{
|
||||
return [
|
||||
'date',
|
||||
'datetime',
|
||||
'time',
|
||||
];
|
||||
}
|
||||
|
||||
public static function transformDateTimeToDatabaseValue(
|
||||
?\DateTimeInterface $datetime,
|
||||
bool $isNullable,
|
||||
string $format,
|
||||
?string $persistenceType,
|
||||
): int|string|null {
|
||||
if ($datetime === null) {
|
||||
if ($isNullable) {
|
||||
return null;
|
||||
}
|
||||
if ($persistenceType === null) {
|
||||
return 0;
|
||||
}
|
||||
return self::getDateTimeFormats()[$persistenceType]['empty'] ?? null;
|
||||
}
|
||||
|
||||
if (!$datetime instanceof \DateTimeImmutable) {
|
||||
$datetime = \DateTimeImmutable::createFromInterface($datetime);
|
||||
}
|
||||
|
||||
// Apply format-specific normalizations
|
||||
if ($format === 'time') {
|
||||
// time(sec) is stored as elapsed seconds in DB, hence we base the time on 1970-01-01
|
||||
$datetime = $datetime->setDate(1970, 01, 01)->setTime((int)$datetime->format('H'), (int)$datetime->format('i'), 0);
|
||||
} elseif ($format === 'timesec' || $persistenceType === 'time') {
|
||||
$datetime = $datetime->setDate(1970, 01, 01);
|
||||
} elseif ($format === 'date' || $persistenceType === 'date') {
|
||||
$datetime = $datetime->setTime(0, 0, 0);
|
||||
}
|
||||
// datetimesec is a "normal" date and needs no removal/adjustment of seconds or date.
|
||||
|
||||
// Native DATETIME, DATE or TIME field
|
||||
if (in_array($persistenceType, self::getDateTimeTypes(), true)) {
|
||||
$dateTimeFormats = self::getDateTimeFormats();
|
||||
$persistenceFormat = $dateTimeFormats[$persistenceType]['format'];
|
||||
if ($persistenceType === 'datetime') {
|
||||
// native DATETIME values are stored in server LOCALTIME. Force conversion to the servers current timezone.
|
||||
$datetime = $datetime->setTimezone(new \DateTimeZone(date_default_timezone_get()));
|
||||
}
|
||||
|
||||
return $datetime->format($persistenceFormat);
|
||||
}
|
||||
|
||||
// Time is stored in seconds for integer fields
|
||||
if ($format === 'timesec' || $format === 'time') {
|
||||
return (int)$datetime->format('H') * 3600 + (int)$datetime->format('i') * 60 + (int)$datetime->format('s');
|
||||
}
|
||||
|
||||
// Encode as unix timestamp (int) if no native field is used
|
||||
return $datetime->getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Quote database table/column names indicated by {#identifier} markup in a SQL fragment string.
|
||||
* This is an intermediate step to make SQL fragments in Typoscript and TCA database agnostic.
|
||||
*/
|
||||
public static function quoteDatabaseIdentifiers(Connection $connection, string $sql): string
|
||||
{
|
||||
if (str_contains($sql, '{#')) {
|
||||
$sql = preg_replace_callback(
|
||||
'/{#(?P<identifier>[^}]+)}/',
|
||||
static function (array $matches) use ($connection) {
|
||||
return $connection->quoteIdentifier($matches['identifier']);
|
||||
},
|
||||
$sql
|
||||
);
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?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\Query\Restriction;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Base class for query restriction collections
|
||||
*/
|
||||
abstract class AbstractRestrictionContainer implements QueryRestrictionContainerInterface
|
||||
{
|
||||
/**
|
||||
* @var QueryRestrictionInterface[]
|
||||
*/
|
||||
protected $restrictions = [];
|
||||
|
||||
/**
|
||||
* @var QueryRestrictionInterface[]
|
||||
*/
|
||||
protected $enforcedRestrictions = [];
|
||||
|
||||
/**
|
||||
* Main method to build expressions for given tables.
|
||||
* Iterating over all registered expressions and combine them with AND
|
||||
*
|
||||
* @param array $queriedTables Array of tables, where array key is table alias and value is a table name
|
||||
* @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
|
||||
* @return CompositeExpression The result of query builder expression(s)
|
||||
*/
|
||||
public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
|
||||
{
|
||||
$constraints = [];
|
||||
foreach ($this->restrictions as $restriction) {
|
||||
$constraints[] = $restriction->buildExpression($queriedTables, $expressionBuilder);
|
||||
}
|
||||
return $expressionBuilder->and(...$constraints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all restrictions stored within this container
|
||||
*/
|
||||
public function removeAll(): QueryRestrictionContainerInterface
|
||||
{
|
||||
$this->restrictions = $this->enforcedRestrictions;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove restriction of a given type
|
||||
*
|
||||
* @param string $restrictionType Class name of the restriction to be removed
|
||||
*/
|
||||
public function removeByType(string $restrictionType): QueryRestrictionContainerInterface
|
||||
{
|
||||
foreach ($this->restrictions as $type => $instance) {
|
||||
if ($instance instanceof $restrictionType) {
|
||||
unset($this->restrictions[$type]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->enforcedRestrictions as $type => $instance) {
|
||||
if ($instance instanceof $restrictionType) {
|
||||
unset($this->enforcedRestrictions[$type]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new restriction instance to this collection
|
||||
*/
|
||||
public function add(QueryRestrictionInterface $restriction): QueryRestrictionContainerInterface
|
||||
{
|
||||
$this->restrictions[get_class($restriction)] = $restriction;
|
||||
if ($restriction instanceof EnforceableQueryRestrictionInterface && $restriction->isEnforced()) {
|
||||
$this->enforcedRestrictions[get_class($restriction)] = $restriction;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for restrictions.
|
||||
* Currently only instantiates the class.
|
||||
*
|
||||
* @param string $restrictionClass
|
||||
*/
|
||||
protected function createRestriction($restrictionClass): QueryRestrictionInterface
|
||||
{
|
||||
return GeneralUtility::makeInstance($restrictionClass);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?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\Query\Restriction;
|
||||
|
||||
/**
|
||||
* This is the container with restrictions, that are added to any doctrine query
|
||||
*/
|
||||
class DefaultRestrictionContainer extends AbstractRestrictionContainer
|
||||
{
|
||||
/**
|
||||
* Default restriction classes.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $defaultRestrictionTypes = [
|
||||
DeletedRestriction::class,
|
||||
HiddenRestriction::class,
|
||||
StartTimeRestriction::class,
|
||||
EndTimeRestriction::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Creates instances of the registered default restriction classes
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
foreach ($this->defaultRestrictionTypes as $restrictionType) {
|
||||
$this->add($this->createRestriction($restrictionType));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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\Query\Restriction;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
|
||||
/**
|
||||
* Restriction to respect the soft-delete functionality of TYPO3.
|
||||
* Filters out records, that were marked as deleted.
|
||||
*/
|
||||
class DeletedRestriction implements QueryRestrictionInterface
|
||||
{
|
||||
/**
|
||||
* Main method to build expressions for given tables
|
||||
* Evaluates the ctrl/delete flag of the table and adds the according restriction if set
|
||||
*
|
||||
* @param array $queriedTables Array of tables, where array key is table alias and value is a table name
|
||||
* @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
|
||||
* @return CompositeExpression The result of query builder expression(s)
|
||||
*/
|
||||
public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
|
||||
{
|
||||
$tcaSchemaFactory = $expressionBuilder->getContainer()->get(TcaSchemaFactory::class);
|
||||
$constraints = [];
|
||||
foreach ($queriedTables as $tableAlias => $tableName) {
|
||||
if (!$tcaSchemaFactory->has($tableName)) {
|
||||
continue;
|
||||
}
|
||||
$schema = $tcaSchemaFactory->get($tableName);
|
||||
if ($schema->hasCapability(TcaSchemaCapability::SoftDelete)) {
|
||||
$constraints[] = $expressionBuilder->eq(
|
||||
$tableAlias . '.' . $schema->getCapability(TcaSchemaCapability::SoftDelete)->getFieldName(),
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
return $expressionBuilder->and(...$constraints);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?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\Query\Restriction;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
|
||||
/**
|
||||
* Restriction to make queries for pages doktype-aware.
|
||||
*/
|
||||
class DocumentTypeExclusionRestriction implements QueryRestrictionInterface
|
||||
{
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
protected $doktypes;
|
||||
|
||||
/**
|
||||
* @param int[]|int $doktype
|
||||
*/
|
||||
public function __construct($doktype)
|
||||
{
|
||||
if (is_array($doktype)) {
|
||||
$this->doktypes = $doktype;
|
||||
} else {
|
||||
$this->doktypes = [$doktype];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method to build expressions for given tables
|
||||
*
|
||||
* @param array $queriedTables Array of tables, where array key is table alias and value is a table name
|
||||
* @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
|
||||
* @return CompositeExpression The result of query builder expression(s)
|
||||
*/
|
||||
public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
|
||||
{
|
||||
$constraints = [];
|
||||
|
||||
foreach ($queriedTables as $tableAlias => $tableName) {
|
||||
if ($tableName !== 'pages') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$constraints[] = $expressionBuilder->notIn($tableAlias . '.doktype', $this->doktypes);
|
||||
}
|
||||
|
||||
return $expressionBuilder->and(...$constraints);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?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\Query\Restriction;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
|
||||
/**
|
||||
* Restriction to filter records with an end time set that has passed
|
||||
*/
|
||||
class EndTimeRestriction implements QueryRestrictionInterface
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $accessTimeStamp;
|
||||
|
||||
public function __construct(?int $accessTimeStamp = null)
|
||||
{
|
||||
$this->accessTimeStamp = $accessTimeStamp ?: ($GLOBALS['SIM_ACCESS_TIME'] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method to build expressions for given tables
|
||||
* Evaluates the ctrl/enablecolumns/endtime flag of the table and adds the according restriction if set
|
||||
*
|
||||
* @param array $queriedTables Array of tables, where array key is table alias and value is a table name
|
||||
* @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
|
||||
* @return CompositeExpression The result of query builder expression(s)
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
|
||||
{
|
||||
$tcaSchemaFactory = $expressionBuilder->getContainer()->get(TcaSchemaFactory::class);
|
||||
$constraints = [];
|
||||
foreach ($queriedTables as $tableAlias => $tableName) {
|
||||
if (!$tcaSchemaFactory->has($tableName)) {
|
||||
continue;
|
||||
}
|
||||
$schema = $tcaSchemaFactory->get($tableName);
|
||||
if ($schema->hasCapability(TcaSchemaCapability::RestrictionEndTime)) {
|
||||
if (empty($this->accessTimeStamp)) {
|
||||
throw new \RuntimeException(
|
||||
'accessTimeStamp needs to be set to an integer value, but is empty! Maybe $GLOBALS[\'SIM_ACCESS_TIME\'] has been overridden somewhere?',
|
||||
1462821084
|
||||
);
|
||||
}
|
||||
$fieldName = $tableAlias . '.' . $schema->getCapability(TcaSchemaCapability::RestrictionEndTime)->getFieldName();
|
||||
$constraints[] = $expressionBuilder->or(
|
||||
$expressionBuilder->eq($fieldName, 0),
|
||||
$expressionBuilder->gt($fieldName, (int)$this->accessTimeStamp)
|
||||
);
|
||||
}
|
||||
}
|
||||
return $expressionBuilder->and(...$constraints);
|
||||
}
|
||||
}
|
||||
@@ -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\Query\Restriction;
|
||||
|
||||
/**
|
||||
* Can be added to QueryRestrictionInterface implementations.
|
||||
* Restrictions implementing this interface will not be removed when removeAll()
|
||||
* is called on the container and isEnforced() returns true.
|
||||
* It can be removed though, when explicitly calling removeByType()
|
||||
*/
|
||||
interface EnforceableQueryRestrictionInterface
|
||||
{
|
||||
/**
|
||||
* When returning false, restriction will be removed when removeAll()
|
||||
* is called on the container
|
||||
*/
|
||||
public function isEnforced(): bool;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?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\Query\Restriction;
|
||||
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Context\UserAspect;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Restriction to filter records, which are limited to the given user groups
|
||||
*/
|
||||
class FrontendGroupRestriction implements QueryRestrictionInterface
|
||||
{
|
||||
protected array $frontendGroupIds;
|
||||
|
||||
/**
|
||||
* @param array|null $frontendGroupIds Normalized array with user groups of currently logged in user (typically found in the Frontend Context)
|
||||
*/
|
||||
public function __construct(?array $frontendGroupIds = null)
|
||||
{
|
||||
if ($frontendGroupIds !== null) {
|
||||
$this->frontendGroupIds = $frontendGroupIds;
|
||||
} else {
|
||||
/** @var UserAspect $frontendUserAspect */
|
||||
$frontendUserAspect = GeneralUtility::makeInstance(Context::class)->getAspect('frontend.user');
|
||||
$this->frontendGroupIds = $frontendUserAspect->getGroupIds();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method to build expressions for given tables
|
||||
* Evaluates the ctrl/enablecolumns/fe_group flag of the table and adds the according restriction if set
|
||||
*
|
||||
* @param array $queriedTables Array of tables, where array key is table alias and value is a table name
|
||||
* @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
|
||||
* @return CompositeExpression The result of query builder expression(s)
|
||||
*/
|
||||
public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
|
||||
{
|
||||
$tcaSchemaFactory = $expressionBuilder->getContainer()->get(TcaSchemaFactory::class);
|
||||
$constraints = [];
|
||||
foreach ($queriedTables as $tableAlias => $tableName) {
|
||||
if (!$tcaSchemaFactory->has($tableName)) {
|
||||
continue;
|
||||
}
|
||||
$schema = $tcaSchemaFactory->get($tableName);
|
||||
if ($schema->hasCapability(TcaSchemaCapability::RestrictionUserGroup)) {
|
||||
$fieldName = $tableAlias . '.' . $schema->getCapability(TcaSchemaCapability::RestrictionUserGroup)->getFieldName();
|
||||
// Allow records where no group access has been configured (field values NULL, 0 or empty string)
|
||||
$tableConstraints = [
|
||||
$expressionBuilder->isNull($fieldName),
|
||||
$expressionBuilder->eq($fieldName, $expressionBuilder->literal('')),
|
||||
$expressionBuilder->eq($fieldName, $expressionBuilder->literal('0')),
|
||||
];
|
||||
foreach ($this->frontendGroupIds as $frontendGroupId) {
|
||||
$tableConstraints[] = $expressionBuilder->inSet(
|
||||
$fieldName,
|
||||
$expressionBuilder->literal((string)($frontendGroupId ?? ''))
|
||||
);
|
||||
}
|
||||
$constraints[] = $expressionBuilder->or(...$tableConstraints);
|
||||
}
|
||||
}
|
||||
return $expressionBuilder->and(...$constraints);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?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\Query\Restriction;
|
||||
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* A collection of restrictions to be used in frontend context.
|
||||
* This is a replacement for PageRepository::enableFields()
|
||||
*/
|
||||
class FrontendRestrictionContainer extends AbstractRestrictionContainer
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $defaultRestrictionTypes = [
|
||||
DeletedRestriction::class,
|
||||
WorkspaceRestriction::class,
|
||||
HiddenRestriction::class,
|
||||
StartTimeRestriction::class,
|
||||
EndTimeRestriction::class,
|
||||
FrontendGroupRestriction::class,
|
||||
];
|
||||
|
||||
protected Context $context;
|
||||
|
||||
/**
|
||||
* FrontendRestrictionContainer constructor.
|
||||
* Initializes the default restrictions for frontend requests
|
||||
*/
|
||||
public function __construct(?Context $context = null)
|
||||
{
|
||||
$this->context = $context ?? GeneralUtility::makeInstance(Context::class);
|
||||
foreach ($this->defaultRestrictionTypes as $restrictionType) {
|
||||
$this->add($this->createRestriction($restrictionType));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method to build expressions for given tables
|
||||
* Iterates over all registered restrictions and removes the hidden restriction if preview is requested
|
||||
*
|
||||
* @param array $queriedTables Array of tables, where array key is table alias and value is a table name
|
||||
* @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
|
||||
* @return CompositeExpression The result of query builder expression(s)
|
||||
*/
|
||||
public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
|
||||
{
|
||||
$constraints = [];
|
||||
foreach ($this->restrictions as $restriction) {
|
||||
foreach ($queriedTables as $tableAlias => $tableName) {
|
||||
$disableRestriction = false;
|
||||
if ($restriction instanceof HiddenRestriction || $restriction instanceof StartTimeRestriction || $restriction instanceof EndTimeRestriction) {
|
||||
$visibilityAspect = $this->context->getAspect('visibility');
|
||||
if ($restriction instanceof HiddenRestriction) {
|
||||
// If display of hidden records is requested, we must disable the hidden restriction.
|
||||
if ($tableName === 'pages') {
|
||||
$disableRestriction = $visibilityAspect->includeHiddenPages();
|
||||
} else {
|
||||
$disableRestriction = $visibilityAspect->includeHiddenContent();
|
||||
}
|
||||
}
|
||||
if ($restriction instanceof StartTimeRestriction || $restriction instanceof EndTimeRestriction) {
|
||||
$disableRestriction = $visibilityAspect->includeScheduledRecords();
|
||||
}
|
||||
}
|
||||
if (!$disableRestriction) {
|
||||
$constraints[] = $restriction->buildExpression([$tableAlias => $tableName], $expressionBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $expressionBuilder->and(...$constraints);
|
||||
}
|
||||
|
||||
protected function createRestriction($restrictionClass): QueryRestrictionInterface
|
||||
{
|
||||
if ($restrictionClass === WorkspaceRestriction::class) {
|
||||
return GeneralUtility::makeInstance($restrictionClass, (int)$this->context->getPropertyFromAspect('workspace', 'id', 0));
|
||||
}
|
||||
if ($restrictionClass === FrontendGroupRestriction::class) {
|
||||
return GeneralUtility::makeInstance($restrictionClass, $this->context->getPropertyFromAspect('frontend.user', 'groupIds', []));
|
||||
}
|
||||
|
||||
return parent::createRestriction($restrictionClass);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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\Query\Restriction;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
|
||||
/**
|
||||
* Restriction to filter records that have been marked as hidden
|
||||
*/
|
||||
class HiddenRestriction implements QueryRestrictionInterface
|
||||
{
|
||||
/**
|
||||
* Main method to build expressions for given tables
|
||||
* Evaluates the ctrl/enablecolumns/disabled flag of the table and adds the according restriction if set
|
||||
*
|
||||
* @param array $queriedTables Array of tables, where array key is table alias and value is a table name
|
||||
* @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
|
||||
* @return CompositeExpression The result of query builder expression(s)
|
||||
*/
|
||||
public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
|
||||
{
|
||||
$tcaSchemaFactory = $expressionBuilder->getContainer()->get(TcaSchemaFactory::class);
|
||||
$constraints = [];
|
||||
foreach ($queriedTables as $tableAlias => $tableName) {
|
||||
if (!$tcaSchemaFactory->has($tableName)) {
|
||||
continue;
|
||||
}
|
||||
$schema = $tcaSchemaFactory->get($tableName);
|
||||
if ($schema->hasCapability(TcaSchemaCapability::RestrictionDisabledField)) {
|
||||
$constraints[] = $expressionBuilder->eq(
|
||||
$tableAlias . '.' . $schema->getCapability(TcaSchemaCapability::RestrictionDisabledField)->getFieldName(),
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
return $expressionBuilder->and(...$constraints);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?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\Query\Restriction;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
|
||||
/**
|
||||
* Restriction container that applies added restrictions only to the given table aliases.
|
||||
* Enforced restrictions are treated equally to all other restrictions.
|
||||
*/
|
||||
class LimitToTablesRestrictionContainer implements QueryRestrictionContainerInterface
|
||||
{
|
||||
/**
|
||||
* @var QueryRestrictionInterface[]
|
||||
*/
|
||||
private $restrictions = [];
|
||||
|
||||
/**
|
||||
* @var QueryRestrictionContainerInterface[]
|
||||
*/
|
||||
private $restrictionContainer = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $applicableTableAliases;
|
||||
|
||||
public function removeAll(): QueryRestrictionContainerInterface
|
||||
{
|
||||
$this->applicableTableAliases = $this->restrictions = $this->restrictionContainer = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeByType(string $restrictionType): QueryRestrictionContainerInterface
|
||||
{
|
||||
unset($this->applicableTableAliases[$restrictionType], $this->restrictions[$restrictionType]);
|
||||
foreach ($this->restrictionContainer as $restrictionContainer) {
|
||||
$restrictionContainer->removeByType($restrictionType);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function add(QueryRestrictionInterface $restriction): QueryRestrictionContainerInterface
|
||||
{
|
||||
$this->restrictions[get_class($restriction)] = $restriction;
|
||||
if ($restriction instanceof QueryRestrictionContainerInterface) {
|
||||
$this->restrictionContainer[get_class($restriction)] = $restriction;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the restriction, but also remembers which table aliases it should be applied to
|
||||
*
|
||||
* @param array $tableAliases flat array of table aliases, not table names
|
||||
*/
|
||||
public function addForTables(QueryRestrictionInterface $restriction, array $tableAliases): QueryRestrictionContainerInterface
|
||||
{
|
||||
$this->applicableTableAliases[get_class($restriction)] = $tableAliases;
|
||||
return $this->add($restriction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method to build expressions for given tables, but respecting configured filters.
|
||||
*
|
||||
* @param array $queriedTables Array of tables, where array key is table alias and value is a table name
|
||||
* @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
|
||||
* @return CompositeExpression The result of query builder expression(s)
|
||||
*/
|
||||
public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
|
||||
{
|
||||
$constraints = [];
|
||||
foreach ($this->restrictions as $name => $restriction) {
|
||||
$constraints[] = $restriction->buildExpression(
|
||||
$this->filterApplicableTableAliases($queriedTables, $name),
|
||||
$expressionBuilder
|
||||
);
|
||||
}
|
||||
return $expressionBuilder->and(...$constraints);
|
||||
}
|
||||
|
||||
private function filterApplicableTableAliases(array $queriedTables, string $name): array
|
||||
{
|
||||
if (!isset($this->applicableTableAliases[$name])) {
|
||||
return $queriedTables;
|
||||
}
|
||||
|
||||
$filteredTables = [];
|
||||
foreach ($this->applicableTableAliases[$name] as $tableAlias) {
|
||||
if (isset($queriedTables[$tableAlias])) {
|
||||
$filteredTables[$tableAlias] = $queriedTables[$tableAlias];
|
||||
}
|
||||
}
|
||||
|
||||
return $filteredTables;
|
||||
}
|
||||
}
|
||||
@@ -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\Query\Restriction;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
|
||||
/**
|
||||
* Restriction to filter records that only reside on a specific list of page IDs
|
||||
*/
|
||||
final readonly class PageIdListRestriction implements QueryRestrictionInterface
|
||||
{
|
||||
public function __construct(
|
||||
private array $tableNames,
|
||||
private array $pageIds
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Main method to build expressions for given tables
|
||||
*
|
||||
* @param array $queriedTables Array of tables, where array key is table alias and value is a table name
|
||||
* @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
|
||||
* @return CompositeExpression The result of query builder expression(s)
|
||||
*/
|
||||
public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
|
||||
{
|
||||
$constraints = [];
|
||||
foreach ($queriedTables as $tableAlias => $tableName) {
|
||||
if (empty($this->tableNames) || in_array($tableAlias, $this->tableNames, true)) {
|
||||
$constraints[] = $expressionBuilder->in(
|
||||
$tableAlias . '.pid',
|
||||
array_map(intval(...), $this->pageIds)
|
||||
);
|
||||
}
|
||||
}
|
||||
return $expressionBuilder->and(...$constraints);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?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\Query\Restriction;
|
||||
|
||||
use TYPO3\CMS\Core\Context\UserAspect;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
|
||||
/**
|
||||
* Restriction to make queries respect backend user rights for pages.
|
||||
*
|
||||
* Adds a WHERE-clause for the pages-table where user permissions according to input argument, $permissions, is validated.
|
||||
* $permissions is the "mask" used to select - see Permission Bitset.
|
||||
* E.g. if $perms is 1 then you'll get all pages that a user can actually see!
|
||||
* 2^0 = show (1)
|
||||
* 2^1 = edit (2)
|
||||
* 2^2 = delete (4)
|
||||
* 2^3 = new (8)
|
||||
* If the user is 'admin' no validation is used.
|
||||
*
|
||||
* If the user is not set at all (->user is not an array), then "AND 1=0" is returned (will cause no selection results at all)
|
||||
*
|
||||
* The 95% use of this function is "->getPagePermsClause(1)" which will
|
||||
* return WHERE clauses for *selecting* pages in backend listings - in other words this will check read permissions.
|
||||
*/
|
||||
class PagePermissionRestriction implements QueryRestrictionInterface
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $permissions;
|
||||
|
||||
/**
|
||||
* @var UserAspect
|
||||
*/
|
||||
protected $userAspect;
|
||||
|
||||
public function __construct(UserAspect $userAspect, int $permissions)
|
||||
{
|
||||
$this->permissions = $permissions;
|
||||
$this->userAspect = $userAspect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method to build expressions for given tables
|
||||
*
|
||||
* @param array $queriedTables Array of tables, where array key is table alias and value is a table name
|
||||
* @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
|
||||
* @return CompositeExpression The result of query builder expression(s)
|
||||
*/
|
||||
public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
|
||||
{
|
||||
$constraints = [];
|
||||
|
||||
foreach ($queriedTables as $tableAlias => $tableName) {
|
||||
if ($tableName !== 'pages') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$constraint = $this->buildUserConstraints($expressionBuilder, $tableAlias);
|
||||
if ($constraint) {
|
||||
$constraints[] = $expressionBuilder->and($constraint);
|
||||
}
|
||||
}
|
||||
|
||||
return $expressionBuilder->and(...$constraints);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|CompositeExpression|null
|
||||
* @throws \TYPO3\CMS\Core\Context\Exception\AspectPropertyNotFoundException
|
||||
*/
|
||||
protected function buildUserConstraints(ExpressionBuilder $expressionBuilder, string $tableAlias)
|
||||
{
|
||||
if (!$this->userAspect->isLoggedIn()) {
|
||||
return $expressionBuilder->comparison(1, ExpressionBuilder::EQ, 0);
|
||||
}
|
||||
if ($this->userAspect->isAdmin()) {
|
||||
return null;
|
||||
}
|
||||
// User permissions
|
||||
$constraint = $expressionBuilder->or(
|
||||
$expressionBuilder->comparison(
|
||||
$expressionBuilder->bitAnd($tableAlias . '.perms_everybody', $this->permissions),
|
||||
ExpressionBuilder::EQ,
|
||||
$this->permissions
|
||||
),
|
||||
$expressionBuilder->and(
|
||||
$expressionBuilder->eq($tableAlias . '.perms_userid', $this->userAspect->get('id')),
|
||||
$expressionBuilder->comparison(
|
||||
$expressionBuilder->bitAnd($tableAlias . '.perms_user', $this->permissions),
|
||||
ExpressionBuilder::EQ,
|
||||
$this->permissions
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// User groups (if any are set)
|
||||
$groupIds = array_map(intval(...), $this->userAspect->getGroupIds());
|
||||
if (!empty($groupIds)) {
|
||||
$constraint = $constraint->with(
|
||||
$expressionBuilder->and(
|
||||
$expressionBuilder->in(
|
||||
$tableAlias . '.perms_groupid',
|
||||
$groupIds
|
||||
),
|
||||
$expressionBuilder->comparison(
|
||||
$expressionBuilder->bitAnd($tableAlias . '.perms_group', $this->permissions),
|
||||
ExpressionBuilder::EQ,
|
||||
$this->permissions
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
return $constraint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?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\Query\Restriction;
|
||||
|
||||
/**
|
||||
* Interface that all restriction collections must implement.
|
||||
* It is an extension of the QueryRestrictionInterface, so collections can be treated as single restriction
|
||||
*/
|
||||
interface QueryRestrictionContainerInterface extends QueryRestrictionInterface
|
||||
{
|
||||
/**
|
||||
* Removes all restrictions stored within this container
|
||||
*
|
||||
* @return QueryRestrictionContainerInterface
|
||||
*/
|
||||
public function removeAll();
|
||||
|
||||
/**
|
||||
* Remove restriction of a given type
|
||||
*
|
||||
* @param string $restrictionType Class name of the restriction to be removed
|
||||
* @return QueryRestrictionContainerInterface
|
||||
*/
|
||||
public function removeByType(string $restrictionType);
|
||||
|
||||
/**
|
||||
* Add a new restriction instance to this collection
|
||||
*
|
||||
* @return QueryRestrictionContainerInterface
|
||||
*/
|
||||
public function add(QueryRestrictionInterface $restriction);
|
||||
}
|
||||
@@ -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\Query\Restriction;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
|
||||
/**
|
||||
* The main restriction interface. All restrictions (including the collections) must implement this.
|
||||
*/
|
||||
interface QueryRestrictionInterface
|
||||
{
|
||||
/**
|
||||
* Main method to build expressions for given tables
|
||||
*
|
||||
* @param array $queriedTables Array of tables, where array key is table alias and value is a table name
|
||||
* @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
|
||||
* @return CompositeExpression The result of query builder expression(s)
|
||||
*/
|
||||
public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?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\Query\Restriction;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
|
||||
/**
|
||||
* Restriction to filter records which are not stored on the root page.
|
||||
*/
|
||||
class RootLevelRestriction implements QueryRestrictionInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $tableNames;
|
||||
|
||||
public function __construct(array $tableNames = [])
|
||||
{
|
||||
$this->tableNames = $tableNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method to build expressions for given tables
|
||||
*
|
||||
* @param array $queriedTables Array of tables, where array key is table alias and value is a table name
|
||||
* @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
|
||||
* @return CompositeExpression The result of query builder expression(s)
|
||||
*/
|
||||
public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
|
||||
{
|
||||
$constraints = [];
|
||||
foreach ($queriedTables as $tableAlias => $tableName) {
|
||||
if (empty($this->tableNames) || in_array($tableAlias, $this->tableNames, true)) {
|
||||
$constraints[] = $expressionBuilder->eq(
|
||||
$tableAlias . '.pid',
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
return $expressionBuilder->and(...$constraints);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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\Query\Restriction;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
|
||||
/**
|
||||
* Restriction to filter records, that should not be shown until the start time has been reached
|
||||
*/
|
||||
class StartTimeRestriction implements QueryRestrictionInterface
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $accessTimeStamp;
|
||||
|
||||
public function __construct(?int $accessTimeStamp = null)
|
||||
{
|
||||
$this->accessTimeStamp = $accessTimeStamp ?: ($GLOBALS['SIM_ACCESS_TIME'] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method to build expressions for given tables
|
||||
* Evaluates the ctrl/enablecolumns/starttime flag of the table and adds the according restriction if set
|
||||
*
|
||||
* @param array $queriedTables Array of tables, where array key is table alias and value is a table name
|
||||
* @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
|
||||
* @return CompositeExpression The result of query builder expression(s)
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
|
||||
{
|
||||
$tcaSchemaFactory = $expressionBuilder->getContainer()->get(TcaSchemaFactory::class);
|
||||
$constraints = [];
|
||||
foreach ($queriedTables as $tableAlias => $tableName) {
|
||||
if (!$tcaSchemaFactory->has($tableName)) {
|
||||
continue;
|
||||
}
|
||||
$schema = $tcaSchemaFactory->get($tableName);
|
||||
if ($schema->hasCapability(TcaSchemaCapability::RestrictionStartTime)) {
|
||||
if (empty($this->accessTimeStamp)) {
|
||||
throw new \RuntimeException(
|
||||
'accessTimeStamp needs to be set to an integer value, but is empty! Maybe $GLOBALS[\'SIM_ACCESS_TIME\'] has been overridden somewhere?',
|
||||
1462820645
|
||||
);
|
||||
}
|
||||
$constraints[] = $expressionBuilder->lte(
|
||||
$tableAlias . '.' . $schema->getCapability(TcaSchemaCapability::RestrictionStartTime)->getFieldName(),
|
||||
(int)$this->accessTimeStamp
|
||||
);
|
||||
}
|
||||
}
|
||||
return $expressionBuilder->and(...$constraints);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?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\Query\Restriction;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Versioning\VersionState;
|
||||
|
||||
/**
|
||||
* Restriction to make queries workspace-aware. This restriction ALWAYS fetches the live version
|
||||
* plus in current workspace the workspace records.
|
||||
* It does not care about the state, as this should be done by overlays.
|
||||
*
|
||||
* As workspaces cannot be fully overlaid within ONE query, this query does the following:
|
||||
* - In live context, only fetch published records
|
||||
* - In a workspace, fetch all LIVE records and all workspace records which do not have "1" (= all new placeholders get fetched as well)
|
||||
*
|
||||
* This means, that all records which are fetched need to run through either
|
||||
* - BackendUtility::getRecordWSOL() (when having one or a few records)
|
||||
* - PageRepository->versionOL()
|
||||
* - PlainDataResolver (when having lots of records)
|
||||
*/
|
||||
class WorkspaceRestriction implements QueryRestrictionInterface
|
||||
{
|
||||
protected int $workspaceId;
|
||||
|
||||
/**
|
||||
* Used to also query records within a workspace, which is useful for DB queries
|
||||
* that check for a specific field (e.g. "slug") which might have changed within a workspace.
|
||||
* Please note that some duplicates might be shown and the "reduce" logic needs to be
|
||||
* added after querying. Setting this flag might also be a problem when using the DB query
|
||||
* with limit and offset settings.
|
||||
*/
|
||||
protected bool $includeAllVersionedRecords;
|
||||
|
||||
public function __construct(int $workspaceId = 0, bool $includeAllVersionedRecords = false)
|
||||
{
|
||||
$this->workspaceId = $workspaceId;
|
||||
$this->includeAllVersionedRecords = $includeAllVersionedRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method to build expressions for given tables
|
||||
*
|
||||
* @param array $queriedTables Array of tables, where array key is table alias and value is a table name
|
||||
* @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
|
||||
* @return CompositeExpression The result of query builder expression(s)
|
||||
*/
|
||||
public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
|
||||
{
|
||||
$tcaSchemaFactory = $expressionBuilder->getContainer()->get(TcaSchemaFactory::class);
|
||||
$constraints = [];
|
||||
foreach ($queriedTables as $tableAlias => $tableName) {
|
||||
if (!$tcaSchemaFactory->has($tableName) || !$tcaSchemaFactory->get($tableName)->isWorkspaceAware()) {
|
||||
continue;
|
||||
}
|
||||
if ($this->workspaceId === 0) {
|
||||
// Only include records from live workspace
|
||||
$workspaceIdExpression = $expressionBuilder->eq($tableAlias . '.t3ver_wsid', 0);
|
||||
} else {
|
||||
// Include live records PLUS records from the given workspace
|
||||
$workspaceIdExpression = $expressionBuilder->in(
|
||||
$tableAlias . '.t3ver_wsid',
|
||||
[0, $this->workspaceId]
|
||||
);
|
||||
}
|
||||
// Always filter out versioned records that have an "offline" record
|
||||
// But include moved records AND newly created records (t3ver_oid=0)
|
||||
if ($this->includeAllVersionedRecords === false) {
|
||||
$constraints[] = $expressionBuilder->and(
|
||||
$workspaceIdExpression,
|
||||
$expressionBuilder->or(
|
||||
$expressionBuilder->eq(
|
||||
$tableAlias . '.t3ver_oid',
|
||||
0
|
||||
),
|
||||
$expressionBuilder->eq(
|
||||
$tableAlias . '.t3ver_state',
|
||||
VersionState::MOVE_POINTER->value
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Include live records plus records from the given workspace
|
||||
// but never include versioned records marked as deleted
|
||||
$constraints[] = $expressionBuilder->and(
|
||||
$workspaceIdExpression,
|
||||
$expressionBuilder->neq(
|
||||
$tableAlias . '.t3ver_state',
|
||||
VersionState::DELETE_PLACEHOLDER->value
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return $expressionBuilder->and(...$constraints);
|
||||
}
|
||||
}
|
||||
@@ -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\Query;
|
||||
|
||||
class UnsupportedPreparedStatementParameterTypeException extends \InvalidArgumentException
|
||||
{
|
||||
public static function new(string $parameterType): UnsupportedPreparedStatementParameterTypeException
|
||||
{
|
||||
return new self(
|
||||
sprintf(
|
||||
"Parameter type '%s' is not allowed for prepared statement retrieved from QueryBuilder. Use executeQuery() or executeStatement() directly.",
|
||||
$parameterType
|
||||
),
|
||||
1639245170
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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\Query;
|
||||
|
||||
final readonly class With implements \Stringable
|
||||
{
|
||||
/**
|
||||
* @param string[] $fields
|
||||
* @param string[] $dependencies
|
||||
*/
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private array $fields,
|
||||
private array $dependencies,
|
||||
private string|ConcreteQueryBuilder|QueryBuilder $expression,
|
||||
private bool $recursive,
|
||||
) {}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function isRecursive(): bool
|
||||
{
|
||||
return $this->recursive;
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
public function getSQL(): string
|
||||
{
|
||||
$fields = '';
|
||||
|
||||
if ($this->fields !== []) {
|
||||
$fields = sprintf(' (%s)', implode(', ', $this->fields));
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'%s%s AS (%s)',
|
||||
$this->getName(),
|
||||
$fields,
|
||||
$this->expression,
|
||||
);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getSQL();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?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\Query;
|
||||
|
||||
use TYPO3\CMS\Core\Service\DependencyOrderingService;
|
||||
|
||||
/**
|
||||
* @internal experimental and not part of public core API.
|
||||
*/
|
||||
final class WithCollection implements \Stringable
|
||||
{
|
||||
/**
|
||||
* @var With[]
|
||||
*/
|
||||
private array $with = [];
|
||||
private bool $recursive = false;
|
||||
|
||||
public function set(With ...$with): WithCollection
|
||||
{
|
||||
return $this->reset()->add(...array_values($with));
|
||||
}
|
||||
|
||||
public function add(With ...$with): WithCollection
|
||||
{
|
||||
foreach ($with as $singleWith) {
|
||||
$this->with[] = $singleWith;
|
||||
if ($singleWith->isRecursive()) {
|
||||
$this->recursive = true;
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function reset(): WithCollection
|
||||
{
|
||||
$this->recursive = false;
|
||||
$this->with = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this->with === [];
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
if ($this->with === []) {
|
||||
return '';
|
||||
}
|
||||
$parts = [];
|
||||
foreach ($this->getSortedParts() as $part) {
|
||||
$parts[] = (string)$part;
|
||||
}
|
||||
return sprintf(
|
||||
'%s %s',
|
||||
($this->recursive ? 'WITH RECURSIVE' : 'WITH'),
|
||||
implode(', ', $parts)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return With[]
|
||||
*/
|
||||
private function getSortedParts(): array
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($this->prepareParts() as $part) {
|
||||
$parts[] = $part['instance'];
|
||||
}
|
||||
return $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{instance: With, before: string[], after: string[]}>
|
||||
*/
|
||||
private function prepareParts(): array
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($this->with as $part) {
|
||||
$parts[$part->getName()] = [
|
||||
'instance' => $part,
|
||||
'before' => [],
|
||||
'after' => $part->getDependencies(),
|
||||
];
|
||||
}
|
||||
return (new DependencyOrderingService())->orderByDependencies($parts);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user