From 4d056965fddd3768d669b7bdcd0a039a3c1ea1ba Mon Sep 17 00:00:00 2001
From: Oliver Hader <oliver@typo3.org>
Date: Wed, 19 Apr 2017 21:24:38 +0200
Subject: [PATCH] [BUGFIX] Page tree request in a workspaces times out

Tryign to determine workspace versions for a particular database table
results in a very long process execution time and possible timeout due
to the following reasons:

* in general a bug was introduced during the Doctrine DBAL migration
  which leads to misbehaviors in resolving versions for pages
* the SQL query implicitly creates an INNER JOIN with a huge result
  set that takes a long query time
* invalid types leading to possible flaws when using prepared statements

The SQL query has been split into using sub-queries now.

Change-Id: I4e4f69815bd73f0562f7ffbd6d411b417be7a18a
Resolves: #80898
Releases: master, 8.7
Reviewed-on: https://review.typo3.org/52506
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Xavier Perseguers <xavier@typo3.org>
Tested-by: Xavier Perseguers <xavier@typo3.org>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
---
 .../Classes/Service/WorkspaceService.php      | 85 ++++++++++++-------
 1 file changed, 56 insertions(+), 29 deletions(-)

diff --git a/typo3/sysext/workspaces/Classes/Service/WorkspaceService.php b/typo3/sysext/workspaces/Classes/Service/WorkspaceService.php
index 4f8cbf6eccc2..279bb5467416 100644
--- a/typo3/sysext/workspaces/Classes/Service/WorkspaceService.php
+++ b/typo3/sysext/workspaces/Classes/Service/WorkspaceService.php
@@ -18,6 +18,7 @@ use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
@@ -1021,43 +1022,55 @@ class WorkspaceService implements SingletonInterface
         if (!isset($this->pagesWithVersionsInTable[$workspaceId][$tableName])) {
             $this->pagesWithVersionsInTable[$workspaceId][$tableName] = [];
 
-            // Consider records that are moved to a different page
-            $movePointer = new VersionState(VersionState::MOVE_POINTER);
-
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
             $queryBuilder->getRestrictions()
                 ->removeAll()
                 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
 
+            $movePointerParameter = $queryBuilder->createNamedParameter(
+                VersionState::MOVE_POINTER,
+               \PDO::PARAM_INT
+            );
+            $workspaceIdParameter = $queryBuilder->createNamedParameter(
+                $workspaceId,
+                \PDO::PARAM_INT
+            );
+            $pageIdParameter = $queryBuilder->createNamedParameter(
+                -1,
+                \PDO::PARAM_INT
+            );
+            // create sub-queries, parameters are available for main query
+            $versionQueryBuilder = $this->createQueryBuilderForTable($tableName)
+                ->select('A.t3ver_oid')
+                ->from($tableName, 'A')
+                ->where(
+                    $queryBuilder->expr()->eq('A.pid', $pageIdParameter),
+                    $queryBuilder->expr()->eq('A.t3ver_wsid', $workspaceIdParameter),
+                    $queryBuilder->expr()->neq('A.t3ver_state', $movePointerParameter)
+                );
+            $movePointerQueryBuilder = $this->createQueryBuilderForTable($tableName)
+                ->select('A.t3ver_oid')
+                ->from($tableName, 'A')
+                ->where(
+                    $queryBuilder->expr()->eq('A.pid', $pageIdParameter),
+                    $queryBuilder->expr()->eq('A.t3ver_wsid', $workspaceIdParameter),
+                    $queryBuilder->expr()->eq('A.t3ver_state', $movePointerParameter)
+                );
+            $subQuery = '%s IN (%s)';
+            // execute main query
             $result = $queryBuilder
                 ->select('B.pid AS pageId')
-                ->from($tableName, 'A')
                 ->from($tableName, 'B')
-                ->where(
-                    $queryBuilder->expr()->eq(
-                        'A.pid',
-                        $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
+                ->orWhere(
+                    sprintf(
+                        $subQuery,
+                        $queryBuilder->quoteIdentifier('B.uid'),
+                        $versionQueryBuilder->getSQL()
                     ),
-                    $queryBuilder->expr()->eq(
-                        'A.t3ver_wsid',
-                        $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
-                    ),
-                    $queryBuilder->expr()->orX(
-                        $queryBuilder->expr()->andX(
-                            $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid')),
-                            $queryBuilder->expr()->neq(
-                                'A.t3ver_state',
-                                $queryBuilder->createNamedParameter($movePointer, \PDO::PARAM_STR)
-                            )
-
-                        ),
-                        $queryBuilder->expr()->andX(
-                            $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.t3ver_move_id')),
-                            $queryBuilder->expr()->eq(
-                                'A.t3ver_state',
-                                $queryBuilder->createNamedParameter($movePointer, \PDO::PARAM_STR)
-                            )
-                        )
+                    sprintf(
+                        $subQuery,
+                        $queryBuilder->quoteIdentifier('B.t3ver_move_id'),
+                        $movePointerQueryBuilder->getSQL()
                     )
                 )
                 ->groupBy('pageId')
@@ -1065,7 +1078,7 @@ class WorkspaceService implements SingletonInterface
 
             $pageIds = [];
             while ($row = $result->fetch()) {
-                $pageIds[$row['uid']] = $row;
+                $pageIds[$row['pageId']] = true;
             }
 
             $this->pagesWithVersionsInTable[$workspaceId][$tableName] = $pageIds;
@@ -1086,6 +1099,20 @@ class WorkspaceService implements SingletonInterface
         return $this->pagesWithVersionsInTable[$workspaceId][$tableName];
     }
 
+    /**
+     * @param string $tableName
+     * @return QueryBuilder
+     */
+    protected function createQueryBuilderForTable(string $tableName)
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable($tableName);
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+        return $queryBuilder;
+    }
+
     /**
      * @return \TYPO3\CMS\Extbase\Object\ObjectManager
      */
-- 
GitLab