[BUGFIX] Process large uid lists in chunks in RelationHandler 93/53193/19
authorSascha Egerer <sascha@sascha-egerer.de>
Mon, 12 Jun 2017 14:57:33 +0000 (16:57 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Tue, 23 Jan 2018 20:00:36 +0000 (21:00 +0100)
Uid lists in the relation handler can be very big. To avoid exceeding
query limits like maximum number of placeholder per query or the max
allowed statement length these large lists are split into chunks of
safe length before processing.

Change-Id: I176acb85feb91c6162a77016c1918cf5a992625c
Resolves: #81555
References: #80875
Releases: master, 8.7
Reviewed-on: https://review.typo3.org/53193
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Manuel Selbach <manuel_selbach@yahoo.de>
Tested-by: Manuel Selbach <manuel_selbach@yahoo.de>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/core/Classes/Database/Platform/PlatformInformation.php [new file with mode: 0644]
typo3/sysext/core/Classes/Database/RelationHandler.php
typo3/sysext/core/Classes/Database/Schema/ConnectionMigrator.php
typo3/sysext/core/Tests/Unit/Database/Platform/PlatformInformationTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Database/Schema/ConnectionMigratorTest.php

diff --git a/typo3/sysext/core/Classes/Database/Platform/PlatformInformation.php b/typo3/sysext/core/Classes/Database/Platform/PlatformInformation.php
new file mode 100644 (file)
index 0000000..dfe645a
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+declare(strict_types=1);
+
+namespace TYPO3\CMS\Core\Database\Platform;
+
+/*
+ * 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!
+ */
+
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+use Doctrine\DBAL\Platforms\MySqlPlatform;
+use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
+use Doctrine\DBAL\Platforms\SqlitePlatform;
+use Doctrine\DBAL\Platforms\SQLServerPlatform;
+
+/**
+ * Helper to handle platform specific details
+ *
+ * @internal
+ */
+class PlatformInformation
+{
+    /**
+     * @var array
+     */
+    protected static $identifierLimits = [
+        'mysql' => 63,
+        'postgresql' => 63,
+        'sqlserver' => 128,
+        'sqlite' => 1024, // arbitrary limit, SQLite is only limited by the total statement length
+    ];
+
+    /**
+     * @var array
+     */
+    protected static $bindParameterLimits = [
+        'mysql' => 65535,
+        'postgresql' => 34464,
+        'sqlserver' => 2100,
+        'sqlite' => 999,
+    ];
+
+    /**
+     * Return information about the maximum supported length for a SQL identifier.
+     *
+     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
+     * @return int
+     * @internal
+     */
+    public static function getMaxIdentifierLength(AbstractPlatform $platform): int
+    {
+        $platformName = static::getPlatformIdentifier($platform);
+
+        return self::$identifierLimits[$platformName];
+    }
+
+    /**
+     * Return information about the maximum number of bound parameters supported on this platform
+     *
+     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
+     * @return int
+     * @internal
+     */
+    public static function getMaxBindParameters(AbstractPlatform $platform): int
+    {
+        $platformName = static::getPlatformIdentifier($platform);
+
+        return self::$bindParameterLimits[$platformName];
+    }
+
+    /**
+     * Return the platform shortname to use as a lookup key
+     *
+     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
+     * @return string
+     * @throws \RuntimeException
+     * @internal
+     */
+    protected static function getPlatformIdentifier(AbstractPlatform $platform): string
+    {
+        if ($platform instanceof MySqlPlatform) {
+            return 'mysql';
+        }
+        if ($platform instanceof PostgreSqlPlatform) {
+            return 'postgresql';
+        }
+        if ($platform instanceof SQLServerPlatform) {
+            return 'sqlserver';
+        }
+        if ($platform instanceof SqlitePlatform) {
+            return 'sqlite';
+        }
+        throw new \RuntimeException(
+                'Unsupported Databaseplatform "' . get_class($platform) . '" detected in PlatformInformation',
+                1500958070
+            );
+    }
+}
index 435212c..84d79db 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Core\Database;
  */
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\DataHandling\PlainDataResolver;
@@ -478,22 +479,23 @@ class RelationHandler
         } elseif (count($this->tableArray) === 1) {
             reset($this->tableArray);
             $table = key($this->tableArray);
-            $uidList = implode(',', current($this->tableArray));
-            if ($uidList) {
+            $connection = $this->getConnectionForTableName($table);
+            $maxBindParameters = PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
+
+            foreach (array_chunk(current($this->tableArray), $maxBindParameters - 10, true) as $chunk) {
+                if (empty($chunk)) {
+                    continue;
+                }
                 $this->itemArray = [];
                 $this->tableArray = [];
-                $queryBuilder = $this->getConnectionForTableName($table)
-                    ->createQueryBuilder();
+                $queryBuilder = $connection->createQueryBuilder();
                 $queryBuilder->getRestrictions()->removeAll();
                 $queryBuilder->select('uid')
                     ->from($table)
                     ->where(
                         $queryBuilder->expr()->in(
                             'uid',
-                            $queryBuilder->createNamedParameter(
-                                GeneralUtility::intExplode(',', $uidList),
-                                Connection::PARAM_INT_ARRAY
-                            )
+                            $queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
                         )
                     );
                 foreach (QueryHelper::parseOrderBy((string)$sortby) as $orderPair) {
@@ -1206,10 +1208,12 @@ class RelationHandler
     public function getFromDB()
     {
         // Traverses the tables listed:
-        foreach ($this->tableArray as $table => $val) {
-            if (is_array($val)) {
-                $itemList = implode(',', $val);
-                if ($itemList) {
+        foreach ($this->tableArray as $table => $ids) {
+            if (is_array($ids) && !empty($ids)) {
+                $connection = $this->getConnectionForTableName($table);
+                $maxBindParameters = PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
+
+                foreach (array_chunk($ids, $maxBindParameters - 10, true) as $chunk) {
                     if ($this->fetchAllFields) {
                         $fields = '*';
                     } else {
@@ -1227,20 +1231,18 @@ class RelationHandler
                             $fields .= ',' . $GLOBALS['TCA'][$table]['ctrl']['thumbnail'];
                         }
                     }
-                    $queryBuilder = $this->getConnectionForTableName($table)
-                        ->createQueryBuilder();
+                    $queryBuilder = $connection->createQueryBuilder();
                     $queryBuilder->getRestrictions()->removeAll();
                     $queryBuilder->select(...(GeneralUtility::trimExplode(',', $fields, true)))
                         ->from($table)
                         ->where($queryBuilder->expr()->in(
                             'uid',
-                            $queryBuilder->createNamedParameter(
-                                GeneralUtility::intExplode(',', $itemList),
-                                Connection::PARAM_INT_ARRAY
-                            )
+                            $queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
                         ));
                     if ($this->additionalWhere[$table]) {
-                        $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($this->additionalWhere[$table]));
+                        $queryBuilder->andWhere(
+                            QueryHelper::stripLogicalOperatorPrefix($this->additionalWhere[$table])
+                        );
                     }
                     $statement = $queryBuilder->execute();
                     while ($row = $statement->fetch()) {
@@ -1449,32 +1451,32 @@ class RelationHandler
     protected function purgeVersionedIds($tableName, array $ids)
     {
         $ids = array_combine($ids, $ids);
+        $connection = $this->getConnectionForTableName($tableName);
+        $maxBindParameters = PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
 
-        $queryBuilder = $this->getConnectionForTableName($tableName)
-            ->createQueryBuilder();
-        $queryBuilder->getRestrictions()->removeAll();
-        $versions = $queryBuilder->select('uid', 't3ver_oid', 't3ver_state')
-            ->from($tableName)
-            ->where(
-                $queryBuilder->expr()->eq(
-                    'pid',
-                    $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
-                ),
-                $queryBuilder->expr()->in(
-                    't3ver_oid',
-                    $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY)
-                ),
-                $queryBuilder->expr()->neq(
-                    't3ver_wsid',
-                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+        foreach (array_chunk($ids, $maxBindParameters - 10, true) as $chunk) {
+            $queryBuilder = $connection->createQueryBuilder();
+            $queryBuilder->getRestrictions()->removeAll();
+            $result = $queryBuilder->select('uid', 't3ver_oid', 't3ver_state')
+                ->from($tableName)
+                ->where(
+                    $queryBuilder->expr()->eq(
+                        'pid',
+                        $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
+                    ),
+                    $queryBuilder->expr()->in(
+                        't3ver_oid',
+                        $queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
+                    ),
+                    $queryBuilder->expr()->neq(
+                        't3ver_wsid',
+                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                    )
                 )
-            )
-            ->orderBy('t3ver_state', 'DESC')
-            ->execute()
-            ->fetchAll();
+                ->orderBy('t3ver_state', 'DESC')
+                ->execute();
 
-        if (!empty($versions)) {
-            foreach ($versions as $version) {
+            while ($version = $result->fetch()) {
                 $versionId = $version['uid'];
                 if (isset($ids[$versionId])) {
                     unset($ids[$versionId]);
@@ -1495,32 +1497,32 @@ class RelationHandler
     protected function purgeLiveVersionedIds($tableName, array $ids)
     {
         $ids = array_combine($ids, $ids);
+        $connection = $this->getConnectionForTableName($tableName);
+        $maxBindParameters = PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
 
-        $queryBuilder = $this->getConnectionForTableName($tableName)
-            ->createQueryBuilder();
-        $queryBuilder->getRestrictions()->removeAll();
-        $versions = $queryBuilder->select('uid', 't3ver_oid', 't3ver_state')
-            ->from($tableName)
-            ->where(
-                $queryBuilder->expr()->eq(
-                    'pid',
-                    $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
-                ),
-                $queryBuilder->expr()->in(
-                    't3ver_oid',
-                    $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY)
-                ),
-                $queryBuilder->expr()->neq(
-                    't3ver_wsid',
-                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+        foreach (array_chunk($ids, $maxBindParameters - 10, true) as $chunk) {
+            $queryBuilder = $connection->createQueryBuilder();
+            $queryBuilder->getRestrictions()->removeAll();
+            $result = $queryBuilder->select('uid', 't3ver_oid', 't3ver_state')
+                ->from($tableName)
+                ->where(
+                    $queryBuilder->expr()->eq(
+                        'pid',
+                        $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
+                    ),
+                    $queryBuilder->expr()->in(
+                        't3ver_oid',
+                        $queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
+                    ),
+                    $queryBuilder->expr()->neq(
+                        't3ver_wsid',
+                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                    )
                 )
-            )
-            ->orderBy('t3ver_state', 'DESC')
-            ->execute()
-            ->fetchAll();
+                ->orderBy('t3ver_state', 'DESC')
+                ->execute();
 
-        if (!empty($versions)) {
-            foreach ($versions as $version) {
+            while ($version = $result->fetch()) {
                 $versionId = $version['uid'];
                 $liveId = $version['t3ver_oid'];
                 if (isset($ids[$liveId]) && isset($ids[$versionId])) {
@@ -1542,41 +1544,41 @@ class RelationHandler
     protected function purgeDeletePlaceholder($tableName, array $ids)
     {
         $ids = array_combine($ids, $ids);
+        $connection = $this->getConnectionForTableName($tableName);
+        $maxBindParameters = PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
 
-        $queryBuilder = $this->getConnectionForTableName($tableName)
-            ->createQueryBuilder();
-        $queryBuilder->getRestrictions()->removeAll();
-        $versions = $queryBuilder->select('uid', 't3ver_oid', 't3ver_state')
-            ->from($tableName)
-            ->where(
-                $queryBuilder->expr()->eq(
-                    'pid',
-                    $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
-                ),
-                $queryBuilder->expr()->in(
-                    't3ver_oid',
-                    $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY)
-                ),
-                $queryBuilder->expr()->neq(
-                    't3ver_wsid',
-                    $queryBuilder->createNamedParameter(
-                        $this->getWorkspaceId(),
-                        \PDO::PARAM_INT
-                    )
-                ),
-                $queryBuilder->expr()->eq(
-                    't3ver_state',
-                    $queryBuilder->createNamedParameter(
-                        (string)VersionState::cast(VersionState::DELETE_PLACEHOLDER),
-                        \PDO::PARAM_INT
+        foreach (array_chunk($ids, $maxBindParameters - 10, true) as $chunk) {
+            $queryBuilder = $connection->createQueryBuilder();
+            $queryBuilder->getRestrictions()->removeAll();
+            $result = $queryBuilder->select('uid', 't3ver_oid', 't3ver_state')
+                ->from($tableName)
+                ->where(
+                    $queryBuilder->expr()->eq(
+                        'pid',
+                        $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
+                    ),
+                    $queryBuilder->expr()->in(
+                        't3ver_oid',
+                        $queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
+                    ),
+                    $queryBuilder->expr()->neq(
+                        't3ver_wsid',
+                        $queryBuilder->createNamedParameter(
+                            $this->getWorkspaceId(),
+                            \PDO::PARAM_INT
+                        )
+                    ),
+                    $queryBuilder->expr()->eq(
+                        't3ver_state',
+                        $queryBuilder->createNamedParameter(
+                            (string)VersionState::cast(VersionState::DELETE_PLACEHOLDER),
+                            \PDO::PARAM_INT
+                        )
                     )
                 )
-            )
-            ->execute()
-            ->fetchAll();
+                ->execute();
 
-        if (!empty($versions)) {
-            foreach ($versions as $version) {
+            while ($version = $result->fetch()) {
                 $liveId = $version['t3ver_oid'];
                 if (isset($ids[$liveId])) {
                     unset($ids[$liveId]);
index 8201f59..f99881d 100644 (file)
@@ -28,6 +28,7 @@ use Doctrine\DBAL\Schema\SchemaDiff;
 use Doctrine\DBAL\Schema\Table;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -43,48 +44,6 @@ class ConnectionMigrator
     protected $deletedPrefix = 'zzz_deleted_';
 
     /**
-     * @var array
-     */
-    protected $tableAndFieldMaxNameLengthsPerDbPlatform = [
-        'default' => [
-            'tables' => 30,
-            'columns' => 30
-        ],
-        'mysql' => [
-            'tables' => 64,
-            'columns' => 64
-        ],
-        'drizzle_pdo_mysql' => 'mysql',
-        'mysqli' => 'mysql',
-        'pdo_mysql' => 'mysql',
-        'pdo_sqlite' => 'mysql',
-        'postgresql' => [
-            'tables' => 63,
-            'columns' => 63
-        ],
-        'sqlserver' => [
-            'tables' => 128,
-            'columns' => 128
-        ],
-        'pdo_sqlsrv' => 'sqlserver',
-        'sqlsrv' => 'sqlserver',
-        'ibm' => [
-            'tables' => 30,
-            'columns' => 30
-        ],
-        'ibm_db2' => 'ibm',
-        'pdo_ibm' => 'ibm',
-        'oci8' => [
-            'tables' => 30,
-            'columns' => 30
-        ],
-        'sqlanywhere' => [
-            'tables' => 128,
-            'columns' => 128
-        ]
-    ];
-
-    /**
      * @var Connection
      */
     protected $connection;
@@ -940,7 +899,11 @@ class ConnectionMigrator
             );
 
             $tableDiff->newName = $this->connection->getDatabasePlatform()->quoteIdentifier(
-                substr($this->deletedPrefix . $removedTable->getName(), 0, $this->getMaxTableNameLength())
+                substr(
+                    $this->deletedPrefix . $removedTable->getName(),
+                    0,
+                    PlatformInformation::getMaxIdentifierLength($this->connection->getDatabasePlatform())
+                )
             );
             $schemaDiff->changedTables[$index] = $tableDiff;
             unset($schemaDiff->removedTables[$index]);
@@ -974,7 +937,7 @@ class ConnectionMigrator
                 $renamedColumnName = substr(
                     $this->deletedPrefix . $removedColumn->getName(),
                     0,
-                    $this->getMaxColumnNameLength()
+                    PlatformInformation::getMaxIdentifierLength($this->connection->getDatabasePlatform())
                 );
                 $renamedColumn = new Column(
                     $this->connection->quoteIdentifier($renamedColumnName),
@@ -1044,56 +1007,6 @@ class ConnectionMigrator
     }
 
     /**
-     * Retrieve the database platform-specific limitations on column and schema name sizes as
-     * defined in the tableAndFieldMaxNameLengthsPerDbPlatform property.
-     *
-     * @param string $databasePlatform
-     * @return array
-     */
-    protected function getTableAndFieldNameMaxLengths(string $databasePlatform = '')
-    {
-        if ($databasePlatform === '') {
-            $databasePlatform = $this->connection->getDatabasePlatform()->getName();
-        }
-        $databasePlatform = strtolower($databasePlatform);
-
-        if (isset($this->tableAndFieldMaxNameLengthsPerDbPlatform[$databasePlatform])) {
-            $nameLengthRestrictions = $this->tableAndFieldMaxNameLengthsPerDbPlatform[$databasePlatform];
-        } else {
-            $nameLengthRestrictions = $this->tableAndFieldMaxNameLengthsPerDbPlatform['default'];
-        }
-
-        if (is_string($nameLengthRestrictions)) {
-            return $this->getTableAndFieldNameMaxLengths($nameLengthRestrictions);
-        }
-        return $nameLengthRestrictions;
-    }
-
-    /**
-     * Get the maximum table name length possible for the given DB platform.
-     *
-     * @param string $databasePlatform
-     * @return string
-     */
-    protected function getMaxTableNameLength(string $databasePlatform = '')
-    {
-        $nameLengthRestrictions = $this->getTableAndFieldNameMaxLengths($databasePlatform);
-        return $nameLengthRestrictions['tables'];
-    }
-
-    /**
-     * Get the maximum column name length possible for the given DB platform.
-     *
-     * @param string $databasePlatform
-     * @return string
-     */
-    protected function getMaxColumnNameLength(string $databasePlatform = '')
-    {
-        $nameLengthRestrictions = $this->getTableAndFieldNameMaxLengths($databasePlatform);
-        return $nameLengthRestrictions['columns'];
-    }
-
-    /**
      * Return the amount of records in the given table.
      *
      * @param string $tableName
diff --git a/typo3/sysext/core/Tests/Unit/Database/Platform/PlatformInformationTest.php b/typo3/sysext/core/Tests/Unit/Database/Platform/PlatformInformationTest.php
new file mode 100644 (file)
index 0000000..b4089f5
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+declare(strict_types=1);
+
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Platform;
+
+/*
+ * 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!
+ */
+
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+use Doctrine\DBAL\Platforms\MySqlPlatform;
+use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
+use Doctrine\DBAL\Platforms\SqlitePlatform;
+use Doctrine\DBAL\Platforms\SQLServerPlatform;
+use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Test case
+ */
+class PlatformInformationTest extends UnitTestCase
+{
+    /**
+     * Test cases for stripping of leading logical operators in where constraints.
+     *
+     * @return array
+     */
+    public function platformDataProvider(): array
+    {
+        return [
+            'mysql' => [$this->prophesize(MySqlPlatform::class)->reveal()],
+            'postgresql' => [$this->prophesize(PostgreSqlPlatform::class)->reveal()],
+            'sqlserver' => [$this->prophesize(SQLServerPlatform::class)->reveal()],
+            'sqlite' => [$this->prophesize(SqlitePlatform::class)->reveal()],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider platformDataProvider
+     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
+     */
+    public function maxBindParameters(AbstractPlatform $platform)
+    {
+        $this->assertGreaterThanOrEqual(1, PlatformInformation::getMaxBindParameters($platform));
+    }
+
+    /**
+     * @test
+     */
+    public function maxBindParametersWithUnknownPlatform()
+    {
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionCode(1500958070);
+        $platform = $this->prophesize(AbstractPlatform::class)->reveal();
+        $this->assertGreaterThanOrEqual(1, PlatformInformation::getMaxBindParameters($platform));
+    }
+
+    /**
+     * @test
+     * @dataProvider platformDataProvider
+     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
+     */
+    public function maxIdentifierLength(AbstractPlatform $platform)
+    {
+        $this->assertGreaterThanOrEqual(1, PlatformInformation::getMaxIdentifierLength($platform));
+    }
+
+    /**
+     * @test
+     */
+    public function maxIdentifierLengthWithUnknownPlatform()
+    {
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionCode(1500958070);
+        $platform = $this->prophesize(AbstractPlatform::class)->reveal();
+        $this->assertGreaterThanOrEqual(1, PlatformInformation::getMaxIdentifierLength($platform));
+    }
+}
index d63c3ff..1d649dd 100644 (file)
@@ -16,10 +16,14 @@ namespace TYPO3\CMS\Core\Tests\Unit\Database;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Doctrine\DBAL\Platforms\MySqlPlatform;
 use Doctrine\DBAL\Schema\Column;
 use Doctrine\DBAL\Schema\SchemaDiff;
 use Doctrine\DBAL\Schema\Table;
+use Doctrine\DBAL\Types\Type;
+use Prophecy\Argument;
 use TYPO3\CMS\Core\Database\Connection;
+use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
 use TYPO3\CMS\Core\Database\Schema\ConnectionMigrator;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -29,64 +33,39 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 class ConnectionMigratorTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
 {
     /**
-     * @var array
+     * @var \Doctrine\DBAL\Platforms\AbstractPlatform|\Prophecy\Prophecy\ObjectProphecy
      */
-    protected $tableAndFieldMaxNameLengthsPerDbPlatform = [
-        'default' => [
-            'tables' => 10,
-            'columns' => 10,
-        ],
-        'dbplatform_type1' => [
-            'tables' => 15,
-            'columns' => 15,
-        ],
-        'dbplatform_type2' => 'dbplatform_type1'
-    ];
+    protected $platform;
 
     /**
-     * Utility method to quickly create a 'ConnectionMigratorMock' instance for
-     * a specific database platform.
-     *
-     * @param string $databasePlatformName
-     * @return \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface
+     * @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface
      */
-    private function getConnectionMigratorMock($databasePlatformName = 'default')
-    {
-        $platformMock = $this->getMockBuilder(\Doctrine\DBAL\Platforms\AbstractPlatform::class)->disableOriginalConstructor()->getMock();
-        $platformMock->method('getName')->willReturn($databasePlatformName);
-        $platformMock->method('quoteIdentifier')->willReturnArgument(0);
-
-        $connectionMock = $this->getMockBuilder(Connection::class)->setMethods(['getDatabasePlatform', 'quoteIdentifier'])->disableOriginalConstructor()->getMock();
-        $connectionMock->method('getDatabasePlatform')->willReturn($platformMock);
-        $connectionMock->method('quoteIdentifier')->willReturnArgument(0);
-
-        $connectionMigrator = $this->getAccessibleMock(ConnectionMigrator::class, null, [], '', false);
-        $connectionMigrator->_set('connection', $connectionMock);
-        $connectionMigrator->_set('tableAndFieldMaxNameLengthsPerDbPlatform', $this->tableAndFieldMaxNameLengthsPerDbPlatform);
+    protected $subject;
 
-        return $connectionMigrator;
-    }
+    /**
+     * @var int
+     */
+    protected $maxIdentifierLength = -1;
 
     /**
-     * Utility method to create a table mock instance with a much too long
-     * table name in any case.
-     *
-     * @return \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface
+     * Set up the test subject
      */
-    private function getTableMock()
+    protected function setUp()
     {
-        $ridiculouslyLongTableName = 'table_name_that_is_ridiculously_long_' . bin2hex(random_bytes(100));
-        $tableMock = $this->getAccessibleMock(Table::class, ['getQuotedName', 'getName'], [$ridiculouslyLongTableName]);
-        $tableMock->expects($this->any())
-            ->method('getQuotedName')
-            ->withAnyParameters()
-            ->willReturn($ridiculouslyLongTableName);
-        $tableMock->expects($this->any())
-            ->method('getName')
-            ->withAnyParameters()
-            ->willReturn($ridiculouslyLongTableName);
-
-        return $tableMock;
+        parent::setUp();
+
+        $platformMock = $this->prophesize(MySqlPlatform::class);
+        $platformMock->quoteIdentifier(Argument::any())->willReturnArgument(0);
+        $this->platform = $platformMock->reveal();
+
+        $connectionMock = $this->prophesize(Connection::class);
+        $connectionMock->getDatabasePlatform()->willReturn($this->platform);
+        $connectionMock->quoteIdentifier(Argument::any())->willReturnArgument(0);
+
+        $this->maxIdentifierLength = PlatformInformation::getMaxIdentifierLength($this->platform);
+
+        $this->subject = $this->getAccessibleMock(ConnectionMigrator::class, null, [], '', false);
+        $this->subject->_set('connection', $connectionMock->reveal());
     }
 
     /**
@@ -94,15 +73,12 @@ class ConnectionMigratorTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestC
      */
     public function tableNamesStickToTheMaximumCharactersWhenPrefixedForRemoval()
     {
-        $connectionMigrator = $this->getConnectionMigratorMock('dbplatform_type1');
-        $tableMock = $this->getTableMock();
-
-        $originalSchemaDiff = GeneralUtility::makeInstance(SchemaDiff::class, null, null, [$tableMock]);
-        $renamedSchemaDiff = $connectionMigrator->_call('migrateUnprefixedRemovedTablesToRenames', $originalSchemaDiff);
+        $originalSchemaDiff = GeneralUtility::makeInstance(SchemaDiff::class, null, null, [$this->getTable()]);
+        $renamedSchemaDiff = $this->subject->_call('migrateUnprefixedRemovedTablesToRenames', $originalSchemaDiff);
 
         $this->assertStringStartsWith('zzz_deleted_', $renamedSchemaDiff->changedTables[0]->newName);
-        $this->assertLessThanOrEqual(
-            $this->tableAndFieldMaxNameLengthsPerDbPlatform['dbplatform_type1']['tables'],
+        $this->assertEquals(
+            $this->maxIdentifierLength,
             strlen($renamedSchemaDiff->changedTables[0]->newName)
         );
     }
@@ -110,62 +86,53 @@ class ConnectionMigratorTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestC
     /**
      * @test
      */
-    public function databasePlatformNamingRestrictionGetsResolved()
+    public function columnNamesStickToTheMaximumCharactersWhenPrefixedForRemoval()
     {
-        $connectionMigrator = $this->getConnectionMigratorMock('dbplatform_type2');
-        $tableMock = $this->getTableMock();
+        $originalSchemaDiff = GeneralUtility::makeInstance(SchemaDiff::class, null, null, [$this->getTable()]);
+        $originalSchemaDiff->changedTables[0]->removedColumns[] = $this->getColumn();
+        $renamedSchemaDiff = $this->subject->_call('migrateUnprefixedRemovedFieldsToRenames', $originalSchemaDiff);
 
-        $originalSchemaDiff = GeneralUtility::makeInstance(SchemaDiff::class, null, null, [$tableMock]);
-        $renamedSchemaDiff = $connectionMigrator->_call('migrateUnprefixedRemovedTablesToRenames', $originalSchemaDiff);
-
-        $this->assertLessThanOrEqual(
-            $this->tableAndFieldMaxNameLengthsPerDbPlatform['dbplatform_type1']['tables'],
-            strlen($renamedSchemaDiff->changedTables[0]->newName)
+        $this->assertStringStartsWith(
+            'zzz_deleted_',
+            $renamedSchemaDiff->changedTables[0]->changedColumns[0]->column->getName()
+        );
+        $this->assertEquals(
+            $this->maxIdentifierLength,
+            strlen($renamedSchemaDiff->changedTables[0]->changedColumns[0]->column->getName())
         );
     }
 
     /**
-     * @test
+     * Utility method to create a table instance with name that exceeds the identifier limits.
+     *
+     * @return Table
      */
-    public function whenPassingAnUnknownDatabasePlatformTheDefaultTableAndFieldNameRestrictionsApply()
+    protected function getTable(): Table
     {
-        $connectionMigrator = $this->getConnectionMigratorMock('dummydbplatformthatdoesntexist');
-        $tableMock = $this->getTableMock();
-
-        $originalSchemaDiff = GeneralUtility::makeInstance(SchemaDiff::class, null, null, [$tableMock]);
-        $renamedSchemaDiff = $connectionMigrator->_call('migrateUnprefixedRemovedTablesToRenames', $originalSchemaDiff);
-
-        $this->assertLessThanOrEqual(
-            $this->tableAndFieldMaxNameLengthsPerDbPlatform['default']['tables'],
-            strlen($renamedSchemaDiff->changedTables[0]->newName)
+        $tableName = 'table_name_that_is_ridiculously_long_' . bin2hex(random_bytes(100));
+        $table = GeneralUtility::makeInstance(
+            Table::class,
+            $tableName
         );
+
+        return $table;
     }
 
     /**
-     * @test
+     * Utility method to create a column instance with name that exceeds the identifier limits.
+     *
+     *
+     * @return Column
      */
-    public function columnNamesStickToTheMaximumCharactersWhenPrefixedForRemoval()
+    protected function getColumn(): Column
     {
-        $connectionMigrator = $this->getConnectionMigratorMock('dbplatform_type1');
-        $tableMock = $this->getAccessibleMock(Table::class, ['getQuotedName'], ['test_table']);
-        $columnMock = $this->getAccessibleMock(
+        $columnName = 'column_name_that_is_ridiculously_long_' . bin2hex(random_bytes(100));
+        $column = GeneralUtility::makeInstance(
             Column::class,
-            ['getQuotedName'],
-            [
-                'a_column_name_waaaaay_over_20_characters',
-                $this->getAccessibleMock(\Doctrine\DBAL\Types\StringType::class, [], [], '', false)
-            ]
+            $columnName,
+            Type::getType('string')
         );
-        $columnMock->expects($this->any())->method('getQuotedName')->withAnyParameters()->will($this->returnValue('a_column_name_waaaaay_over_20_characters'));
 
-        $originalSchemaDiff = GeneralUtility::makeInstance(SchemaDiff::class, null, null, [$tableMock]);
-        $originalSchemaDiff->changedTables[0]->removedColumns[] = $columnMock;
-        $renamedSchemaDiff = $connectionMigrator->_call('migrateUnprefixedRemovedFieldsToRenames', $originalSchemaDiff);
-
-        $this->assertStringStartsWith('zzz_deleted_', $renamedSchemaDiff->changedTables[0]->changedColumns[0]->column->getName());
-        $this->assertLessThanOrEqual(
-            $this->tableAndFieldMaxNameLengthsPerDbPlatform['dbplatform_type1']['columns'],
-            strlen($renamedSchemaDiff->changedTables[0]->changedColumns[0]->column->getName())
-        );
+        return $column;
     }
 }