diff --git a/typo3/sysext/backend/Classes/Backend/Avatar/DefaultAvatarProvider.php b/typo3/sysext/backend/Classes/Backend/Avatar/DefaultAvatarProvider.php
index a7551b7e6bb5397ce9f1d204e9ed32be731a824b..b5ff3d145519de8f40c54af9fe0a80f9b4d39083 100644
--- a/typo3/sysext/backend/Classes/Backend/Avatar/DefaultAvatarProvider.php
+++ b/typo3/sysext/backend/Classes/Backend/Avatar/DefaultAvatarProvider.php
@@ -13,9 +13,7 @@ namespace TYPO3\CMS\Backend\Backend\Avatar;
  *
  * The TYPO3 project - inspiring people to share!
  */
-use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
 use TYPO3\CMS\Core\Resource\ProcessedFile;
 use TYPO3\CMS\Core\Resource\ResourceFactory;
@@ -67,15 +65,16 @@ class DefaultAvatarProvider implements AvatarProviderInterface
      */
     protected function getAvatarFileUid($beUserId)
     {
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference');
         $file = $queryBuilder
             ->select('uid_local')
             ->from('sys_file_reference')
-            ->where($queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('be_users')))
-            ->andWhere($queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('avatar')))
-            ->andWhere($queryBuilder->expr()->eq('table_local', $queryBuilder->createNamedParameter('sys_file')))
-            ->andWhere($queryBuilder->expr()->eq('uid_foreign', (int)$beUserId))
+            ->where(
+                $queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('be_users')),
+                $queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('avatar')),
+                $queryBuilder->expr()->eq('table_local', $queryBuilder->createNamedParameter('sys_file')),
+                $queryBuilder->expr()->eq('uid_foreign', (int)$beUserId)
+            )
             ->execute()
             ->fetchColumn();
 
diff --git a/typo3/sysext/backend/Classes/Clipboard/Clipboard.php b/typo3/sysext/backend/Classes/Clipboard/Clipboard.php
index 30848c57597a5cdfa4197e39ac7607e17868952f..8a907c046e780d2436e53fa81b5bb5ef2d47a4dd 100644
--- a/typo3/sysext/backend/Classes/Clipboard/Clipboard.php
+++ b/typo3/sysext/backend/Classes/Clipboard/Clipboard.php
@@ -16,7 +16,7 @@ namespace TYPO3\CMS\Backend\Clipboard;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider;
@@ -496,23 +496,24 @@ class Clipboard
     {
         $lines = array();
         $tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
-        if ($table != 'pages' && BackendUtility::isTableLocalizable($table) && !$tcaCtrl['transOrigPointerTable']) {
+        if ($table !== 'pages' && BackendUtility::isTableLocalizable($table) && !$tcaCtrl['transOrigPointerTable']) {
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
-            $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+
             $queryBuilder
                 ->select('*')
                 ->from($table)
-                ->where($queryBuilder->expr()->eq($tcaCtrl['transOrigPointerField'], (int)$parentRec['uid']))
-                ->andWhere($queryBuilder->expr()->neq($tcaCtrl['languageField'], 0));
-            if (isset($tcaCtrl['delete']) && $tcaCtrl['delete']) {
-                $queryBuilder->andWhere($queryBuilder->expr()->eq($tcaCtrl['delete'], 0));
-            }
+                ->where(
+                    $queryBuilder->expr()->eq($tcaCtrl['transOrigPointerField'], (int)$parentRec['uid']),
+                    $queryBuilder->expr()->neq($tcaCtrl['languageField'], 0)
+                );
+
             if (isset($tcaCtrl['versioningWS']) && $tcaCtrl['versioningWS']) {
-                $queryBuilder->andWhere($queryBuilder->expr()->eq('t3ver_wsid', $parentRec['t3ver_wsid']));
+                $queryBuilder->andWhere($queryBuilder->expr()->eq('t3ver_wsid', (int)$parentRec['t3ver_wsid']));
             }
-            $rows = $queryBuilder
-                ->execute()
-                ->fetchAll();
+            $rows = $queryBuilder->execute()->fetchAll();
             if (is_array($rows)) {
                 $modeData = '';
                 if ($pad == 'normal') {
@@ -1120,5 +1121,4 @@ class Clipboard
     {
         return $GLOBALS['BE_USER'];
     }
-
 }
diff --git a/typo3/sysext/backend/Classes/Configuration/TranslationConfigurationProvider.php b/typo3/sysext/backend/Classes/Configuration/TranslationConfigurationProvider.php
index ece68f594cc9b37614080bf038302f95e3d75974..c621fe68ecdf5cac2e8e47ac68eba4747b7df2c5 100644
--- a/typo3/sysext/backend/Classes/Configuration/TranslationConfigurationProvider.php
+++ b/typo3/sysext/backend/Classes/Configuration/TranslationConfigurationProvider.php
@@ -16,7 +16,8 @@ namespace TYPO3\CMS\Backend\Configuration;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
+use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Lang\LanguageService;
@@ -123,18 +124,21 @@ class TranslationConfigurationProvider
             $selFieldList = 'uid,' . $GLOBALS['TCA'][$translationTable]['ctrl']['languageField'];
         }
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($translationTable);
-        $queryBuilder->getQueryContext()->setContext(QueryContextType::BACKEND_NO_VERSIONING_PLACEHOLDERS);
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
         $queryBuilder
             ->select(...GeneralUtility::trimExplode(',', $selFieldList))
             ->from($translationTable)
-            ->where($queryBuilder->expr()->eq($GLOBALS['TCA'][$translationTable]['ctrl']['transOrigPointerField'], (int)$uid))
-            ->andWhere($queryBuilder->expr()->eq('pid', (int)($table === 'pages' ? $row['uid'] : $row['pid'])));
+            ->where(
+                $queryBuilder->expr()->eq($GLOBALS['TCA'][$translationTable]['ctrl']['transOrigPointerField'], (int)$uid),
+                $queryBuilder->expr()->eq('pid', (int)($table === 'pages' ? $row['uid'] : $row['pid']))
+            );
         if (!$languageUid) {
-            $queryBuilder
-                ->andWhere($queryBuilder->expr()->gt($GLOBALS['TCA'][$translationTable]['ctrl']['languageField'], 0));
+            $queryBuilder->andWhere($queryBuilder->expr()->gt($GLOBALS['TCA'][$translationTable]['ctrl']['languageField'], 0));
         } else {
-            $queryBuilder
-                ->andWhere($queryBuilder->expr()->eq($GLOBALS['TCA'][$translationTable]['ctrl']['languageField'], (int)$languageUid));
+            $queryBuilder->andWhere($queryBuilder->expr()->eq($GLOBALS['TCA'][$translationTable]['ctrl']['languageField'], (int)$languageUid));
         }
         $translationRecords = $queryBuilder
             ->execute()
diff --git a/typo3/sysext/backend/Classes/History/RecordHistory.php b/typo3/sysext/backend/Classes/History/RecordHistory.php
index f54c76386aa6a932cf122aa1bc702886207d3e97..28ced0ac67bf46d12dd372f8022529725730cf33 100644
--- a/typo3/sysext/backend/Classes/History/RecordHistory.php
+++ b/typo3/sysext/backend/Classes/History/RecordHistory.php
@@ -16,8 +16,6 @@ namespace TYPO3\CMS\Backend\History;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
@@ -171,12 +169,11 @@ class RecordHistory
     public function toggleHighlight($uid)
     {
         $uid = (int)$uid;
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_history');
         $row = $queryBuilder
             ->select('snapshot')
             ->from('sys_history')
-            ->where($queryBuilder->expr()->eq('uid', $uid))
+            ->where($queryBuilder->expr()->eq('uid', (int)$uid))
             ->execute()
             ->fetch();
 
@@ -185,7 +182,7 @@ class RecordHistory
             $queryBuilder
                 ->update('sys_history')
                 ->set('snapshot', (int)!$row['snapshot'])
-                ->where($queryBuilder->expr()->eq('uid', $uid))
+                ->where($queryBuilder->expr()->eq('uid', (int)$uid))
                 ->execute();
         }
     }
@@ -661,17 +658,15 @@ class RecordHistory
         if ($elParts[0] == 'pages' && $this->showSubElements && $this->hasPageAccess('pages', $elParts[1])) {
             foreach ($GLOBALS['TCA'] as $tablename => $value) {
                 // check if there are records on the page
-                /** @var QueryBuilder $queryBuilder */
                 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tablename);
-                $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+                $queryBuilder->getRestrictions()->removeAll();
 
                 $rows = $queryBuilder
                     ->select('uid')
                     ->from($tablename)
                     ->where($queryBuilder->expr()->eq('pid', (int)$elParts[1]))
-                    ->execute()
-                    ->fetchAll();
-                if (empty($rows)) {
+                    ->execute();
+                if ($rows->rowCount() === 0) {
                     continue;
                 }
                 foreach ($rows as $row) {
@@ -709,7 +704,6 @@ class RecordHistory
         // If table is found in $GLOBALS['TCA']:
         $uid = $this->resolveElement($table, $uid);
         // Selecting the $this->maxSteps most recent states:
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_history');
         $rows = $queryBuilder
             ->select('sys_history.*', 'sys_log.userid', 'sys_log.log_data')
@@ -719,10 +713,10 @@ class RecordHistory
                 $queryBuilder->expr()->eq(
                     'sys_history.sys_log_uid',
                     $queryBuilder->quoteIdentifier('sys_log.uid')
-                )
+                ),
+                $queryBuilder->expr()->eq('sys_history.tablename', $queryBuilder->createNamedParameter($table)),
+                $queryBuilder->expr()->eq('sys_history.recuid', (int)$uid)
             )
-            ->andWhere($queryBuilder->expr()->eq('sys_history.tablename', $queryBuilder->createNamedParameter($table)))
-            ->andWhere($queryBuilder->expr()->eq('sys_history.recuid', (int)$uid))
             ->orderBy('sys_log.uid', 'DESC')
             ->setMaxResults((int)$this->maxSteps)
             ->execute()
@@ -759,28 +753,28 @@ class RecordHistory
         // SELECT INSERTS/DELETES
         if ($this->showInsertDelete) {
             // Select most recent inserts and deletes // WITHOUT snapshots
-            /** @var QueryBuilder $queryBuilder */
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log');
-            $rows = $queryBuilder
+            $result = $queryBuilder
                 ->select('uid', 'userid', 'action', 'tstamp', 'log_data')
                 ->from('sys_log')
-                ->where($queryBuilder->expr()->eq('type', 1))
-                ->andWhere($queryBuilder->expr()->orX(
-                    $queryBuilder->expr()->eq('action', 1),
-                    $queryBuilder->expr()->eq('action', 3)
-                ))
-                ->andWhere($queryBuilder->expr()->eq('tablename', $queryBuilder->createNamedParameter($table)))
-                ->andWhere($queryBuilder->expr()->eq('recuid', (int)$uid))
+                ->where(
+                    $queryBuilder->expr()->eq('type', 1),
+                    $queryBuilder->expr()->orX(
+                        $queryBuilder->expr()->eq('action', 1),
+                        $queryBuilder->expr()->eq('action', 3)
+                    ),
+                    $queryBuilder->expr()->eq('tablename', $queryBuilder->createNamedParameter($table)),
+                    $queryBuilder->expr()->eq('recuid', (int)$uid)
+                )
                 ->orderBy('uid', 'DESC')
                 ->setMaxResults((int)$this->maxSteps)
-                ->execute()
-                ->fetchAll();
+                ->execute();
 
             // If none are found, nothing more to do
-            if (empty($rows)) {
+            if ($result->rowCount() === 0) {
                 return $changeLog;
             }
-            foreach ($rows as $row) {
+            foreach ($result as $row) {
                 if ($this->lastSyslogId && $row['uid'] < $this->lastSyslogId) {
                     continue;
                 }
@@ -914,7 +908,6 @@ class RecordHistory
         if (empty($shUid)) {
             return;
         }
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_history');
         $record = $queryBuilder
             ->select('*')
diff --git a/typo3/sysext/backend/Classes/Tree/View/AbstractTreeView.php b/typo3/sysext/backend/Classes/Tree/View/AbstractTreeView.php
index 200e640d6f5f3eaba92c4326f6f928a64af28e7a..9aae1435410739ae59b32a55d5cc746edd5162de 100644
--- a/typo3/sysext/backend/Classes/Tree/View/AbstractTreeView.php
+++ b/typo3/sysext/backend/Classes/Tree/View/AbstractTreeView.php
@@ -20,6 +20,8 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
+use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -834,11 +836,17 @@ abstract class AbstractTreeView
             return $this->getDataCount($res);
         } else {
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
             $count = $queryBuilder
                 ->count('uid')
                 ->from($this->table)
-                ->where($queryBuilder->expr()->eq($this->parentField, $queryBuilder->createNamedParameter($uid)))
-                ->andWhere(QueryHelper::stripLogicalOperatorPrefix($this->clause))
+                ->where(
+                    $queryBuilder->expr()->eq($this->parentField, (int)$uid),
+                    QueryHelper::stripLogicalOperatorPrefix($this->clause)
+                )
                 ->execute()
                 ->fetchColumn();
 
@@ -894,11 +902,17 @@ abstract class AbstractTreeView
             return $parentId;
         } else {
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
             $queryBuilder
                 ->select(...$this->fieldArray)
                 ->from($this->table)
-                ->where($queryBuilder->expr()->eq($this->parentField, $queryBuilder->createNamedParameter($parentId)))
-                ->andWhere(QueryHelper::stripLogicalOperatorPrefix($this->clause));
+                ->where(
+                    $queryBuilder->expr()->eq($this->parentField, (int)$parentId),
+                    QueryHelper::stripLogicalOperatorPrefix($this->clause)
+                );
 
             foreach (QueryHelper::parseOrderBy($this->orderByFields) as $orderPair) {
                 list($fieldName, $order) = $orderPair;
diff --git a/typo3/sysext/backend/Classes/Tree/View/PagePositionMap.php b/typo3/sysext/backend/Classes/Tree/View/PagePositionMap.php
index 1d2b28c0747b39303e8e81d7f9f525218375205a..758fded4616dbdd4973dddc71ebd58edc31bad48 100644
--- a/typo3/sysext/backend/Classes/Tree/View/PagePositionMap.php
+++ b/typo3/sysext/backend/Classes/Tree/View/PagePositionMap.php
@@ -17,7 +17,10 @@ namespace TYPO3\CMS\Backend\Tree\View;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
+use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
@@ -341,15 +344,20 @@ class PagePositionMap
         $lines = array();
         foreach ($colPosArray as $kk => $vv) {
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
-            $queryBuilder->getQueryContext()
-                ->setContext(QueryContextType::BACKEND_NO_VERSIONING_PLACEHOLDERS)
-                ->setIgnoreEnableFields($showHidden);
-
+            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+            if ($showHidden) {
+                $queryBuilder->getRestrictions()
+                    ->removeByType(HiddenRestriction::class)
+                    ->removeByType(StartTimeRestriction::class)
+                    ->removeByType(EndTimeRestriction::class);
+            }
             $queryBuilder
                 ->select('*')
                 ->from('tt_content')
-                ->where($queryBuilder->expr()->eq('pid', (int)$pid))
-                ->andWhere($queryBuilder->expr()->eq('colPos', (int)$vv))
+                ->where(
+                    $queryBuilder->expr()->eq('pid', (int)$pid),
+                    $queryBuilder->expr()->eq('colPos', (int)$vv)
+                )
                 ->orderBy('sorting');
 
             if ((string)$this->cur_sys_language !== '') {
diff --git a/typo3/sysext/belog/Classes/Controller/SystemInformationController.php b/typo3/sysext/belog/Classes/Controller/SystemInformationController.php
index c018a24254b6286832b36a067cfbfe036b4fb389..6c9b498796a47f93a59252ad5b41381e32350988 100644
--- a/typo3/sysext/belog/Classes/Controller/SystemInformationController.php
+++ b/typo3/sysext/belog/Classes/Controller/SystemInformationController.php
@@ -19,7 +19,6 @@ use TYPO3\CMS\Backend\Toolbar\Enumeration\InformationStatus;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Belog\Domain\Model\Constraint;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
 
@@ -51,12 +50,13 @@ class SystemInformationController extends AbstractController
 
         $this->setStartAndEndTimeFromTimeSelector($constraint);
         // we can't use the extbase repository here as the required TypoScript may not be parsed yet
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log');
         $count = $queryBuilder->count('error')
             ->from('sys_log')
-            ->where($queryBuilder->expr()->gte('tstamp', $timestamp))
-            ->andWhere($queryBuilder->expr()->in('error', [-1, 1, 2]))
+            ->where(
+                $queryBuilder->expr()->gte('tstamp', $timestamp),
+                $queryBuilder->expr()->in('error', [-1, 1, 2])
+            )
             ->execute()
             ->fetchColumn(0);
 
diff --git a/typo3/sysext/beuser/Classes/Domain/Repository/BackendUserSessionRepository.php b/typo3/sysext/beuser/Classes/Domain/Repository/BackendUserSessionRepository.php
index f8a694f89d81e59b5536d8dd6618efbc8ec504ec..f676c60111cbfeaa89c08b79898253f752610b61 100644
--- a/typo3/sysext/beuser/Classes/Domain/Repository/BackendUserSessionRepository.php
+++ b/typo3/sysext/beuser/Classes/Domain/Repository/BackendUserSessionRepository.php
@@ -72,9 +72,11 @@ class BackendUserSessionRepository extends Repository
             ->update('be_sessions')
             ->set('ses_userid', $authentication->user['ses_backuserid'])
             ->set('ses_backuserid', 0)
-            ->where($queryBuilder->expr()->eq('ses_id', $queryBuilder->createNamedParameter($GLOBALS['BE_USER']->id)))
-            ->andWhere($queryBuilder->expr()->eq('ses_name', $queryBuilder->createNamedParameter(BackendUserAuthentication::getCookieName())))
-            ->andWhere($queryBuilder->expr()->eq('ses_userid', (int)$GLOBALS['BE_USER']->user['uid']))
+            ->where(
+                $queryBuilder->expr()->eq('ses_id', $queryBuilder->createNamedParameter($GLOBALS['BE_USER']->id)),
+                $queryBuilder->expr()->eq('ses_name', $queryBuilder->createNamedParameter(BackendUserAuthentication::getCookieName())),
+                $queryBuilder->expr()->eq('ses_userid', (int)$GLOBALS['BE_USER']->user['uid'])
+            )
             ->execute();
     }
 }
diff --git a/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php b/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php
index dab73e90ecd53fed5ab0d79876aeba06896a47a4..a2fe2b12fae11c6ed44ca2b45550ed56deee2503 100644
--- a/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php
+++ b/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php
@@ -19,8 +19,14 @@ use TYPO3\CMS\Core\Crypto\Random;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\DatabaseConnection;
-use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
+use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface;
+use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
 use TYPO3\CMS\Core\Exception;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -1050,6 +1056,7 @@ abstract class AbstractUserAuthentication
     {
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
             ->getQueryBuilderForTable($this->session_table);
+        $queryBuilder->setRestrictions($this->userConstraints());
         $queryBuilder->select('*')
             ->from($this->session_table)
             ->from($this->user_table)
@@ -1057,28 +1064,21 @@ abstract class AbstractUserAuthentication
                 $queryBuilder->expr()->eq(
                     $this->session_table . '.ses_id',
                     $queryBuilder->createNamedParameter($this->id)
-                )
-            )
-            ->andWhere(
+                ),
                 $queryBuilder->expr()->eq(
                     $this->session_table . '.ses_name',
                     $queryBuilder->createNamedParameter($this->name)
-                )
-            )
-            // Condition on which to join the session and user table
-            ->andWhere(
+                ),
+                // Condition on which to join the session and user table
                 $queryBuilder->expr()->eq(
                     $this->session_table . '.ses_userid',
                     $queryBuilder->quoteIdentifier($this->user_table . '.' . $this->userid_column)
-                )
-            )
-            ->andWhere(
+                ),
                 $queryBuilder->expr()->eq(
                     $this->session_table . '.ses_hashlock',
                     $queryBuilder->createNamedParameter($this->hashLockClause_getHashInt())
                 )
-            )
-            ->andWhere($this->userConstraints($queryBuilder->expr()));
+            );
 
         if ($this->lockIP) {
             $queryBuilder->andWhere(
@@ -1097,50 +1097,38 @@ abstract class AbstractUserAuthentication
     }
 
     /**
-     * @param ExpressionBuilder $expressionBuilder
-     * @param string $tableAlias
-     * @return \Doctrine\DBAL\Query\Expression\CompositeExpression
+     * This returns the restrictions needed to select the user respecting
+     * enable columns and flags like deleted, hidden, starttime, endtime
+     * and rootLevel
+     *
+     * @return \TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface
      * @internal
      */
-    protected function userConstraints(
-        ExpressionBuilder $expressionBuilder,
-        string $tableAlias = ''
-    ): \Doctrine\DBAL\Query\Expression\CompositeExpression {
-        if ($tableAlias === '') {
-            $tableAlias = $this->user_table;
-        }
+    protected function userConstraints(): QueryRestrictionContainerInterface
+    {
+        $restrictionContainer = GeneralUtility::makeInstance(DefaultRestrictionContainer::class);
 
-        $constraints = $expressionBuilder->andX();
-        if ($this->enablecolumns['rootLevel']) {
-            $constraints->add(
-                $expressionBuilder->eq($tableAlias . '.pid', 0)
-            );
+        if (empty($this->enablecolumns['disabled'])) {
+            $restrictionContainer->removeByType(HiddenRestriction::class);
         }
-        if ($this->enablecolumns['disabled']) {
-            $constraints->add(
-                $expressionBuilder->eq($tableAlias . '.' . $this->enablecolumns['disabled'], 0)
-            );
+
+        if (empty($this->enablecolumns['deleted'])) {
+            $restrictionContainer->removeByType(DeletedRestriction::class);
         }
-        if ($this->enablecolumns['deleted']) {
-            $constraints->add(
-                $expressionBuilder->eq($tableAlias . '.' . $this->enablecolumns['deleted'], 0)
-            );
+
+        if (empty($this->enablecolumns['starttime'])) {
+            $restrictionContainer->removeByType(StartTimeRestriction::class);
         }
-        if ($this->enablecolumns['starttime']) {
-            $constraints->add(
-                $expressionBuilder->lte($tableAlias . '.' . $this->enablecolumns['starttime'], $GLOBALS['EXEC_TIME'])
-            );
+
+        if (empty($this->enablecolumns['endtime'])) {
+            $restrictionContainer->removeByType(EndTimeRestriction::class);
         }
-        if ($this->enablecolumns['endtime']) {
-            $constraints->add(
-                $expressionBuilder->orX(
-                    $expressionBuilder->eq($tableAlias . '.' . $this->enablecolumns['endtime'], 0),
-                    $expressionBuilder->gt($tableAlias . '.' . $this->enablecolumns['endtime'], $GLOBALS['EXEC_TIME'])
-                )
-            );
+
+        if (!empty($this->enablecolumns['rootLevel'])) {
+            $restrictionContainer->add(GeneralUtility::makeInstance(RootLevelRestriction::class, [$this->user_table]));
         }
 
-        return $constraints;
+        return $restrictionContainer;
     }
 
     /**
@@ -1479,7 +1467,10 @@ abstract class AbstractUserAuthentication
         $authInfo['db_user']['username_column'] = $this->username_column;
         $authInfo['db_user']['userident_column'] = $this->userident_column;
         $authInfo['db_user']['usergroup_column'] = $this->usergroup_column;
-        $authInfo['db_user']['enable_clause'] = $this->userConstraints($expressionBuilder);
+        $authInfo['db_user']['enable_clause'] = $this->userConstraints()->buildExpression(
+            [$this->user_table],
+            $expressionBuilder
+        );
         if ($this->checkPid && $this->checkPid_value !== null) {
             $authInfo['db_user']['checkPidList'] = $this->checkPid_value;
             $authInfo['db_user']['check_pid_clause'] = $expressionBuilder->in(
@@ -1521,9 +1512,7 @@ abstract class AbstractUserAuthentication
                 $query->expr()->lt(
                     'ses_tstamp',
                     $query->createNamedParameter((int)($GLOBALS['EXEC_TIME'] - $this->gc_time))
-                )
-            )
-            ->andWhere(
+                ),
                 $query->expr()->eq(
                     'ses_name',
                     $query->createNamedParameter($this->name)
@@ -1604,10 +1593,10 @@ abstract class AbstractUserAuthentication
     public function getRawUserByUid($uid)
     {
         $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
+        $query->setRestrictions($this->userConstraints());
         $query->select('*')
             ->from($this->user_table)
-            ->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
-            ->andWhere($this->userConstraints($query->expr()));
+            ->where($query->expr()->eq('uid', (int)$uid));
 
         return $query->execute()->fetch();
     }
@@ -1623,10 +1612,10 @@ abstract class AbstractUserAuthentication
     public function getRawUserByName($name)
     {
         $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
+        $query->setRestrictions($this->userConstraints());
         $query->select('*')
             ->from($this->user_table)
-            ->where($query->expr()->eq('username', $query->createNamedParameter($name)))
-            ->andWhere($this->userConstraints($query->expr()));
+            ->where($query->expr()->eq('username', $query->createNamedParameter($name)));
 
         return $query->execute()->fetch();
     }
@@ -1650,6 +1639,7 @@ abstract class AbstractUserAuthentication
         $user = false;
         if ($username || $extraWhere) {
             $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($dbUser['table']);
+            $query->getRestrictions()->removeAll();
 
             $constraints = array_filter([
                 QueryHelper::stripLogicalOperatorPrefix($dbUser['check_pid_clause']),
diff --git a/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php b/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php
index ca0ab847668af15d880d3938d2503a36e39f875b..909f293071c16778c91eb5649a9a0c5ab7c3e954 100644
--- a/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php
+++ b/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php
@@ -17,6 +17,9 @@ namespace TYPO3\CMS\Core\Authentication;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
 use TYPO3\CMS\Core\Resource\ResourceStorage;
 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
@@ -1339,17 +1342,20 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
                 // Explode mounts
                 // Selecting all webmounts with permission clause for reading
                 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+                $queryBuilder->getRestrictions()
+                    ->removeAll()
+                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+
                 $MProws = $queryBuilder->select('uid')
                     ->from('pages')
-                    ->where($queryBuilder->expr()->eq('deleted', 0))
-                    ->andWhere(
+                    // @todo DOCTRINE: check how to make getPagePermsClause() portable
+                    ->where(
+                        $this->getPagePermsClause(1),
                         $queryBuilder->expr()->in(
                             'uid',
                             $queryBuilder->createNamedParameter($this->groupData['webmounts'])
                         )
                     )
-                    // @todo DOCTRINE: check how to make getPagePermsClause() portable
-                    ->andWhere($this->getPagePermsClause(1))
                     ->execute()
                     ->fetchAll();
                 $MProws = array_column(($MProws ?: []), 'uid', 'uid');
@@ -1382,8 +1388,6 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->usergroup_table);
         $expressionBuilder = $queryBuilder->expr();
         $constraints = $expressionBuilder->andX(
-            $expressionBuilder->eq('deleted', 0),
-            $expressionBuilder->eq('hidden', 0),
             $expressionBuilder->eq('pid', 0),
             $expressionBuilder->in('uid', GeneralUtility::intExplode(',', $grList)),
             $expressionBuilder->orX(
@@ -1582,11 +1586,15 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
             $orderBy = $GLOBALS['TCA']['sys_filemounts']['ctrl']['default_sortby'] ?? 'sorting';
 
             $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_filemounts');
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                ->add(GeneralUtility::makeInstance(HiddenRestriction::class))
+                ->add(GeneralUtility::makeInstance(RootLevelRestriction::class));
+
             $queryBuilder->select('*')
                 ->from('sys_filemounts')
-                ->where($queryBuilder->expr()->eq('hidden', 0))
-                ->andWhere($queryBuilder->expr()->eq('pid', 0))
-                ->andWhere($queryBuilder->expr()->in('uid', $fileMounts));
+                ->where($queryBuilder->expr()->in('uid', array_map('intval', $fileMounts)));
 
             foreach (QueryHelper::parseOrderBy($orderBy) as $fieldAndDirection) {
                 $queryBuilder->addOrderBy(...$fieldAndDirection);
@@ -2024,13 +2032,13 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
                 default:
                     if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
                         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace');
+                        $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(RootLevelRestriction::class));
                         $wsRec = $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields))
                             ->from('sys_workspace')
-                            ->where($queryBuilder->expr()->eq('pid', 0))
-                            ->andWhere(
+                            ->where(
                                 $queryBuilder->expr()->eq(
                                     'uid',
-                                    $queryBuilder->createNamedParameter((int)$wsRec)
+                                    (int)$wsRec
                                 )
                             )
                             ->orderBy('title')
@@ -2195,9 +2203,9 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
         } elseif (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
             // Traverse custom workspaces:
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace');
+            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(RootLevelRestriction::class));
             $workspaces = $queryBuilder->select('uid', 'title', 'adminusers', 'reviewers')
                 ->from('sys_workspace')
-                ->where($queryBuilder->expr()->eq('pid', 0))
                 ->orderBy('title')
                 ->execute()
                 ->fetchAll(\PDO::FETCH_ASSOC);
@@ -2322,9 +2330,11 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
             $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_log');
             $queryBuilder->select('tstamp')
                 ->from('sys_log')
-                ->where($queryBuilder->expr()->eq('type', 255))
-                ->andWhere($queryBuilder->expr()->eq('action', 4))
-                ->andWhere($queryBuilder->expr()->gt('tstamp', $queryBuilder->createNamedParameter((int)$theTimeBack)))
+                ->where(
+                    $queryBuilder->expr()->eq('type', 255),
+                    $queryBuilder->expr()->eq('action', 4),
+                    $queryBuilder->expr()->gt('tstamp', (int)$theTimeBack)
+                )
                 ->orderBy('tstamp', 'DESC')
                 ->setMaxResults(1);
             if ($testRow = $queryBuilder->execute()->fetch(\PDO::FETCH_ASSOC)) {
@@ -2334,10 +2344,12 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
             $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_log');
             $result = $queryBuilder->select('*')
                 ->from('sys_log')
-                ->where($queryBuilder->expr()->eq('type', 255))
-                ->andWhere($queryBuilder->expr()->eq('action', 3))
-                ->andWhere($queryBuilder->expr()->neq('error', 0))
-                ->andWhere($queryBuilder->expr()->gt('tstamp', $queryBuilder->createNamedParameter((int)$theTimeBack)))
+                ->where(
+                    $queryBuilder->expr()->eq('type', 255),
+                    $queryBuilder->expr()->eq('action', 3),
+                    $queryBuilder->expr()->neq('error', 0),
+                    $queryBuilder->expr()->gt('tstamp', (int)$theTimeBack)
+                )
                 ->orderBy('tstamp')
                 ->execute();
 
@@ -2600,7 +2612,7 @@ This is a dump of the failures:
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
             $isUserAllowedToLogin = (bool)$queryBuilder->count('uid')
                 ->from('be_users')
-                ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($backendUserId)))
+                ->where($queryBuilder->expr()->eq('uid', (int)$backendUserId))
                 ->andWhere($queryBuilder->expr()->eq('admin', 1))
                 ->execute()
                 ->fetchColumn(0);
diff --git a/typo3/sysext/core/Classes/Database/Connection.php b/typo3/sysext/core/Classes/Database/Connection.php
index c23f8867bcebc9fd6b2955123b2ab13e998a4487..0f78d8073617c4558eae946d3f748dadf559fc27 100644
--- a/typo3/sysext/core/Classes/Database/Connection.php
+++ b/typo3/sysext/core/Classes/Database/Connection.php
@@ -21,7 +21,6 @@ use Doctrine\DBAL\Driver;
 use Doctrine\DBAL\Driver\Statement;
 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
-use TYPO3\CMS\Core\Database\Query\QueryContext;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 class Connection extends \Doctrine\DBAL\Connection
@@ -75,12 +74,11 @@ class Connection extends \Doctrine\DBAL\Connection
     /**
      * Creates a new instance of a SQL query builder.
      *
-     * @param \TYPO3\CMS\Core\Database\Query\QueryContext $queryContext
      * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
      */
-    public function createQueryBuilder(QueryContext $queryContext = null): QueryBuilder
+    public function createQueryBuilder(): QueryBuilder
     {
-        return GeneralUtility::makeInstance(QueryBuilder::class, $this, $queryContext);
+        return GeneralUtility::makeInstance(QueryBuilder::class, $this);
     }
 
     /**
diff --git a/typo3/sysext/core/Classes/Database/Query/Expression/CompositeExpression.php b/typo3/sysext/core/Classes/Database/Query/Expression/CompositeExpression.php
index 14affb6d74f8ac16de7ceaf922104e41491ecec0..ea87c2f3ccfb65773841bf5e70516046e978f052 100644
--- a/typo3/sysext/core/Classes/Database/Query/Expression/CompositeExpression.php
+++ b/typo3/sysext/core/Classes/Database/Query/Expression/CompositeExpression.php
@@ -23,6 +23,8 @@ class CompositeExpression extends \Doctrine\DBAL\Query\Expression\CompositeExpre
 {
     /**
      * Retrieves the string representation of this composite expression.
+     * If expression is empty, just return an empty string.
+     * Native Doctrine expression would return () instead.
      *
      * @return string
      */
@@ -31,7 +33,26 @@ class CompositeExpression extends \Doctrine\DBAL\Query\Expression\CompositeExpre
         if ($this->count() === 0) {
             return '';
         }
-
         return parent::__toString();
     }
+
+    /**
+     * Adds an expression to composite expression.
+     *
+     * @param mixed $part
+     *
+     * @return \Doctrine\DBAL\Query\Expression\CompositeExpression
+     */
+    public function add($part)
+    {
+        // Due to a bug in Doctrine DBAL, we must add our own check here,
+        // which we luckily can, as we use a subclass anyway.
+        // @see https://github.com/doctrine/dbal/issues/2388
+        $isEmpty = $part instanceof self ? $part->count() === 0 : empty($part);
+        if (!$isEmpty) {
+            parent::add($part);
+        }
+
+        return $this;
+    }
 }
diff --git a/typo3/sysext/core/Classes/Database/Query/QueryBuilder.php b/typo3/sysext/core/Classes/Database/Query/QueryBuilder.php
index 1530b212e6b3b35b252b4935e6571d71d32294b7..7cdf3ed280db45f1d674563a587dea1dbad03d9a 100644
--- a/typo3/sysext/core/Classes/Database/Query/QueryBuilder.php
+++ b/typo3/sysext/core/Classes/Database/Query/QueryBuilder.php
@@ -18,6 +18,8 @@ namespace TYPO3\CMS\Core\Database\Query;
 use Doctrine\DBAL\Query\Expression\CompositeExpression;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
+use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -51,52 +53,49 @@ class QueryBuilder
     protected $concreteQueryBuilder;
 
     /**
-     * @var QueryContext
+     * @var QueryRestrictionContainerInterface
      */
-    protected $queryContext;
+    protected $restrictionContainer;
 
     /**
      * Initializes a new QueryBuilder.
      *
      * @param Connection $connection The DBAL Connection.
-     * @param QueryContext $queryContext
+     * @param QueryRestrictionContainerInterface $restrictionContainer
      * @param \Doctrine\DBAL\Query\QueryBuilder $concreteQueryBuilder
      */
     public function __construct(
         Connection $connection,
-        QueryContext $queryContext = null,
+        QueryRestrictionContainerInterface $restrictionContainer = null,
         \Doctrine\DBAL\Query\QueryBuilder $concreteQueryBuilder = null
     ) {
         $this->connection = $connection;
+        $this->restrictionContainer = $restrictionContainer ?: GeneralUtility::makeInstance(DefaultRestrictionContainer::class);
+        $this->concreteQueryBuilder = $concreteQueryBuilder ?: GeneralUtility::makeInstance(\Doctrine\DBAL\Query\QueryBuilder::class, $connection);
+    }
 
-        if ($queryContext === null) {
-            $queryContext = GeneralUtility::makeInstance(QueryContext::class);
-        }
-        $this->queryContext = $queryContext;
-
-        if ($concreteQueryBuilder === null) {
-            $concreteQueryBuilder = GeneralUtility::makeInstance(
-                \Doctrine\DBAL\Query\QueryBuilder::class,
-                $connection
-            );
-        }
-        $this->concreteQueryBuilder = $concreteQueryBuilder;
+    /**
+     * @return QueryRestrictionContainerInterface
+     */
+    public function getRestrictions()
+    {
+        return $this->restrictionContainer;
     }
 
     /**
-     * @return QueryContext
+     * @param QueryRestrictionContainerInterface $restrictionContainer
      */
-    public function getQueryContext(): QueryContext
+    public function setRestrictions(QueryRestrictionContainerInterface $restrictionContainer)
     {
-        return $this->queryContext;
+        $this->restrictionContainer = $restrictionContainer;
     }
 
     /**
-     * @param QueryContext $queryContext
+     * Re-apply default restrictions
      */
-    public function setQueryContext(QueryContext $queryContext)
+    public function resetRestrictions()
     {
-        $this->queryContext = $queryContext;
+        $this->restrictionContainer = GeneralUtility::makeInstance(DefaultRestrictionContainer::class);
     }
 
     /**
@@ -167,12 +166,12 @@ class QueryBuilder
             return $this->concreteQueryBuilder->execute();
         }
 
-        // set additional query restrictions based on context & TCA config
-        $originalWhereConditions = $this->addAdditonalWhereConditions();
+        // Set additional query restrictions
+        $originalWhereConditions = $this->addAdditionalWhereConditions();
 
         $result = $this->concreteQueryBuilder->execute();
 
-        // restore the original query conditions in case the user keeps
+        // Restore the original query conditions in case the user keeps
         // on modifying the state.
         $this->concreteQueryBuilder->add('where', $originalWhereConditions, false);
 
@@ -193,12 +192,12 @@ class QueryBuilder
             return $this->concreteQueryBuilder->getSQL();
         }
 
-        // set additional query restrictions based on context & TCA config
-        $originalWhereConditions = $this->addAdditonalWhereConditions();
+        // Set additional query restrictions
+        $originalWhereConditions = $this->addAdditionalWhereConditions();
 
         $sql = $this->concreteQueryBuilder->getSQL();
 
-        // restore the original query conditions in case the user keeps
+        // Restore the original query conditions in case the user keeps
         // on modifying the state.
         $this->concreteQueryBuilder->add('where', $originalWhereConditions, false);
 
@@ -900,6 +899,18 @@ class QueryBuilder
         return $this->concreteQueryBuilder->createPositionalParameter($value, $type);
     }
 
+    /**
+     * 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, '_%');
+    }
+
     /**
      * Quotes a given input parameter.
      *
@@ -1054,28 +1065,24 @@ class QueryBuilder
      *
      * @return \Doctrine\DBAL\Query\Expression\CompositeExpression|mixed
      */
-    protected function addAdditonalWhereConditions()
+    protected function addAdditionalWhereConditions()
     {
-        $queryRestrictionBuilder = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            $this->getQueriedTables(),
-            $this->expr(),
-            $this->getQueryContext()
-        );
-
         $originalWhereConditions = $this->concreteQueryBuilder->getQueryPart('where');
-        if ($originalWhereConditions instanceof CompositeExpression) {
-            $originalWhereConditions = clone($originalWhereConditions);
+        $expression = $this->restrictionContainer->buildExpression($this->getQueriedTables(), $this->expr());
+        // This check would be obsolete, as the composite expression would not add empty expressions anyway
+        // But we keep it here to only clone the previous state, in case we really will change it.
+        // Once we remove this state preserving functionality, we can remove the count check here
+        // and just add the expression to the query builder.
+        if ($expression->count() > 0) {
+            if ($originalWhereConditions instanceof CompositeExpression) {
+                // Save the original query conditions so we can restore
+                // them after the query has been built.
+                $originalWhereConditions = clone($originalWhereConditions);
+            }
+            $this->concreteQueryBuilder->andWhere($expression);
         }
 
-        $additionalQueryRestrictions = $queryRestrictionBuilder->getVisibilityConstraints();
-
-        if ($additionalQueryRestrictions->count() !== 0) {
-            // save the original query conditions so we can restore
-            // them after the query has been built.
-
-            $this->concreteQueryBuilder->andWhere($additionalQueryRestrictions);
-        }
+        // @todo add hook to be able to add additional restrictions
 
         return $originalWhereConditions;
     }
diff --git a/typo3/sysext/core/Classes/Database/Query/QueryContext.php b/typo3/sysext/core/Classes/Database/Query/QueryContext.php
deleted file mode 100644
index caf0e4d8ff062a8155704281ba0ec9c3a6edfcbe..0000000000000000000000000000000000000000
--- a/typo3/sysext/core/Classes/Database/Query/QueryContext.php
+++ /dev/null
@@ -1,596 +0,0 @@
-<?php
-declare (strict_types = 1);
-namespace TYPO3\CMS\Core\Database\Query;
-
-/*
- * 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 TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Frontend\Page\PageRepository;
-
-/**
- * TYPO3 / TCA specific query settings that deal with enable/hidden fields,
- * frontend groups and start-/endtimes.
- */
-class QueryContext
-{
-    /**
-     * The context for which the restraints are to be built.
-     *
-     * @var QueryContextType
-     */
-    protected $context;
-
-    /**
-     * @var int[]
-     */
-    protected $memberGroups = null;
-
-    /**
-     * @var int
-     */
-    protected $currentWorkspace = null;
-
-    /**
-     * @var int
-     */
-    protected $accessTime = null;
-
-    /**
-     * Global flag if hidden records are to be included in the query result.
-     *
-     * In PageRepository::enableFields() this is called showHidden
-     *
-     * @var bool
-     */
-    protected $includeHidden = null;
-
-    /**
-     * Global flag if deleted records are to be included in the query result.
-     *
-     * @var bool
-     */
-    protected $includeDeleted = false;
-
-    /**
-     * Per table flag if deleted records are to be included in the query result.
-     *
-     * @var array
-     */
-    protected $includeDeletedForTable = [];
-
-    /**
-     * Global flag if records in a non-default versioned state should be
-     * included in the query results.
-     *
-     * In PageRepository the flag is called versioningPreview
-     *
-     * @var bool
-     */
-    protected $includePlaceholders = null;
-
-    /**
-     * Global flag if versioned records are to be included in the query result.
-     * Also influences if enable fields are respected for the query.
-     *
-     * In PageRepository the flag is called noVersionPreview
-     *
-     * @var bool
-     */
-    protected $includeVersionedRecords = false;
-
-    /**
-     * Global flag if enable fields are going to be checked for the query.
-     *
-     * @var bool
-     */
-    protected $ignoreEnableFields = false;
-
-    /**
-     * Global list of enable columns that are not checked for the query.
-     * This list is only checked if $ignoreEnableFields is enabled.
-     *
-     * @var string[]
-     */
-    protected $ignoredEnableFields = [];
-
-    /**
-     * Per table list of enable columns that are not checked for the query.
-     * This list is only checked if $ignoreEnableFields is enabled.
-     *
-     * @var string[]
-     */
-    protected $ignoredEnableFieldsForTable = []; // Per Table list of ignored columns
-
-    /**
-     * Associative array of table configs to override the TCA definition. If a table
-     * is not configured here the setup information from the TCA will be used.
-     *
-     * The array key is the table name, the value is in the format
-     * [
-     *   'deleted' => 'fieldName',
-     *   'versioningWS' => true,
-     *   'enablecolumns' => [ 'disabled' => hidden, ... ]
-     * ]
-     *
-     * @var array
-     */
-    protected $tableConfigs = [];
-
-    /**
-     * QueryContext constructor.
-     *
-     * @param string $context A valid QueryContextType
-     */
-    public function __construct(string $context = QueryContextType::AUTO)
-    {
-        $this->context = GeneralUtility::makeInstance(QueryContextType::class, $context);
-    }
-
-    /**
-     * @return string
-     */
-    public function getContext(): string
-    {
-        if ($this->context->equals(QueryContextType::AUTO)) {
-            if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_FE) {
-                return QueryContextType::FRONTEND;
-            } elseif (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_BE) {
-                return QueryContextType::BACKEND;
-            } else {
-                return QueryContextType::UNRESTRICTED;
-            }
-        }
-
-        return (string)$this->context;
-    }
-
-    /**
-     * Set the context in which the query is going to be run.
-     * Used by the QueryRestrictionBuilder to determine the restrictions to be placed.
-     *
-     * @param string $context
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setContext(string $context): QueryContext
-    {
-        $this->context = GeneralUtility::makeInstance(QueryContextType::class, $context);
-
-        return $this;
-    }
-
-    /**
-     * Get a list of member groups (fe_groups) that will be used in when building
-     * query restrictions in FE context.
-     *
-     * @return int[]
-     */
-    public function getMemberGroups(): array
-    {
-        // If the member groups have not been explicitly set
-        // the group list from the frontend controller context
-        // will be inherited
-        if ($this->memberGroups === null) {
-            $this->memberGroups = GeneralUtility::intExplode(
-                ',',
-                $this->getTypoScriptFrontendController()->gr_list,
-                true
-            );
-        }
-
-        return (array)$this->memberGroups;
-    }
-
-    /**
-     * Set the member groups that will be checked in frontend context.
-     *
-     * @param int[] $memberGroups
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setMemberGroups(array $memberGroups): QueryContext
-    {
-        $this->memberGroups = $memberGroups;
-
-        return $this;
-    }
-
-    /**
-     * Get the current workspace. If not actively defined it will fall back
-     * to the current workspace set in the PageRepository.
-     *
-     * @return int
-     */
-    public function getCurrentWorkspace(): int
-    {
-        return $this->currentWorkspace ?? (int)$this->getPageRepository()->versioningWorkspaceId;
-    }
-
-    /**
-     * Set the current workspace id.
-     *
-     * @param int $currentWorkspace
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setCurrentWorkspace(int $currentWorkspace): QueryContext
-    {
-        $this->currentWorkspace = $currentWorkspace;
-
-        return $this;
-    }
-
-    /**
-     * Return the current accesstime. If not explictly set fall back to the
-     * value of $GLOBALS['SIM_ACCESS_TIME']
-     *
-     * @return int
-     */
-    public function getAccessTime(): int
-    {
-        if ($this->accessTime === null) {
-            return empty($GLOBALS['SIM_ACCESS_TIME']) ? 0 : (int)$GLOBALS['SIM_ACCESS_TIME'];
-        }
-
-        return $this->accessTime;
-    }
-
-    /**
-     * Set the current access time.
-     *
-     * @param int $accessTime
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setAccessTime(int $accessTime): QueryContext
-    {
-        $this->accessTime = $accessTime;
-
-        return $this;
-    }
-
-    /**
-     * Returns the global setting wether hidden records should be included
-     * in the query result. Preferrably getIncludeHiddenForTable() should
-     * be used as the proper information from TSFE can be inherited by
-     * using the table name information.
-     *
-     * Defaults to false in case the flag has not been explictly set.
-     *
-     * @return bool
-     * @internal
-     */
-    public function getIncludeHidden(): bool
-    {
-        // Casting to bool to accomodate for the legacy fallback:
-        // When showHidden has not been explicitly set it's going to
-        // be determined by the settings in the TyposcriptFrontendController.
-        // As we don't now the table being queried here it's better to use
-        // getIncludeHiddenForTable()
-        return (bool)$this->includeHidden;
-    }
-
-    /**
-     * Flag if hidden records for the given table should be included in the query result.
-     * If $includeHidden has not been explictly set the information from TSFE will be
-     * used to determine the setting.
-     *
-     * @param string $table
-     * @return bool
-     */
-    public function getIncludeHiddenForTable(string $table): bool
-    {
-        if ($this->includeHidden === null && is_object($this->getTypoScriptFrontendController())) {
-            $showHidden = $table === 'pages' || $table === 'pages_language_overlay'
-                ? $this->getTypoScriptFrontendController()->showHiddenPage
-                : $this->getTypoScriptFrontendController()->showHiddenRecords;
-
-            if ($showHidden === -1) {
-                $showHidden = false;
-            }
-
-            $this->includeHidden = (bool)$showHidden;
-        }
-
-        return (bool)$this->includeHidden;
-    }
-
-    /**
-     * Set if hidden records should be part of the query result set.
-     *
-     * @param bool $includeHidden
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setIncludeHidden(bool $includeHidden): QueryContext
-    {
-        $this->includeHidden = $includeHidden;
-
-        return $this;
-    }
-
-    /**
-     * Get if deleted records should be part of the query result set at all.
-     *
-     * @return bool
-     */
-    public function getIncludeDeleted(): bool
-    {
-        return $this->includeDeleted;
-    }
-
-    /**
-     * Set wether deleted records shoult be part of the query result.
-     *
-     * @param bool $includeDeleted
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setIncludeDeleted(bool $includeDeleted): QueryContext
-    {
-        $this->includeDeleted = $includeDeleted;
-
-        return $this;
-    }
-
-    /**
-     * Get if records in a non-default versioning state should be part of the query result set.
-     *
-     * @return bool
-     */
-    public function getIncludePlaceholders(): bool
-    {
-        if ($this->includePlaceholders === null) {
-            $this->includePlaceholders = $this->getPageRepository()->versioningPreview;
-        }
-
-        return (bool)$this->includePlaceholders;
-    }
-
-    /**
-     * Set if records in a non-default versioning state should be part of the query result set.
-     *
-     * @param bool $includePlaceholders
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setIncludePlaceholders(bool $includePlaceholders): QueryContext
-    {
-        $this->includePlaceholders = $includePlaceholders;
-
-        return $this;
-    }
-
-    /**
-     * Get if versioned records shoult be part of the query result set.
-     *
-     * @return bool
-     */
-    public function getIncludeVersionedRecords(): bool
-    {
-        return $this->includeVersionedRecords;
-    }
-
-    /**
-     * Set if versioned records should be part of the query result set.
-     *
-     * @param bool $includeVersionedRecords
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setIncludeVersionedRecords(bool $includeVersionedRecords): QueryContext
-    {
-        $this->includeVersionedRecords = $includeVersionedRecords;
-
-        return $this;
-    }
-
-    /**
-     * Get if enable fields should be ignored for this query.
-     *
-     * @return bool
-     */
-    public function getIgnoreEnableFields(): bool
-    {
-        return $this->ignoreEnableFields;
-    }
-
-    /**
-     * Set if enable fields should be ignored for this query.
-     *
-     * @param bool $ignoreEnableFields
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setIgnoreEnableFields(bool $ignoreEnableFields): QueryContext
-    {
-        $this->ignoreEnableFields = $ignoreEnableFields;
-
-        return $this;
-    }
-
-    /**
-     * Return global list of ignored enable columns for the query.
-     * Can be overridden per table. Only checked if $ignoreEnableFields is enabled.
-     *
-     * @return string[]
-     */
-    public function getIgnoredEnableFields(): array
-    {
-        return $this->ignoredEnableFields;
-    }
-
-    /**
-     * Set the global list of ignored enable columns.
-     *
-     * @param string[] $ignoredEnableFields
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setIgnoredEnableFields(array $ignoredEnableFields): QueryContext
-    {
-        $this->ignoredEnableFields = $ignoredEnableFields;
-
-        return $this;
-    }
-
-    /**
-     * Get the ignored enable columns for this table.
-     * If no specific list has been defined the global list will be returned.
-     *
-     * @param string $table
-     * @return string[]
-     */
-    public function getIgnoredEnableFieldsForTable(string $table): array
-    {
-        if (isset($this->ignoredEnableFieldsForTable[$table])) {
-            return $this->ignoredEnableFieldsForTable[$table];
-        } elseif (!empty($this->ignoredEnableFields)) {
-            return $this->ignoredEnableFields;
-        }
-
-        return [];
-    }
-
-    /**
-     * @param string $table
-     * @param string[] $ignoredEnableFieldsForTable
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setIgnoredEnableFieldsForTable(string $table, array $ignoredEnableFieldsForTable): QueryContext
-    {
-        $this->ignoredEnableFieldsForTable[$table] = $ignoredEnableFieldsForTable;
-
-        return $this;
-    }
-
-    /**
-     * Get if deleted records for this table should be included in the query result set.
-     *
-     * @param string $table
-     * @return bool
-     */
-    public function getIncludeDeletedForTable(string $table): bool
-    {
-        return $this->includeDeletedForTable[$table] ?? false;
-    }
-
-    /**
-     * Set if deleted records for this table should be included in the query result.
-     *
-     * @param string $table
-     * @param bool $includeDeletedForTable
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setIncludeDeletedForTable(string $table, bool $includeDeletedForTable): QueryContext
-    {
-        $this->includeDeletedForTable[$table] = $includeDeletedForTable;
-
-        return $this;
-    }
-
-    /**
-     * Get the table configuration information for all tables.
-     *
-     * @return array
-     */
-    public function getTableConfigs(): array
-    {
-        return $this->tableConfigs;
-    }
-
-    /**
-     * Set the table configuration for all tables.
-     *
-     * @param array $tableConfigs
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setTableConfigs(array $tableConfigs): QueryContext
-    {
-        $this->tableConfigs = $tableConfigs;
-
-        return $this;
-    }
-
-    /**
-     * Get the table configuration for a single table.
-     *
-     * @param string $table
-     * @return array
-     */
-    public function getTableConfig(string $table): array
-    {
-        return $this->tableConfigs[$table] ?? $this->getTcaDefiniton($table);
-    }
-
-    /**
-     * Get the TCA definition for a tables and extract the relevant parts
-     * of the table configuration.
-     *
-     * @param string $table
-     * @return array
-     */
-    protected function getTcaDefiniton(string $table): array
-    {
-        $ctrlDefiniton = $GLOBALS['TCA'][$table]['ctrl'] ?? [];
-        return array_intersect_key(
-            $ctrlDefiniton,
-            ['delete' => true, 'versioningWS' => true, 'enablecolumns' => true]
-        );
-    }
-
-    /**
-     * Add a table configuration entry to the table config array.
-     *
-     * @param string $table
-     * @param string $deletedField
-     * @param bool $versioningSupport
-     * @param array $enableColumns
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function addTableConfig(
-        string $table,
-        string $deletedField = null,
-        bool $versioningSupport = false,
-        array $enableColumns = []
-    ): QueryContext {
-        $this->tableConfigs[$table] = [
-            'deleted' => $deletedField,
-            'versioningWS' => $versioningSupport,
-            'enablecolumns' => $enableColumns
-        ];
-    }
-
-    /**
-     * Remove a table override from the config array.
-     *
-     * @param string $table
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function removeTableConfig(string $table): QueryContext
-    {
-        unset($this->tableConfigs[$table]);
-
-        return $this;
-    }
-
-    /**
-     * @return \TYPO3\CMS\Frontend\Page\PageRepository
-     */
-    protected function getPageRepository(): PageRepository
-    {
-        if ($this->getContext() === QueryContextType::FRONTEND && is_object($this->getTypoScriptFrontendController())) {
-            return $this->getTypoScriptFrontendController()->sys_page;
-        } else {
-            return GeneralUtility::makeInstance(PageRepository::class);
-        }
-    }
-
-    /**
-     * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
-     */
-    protected function getTypoScriptFrontendController()
-    {
-        return $GLOBALS['TSFE'];
-    }
-}
diff --git a/typo3/sysext/core/Classes/Database/Query/QueryContextType.php b/typo3/sysext/core/Classes/Database/Query/QueryContextType.php
deleted file mode 100644
index 8b20d795470f8152d90766480be46170d3d64c46..0000000000000000000000000000000000000000
--- a/typo3/sysext/core/Classes/Database/Query/QueryContextType.php
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-declare (strict_types = 1);
-namespace TYPO3\CMS\Core\Database\Query;
-
-/*
- * 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!
- */
-
-/**
- * Enumeration object for query context type
- *
- */
-class QueryContextType extends \TYPO3\CMS\Core\Type\Enumeration
-{
-    const __default = self::AUTO;
-
-    /**
-     * Constants reflecting the query context type
-     */
-    const AUTO = 'AUTO';
-    const UNRESTRICTED = 'UNRESTRICTED';
-    const FRONTEND = 'FRONTEND';
-    const BACKEND = 'BACKEND';
-    const BACKEND_NO_VERSIONING_PLACEHOLDERS = 'BACKEND_NO_VERSIONING_PLACEHOLDERS';
-
-    /**
-     * @param mixed $type
-     */
-    public function __construct($type = null)
-    {
-        if ($type !== null) {
-            $type = strtoupper((string)$type);
-        }
-
-        parent::__construct($type);
-    }
-}
diff --git a/typo3/sysext/core/Classes/Database/Query/QueryRestrictionBuilder.php b/typo3/sysext/core/Classes/Database/Query/QueryRestrictionBuilder.php
deleted file mode 100644
index 66ba323c69a1f4e3b95d58f5b4f02dd179e542de..0000000000000000000000000000000000000000
--- a/typo3/sysext/core/Classes/Database/Query/QueryRestrictionBuilder.php
+++ /dev/null
@@ -1,383 +0,0 @@
-<?php
-declare (strict_types = 1);
-namespace TYPO3\CMS\Core\Database\Query;
-
-/*
- * 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 TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
-use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Versioning\VersionState;
-
-/**
- * Builder for SQL query constraints based on TCA settings.
- * The resulting composite expressions can be added to a query
- * being built using the QueryBuilder object.
- *
- * The restrictions being built by this class are to be used for all
- * select queries done by the QueryBuilder to avoid returning data
- * that should not be available to the caller based on the current
- * TYPO3 context.
- *
- * Restrictions that will be created can be configured using the
- * QuerySettings on the main QueryBuilder object.
- *
- * WARNING: This code has cross cutting concerns as it requires access
- * to the TypoScriptFrontEndController and $GLOBALS['TCA'] to build the
- * right queries.
- */
-class QueryRestrictionBuilder
-{
-    /**
-     * @var \TYPO3\CMS\Frontend\Page\PageRepository
-     */
-    protected $pageRepository;
-
-    /**
-     * @var \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder
-     */
-    protected $expressionBuilder;
-
-    /**
-     * @var \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    protected $queryContext;
-
-    /**
-     * @var string[]
-     */
-    protected $queriedTables = [];
-
-    /**
-     * Initializes a new QueryBuilder.
-     *
-     * @param string[] $queriedTables
-     * @param ExpressionBuilder $expressionBuilder The ExpressionBuilder with which to create restrictions
-     * @param \TYPO3\CMS\Core\Database\Query\QueryContext $queryContext
-     */
-    public function __construct(
-        array $queriedTables,
-        ExpressionBuilder $expressionBuilder,
-        QueryContext $queryContext = null
-    ) {
-        $this->queriedTables = $queriedTables;
-        $this->expressionBuilder = $expressionBuilder;
-        $this->queryContext = $queryContext ?? GeneralUtility::makeInstance(QueryContext::class);
-    }
-
-    /**
-     * Returns a composite expression to add visibility restrictions for
-     * the selected tables based on the current context (FE/BE).
-     *
-     * You need to check if any conditions are added to the CompositeExpression
-     * before adding it to your query using `->count()`.
-     *
-     * @return CompositeExpression
-     */
-    public function getVisibilityConstraints(): CompositeExpression
-    {
-        switch ($this->queryContext->getContext()) {
-            case QueryContextType::FRONTEND:
-                return $this->getFrontendVisibilityRestrictions();
-            case QueryContextType::BACKEND:
-            case QueryContextType::BACKEND_NO_VERSIONING_PLACEHOLDERS:
-                return $this->getBackendVisibilityConstraints();
-            case QueryContextType::UNRESTRICTED:
-                return $this->expressionBuilder->andX();
-            default:
-                throw new \RuntimeException(
-                    'Unknown TYPO3 Context / Request type: "' . TYPO3_REQUESTTYPE . '".',
-                    1459708283
-                );
-        }
-    }
-
-    /**
-     * Returns a composite expression takeing into account visibility restrictions
-     * imposed by enableFields, versioning/workspaces and deletion.
-     *
-     * @return \TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression
-     * @throws \LogicException
-     */
-    protected function getFrontendVisibilityRestrictions(): CompositeExpression
-    {
-        $queryContext = $this->queryContext;
-        $ignoreEnableFields = $queryContext->getIgnoreEnableFields();
-        $includeDeleted = $queryContext->getIncludeDeleted();
-
-        if (!$ignoreEnableFields && $includeDeleted) {
-            throw new \LogicException(
-                'The query settings "ignoreEnableFields=FALSE" and "includeDeleted=TRUE" can not be used together '
-                . 'in frontend context.',
-                1459690516
-            );
-        }
-
-        $constraints = [];
-        foreach ($this->queriedTables as $tableName => $tableAlias) {
-            $tableConfig = $queryContext->getTableConfig($tableName);
-            if (!$ignoreEnableFields && !$includeDeleted) {
-                $constraint = $this->getEnableFieldConstraints(
-                    $tableName,
-                    $tableAlias,
-                    $queryContext->getIncludeHiddenForTable($tableName),
-                    [],
-                    $queryContext->getIncludeVersionedRecords()
-                );
-                if ($constraint->count() !== 0) {
-                    $constraints[] = $constraint;
-                }
-            } elseif ($ignoreEnableFields && !$includeDeleted) {
-                if (!empty($queryContext->getIgnoredEnableFieldsForTable($tableName))) {
-                    $constraint = $this->getEnableFieldConstraints(
-                        $tableName,
-                        $tableAlias,
-                        $queryContext->getIncludeHiddenForTable($tableName),
-                        $queryContext->getIgnoredEnableFieldsForTable($tableName),
-                        $queryContext->getIncludeVersionedRecords()
-                    );
-                    if ($constraint->count() !== 0) {
-                        $constraints[] = $constraint;
-                    }
-                } elseif (!empty($tableConfig['delete'])) {
-                    $tablePrefix = empty($tableAlias) ? $tableName : $tableAlias;
-                    $constraints[] = $this->expressionBuilder->eq(
-                        $tablePrefix . '.' . $tableConfig['delete'],
-                        0
-                    );
-                }
-            }
-        }
-
-        return $this->expressionBuilder->andX(...$constraints);
-    }
-
-    /**
-     * Returns a composite expression to restrict access to records for the backend context.
-     *
-     * @return CompositeExpression
-     * @todo: Lots of code duplication, check how/if this can be merged with the "getEnableFieldConstraints"
-     * @todo: after the test cases are done for backend and frontend.
-     */
-    protected function getBackendVisibilityConstraints(): CompositeExpression
-    {
-        $queryContext = $this->queryContext;
-        $ignoreEnableFields = $queryContext->getIgnoreEnableFields();
-        $includeDeleted = $queryContext->getIncludeDeleted();
-
-        $constraints = [];
-        $expressionBuilder = $this->expressionBuilder;
-
-        foreach ($this->queriedTables as $tableName => $tableAlias) {
-            $tableConfig = $queryContext->getTableConfig($tableName);
-            $tablePrefix = empty($tableAlias) ? $tableName : $tableAlias;
-
-            if (empty($tableConfig)) {
-                // No restrictions for this table, not configured by TCA
-                continue;
-            }
-
-            if (!$ignoreEnableFields && is_array($tableConfig['enablecolumns'])) {
-                $enableColumns = $tableConfig['enablecolumns'];
-
-                if (isset($enableColumns['disabled'])) {
-                    $constraints[] = $expressionBuilder->eq(
-                        $tablePrefix . '.' . $enableColumns['disabled'],
-                        0
-                    );
-                }
-                if ($enableColumns['starttime']) {
-                    $constraints[] = $expressionBuilder->lte(
-                        $tablePrefix . '.' . $enableColumns['starttime'],
-                        $queryContext->getAccessTime()
-                    );
-                }
-                if ($enableColumns['endtime']) {
-                    $fieldName = $tablePrefix . '.' . $enableColumns['endtime'];
-                    $constraints[] = $expressionBuilder->orX(
-                        $expressionBuilder->eq($fieldName, 0),
-                        $expressionBuilder->gt($fieldName, $queryContext->getAccessTime())
-                    );
-                }
-            }
-
-            if (!$includeDeleted && !empty($tableConfig['delete'])) {
-                $constraints[] = $this->expressionBuilder->eq(
-                    $tablePrefix . '.' . $tableConfig['delete'],
-                    0
-                );
-            }
-
-            if ($queryContext->getContext() === QueryContextType::BACKEND_NO_VERSIONING_PLACEHOLDERS
-                && !empty($tableConfig['versioningWS'])
-            ) {
-                $constraints[] = $this->expressionBuilder->orX(
-                    $expressionBuilder->lte(
-                        $tablePrefix . '.t3ver_state',
-                        new VersionState(VersionState::DEFAULT_STATE)
-                    ),
-                    $expressionBuilder->eq($tablePrefix . '.t3ver_wsid', $queryContext->getCurrentWorkspace())
-                );
-            }
-        }
-
-        return $expressionBuilder->andX(...$constraints);
-    }
-
-    /**
-     * @param string $tableName The table name to query
-     * @param string|null $tableAlias The table alias to use for constraints. $tableName used when empty.
-     * @param bool $showHidden Select hidden records
-     * @param string[] $ignoreFields Names of enable columns to be ignored
-     * @param bool $noVersionPreview If set, enableFields will be applied regardless of any versioning preview
-     *                               settings which might otherwise disable enableFields
-     * @return \TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression
-     */
-    protected function getEnableFieldConstraints(
-        string $tableName,
-        string $tableAlias = null,
-        bool $showHidden = false,
-        array $ignoreFields = [],
-        bool $noVersionPreview = false
-    ): CompositeExpression {
-        $queryContext = $this->queryContext;
-        $tableConfig = $queryContext->getTableConfig($tableName);
-
-        if (empty($tableConfig)) {
-            // No restrictions for this table, not configured by TCA
-            return $this->expressionBuilder->andX();
-        }
-
-        $tablePrefix = empty($tableAlias) ? $tableName : $tableAlias;
-
-        $constraints = [];
-        $expressionBuilder = $this->expressionBuilder;
-
-        // Restrict based on deleted flag of records
-        if (!empty($tableConfig['delete'])) {
-            $constraints[] = $expressionBuilder->eq($tablePrefix . '.deleted', 0);
-        }
-
-        // Restrict based on Workspaces / Versioning
-        if (!empty($tableConfig['versioningWS'])) {
-            if (!$queryContext->getIncludePlaceholders()) {
-                // Filter out placeholder records (new/moved/deleted items) in case we are NOT in a versioning preview
-                // (This means that means we are online!)
-                $constraints[] = $expressionBuilder->lte(
-                    $tablePrefix . '.t3ver_state',
-                    new VersionState(VersionState::DEFAULT_STATE)
-                );
-            } elseif ($tableName !== 'pages') {
-                // Show only records of the live and current workspace in case we are in a versioning preview
-                $constraints[] = $expressionBuilder->orX(
-                    $expressionBuilder->eq($tablePrefix . '.t3ver_wsid', 0),
-                    $expressionBuilder->eq($tablePrefix . '.t3ver_wsid', $queryContext->getCurrentWorkspace())
-                );
-            }
-
-            // Filter out versioned records
-            if (!$noVersionPreview && !in_array('pid', $ignoreFields)) {
-                $constraints[] = $expressionBuilder->neq($tablePrefix . '.pid', -1);
-            }
-        }
-
-        // Restrict based on enable fields. In case of versioning-preview, enableFields are ignored
-        // and later checked in versionOL().
-        if (is_array($tableConfig['enablecolumns'])
-            && (!$queryContext->getIncludePlaceholders() || empty($tableConfig['versioningWS']) || $noVersionPreview)
-        ) {
-            $enableColumns = $tableConfig['enablecolumns'];
-
-            // Filter out disabled records
-            if (isset($enableColumns['disabled']) && !$showHidden && !in_array('disabled', $ignoreFields)) {
-                $constraints[] = $expressionBuilder->eq(
-                    $tablePrefix . '.' . $enableColumns['disabled'],
-                    0
-                );
-            }
-
-            // Filter out records where the starttime has not yet been reached.
-            if (isset($enableColumns['starttime']) && !in_array('starttime', $ignoreFields)) {
-                $constraints[] = $expressionBuilder->lte(
-                    $tablePrefix . '.' . $enableColumns['starttime'],
-                    $queryContext->getAccessTime()
-                );
-            }
-
-            // Filter out records with a set endtime where the time is in the past.
-            if (isset($enableColumns['endtime']) && !in_array('endtime', $ignoreFields)) {
-                $constraints[] = $expressionBuilder->orX(
-                    $expressionBuilder->eq($tablePrefix . '.' . $enableColumns['endtime'], 0),
-                    $expressionBuilder->gt(
-                        $tablePrefix . '.' . $enableColumns['endtime'],
-                        $queryContext->getAccessTime()
-                    )
-                );
-            }
-
-            // Filter out records based on the frondend user groups
-            if ($enableColumns['fe_group'] && !in_array('fe_group', $ignoreFields)) {
-                $constraints[] = $this->getFrontendUserGroupConstraints(
-                    $tablePrefix,
-                    $enableColumns['fe_group']
-                );
-            }
-
-            // Call hook functions for additional enableColumns
-            if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['addEnableColumns'])) {
-                $_params = [
-                    'table' => $tableName,
-                    'tableAlias' => $tableAlias,
-                    'tablePrefix' => $tablePrefix,
-                    'show_hidden' => $showHidden,
-                    'ignore_array' => $ignoreFields,
-                    'ctrl' => $tableConfig
-                ];
-                foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['addEnableColumns'] as $_funcRef) {
-                    $constraint = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
-
-                    $constraints[] = preg_replace('/^(?:AND[[:space:]]*)+/i', '', trim($constraint));
-                }
-            }
-        }
-
-        return $expressionBuilder->andX(...$constraints);
-    }
-
-    /**
-     * @param string $tableName The table name to build constraints for
-     * @param string $fieldName The field name to build constraints for
-     *
-     * @return CompositeExpression
-     */
-    protected function getFrontendUserGroupConstraints(string $tableName, string $fieldName): CompositeExpression
-    {
-        $expressionBuilder = $this->expressionBuilder;
-        // Allow records where no group access has been configured (field values NULL, 0 or empty string)
-        $constraints = [
-            $expressionBuilder->isNull($tableName . '.' . $fieldName),
-            $expressionBuilder->eq($tableName . '.' . $fieldName, $expressionBuilder->literal('')),
-            $expressionBuilder->eq($tableName . '.' . $fieldName, $expressionBuilder->literal('0')),
-        ];
-
-        foreach ($this->queryContext->getMemberGroups() as $value) {
-            $constraints[] = $expressionBuilder->inSet(
-                $tableName . '.' . $fieldName,
-                $expressionBuilder->literal((string)$value)
-            );
-        }
-
-        return $expressionBuilder->orX(...$constraints);
-    }
-}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/AbstractRestrictionContainer.php b/typo3/sysext/core/Classes/Database/Query/Restriction/AbstractRestrictionContainer.php
new file mode 100644
index 0000000000000000000000000000000000000000..5da68dcf58a462286019510ba3d42614d3a577f3
--- /dev/null
+++ b/typo3/sysext/core/Classes/Database/Query/Restriction/AbstractRestrictionContainer.php
@@ -0,0 +1,95 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * 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 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 = [];
+
+    /**
+     * 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 name and value potentially an alias
+     * @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->andX(...$constraints);
+    }
+
+    /**
+     * Removes all restrictions stored within this container
+     *
+     * @return QueryRestrictionContainerInterface
+     */
+    public function removeAll()
+    {
+        $this->restrictions = [];
+        return $this;
+    }
+
+    /**
+     * Remove restriction of a given type
+     *
+     * @param string $restrictionType Class name of the restriction to be removed
+     * @return QueryRestrictionContainerInterface
+     */
+    public function removeByType(string $restrictionType)
+    {
+        unset($this->restrictions[$restrictionType]);
+        return $this;
+    }
+
+    /**
+     * Add a new restriction instance to this collection
+     *
+     * @param QueryRestrictionInterface $restriction
+     * @return QueryRestrictionContainerInterface
+     */
+    public function add(QueryRestrictionInterface $restriction)
+    {
+        $this->restrictions[get_class($restriction)] = $restriction;
+        return $this;
+    }
+
+    /**
+     * Factory method for restrictions.
+     * Currently only instantiates the class.
+     *
+     * @param string $restrictionClass
+     * @return QueryRestrictionInterface
+     */
+    protected function createRestriction($restrictionClass)
+    {
+        return GeneralUtility::makeInstance($restrictionClass);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/BackendWorkspaceRestriction.php b/typo3/sysext/core/Classes/Database/Query/Restriction/BackendWorkspaceRestriction.php
new file mode 100644
index 0000000000000000000000000000000000000000..0223e9a2291618fe102e71f8fd8f4a4d2ff41c60
--- /dev/null
+++ b/typo3/sysext/core/Classes/Database/Query/Restriction/BackendWorkspaceRestriction.php
@@ -0,0 +1,85 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Core\Versioning\VersionState;
+
+/**
+ * Restriction to make queries in TYPO3 backend context versioning/ workspace aware
+ */
+class BackendWorkspaceRestriction implements QueryRestrictionInterface
+{
+    /**
+     * @var int
+     */
+    protected $workspaceId;
+
+    /**
+     * @var bool
+     */
+    protected $includeRowsForWorkspaceOverlay;
+
+    /**
+     * @param int $workspaceId
+     * @param bool $includeRowsForWorkspaceOverlay
+     */
+    public function __construct(int $workspaceId = null, $includeRowsForWorkspaceOverlay = true)
+    {
+        $this->workspaceId = $workspaceId === null ? $GLOBALS['BE_USER']->workspace : $workspaceId;
+        $this->includeRowsForWorkspaceOverlay = $includeRowsForWorkspaceOverlay;
+    }
+
+    /**
+     * Main method to build expressions for given tables
+     *
+     * @param array $queriedTables Array of tables, where array key is table name and value potentially an alias
+     * @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 $tableName => $tableAlias) {
+            $workspaceEnabled = $GLOBALS['TCA'][$tableName]['ctrl']['versioningWS'] ?? null;
+            if (!empty($workspaceEnabled)) {
+                $tablePrefix = $tableAlias ?: $tableName;
+                $workspaceIdExpression = $expressionBuilder->eq(
+                    $tablePrefix . '.t3ver_wsid',
+                    (int)$this->workspaceId
+                );
+                if ($this->includeRowsForWorkspaceOverlay) {
+                    $constraints[] = $expressionBuilder->orX(
+                        $workspaceIdExpression,
+                        $expressionBuilder->lte(
+                            $tablePrefix . '.t3ver_state',
+                            new VersionState(VersionState::DEFAULT_STATE)
+                        )
+                    );
+                } else {
+                    $comparisonExpression = $this->workspaceId === 0 ? 'neq' : 'eq';
+                    $constraints[] = $workspaceIdExpression;
+                    $constraints[] = $expressionBuilder->{$comparisonExpression}(
+                        $tablePrefix . '.pid',
+                        -1
+                    );
+                }
+            }
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/DefaultRestrictionContainer.php b/typo3/sysext/core/Classes/Database/Query/Restriction/DefaultRestrictionContainer.php
new file mode 100644
index 0000000000000000000000000000000000000000..387cdc14607846fa76fa9dfb53e65c5eb5fd5135
--- /dev/null
+++ b/typo3/sysext/core/Classes/Database/Query/Restriction/DefaultRestrictionContainer.php
@@ -0,0 +1,44 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * 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!
+ */
+
+/**
+ * This is the container with restrictions, that are added to any doctrine query
+ */
+class DefaultRestrictionContainer extends AbstractRestrictionContainer
+{
+    /**
+     * Default restriction classes.
+     *
+     * @var QueryRestrictionInterface[]
+     */
+    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));
+        }
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/DeletedRestriction.php b/typo3/sysext/core/Classes/Database/Query/Restriction/DeletedRestriction.php
new file mode 100644
index 0000000000000000000000000000000000000000..9e4a192e01267600498655c89ff54f9a7acf9320
--- /dev/null
+++ b/typo3/sysext/core/Classes/Database/Query/Restriction/DeletedRestriction.php
@@ -0,0 +1,50 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+
+/**
+ * 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 name and value potentially an alias
+     * @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 $tableName => $tableAlias) {
+            $deletedFieldName = $GLOBALS['TCA'][$tableName]['ctrl']['delete'] ?? null;
+            if (!empty($deletedFieldName)) {
+                $tablePrefix = $tableAlias ?: $tableName;
+                $constraints[] = $expressionBuilder->eq(
+                    $tablePrefix . '.' . $deletedFieldName,
+                    0
+                );
+            }
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/EndTimeRestriction.php b/typo3/sysext/core/Classes/Database/Query/Restriction/EndTimeRestriction.php
new file mode 100644
index 0000000000000000000000000000000000000000..014cb4587d2c08fe3def07e33cfca8bc15095059
--- /dev/null
+++ b/typo3/sysext/core/Classes/Database/Query/Restriction/EndTimeRestriction.php
@@ -0,0 +1,69 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+
+/**
+ * Restriction to filter records with an end time set that has passed
+ */
+class EndTimeRestriction implements QueryRestrictionInterface
+{
+    /**
+     * @var int
+     */
+    protected $accessTimeStamp;
+
+    /**
+     * @param int $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 name and value potentially an alias
+     * @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
+    {
+        $constraints = [];
+        foreach ($queriedTables as $tableName => $tableAlias) {
+            $endTimeFieldName = $GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns']['endtime'] ?? null;
+            if (!empty($endTimeFieldName)) {
+                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 ?: $tableName) . '.' . $endTimeFieldName;
+                $constraints[] = $expressionBuilder->orX(
+                    $expressionBuilder->eq($fieldName, 0),
+                    $expressionBuilder->gt($fieldName, (int)$this->accessTimeStamp)
+                );
+            }
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/FrontendGroupRestriction.php b/typo3/sysext/core/Classes/Database/Query/Restriction/FrontendGroupRestriction.php
new file mode 100644
index 0000000000000000000000000000000000000000..efabc4e49b4b93d28c4d794fcbf440af3d53c4d1
--- /dev/null
+++ b/typo3/sysext/core/Classes/Database/Query/Restriction/FrontendGroupRestriction.php
@@ -0,0 +1,70 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+
+/**
+ * Restriction to filter records, which are limited to the given user groups
+ */
+class FrontendGroupRestriction implements QueryRestrictionInterface
+{
+    /**
+     * @var array
+     */
+    protected $frontendGroupIds;
+
+    /**
+     * @param array $frontendGroupIds Normalized array with user groups of currently logged in user (typically expolded value of $GLOBALS['TSFE']->gr_list
+     */
+    public function __construct(array $frontendGroupIds = null)
+    {
+        $this->frontendGroupIds = $frontendGroupIds === null ? explode(',', $GLOBALS['TSFE']->gr_list) : $frontendGroupIds;
+    }
+
+    /**
+     * 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 name and value potentially an alias
+     * @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 $tableName => $tableAlias) {
+            $groupFieldName = $GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns']['fe_group'] ?? null;
+            if (!empty($groupFieldName)) {
+                $fieldName = ($tableAlias ?: $tableName) . '.' . $groupFieldName;
+                // Allow records where no group access has been configured (field values NULL, 0 or empty string)
+                $constraints = [
+                    $expressionBuilder->isNull($fieldName),
+                    $expressionBuilder->eq($fieldName, $expressionBuilder->literal('')),
+                    $expressionBuilder->eq($fieldName, $expressionBuilder->literal('0')),
+                ];
+                foreach ($this->frontendGroupIds as $frontendGroupId) {
+                    $constraints[] = $expressionBuilder->inSet(
+                        $fieldName,
+                        $expressionBuilder->literal((string)$frontendGroupId)
+                    );
+                }
+            }
+        }
+        return $expressionBuilder->orX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/FrontendRestrictionContainer.php b/typo3/sysext/core/Classes/Database/Query/Restriction/FrontendRestrictionContainer.php
new file mode 100644
index 0000000000000000000000000000000000000000..5444fa0aa1495338a46c40fd21ca15910388bc6d
--- /dev/null
+++ b/typo3/sysext/core/Classes/Database/Query/Restriction/FrontendRestrictionContainer.php
@@ -0,0 +1,78 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
+
+/**
+ * A collection of restrictions to be used in frontend context.
+ * This is a replacement for PageRepository::enableFields()
+ */
+class FrontendRestrictionContainer extends AbstractRestrictionContainer
+{
+    /**
+     * @var QueryRestrictionInterface[]
+     */
+    protected $defaultRestrictionTypes = [
+        DeletedRestriction::class,
+        FrontendWorkspaceRestriction::class,
+        HiddenRestriction::class,
+        StartTimeRestriction::class,
+        EndTimeRestriction::class,
+        FrontendGroupRestriction::class,
+    ];
+
+    /**
+     * FrontendRestrictionContainer constructor.
+     * Initializes the default restrictions for frontend requests
+     */
+    public function __construct()
+    {
+        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 name and value potentially an alias
+     * @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 = [];
+        /** @var TypoScriptFrontendController $typoScriptFrontendController */
+        $typoScriptFrontendController = $GLOBALS['TSFE'];
+        foreach ($this->restrictions as $restriction) {
+            foreach ($queriedTables as $tableName => $tableAlias) {
+                $disableRestriction = false;
+                if ($restriction instanceof HiddenRestriction) {
+                    // If display of hidden records is requested, we must disable the hidden restriction.
+                    $disableRestriction = $tableName === 'pages' ? $typoScriptFrontendController->showHiddenPage : $typoScriptFrontendController->showHiddenRecords;
+                }
+                if (!$disableRestriction) {
+                    $constraints[] = $restriction->buildExpression([$tableName => $tableAlias], $expressionBuilder);
+                }
+            }
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/FrontendWorkspaceRestriction.php b/typo3/sysext/core/Classes/Database/Query/Restriction/FrontendWorkspaceRestriction.php
new file mode 100644
index 0000000000000000000000000000000000000000..744785dd90e84ad143cb4dcfbeeadb5aa40912a0
--- /dev/null
+++ b/typo3/sysext/core/Classes/Database/Query/Restriction/FrontendWorkspaceRestriction.php
@@ -0,0 +1,91 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Core\Versioning\VersionState;
+
+/**
+ * Restriction to filter records for fronted workspaces preview
+ */
+class FrontendWorkspaceRestriction implements QueryRestrictionInterface
+{
+    /**
+     * @var int
+     */
+    protected $workspaceId;
+
+    /**
+     * @var bool
+     */
+    protected $includeRowsForWorkspacePreview;
+
+    /**
+     * @var bool
+     */
+    protected $enforceLiveRowsOnly;
+
+    /**
+     * @param int $workspaceId (PageRepository::$versioningWorkspaceId property)
+     * @param bool $includeRowsForWorkspacePreview (PageRepository::$versioningPreview property)
+     * @param bool $enforceLiveRowsOnly (!$noVersionPreview argument from PageRepository::enableFields()) This is ONLY for use in PageRepository class and most likely will be removed
+     */
+    public function __construct(int $workspaceId = null, bool $includeRowsForWorkspacePreview = null, bool $enforceLiveRowsOnly = true)
+    {
+        $this->workspaceId = $workspaceId === null ? $GLOBALS['TSFE']->sys_page->versioningWorkspaceId : $workspaceId;
+        $this->includeRowsForWorkspacePreview = $includeRowsForWorkspacePreview === null ? $GLOBALS['TSFE']->sys_page->versioningPreview : $includeRowsForWorkspacePreview;
+        $this->enforceLiveRowsOnly = $enforceLiveRowsOnly;
+    }
+
+    /**
+     * Main method to build expressions for given tables
+     * Evaluates the ctrl/versioningWS flag of the table and adds various workspace related restrictions if set
+     *
+     * @param array $queriedTables Array of tables, where array key is table name and value potentially an alias
+     * @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 $tableName => $tableAlias) {
+            $workspaceEnabled = $GLOBALS['TCA'][$tableName]['ctrl']['versioningWS'] ?? null;
+            if (!empty($workspaceEnabled)) {
+                $tablePrefix = $tableAlias ?: $tableName;
+                if (!$this->includeRowsForWorkspacePreview) {
+                    // Filter out placeholder records (new/moved/deleted items)
+                    // in case we are NOT in a versioning preview (That means we are online!)
+                    $constraints[] = $expressionBuilder->lte(
+                        $tablePrefix . '.t3ver_state',
+                        new VersionState(VersionState::DEFAULT_STATE)
+                    );
+                } elseif ($tableName !== 'pages') {
+                    // Show only records of the live and current workspace in case we are in a versioning preview
+                    $constraints[] = $expressionBuilder->orX(
+                        $expressionBuilder->eq($tablePrefix . '.t3ver_wsid', 0),
+                        $expressionBuilder->eq($tablePrefix . '.t3ver_wsid', (int)$this->workspaceId)
+                    );
+                }
+                // Filter out versioned records
+                if ($this->enforceLiveRowsOnly) {
+                    $constraints[] = $expressionBuilder->neq($tablePrefix . '.pid', -1);
+                }
+            }
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/HiddenRestriction.php b/typo3/sysext/core/Classes/Database/Query/Restriction/HiddenRestriction.php
new file mode 100644
index 0000000000000000000000000000000000000000..227262aaff6768434a0e20aea63c280a266fe02e
--- /dev/null
+++ b/typo3/sysext/core/Classes/Database/Query/Restriction/HiddenRestriction.php
@@ -0,0 +1,49 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+
+/**
+ * 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 name and value potentially an alias
+     * @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 $tableName => $tableAlias) {
+            $hiddenFieldName = $GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns']['disabled'] ?? null;
+            if (!empty($hiddenFieldName)) {
+                $tablePrefix = $tableAlias ?: $tableName;
+                $constraints[] = $expressionBuilder->eq(
+                    $tablePrefix . '.' . $hiddenFieldName,
+                    0
+                );
+            }
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/QueryRestrictionContainerInterface.php b/typo3/sysext/core/Classes/Database/Query/Restriction/QueryRestrictionContainerInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..6abc72d10c50cd815c394656978a8f454b6facfa
--- /dev/null
+++ b/typo3/sysext/core/Classes/Database/Query/Restriction/QueryRestrictionContainerInterface.php
@@ -0,0 +1,46 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * 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!
+ */
+
+/**
+ * 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
+     *
+     * @param QueryRestrictionInterface $restriction
+     * @return QueryRestrictionContainerInterface
+     */
+    public function add(QueryRestrictionInterface $restriction);
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/QueryRestrictionInterface.php b/typo3/sysext/core/Classes/Database/Query/Restriction/QueryRestrictionInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..914e8c0811225fd8a61205e1189c83a95e883c95
--- /dev/null
+++ b/typo3/sysext/core/Classes/Database/Query/Restriction/QueryRestrictionInterface.php
@@ -0,0 +1,34 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * 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 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 name and value potentially an alias
+     * @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;
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/RootLevelRestriction.php b/typo3/sysext/core/Classes/Database/Query/Restriction/RootLevelRestriction.php
new file mode 100644
index 0000000000000000000000000000000000000000..ab56b85219f39ea61f000d0d84b433e906ee7655
--- /dev/null
+++ b/typo3/sysext/core/Classes/Database/Query/Restriction/RootLevelRestriction.php
@@ -0,0 +1,60 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * 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 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;
+
+    /**
+     * @param array $tableNames
+     */
+    public function __construct(array $tableNames = array())
+    {
+        $this->tableNames = $tableNames;
+    }
+
+    /**
+     * Main method to build expressions for given tables
+     *
+     * @param array $queriedTables Array of tables, where array key is table name and value potentially an alias
+     * @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 $tableName => $tableAlias) {
+            $tablePrefix = $tableAlias ?: $tableName;
+            if (empty($this->tableNames) || in_array($tablePrefix, $this->tableNames, true)) {
+                $constraints[] = $expressionBuilder->eq(
+                    $tablePrefix . '.pid',
+                    0
+                );
+            }
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/StartTimeRestriction.php b/typo3/sysext/core/Classes/Database/Query/Restriction/StartTimeRestriction.php
new file mode 100644
index 0000000000000000000000000000000000000000..cbc960acbb55790949d100770a64057ae5192ca5
--- /dev/null
+++ b/typo3/sysext/core/Classes/Database/Query/Restriction/StartTimeRestriction.php
@@ -0,0 +1,69 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+
+/**
+ * Restriction to filter records, that should not be shown until the start time has been reached
+ */
+class StartTimeRestriction implements QueryRestrictionInterface
+{
+    /**
+     * @param int $accessTimeStamp
+     */
+    public function __construct(int $accessTimeStamp = null)
+    {
+        $this->accessTimeStamp = $accessTimeStamp ?: ($GLOBALS['SIM_ACCESS_TIME'] ?? null);
+    }
+
+    /**
+     * @var int
+     */
+    protected $accessTimeStamp;
+
+    /**
+     * 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 name and value potentially an alias
+     * @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
+    {
+        $constraints = [];
+        foreach ($queriedTables as $tableName => $tableAlias) {
+            $startTimeFieldName = $GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns']['starttime'] ?? null;
+            if (!empty($startTimeFieldName)) {
+                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
+                    );
+                }
+                $tablePrefix = $tableAlias ?: $tableName;
+                $constraints[] = $expressionBuilder->lte(
+                    $tablePrefix . '.' . $startTimeFieldName,
+                    (int)$this->accessTimeStamp
+                );
+            }
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Documentation/Changelog/8.1/Feature-75454-DoctrineDBALForDatabaseConnections.rst b/typo3/sysext/core/Documentation/Changelog/8.1/Feature-75454-DoctrineDBALForDatabaseConnections.rst
index d2eab26756829991343e895d0fe97e5c62664976..81831f33747dd4bc8cd86b79f9ded418d30c18a3 100644
--- a/typo3/sysext/core/Documentation/Changelog/8.1/Feature-75454-DoctrineDBALForDatabaseConnections.rst
+++ b/typo3/sysext/core/Documentation/Changelog/8.1/Feature-75454-DoctrineDBALForDatabaseConnections.rst
@@ -57,13 +57,13 @@ The :php:``ConnectionPool`` class can be used like this:
    $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('aTable);
    $query->select('*')
       ->from('aTable)
-      ->where($query->expr()->eq('aField', $query->createNamedParameter($aValue)))
-      ->andWhere(
+      ->where(
+         $query->expr()->eq('aField', $query->createNamedParameter($aValue)),
          $query->expr()->lte(
             'anotherField',
             $query->createNamedParameter($anotherValue)
          )
-      )
+      );
    $rows = $query->execute()->fetchAll();
 
 Extension authors are advised to use the ``ConnectionPool`` and ``Connection`` classes instead of using
diff --git a/typo3/sysext/core/Tests/Unit/Authentication/AbstractUserAuthenticationTest.php b/typo3/sysext/core/Tests/Unit/Authentication/AbstractUserAuthenticationTest.php
index 045a9064db900c857618522ddc1596d8cf38aa8e..b49bbc1865d822c0d26c296bbdcb4d66619f6a49 100644
--- a/typo3/sysext/core/Tests/Unit/Authentication/AbstractUserAuthenticationTest.php
+++ b/typo3/sysext/core/Tests/Unit/Authentication/AbstractUserAuthenticationTest.php
@@ -39,8 +39,9 @@ class AbstractUserAuthenticationTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         $connection->getDatabasePlatform()->willReturn(new MockPlatform());
         $connection->getExpressionBuilder()->willReturn(new ExpressionBuilder($connection->reveal()));
 
-        $queryBuilder = GeneralUtility::makeInstance(
-            QueryBuilder::class,
+        // TODO: This should rather be a functional test if we need a query builder
+        // or we should clean up the code itself to not need to mock internal behavior here
+        $queryBuilder = new QueryBuilder(
             $connection->reveal(),
             null,
             $this->prophesize(\Doctrine\DBAL\Query\QueryBuilder::class)->reveal()
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php
index ec91ddb69c25f4cbce4e9889f4be1b103a7cdd6e..e94e0df2bd83072a416e83e4b4a827744926c86b 100644
--- a/typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php
+++ b/typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php
@@ -19,6 +19,7 @@ use Prophecy\Argument;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Tests\Unit\Database\Mocks\MockPlatform;
 use TYPO3\CMS\Core\Tests\UnitTestCase;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -922,7 +923,7 @@ class QueryBuilderTest extends UnitTestCase
             ->from('pages')
             ->where('uid=1');
 
-        $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND ((pages.hidden = 0) AND (pages.deleted = 0))';
+        $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND ((pages.deleted = 0) AND (pages.hidden = 0))';
         $this->connection->executeQuery($expectedSQL, Argument::cetera())
             ->shouldBeCalled();
 
@@ -969,7 +970,7 @@ class QueryBuilderTest extends UnitTestCase
             ->from('pages')
             ->where('uid=1');
 
-        $expectedSQL = 'SELECT COUNT(uid) FROM pages WHERE (uid=1) AND ((pages.hidden = 0) AND (pages.deleted = 0))';
+        $expectedSQL = 'SELECT COUNT(uid) FROM pages WHERE (uid=1) AND ((pages.deleted = 0) AND (pages.hidden = 0))';
         $this->connection->executeQuery($expectedSQL, Argument::cetera())
             ->shouldBeCalled();
 
@@ -1014,12 +1015,10 @@ class QueryBuilderTest extends UnitTestCase
             ->from('pages')
             ->where('uid=1');
 
-        $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND ((pages.hidden = 0) AND (pages.deleted = 0))';
+        $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND ((pages.deleted = 0) AND (pages.hidden = 0))';
         $this->assertSame($expectedSQL, $subject->getSQL());
 
-        $subject->getQueryContext()
-            ->setIgnoreEnableFields(true)
-            ->setIgnoredEnableFields(['disabled']);
+        $subject->getRestrictions()->removeAll()->add(new DeletedRestriction());
 
         $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND (pages.deleted = 0)';
         $this->assertSame($expectedSQL, $subject->getSQL());
@@ -1063,9 +1062,7 @@ class QueryBuilderTest extends UnitTestCase
             ->from('pages')
             ->where('uid=1');
 
-        $subject->getQueryContext()
-            ->setIgnoreEnableFields(true)
-            ->setIgnoredEnableFields(['disabled']);
+        $subject->getRestrictions()->removeAll()->add(new DeletedRestriction());
 
         $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND (pages.deleted = 0)';
         $this->connection->executeQuery($expectedSQL, Argument::cetera())
@@ -1073,10 +1070,9 @@ class QueryBuilderTest extends UnitTestCase
 
         $subject->execute();
 
-        $subject->getQueryContext()
-            ->setIgnoreEnableFields(false);
+        $subject->resetRestrictions();
 
-        $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND ((pages.hidden = 0) AND (pages.deleted = 0))';
+        $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND ((pages.deleted = 0) AND (pages.hidden = 0))';
         $this->connection->executeQuery($expectedSQL, Argument::cetera())
             ->shouldBeCalled();
 
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/QueryContextTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/QueryContextTest.php
deleted file mode 100644
index a3d28625020027f32bbd6cbe4d9f08e21120812a..0000000000000000000000000000000000000000
--- a/typo3/sysext/core/Tests/Unit/Database/Query/QueryContextTest.php
+++ /dev/null
@@ -1,283 +0,0 @@
-<?php
-declare (strict_types = 1);
-namespace TYPO3\CMS\Core\Tests\Unit\Database\Query;
-
-/*
- * 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 Prophecy\Prophecy\ObjectProphecy;
-use TYPO3\CMS\Core\Database\Query\QueryContext;
-use TYPO3\CMS\Core\Tests\UnitTestCase;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
-use TYPO3\CMS\Frontend\Page\PageRepository;
-
-class QueryContextTest extends UnitTestCase
-{
-    /**
-     * @var QueryContext
-     */
-    protected $subject;
-
-    /**
-     * @var TypoScriptFrontendController|ObjectProphecy
-     */
-    protected $typoScriptFrontendController;
-
-    /**
-     * Create a new database connection mock object for every test.
-     *
-     * @return void
-     */
-    protected function setUp()
-    {
-        parent::setUp();
-
-        $this->typoScriptFrontendController = $this->prophesize(TypoScriptFrontendController::class);
-        $GLOBALS['TSFE'] = $this->typoScriptFrontendController->reveal();
-
-        $this->subject = GeneralUtility::makeInstance(QueryContext::class);
-    }
-
-    /**
-     * @test
-     */
-    public function contextCanBeSetByConstructiorArgument()
-    {
-        $subject = GeneralUtility::makeInstance(QueryContext::class, 'FRONTEND');
-
-        $this->assertSame('FRONTEND', $subject->getContext());
-    }
-
-    /**
-     * @test
-     * @expectedException \TYPO3\CMS\Core\Type\Exception\InvalidEnumerationValueException
-     * @expectedExceptionMessage Invalid value DUMMY for TYPO3\CMS\Core\Database\Query\QueryContextType
-     */
-    public function unknownContextThrowExceptionInConstructor()
-    {
-        GeneralUtility::makeInstance(QueryContext::class, 'DUMMY');
-    }
-
-    /**
-     * @test
-     * @expectedException \TYPO3\CMS\Core\Type\Exception\InvalidEnumerationValueException
-     * @expectedExceptionMessage Invalid value DUMMY for TYPO3\CMS\Core\Database\Query\QueryContextType
-     */
-    public function unknownContextThrowExceptionWhenSet()
-    {
-        $this->subject->setContext('DUMMY');
-    }
-
-    /**
-     * @test
-     */
-    public function getMemberGroupsPrefersExplicitlySetInformation()
-    {
-        $GLOBALS['TSFE']->gr_list = '3,5';
-        $this->subject->setMemberGroups([1, 2]);
-
-        $this->assertSame([1, 2], $this->subject->getMemberGroups());
-    }
-
-    /**
-     * @test
-     */
-    public function getMemberGroupsFallsBackToTSFE()
-    {
-        $GLOBALS['TSFE']->gr_list = '3,5';
-
-        $this->assertSame([3, 5], $this->subject->getMemberGroups());
-    }
-
-    /**
-     * @test
-     */
-    public function getCurrentWorkspacePrefersExplicitlySetInformation()
-    {
-        /** @var PageRepository|ObjectProphecy $pageRepository */
-        $pageRepository = $this->prophesize(PageRepository::class);
-        $pageRepository->versioningWorkspaceId = 3;
-
-        $GLOBALS['TSFE']->sys_page = $pageRepository->reveal();
-
-        $this->subject->setCurrentWorkspace(1);
-        $this->subject->setContext('FRONTEND');
-
-        $this->assertSame(1, $this->subject->getCurrentWorkspace());
-    }
-
-    /**
-     * @test
-     */
-    public function getCurrentWorkspaceFallsBackToTSFE()
-    {
-        /** @var PageRepository|ObjectProphecy $pageRepository */
-        $pageRepository = $this->prophesize(PageRepository::class);
-        $pageRepository->versioningWorkspaceId = 3;
-
-        $GLOBALS['TSFE']->sys_page = $pageRepository->reveal();
-
-        $this->subject->setContext('FRONTEND');
-
-        $this->assertSame(3, $this->subject->getCurrentWorkspace());
-    }
-
-    /**
-     * @test
-     */
-    public function getAccessTimePrefersExplicitlySetInformation()
-    {
-        $GLOBALS['SIM_ACCESS_TIME'] = 100;
-        $this->subject->setAccessTime(200);
-
-        $this->assertSame(200, $this->subject->getAccessTime());
-    }
-
-    /**
-     * @test
-     */
-    public function getAccessTimeFallsBackToTSFE()
-    {
-        $GLOBALS['SIM_ACCESS_TIME'] = 100;
-
-        $this->assertSame(100, $this->subject->getAccessTime());
-    }
-
-    /**
-     * @test
-     */
-    public function getIncludeHiddenForTablePrefersExplicitlySetInformation()
-    {
-        $GLOBALS['TSFE']->showHiddenPage = false;
-        $GLOBALS['TSFE']->showHiddenRecords = false;
-        $this->subject->setIncludeHidden(true);
-
-        $this->assertSame(true, $this->subject->getIncludeHiddenForTable('pages'));
-    }
-
-    /**
-     * @test
-     */
-    public function getIncludeHiddenForTablePagesFallsBackToTSFE()
-    {
-        $GLOBALS['TSFE']->showHiddenPage = true;
-
-        $this->assertSame(true, $this->subject->getIncludeHiddenForTable('pages'));
-    }
-
-    /**
-     * @test
-     */
-    public function getIncludeHiddenForTablePagesLanguageOverlayFallsBackToTSFE()
-    {
-        $GLOBALS['TSFE']->showHiddenPage = true;
-
-        $this->assertSame(true, $this->subject->getIncludeHiddenForTable('pages'));
-    }
-
-    /**
-     * @test
-     */
-    public function getIncludeHiddenForRecordsFallsBackToTSFE()
-    {
-        $GLOBALS['TSFE']->showHiddenRecords = true;
-
-        $this->assertSame(true, $this->subject->getIncludeHiddenForTable('tt_content'));
-    }
-
-    /**
-     * @test
-     */
-    public function getIncludePlaceholdersPrefersExplicitlySetInformation()
-    {
-        $this->subject->setIncludePlaceholders(true);
-
-        $this->assertSame(true, $this->subject->getIncludePlaceholders());
-    }
-
-    /**
-     * @test
-     */
-    public function getIncludePlaceholdersFallsBackToTSFE()
-    {
-        /** @var PageRepository|ObjectProphecy $pageRepository */
-        $pageRepository = $this->prophesize(PageRepository::class);
-        $pageRepository->versioningPreview = true;
-
-        $GLOBALS['TSFE']->sys_page = $pageRepository->reveal();
-
-        $this->subject->setContext('FRONTEND');
-        $this->assertSame(true, $this->subject->getIncludePlaceholders());
-    }
-
-    /**
-     * @test
-     */
-    public function getIgnoredEnableFieldsForTableFallsBackToGlobalList()
-    {
-        $this->subject->setIgnoredEnableFields(['disabled']);
-
-        $this->assertSame(['disabled'], $this->subject->getIgnoredEnableFieldsForTable('pages'));
-    }
-
-    /**
-     * @test
-     */
-    public function getIgnoredEnableFieldsForTablePrefersExplictlySetInformation()
-    {
-        $this->subject->setIgnoredEnableFields(['disabled']);
-        $this->subject->setIgnoredEnableFieldsForTable('pages', ['starttime', 'endtime']);
-
-        $this->assertSame(['starttime', 'endtime'], $this->subject->getIgnoredEnableFieldsForTable('pages'));
-    }
-
-    /**
-     * @test
-     */
-    public function getIgnoredEnableFieldsForTableReturnsEmptyArrayWithoutInformation()
-    {
-        $this->assertSame([], $this->subject->getIgnoredEnableFieldsForTable('pages'));
-    }
-
-    /**
-     * @test
-     */
-    public function getTableConfigPrefersExplicitlySetInformation()
-    {
-        $this->subject->setTableConfigs(['pages' => ['delete' => 'deleted']]);
-        $GLOBALS['TCA']['pages']['ctrl'] = ['delete' => 'deleted'];
-
-        $this->assertSame(['delete' => 'deleted'], $this->subject->getTableConfig('pages'));
-    }
-
-    /**
-     * @test
-     */
-    public function getTableConfigFallsBackToTCA()
-    {
-        $GLOBALS['TCA']['pages']['ctrl'] = [
-            'label' => 'title',
-            'tstamp' => 'tstamp',
-            'delete' => 'deleted',
-            'enablecolumns' => [
-                'disabled' => 'hidden',
-            ],
-        ];
-
-        $this->assertSame(
-            ['delete' => 'deleted', 'enablecolumns' => ['disabled' => 'hidden']],
-            $this->subject->getTableConfig('pages')
-        );
-    }
-}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/QueryRestrictionBuilderTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/QueryRestrictionBuilderTest.php
deleted file mode 100644
index bf0ce978859c8849da95ffe39d253b0d49f87e49..0000000000000000000000000000000000000000
--- a/typo3/sysext/core/Tests/Unit/Database/Query/QueryRestrictionBuilderTest.php
+++ /dev/null
@@ -1,942 +0,0 @@
-<?php
-declare (strict_types = 1);
-namespace TYPO3\CMS\Core\Tests\Unit\Database\Query;
-
-/*
- * 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 Prophecy\Argument;
-use TYPO3\CMS\Core\Database\Connection;
-use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
-use TYPO3\CMS\Core\Database\Query\QueryContext;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
-use TYPO3\CMS\Core\Database\Query\QueryRestrictionBuilder;
-use TYPO3\CMS\Core\Tests\Unit\Database\Mocks\MockPlatform;
-use TYPO3\CMS\Core\Tests\UnitTestCase;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-
-class QueryRestrictionBuilderTest extends UnitTestCase
-{
-    /**
-     * @var array
-     */
-    protected $defaultTableConfig = [
-        'versioningWS' => true,
-        'delete' => 'deleted',
-        'enablecolumns' => [
-            'disabled' => 'hidden',
-            'starttime' => 'starttime',
-            'endtime' => 'endtime',
-            'fe_group' => 'fe_group',
-        ],
-    ];
-
-    /**
-     * @var \TYPO3\CMS\Frontend\Page\PageRepository
-     */
-    protected $pageRepository;
-
-    /**
-     * @var \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
-     */
-    protected $expressionBuilder;
-
-    /**
-     * @var Connection|\Prophecy\Prophecy\ObjectProphecy
-     */
-    protected $connection;
-
-    /**
-     * @var QueryContext
-     */
-    protected $queryContext;
-
-    /**
-     * Create a new database connection mock object for every test.
-     *
-     * @return void
-     */
-    protected function setUp()
-    {
-        parent::setUp();
-
-        $this->connection = $this->prophesize(Connection::class);
-        $this->connection->quoteIdentifier(Argument::cetera())->will(function ($args) {
-            return '"' . join('"."', explode('.', $args[0])) . '"';
-        });
-        $this->connection->quote(Argument::cetera())->will(function ($args) {
-            return "'" . $args[0] . "'";
-        });
-        $this->connection->getDatabasePlatform()->willReturn(new MockPlatform());
-
-        $this->queryContext = GeneralUtility::makeInstance(QueryContext::class);
-        $this->expressionBuilder = GeneralUtility::makeInstance(ExpressionBuilder::class, $this->connection->reveal());
-    }
-
-    /**
-     * @test
-     */
-    public function getVisibilityConstraintsReturnsEmptyConstraintForUnrestrictedContext()
-    {
-        $this->queryContext->setContext(QueryContextType::UNRESTRICTED);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            [],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $this->assertEmpty((string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsSkipsUnconfiguredTables()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $this->assertEmpty((string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithDefaultSettings()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithUserGroups()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setMemberGroups([1, 2])
-            ->setTableConfigs(['pages' => $this->defaultTableConfig]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\') OR (FIND_IN_SET(\'1\', "pages"."fe_group")) OR (FIND_IN_SET(\'2\', "pages"."fe_group")))'
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithVersioningPreview()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setIncludePlaceholders(true)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = '("pages"."deleted" = 0) AND ("pages"."pid" <> -1)';
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithVersioningPreviewAndNoPreviewSet()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setIncludePlaceholders(true)
-            ->setIncludeVersionedRecords(true)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithoutDisabledColumn()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => [
-                'versioningWS' => true,
-                'delete' => 'deleted',
-                'enablecolumns' => [
-                    'starttime' => 'starttime',
-                    'endtime' => 'endtime',
-                    'fe_group' => 'fe_group',
-                ]
-            ]]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithoutStarttimeColumn()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => [
-                    'versioningWS' => true,
-                    'delete' => 'deleted',
-                    'enablecolumns' => [
-                        'disabled' => 'hidden',
-                        'endtime' => 'endtime',
-                        'fe_group' => 'fe_group',
-                    ]
-                ]
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithoutEndtimeColumn()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => [
-                    'versioningWS' => true,
-                    'delete' => 'deleted',
-                    'enablecolumns' => [
-                        'disabled' => 'hidden',
-                        'starttime' => 'starttime',
-                        'fe_group' => 'fe_group',
-                    ]
-                ]
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithoutUsergroupsColumn()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => [
-                    'versioningWS' => true,
-                    'delete' => 'deleted',
-                    'enablecolumns' => [
-                        'disabled' => 'hidden',
-                        'starttime' => 'starttime',
-                        'endtime' => 'endtime',
-                    ]
-                ]
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithIgnoreEnableFieldsSet()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig])
-            ->setIgnoreEnableFields(true);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = '"pages"."deleted" = 0';
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * Data provider for getFrontendVisibilityRestrictionsWithSelectiveIgnoreEnableFieldsSet
-     *
-     * @return array
-     */
-    public function getFrontendVisibilityRestrictionsIgnoreEnableFieldsDataProvider()
-    {
-        return [
-            'disabled' => [
-                ['disabled'],
-            ],
-            'starttime' => [
-                ['starttime'],
-            ],
-            'endtime' => [
-                ['endtime'],
-            ],
-            'starttime, endtime' => [
-                ['starttime', 'endtime'],
-            ],
-            'fe_group' => [
-                ['fe_group'],
-            ],
-            'disabled, starttime, endtime' => [
-                ['disabled', 'starttime', 'endtime'],
-            ],
-            'disabled, starttime, endtime, fe_group' => [
-                ['disabled', 'starttime', 'endtime', 'fe_group'],
-            ],
-        ];
-    }
-
-    /**
-     * @test
-     * @dataProvider getFrontendVisibilityRestrictionsIgnoreEnableFieldsDataProvider
-     * @param string[] $ignoreFields
-     */
-    public function getFrontendVisibilityRestrictionsWithSelectiveIgnoreEnableFieldsSet(array $ignoreFields)
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig])
-            ->setIgnoreEnableFields(true)
-            ->setIgnoredEnableFields($ignoreFields);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSqlFragments = [
-            'deleted' => '("pages"."deleted" = 0)',
-            'versioningWS' => '("pages"."t3ver_state" <= 0) AND ("pages"."pid" <> -1)',
-            'disabled' => '("pages"."hidden" = 0)',
-            'starttime' => '("pages"."starttime" <= 1459706700)',
-            'endtime' => '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            'fe_group' => '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ];
-
-        foreach ($ignoreFields as $fragmentName) {
-            unset($expectedSqlFragments[$fragmentName]);
-        }
-
-        $this->assertSame(join(' AND ', $expectedSqlFragments), (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsForMultipleTablesWithDefaultSettings()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => $this->defaultTableConfig,
-                'tt_content' => $this->defaultTableConfig,
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => '', 'tt_content' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSqlPages = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $expectedSqlTtContent = join(' AND ', [
-            '("tt_content"."deleted" = 0)',
-            '("tt_content"."t3ver_state" <= 0)',
-            '("tt_content"."pid" <> -1)',
-            '("tt_content"."hidden" = 0)',
-            '("tt_content"."starttime" <= 1459706700)',
-            '(("tt_content"."endtime" = 0) OR ("tt_content"."endtime" > 1459706700))',
-            '(("tt_content"."fe_group" IS NULL) OR ("tt_content"."fe_group" = \'\') OR ("tt_content"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame(
-            '(' . $expectedSqlPages . ') AND (' . $expectedSqlTtContent . ')',
-            (string)$subject->getVisibilityConstraints()
-        );
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsForMultipleTablesWithIgnoreEnableFields()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => $this->defaultTableConfig,
-                'tt_content' => $this->defaultTableConfig,
-            ])
-            ->setIgnoreEnableFields(true);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => '', 'tt_content' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("tt_content"."deleted" = 0)',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsForMultipleTablesWithDifferentEnableColumns()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => $this->defaultTableConfig,
-                'tt_content' => [
-                    'versioningWS' => false,
-                    'delete' => 'deleted',
-                    'enablecolumns' => [
-                        'disabled' => 'hidden',
-                    ],
-                ],
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => '', 'tt_content' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame(
-            '(' . $expectedSql . ') AND (("tt_content"."deleted" = 0) AND ("tt_content"."hidden" = 0))',
-            (string)$subject->getVisibilityConstraints()
-        );
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsForJoinedTablesWithDefaultSettings()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => $this->defaultTableConfig,
-                'tt_content' => $this->defaultTableConfig,
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => '', 'tt_content' => 't'],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSqlPages = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $expectedSqlTtContent = join(' AND ', [
-            '("t"."deleted" = 0)',
-            '("t"."t3ver_state" <= 0)',
-            '("t"."pid" <> -1)',
-            '("t"."hidden" = 0)',
-            '("t"."starttime" <= 1459706700)',
-            '(("t"."endtime" = 0) OR ("t"."endtime" > 1459706700))',
-            '(("t"."fe_group" IS NULL) OR ("t"."fe_group" = \'\') OR ("t"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame(
-            '(' . $expectedSqlPages . ') AND (' . $expectedSqlTtContent . ')',
-            (string)$subject->getVisibilityConstraints()
-        );
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsForJoinedTablesWithIgnoreEnableFields()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => $this->defaultTableConfig,
-                'tt_content' => $this->defaultTableConfig,
-            ])
-            ->setIgnoreEnableFields(true);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => '', 'tt_content' => 't'],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("t"."deleted" = 0)',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsForJoinedTablesWithDifferentEnableColumns()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => $this->defaultTableConfig,
-                'tt_content' => [
-                    'versioningWS' => false,
-                    'delete' => 'deleted',
-                    'enablecolumns' => [
-                        'disabled' => 'hidden',
-                    ],
-                ],
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => '', 'tt_content' => 't'],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame(
-            '(' . $expectedSql . ') AND (("t"."deleted" = 0) AND ("t"."hidden" = 0))',
-            (string)$subject->getVisibilityConstraints()
-        );
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsSkipsUnconfiguredTables()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $this->assertEmpty((string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsWithDefaultSettings()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '("pages"."deleted" = 0)',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsWithoutDisabledColumn()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => [
-                'versioningWS' => true,
-                'delete' => 'deleted',
-                'enablecolumns' => [
-                    'starttime' => 'starttime',
-                    'endtime' => 'endtime',
-                    'fe_group' => 'fe_group',
-                ],
-            ]]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '("pages"."deleted" = 0)',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsWithoutStarttimeColumn()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => [
-                    'versioningWS' => true,
-                    'delete' => 'deleted',
-                    'enablecolumns' => [
-                        'disabled' => 'hidden',
-                        'endtime' => 'endtime',
-                        'fe_group' => 'fe_group',
-                    ],
-                ]
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."hidden" = 0)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '("pages"."deleted" = 0)',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsWithoutEndtimeColumn()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => [
-                    'versioningWS' => true,
-                    'delete' => 'deleted',
-                    'enablecolumns' => [
-                        'disabled' => 'hidden',
-                        'starttime' => 'starttime',
-                        'fe_group' => 'fe_group',
-                    ],
-                ]
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '("pages"."deleted" = 0)',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsWithIgnoreEnableFieldsSet()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig])
-            ->setIgnoreEnableFields(true);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = '"pages"."deleted" = 0';
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsWithIncludeDeletedSet()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig])
-            ->setIncludeDeleted(true);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            'disabled' => '("pages"."hidden" = 0)',
-            'starttime' => '("pages"."starttime" <= 1459706700)',
-            'endtime' => '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsWithNoVersionPlaceholdersContext()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND_NO_VERSIONING_PLACEHOLDERS)
-            ->setCurrentWorkspace(4)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            'disabled' => '("pages"."hidden" = 0)',
-            'starttime' => '("pages"."starttime" <= 1459706700)',
-            'endtime' => '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            'deleted' => '("pages"."deleted" = 0)',
-            'placeholders' => '(("pages"."t3ver_state" <= 0) OR ("pages"."t3ver_wsid" = 4))',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsWithoutRestrictions()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig])
-            ->setIncludeDeleted(true)
-            ->setIgnoreEnableFields(true);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $this->assertSame('', (string)$subject->getVisibilityConstraints());
-    }
-
-    // @todo: Test for per table overrides
-}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/AbstractRestrictionTestCase.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/AbstractRestrictionTestCase.php
new file mode 100644
index 0000000000000000000000000000000000000000..6ee152c7088546210cb46e342b8a83ff523a5b34
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/AbstractRestrictionTestCase.php
@@ -0,0 +1,49 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * 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 Prophecy\Argument;
+use TYPO3\CMS\Core\Database\Connection;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Core\Tests\Unit\Database\Mocks\MockPlatform;
+use TYPO3\CMS\Core\Tests\UnitTestCase;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+class AbstractRestrictionTestCase extends UnitTestCase
+{
+    /**
+     * @var \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+     */
+    protected $expressionBuilder;
+
+    /**
+     * Create a new database connection mock object for every test.
+     */
+    protected function setUp()
+    {
+        /** @var Connection|\Prophecy\Prophecy\ObjectProphecy $connection */
+        $connection = $this->prophesize(Connection::class);
+        $connection->quoteIdentifier(Argument::cetera())->will(function ($args) {
+            return '"' . implode('"."', explode('.', $args[0])) . '"';
+        });
+        $connection->quote(Argument::cetera())->will(function ($args) {
+            return '\'' . $args[0] . '\'';
+        });
+        $connection->getDatabasePlatform()->willReturn(new MockPlatform());
+
+        $this->expressionBuilder = GeneralUtility::makeInstance(ExpressionBuilder::class, $connection->reveal());
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/BackendWorkspaceRestrictionTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/BackendWorkspaceRestrictionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d0863dfa372a7a241895097f40339173e9e9bf1b
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/BackendWorkspaceRestrictionTest.php
@@ -0,0 +1,81 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
+
+class BackendWorkspaceRestrictionTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsLiveWorkspaceWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'versioningWS' => 2,
+        ];
+        $subject = new BackendWorkspaceRestriction(0);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."t3ver_wsid" = 0) OR ("aTable"."t3ver_state" <= 0)', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsNonLiveWorkspaceWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'versioningWS' => 2,
+        ];
+        $subject = new BackendWorkspaceRestriction(42);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."t3ver_wsid" = 42) OR ("aTable"."t3ver_state" <= 0)', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsLiveWorkspaceLimitedWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'versioningWS' => 2,
+        ];
+        $subject = new BackendWorkspaceRestriction(0, false);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."t3ver_wsid" = 0) AND ("aTable"."pid" <> -1)', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsNonLiveWorkspaceLimitedWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'versioningWS' => 2,
+        ];
+        $subject = new BackendWorkspaceRestriction(42, false);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."t3ver_wsid" = 42) AND ("aTable"."pid" = -1)', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/DefaultRestrictionContainerTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/DefaultRestrictionContainerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..fa48e0468027ec250136f2f51c9ed9b421267222
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/DefaultRestrictionContainerTest.php
@@ -0,0 +1,51 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
+
+class DefaultRestrictionContainerTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsAllDefaultRestrictions()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'delete' => 'deleted',
+            'enablecolumns' => [
+                'disabled' => 'myHiddenField',
+                'starttime' => 'myStartTimeField',
+                'endtime' => 'myEndTimeField',
+            ],
+        ];
+        $GLOBALS['SIM_ACCESS_TIME'] = 123;
+        $subject = new DefaultRestrictionContainer();
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $more[] = $expression;
+        $expression = $this->expressionBuilder->andX($expression);
+
+        $this->assertSame('("aTable"."deleted" = 0) AND ("aTable"."myHiddenField" = 0) AND ("aTable"."myStartTimeField" <= 123) AND (("aTable"."myEndTimeField" = 0) OR ("aTable"."myEndTimeField" > 123))', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/DeletedRestrictionTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/DeletedRestrictionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..686019050c6e40c3f1c4a7652dd57f6b75613ed3
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/DeletedRestrictionTest.php
@@ -0,0 +1,42 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+
+class DeletedRestrictionTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsDeletedWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'delete' => 'deleted',
+        ];
+        $subject = new DeletedRestriction();
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('"aTable"."deleted" = 0', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/EndTimeRestrictionTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/EndTimeRestrictionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2e9c74207c8d01e3e5471085e331c6c78bda3b75
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/EndTimeRestrictionTest.php
@@ -0,0 +1,64 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
+
+class EndTimeRestrictionTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsThrowsExceptionInStartTimeIfGlobalsAccessTimeIsMissing()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'enablecolumns' => [
+                'endtime' => 'myEndTimeField',
+            ],
+        ];
+        unset($GLOBALS['SIM_ACCESS_TIME']);
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionCode(1462821084);
+
+        $subject = new EndTimeRestriction();
+        $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsStartTimeWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'enablecolumns' => [
+                'endtime' => 'myEndTimeField',
+            ],
+        ];
+
+        $subject = new EndTimeRestriction(42);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."myEndTimeField" = 0) OR ("aTable"."myEndTimeField" > 42)', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendGroupRestrictionTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendGroupRestrictionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..38a4b8fdb3c5e819f849f61523a868d7a3e0eac5
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendGroupRestrictionTest.php
@@ -0,0 +1,59 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Restriction\FrontendGroupRestriction;
+
+class FrontendGroupRestrictionTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsNoAccessGroupWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'enablecolumns' => [
+                'fe_group' => 'myGroupField',
+            ],
+        ];
+        $subject = new FrontendGroupRestriction([]);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."myGroupField" IS NULL) OR ("aTable"."myGroupField" = \'\') OR ("aTable"."myGroupField" = \'0\')', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsGroupWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'enablecolumns' => [
+                'fe_group' => 'myGroupField',
+            ],
+        ];
+        $subject = new FrontendGroupRestriction([2, 3]);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."myGroupField" IS NULL) OR ("aTable"."myGroupField" = \'\') OR ("aTable"."myGroupField" = \'0\') OR (FIND_IN_SET(\'2\', "aTable"."myGroupField")) OR (FIND_IN_SET(\'3\', "aTable"."myGroupField"))', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendRestrictionContainerTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendRestrictionContainerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0927168e5b39db1f68dc8c3a98b72bff36aeaf1b
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendRestrictionContainerTest.php
@@ -0,0 +1,185 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\DatabaseConnection;
+use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
+use TYPO3\CMS\Frontend\Page\PageRepository;
+
+class FrontendRestrictionContainerTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    public function frontendStatesDataProvider()
+    {
+        return [
+            'Live, no preview' => [
+                'tableName' => 'aTable',
+                'workspaceId' => 0,
+                'workspacePreview' => false,
+                'hiddenPagePreview' => false,
+                'hiddenRecordPreview' => false,
+                'feGroupList' => '0,-1',
+                'expectedSQL' => '("aTable"."deleted" = 0) AND (("aTable"."t3ver_state" <= 0) AND ("aTable"."pid" <> -1)) AND ("aTable"."myHiddenField" = 0) AND ("aTable"."myStartTimeField" <= 42) AND (("aTable"."myEndTimeField" = 0) OR ("aTable"."myEndTimeField" > 42)) AND (("aTable"."myGroupField" IS NULL) OR ("aTable"."myGroupField" = \'\') OR ("aTable"."myGroupField" = \'0\') OR (FIND_IN_SET(\'0\', "aTable"."myGroupField")) OR (FIND_IN_SET(\'-1\', "aTable"."myGroupField")))'
+            ],
+            'Live, with hidden record preview' => [
+                'tableName' => 'aTable',
+                'workspaceId' => 0,
+                'workspacePreview' => false,
+                'hiddenPagePreview' => true,
+                'hiddenRecordPreview' => true,
+                'feGroupList' => '0,-1',
+                'expectedSQL' => '("aTable"."deleted" = 0) AND (("aTable"."t3ver_state" <= 0) AND ("aTable"."pid" <> -1)) AND ("aTable"."myStartTimeField" <= 42) AND (("aTable"."myEndTimeField" = 0) OR ("aTable"."myEndTimeField" > 42)) AND (("aTable"."myGroupField" IS NULL) OR ("aTable"."myGroupField" = \'\') OR ("aTable"."myGroupField" = \'0\') OR (FIND_IN_SET(\'0\', "aTable"."myGroupField")) OR (FIND_IN_SET(\'-1\', "aTable"."myGroupField")))'
+            ],
+            'Workspace, with WS preview' => [
+                'tableName' => 'aTable',
+                'workspaceId' => 1,
+                'workspacePreview' => true,
+                'hiddenPagePreview' => false,
+                'hiddenRecordPreview' => false,
+                'feGroupList' => '0,-1',
+                'expectedSQL' => '("aTable"."deleted" = 0) AND ((("aTable"."t3ver_wsid" = 0) OR ("aTable"."t3ver_wsid" = 1)) AND ("aTable"."pid" <> -1)) AND ("aTable"."myHiddenField" = 0) AND ("aTable"."myStartTimeField" <= 42) AND (("aTable"."myEndTimeField" = 0) OR ("aTable"."myEndTimeField" > 42)) AND (("aTable"."myGroupField" IS NULL) OR ("aTable"."myGroupField" = \'\') OR ("aTable"."myGroupField" = \'0\') OR (FIND_IN_SET(\'0\', "aTable"."myGroupField")) OR (FIND_IN_SET(\'-1\', "aTable"."myGroupField")))'
+            ],
+            'Workspace, with WS preview and hidden record preview' => [
+                'tableName' => 'aTable',
+                'workspaceId' => 1,
+                'workspacePreview' => true,
+                'hiddenPagePreview' => true,
+                'hiddenRecordPreview' => true,
+                'feGroupList' => '0,-1',
+                'expectedSQL' => '("aTable"."deleted" = 0) AND ((("aTable"."t3ver_wsid" = 0) OR ("aTable"."t3ver_wsid" = 1)) AND ("aTable"."pid" <> -1)) AND ("aTable"."myStartTimeField" <= 42) AND (("aTable"."myEndTimeField" = 0) OR ("aTable"."myEndTimeField" > 42)) AND (("aTable"."myGroupField" IS NULL) OR ("aTable"."myGroupField" = \'\') OR ("aTable"."myGroupField" = \'0\') OR (FIND_IN_SET(\'0\', "aTable"."myGroupField")) OR (FIND_IN_SET(\'-1\', "aTable"."myGroupField")))'
+            ],
+            'Live page, no preview' => [
+                'tableName' => 'pages',
+                'workspaceId' => 0,
+                'workspacePreview' => false,
+                'hiddenPagePreview' => false,
+                'hiddenRecordPreview' => false,
+                'feGroupList' => '0,-1',
+                'expectedSQL' => '("pages"."deleted" = 0) AND (("pages"."t3ver_state" <= 0) AND ("pages"."pid" <> -1)) AND ("pages"."hidden" = 0) AND ("pages"."starttime" <= 42) AND (("pages"."endtime" = 0) OR ("pages"."endtime" > 42)) AND (("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\') OR (FIND_IN_SET(\'0\', "pages"."fe_group")) OR (FIND_IN_SET(\'-1\', "pages"."fe_group")))'
+            ],
+            'Live page, with hidden page preview' => [
+                'tableName' => 'pages',
+                'workspaceId' => 0,
+                'workspacePreview' => false,
+                'hiddenPagePreview' => true,
+                'hiddenRecordPreview' => true,
+                'feGroupList' => '0,-1',
+                'expectedSQL' => '("pages"."deleted" = 0) AND (("pages"."t3ver_state" <= 0) AND ("pages"."pid" <> -1)) AND ("pages"."starttime" <= 42) AND (("pages"."endtime" = 0) OR ("pages"."endtime" > 42)) AND (("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\') OR (FIND_IN_SET(\'0\', "pages"."fe_group")) OR (FIND_IN_SET(\'-1\', "pages"."fe_group")))'
+            ],
+            'Workspace page, with WS preview' => [
+                'tableName' => 'pages',
+                'workspaceId' => 1,
+                'workspacePreview' => true,
+                'hiddenPagePreview' => false,
+                'hiddenRecordPreview' => false,
+                'feGroupList' => '0,-1',
+                'expectedSQL' => '("pages"."deleted" = 0) AND ("pages"."pid" <> -1) AND ("pages"."hidden" = 0) AND ("pages"."starttime" <= 42) AND (("pages"."endtime" = 0) OR ("pages"."endtime" > 42)) AND (("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\') OR (FIND_IN_SET(\'0\', "pages"."fe_group")) OR (FIND_IN_SET(\'-1\', "pages"."fe_group")))'
+            ],
+            'Workspace page, with WS preview and hidden pages preview' => [
+                'tableName' => 'pages',
+                'workspaceId' => 1,
+                'workspacePreview' => true,
+                'hiddenPagePreview' => true,
+                'hiddenRecordPreview' => true,
+                'feGroupList' => '0,-1',
+                'expectedSQL' => '("pages"."deleted" = 0) AND ("pages"."pid" <> -1) AND ("pages"."starttime" <= 42) AND (("pages"."endtime" = 0) OR ("pages"."endtime" > 42)) AND (("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\') OR (FIND_IN_SET(\'0\', "pages"."fe_group")) OR (FIND_IN_SET(\'-1\', "pages"."fe_group")))'
+            ],
+        ];
+    }
+
+    /**
+     * @param string $tableName
+     * @param int $workspaceId
+     * @param bool $workspacePreview
+     * @param bool $hiddenPagePreview
+     * @param bool $hiddenRecordPreview
+     * @param string $feGroupList
+     * @param string $expectedSQL
+     *
+     * @test
+     * @dataProvider frontendStatesDataProvider
+     */
+    public function buildExpressionAddsCorrectClause(
+        string $tableName,
+        int $workspaceId,
+        bool $workspacePreview,
+        bool $hiddenPagePreview,
+        bool $hiddenRecordPreview,
+        string $feGroupList,
+        string $expectedSQL
+    ) {
+        $GLOBALS['TCA'] = [
+            'aTable' => [
+                'ctrl' => [
+                    'versioningWS' => 2,
+                    'delete' => 'deleted',
+                    'enablecolumns' => [
+                        'disabled' => 'myHiddenField',
+                        'starttime' => 'myStartTimeField',
+                        'endtime' => 'myEndTimeField',
+                        'fe_group' => 'myGroupField',
+                    ],
+                ],
+            ],
+            'pages' => array(
+                'ctrl' => array(
+                    'label' => 'title',
+                    'tstamp' => 'tstamp',
+                    'sortby' => 'sorting',
+                    'type' => 'doktype',
+                    'versioningWS' => true,
+                    'origUid' => 't3_origuid',
+                    'delete' => 'deleted',
+                    'enablecolumns' => array(
+                        'disabled' => 'hidden',
+                        'starttime' => 'starttime',
+                        'endtime' => 'endtime',
+                        'fe_group' => 'fe_group'
+                    ),
+                ),
+                'columns' => array()
+            )
+        ];
+
+        $pageRepository = $this->getMock(PageRepository::class);
+        $pageRepository->versioningWorkspaceId = $workspaceId;
+        $pageRepository->versioningPreview = $workspacePreview;
+
+        $typoScriptFrontendController = new \stdClass();
+        $typoScriptFrontendController->showHiddenPage = $hiddenPagePreview;
+        $typoScriptFrontendController->showHiddenRecords = $hiddenRecordPreview;
+        $typoScriptFrontendController->gr_list = $feGroupList;
+        $typoScriptFrontendController->sys_page = $pageRepository;
+
+        $dbMock = $this->getMock(DatabaseConnection::class, ['quoteStr']);
+        $dbMock->expects($this->any())->method('quoteStr')->willReturnArgument(0);
+
+        $GLOBALS['TSFE'] = $typoScriptFrontendController;
+        $GLOBALS['SIM_ACCESS_TIME'] = 42;
+        $GLOBALS['TYPO3_DB'] = $dbMock;
+
+        $subject = new FrontendRestrictionContainer();
+        $expression = $subject->buildExpression([$tableName => ''], $this->expressionBuilder);
+        $this->assertSame($expectedSQL, (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendWorkspaceRestrictionTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendWorkspaceRestrictionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e7689f7e0c9a47ace9d6e766adb9b8913eb86ae7
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendWorkspaceRestrictionTest.php
@@ -0,0 +1,95 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Restriction\FrontendWorkspaceRestriction;
+use TYPO3\CMS\Frontend\Page\PageRepository;
+
+class FrontendWorkspaceRestrictionTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsLiveWorkspaceWhereClause()
+    {
+        $GLOBALS['TCA'] = [
+            'aTable' => [
+                'ctrl' => [
+                    'versioningWS' => 2,
+                ],
+            ]
+        ];
+
+        $pageRepository = $this->getMock(PageRepository::class);
+        $pageRepository->versioningPreview = false;
+
+        $subject = new FrontendWorkspaceRestriction(0);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."t3ver_state" <= 0) AND ("aTable"."pid" <> -1)', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsNonLiveWorkspaceWhereClause()
+    {
+        $GLOBALS['TCA'] = [
+            'aTable' => [
+                'ctrl' => [
+                    'versioningWS' => 2,
+                ],
+            ]
+        ];
+
+        $pageRepository = $this->getMock(PageRepository::class);
+        $pageRepository->versioningPreview = true;
+        $pageRepository->versioningWorkspaceId = 42;
+
+        $subject = new FrontendWorkspaceRestriction(42, true);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('(("aTable"."t3ver_wsid" = 0) OR ("aTable"."t3ver_wsid" = 42)) AND ("aTable"."pid" <> -1)', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsNonLiveWorkspaceExclusiveWhereClause()
+    {
+        $GLOBALS['TCA'] = [
+            'aTable' => [
+                'ctrl' => [
+                    'versioningWS' => 2,
+                ],
+            ]
+        ];
+
+        $pageRepository = $this->getMock(PageRepository::class);
+        $pageRepository->versioningPreview = true;
+        $pageRepository->versioningWorkspaceId = 42;
+
+        $subject = new FrontendWorkspaceRestriction(42, true, false);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."t3ver_wsid" = 0) OR ("aTable"."t3ver_wsid" = 42)', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/HiddenRestrictionTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/HiddenRestrictionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..41c4f2e8cf962458bcd377b520df4a121972e18d
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/HiddenRestrictionTest.php
@@ -0,0 +1,44 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
+
+class HiddenRestrictionTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsHiddenWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'enablecolumns' => [
+                'disabled' => 'myHiddenField',
+            ],
+        ];
+        $subject = new HiddenRestriction();
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('"aTable"."myHiddenField" = 0', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/RootLevelRestrictionTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/RootLevelRestrictionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c345e2c0ea99f8c8843f27339d9b515b00e52100
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/RootLevelRestrictionTest.php
@@ -0,0 +1,71 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
+
+class RootLevelRestrictionTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsPidWhereClause()
+    {
+        $subject = new RootLevelRestriction();
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('"aTable"."pid" = 0', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsAliasedPidWhereClause()
+    {
+        $subject = new RootLevelRestriction();
+        $expression = $subject->buildExpression(['aTable' => 'aTableAlias'], $this->expressionBuilder);
+        $this->assertSame('"aTableAlias"."pid" = 0', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsPidWhereClauseIfTableIsSpecified()
+    {
+        $subject = new RootLevelRestriction(['aTable']);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('"aTable"."pid" = 0', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsAliasedPidWhereClauseIfAliasIsSpecified()
+    {
+        $subject = new RootLevelRestriction(['aTableAlias']);
+        $expression = $subject->buildExpression(['aTable' => 'aTableAlias'], $this->expressionBuilder);
+        $this->assertSame('"aTableAlias"."pid" = 0', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsSkipsUnrestrictedTablesIfOtherTableIsSpecifiedThanUsedInTheQuery()
+    {
+        $subject = new RootLevelRestriction(['aTable']);
+        $expression = $subject->buildExpression(['anotherTable' => ''], $this->expressionBuilder);
+        $this->assertSame('', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/StartTimeRestrictionTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/StartTimeRestrictionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1dcd324ad8786b2c0cbf6240211f6babc55b95fb
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/StartTimeRestrictionTest.php
@@ -0,0 +1,64 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * 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 TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
+
+class StartTimeRestrictionTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsThrowsExceptionInStartTimeIfGlobalsAccessTimeIsMissing()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'enablecolumns' => [
+                'starttime' => 'myStartTimeField',
+            ],
+        ];
+        unset($GLOBALS['SIM_ACCESS_TIME']);
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionCode(1462820645);
+
+        $subject = new StartTimeRestriction();
+        $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsStartTimeWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'enablecolumns' => [
+                'starttime' => 'myStartTimeField',
+            ],
+        ];
+
+        $subject = new StartTimeRestriction(42);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('"aTable"."myStartTimeField" <= 42', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/felogin/Classes/Controller/FrontendLoginController.php b/typo3/sysext/felogin/Classes/Controller/FrontendLoginController.php
index d4fe68f4adc3ac6e1908b56d3012e990b5a16d2e..5b95ca29b40e67b90bd280b06e020396e40e657e 100644
--- a/typo3/sysext/felogin/Classes/Controller/FrontendLoginController.php
+++ b/typo3/sysext/felogin/Classes/Controller/FrontendLoginController.php
@@ -16,7 +16,7 @@ namespace TYPO3\CMS\Felogin\Controller;
 
 use TYPO3\CMS\Core\Crypto\Random;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
+use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\StringUtility;
 
@@ -227,26 +227,24 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin
             $postedHash = $postData['forgot_hash'];
             $hashData = $this->frontendController->fe_user->getKey('ses', 'forgot_hash');
             if ($postedHash === $hashData['forgot_hash']) {
-
-                /** @var QueryBuilder $queryBuilder */
-                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('fe_users');
+                $userTable = $this->frontendController->fe_user->user_table;
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($userTable);
+                $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
                 $row = $queryBuilder
                     ->select('uid', 'username', 'password', 'email')
-                    ->from('fe_users')
+                    ->from($userTable)
                     ->where(
-                        $queryBuilder->expr()->andX(
-                            $queryBuilder->expr()->orX(
-                                $queryBuilder->expr()->eq(
-                                    'email',
-                                    $queryBuilder->createNamedParameter($this->piVars['forgot_email'])
-                                ),
-                                $queryBuilder->expr()->eq(
-                                    'username',
-                                    $queryBuilder->createNamedParameter($this->piVars['forgot_email'])
-                                )
+                        $queryBuilder->expr()->orX(
+                            $queryBuilder->expr()->eq(
+                                'email',
+                                $queryBuilder->createNamedParameter($this->piVars['forgot_email'])
                             ),
-                            $queryBuilder->expr()->in('pid', GeneralUtility::intExplode(',', $this->spid))
-                        )
+                            $queryBuilder->expr()->eq(
+                                'username',
+                                $queryBuilder->createNamedParameter($this->piVars['forgot_email'])
+                            )
+                        ),
+                        $queryBuilder->expr()->in('pid', GeneralUtility::intExplode(',', $this->spid))
                     )
                     ->execute()
                     ->fetch();
@@ -366,12 +364,13 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin
                         }
 
                         // Save new password and clear DB-hash
-                        /** @var QueryBuilder $queryBuilder */
-                        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('fe_users');
-                        $queryBuilder->update('fe_users')
+                        $userTable = $this->frontendController->fe_user->user_table;
+                        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($userTable);
+                        $queryBuilder->getRestrictions()->removeAll();
+                        $queryBuilder->update($userTable)
                             ->set('password', $newPass)
                             ->set('felogin_forgotHash', '')
-                            ->set('tstamp', $GLOBALS['EXEC_TIME'])
+                            ->set('tstamp', (int)$GLOBALS['EXEC_TIME'])
                             ->where($queryBuilder->expr()->eq('uid', (int)$user['uid']))
                             ->execute();
 
@@ -423,10 +422,11 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin
         $randHashDB = $validEnd . '|' . md5($hash);
 
         // Write hash to DB
-        /** @var QueryBuilder $queryBuilder */
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('fe_users');
-        $queryBuilder->update('fe_users')
-            ->set('felogin_forgotHash', (string)$randHashDB)
+        $userTable = $this->frontendController->fe_user->user_table;
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($userTable);
+        $queryBuilder->getRestrictions()->removeAll();
+        $queryBuilder->update($userTable)
+            ->set('felogin_forgotHash', $randHashDB)
             ->where($queryBuilder->expr()->eq('uid', (int)$user['uid']))
             ->execute();
 
@@ -671,16 +671,14 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin
 
                                 // take the first group with a redirect page
                                 $userGroupTable = $this->frontendController->fe_user->usergroup_table;
-                                /** @var QueryBuilder $queryBuilder */
                                 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($userGroupTable);
+                                $queryBuilder->getRestrictions()->removeAll();
                                 $row = $queryBuilder
                                     ->select('felogin_redirectPid')
                                     ->from($userGroupTable)
                                     ->where(
-                                        $queryBuilder->expr()->andX(
-                                            $queryBuilder->expr()->neq('felogin_redirectPid', $queryBuilder->quote('')),
-                                            $queryBuilder->expr()->in('uid', implode(',', $groupData['uid']))
-                                        )
+                                        $queryBuilder->expr()->neq('felogin_redirectPid', $queryBuilder->quote('')),
+                                        $queryBuilder->expr()->in('uid', array_map('intval', $groupData['uid']))
                                     )
                                     ->execute()
                                     ->fetch();
@@ -693,18 +691,16 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin
                         case 'userLogin':
 
                             $userTable = $this->frontendController->fe_user->user_table;
-                            /** @var QueryBuilder $queryBuilder */
                             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($userTable);
+                            $queryBuilder->getRestrictions()->removeAll();
                             $row = $queryBuilder
                                 ->select('felogin_redirectPid')
                                 ->from($userTable)
                                 ->where(
-                                    $queryBuilder->expr()->andX(
-                                        $queryBuilder->expr()->neq('felogin_redirectPid', $queryBuilder->quote('')),
-                                        $queryBuilder->expr()->eq(
-                                            $this->frontendController->fe_user->userid_column,
-                                            (int)$this->frontendController->fe_user->user['uid']
-                                        )
+                                    $queryBuilder->expr()->neq('felogin_redirectPid', $queryBuilder->quote('')),
+                                    $queryBuilder->expr()->eq(
+                                        $this->frontendController->fe_user->userid_column,
+                                        (int)$this->frontendController->fe_user->user['uid']
                                     )
                                 )
                                 ->execute()
@@ -1046,8 +1042,8 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin
                 // Removes the last path segment and slash sequences like /// (if given):
                 $path = preg_replace('#/+[^/]*$#', '', $parsedUrl['path']);
 
-                /** @var QueryBuilder $queryBuilder */
                 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_domain');
+                $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
                 $localDomains = $queryBuilder->select('domainName')
                     ->from('sys_domain')
                     ->execute()
diff --git a/typo3/sysext/felogin/Tests/Unit/Controller/FrontendLoginControllerTest.php b/typo3/sysext/felogin/Tests/Unit/Controller/FrontendLoginControllerTest.php
index 0757f8bdf93ac3aa001e43a23b93ef6be3533ee6..3edb460dfb96ad5c3fbeae8cc6b8596cc407ed3b 100644
--- a/typo3/sysext/felogin/Tests/Unit/Controller/FrontendLoginControllerTest.php
+++ b/typo3/sysext/felogin/Tests/Unit/Controller/FrontendLoginControllerTest.php
@@ -54,6 +54,8 @@ class FrontendLoginControllerTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     protected function setUp()
     {
+        $GLOBALS['TSFE'] = new \stdClass();
+        $GLOBALS['TSFE']->gr_list = '0,-1';
         $this->testTableName = 'sys_domain';
         $this->testHostName = 'hostname.tld';
         $this->testSitePath = '/';
@@ -84,8 +86,9 @@ class FrontendLoginControllerTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         $connection->getExpressionBuilder()->willReturn(new ExpressionBuilder($connection->reveal()));
         $connection->quoteIdentifier(Argument::cetera())->willReturnArgument(0);
 
-        $queryBuilder = GeneralUtility::makeInstance(
-            QueryBuilder::class,
+        // TODO: This should rather be a functional test if we need a query builder
+        // or we should clean up the code itself to not need to mock internal behavior here
+        $queryBuilder = new QueryBuilder(
             $connection->reveal(),
             null,
             new \Doctrine\DBAL\Query\QueryBuilder($connection->reveal())
diff --git a/typo3/sysext/filelist/Classes/FileFacade.php b/typo3/sysext/filelist/Classes/FileFacade.php
index 131fb9c4f812096e3451a2acca609ea744c37880..413f156049b55a4b094a060608ebdfe55fab7fa6 100644
--- a/typo3/sysext/filelist/Classes/FileFacade.php
+++ b/typo3/sysext/filelist/Classes/FileFacade.php
@@ -18,8 +18,8 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Resource\FileInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Class FileFacade
@@ -256,9 +256,12 @@ class FileFacade
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
             $count = $queryBuilder->count('*')
                 ->from('sys_refindex')
-                ->where($queryBuilder->expr()->eq('ref_table', $queryBuilder->quote('sys_file')))
-                ->andWhere($queryBuilder->expr()->eq('ref_uid', (int)$this->resource->getProperty('uid')))
-                ->andWhere($queryBuilder->expr()->neq('tablename', $queryBuilder->quote('sys_file_metadata')))
+                ->where(
+                    $queryBuilder->expr()->eq('deleted', 0),
+                    $queryBuilder->expr()->eq('ref_table', $queryBuilder->createNamedParameter('sys_file')),
+                    $queryBuilder->expr()->eq('ref_uid', (int)$this->resource->getProperty('uid')),
+                    $queryBuilder->expr()->neq('tablename', $queryBuilder->createNamedParameter('sys_file_metadata'))
+                )
                 ->execute()
                 ->fetchColumn();
 
diff --git a/typo3/sysext/filelist/Classes/FileList.php b/typo3/sysext/filelist/Classes/FileList.php
index 7c40ee37853b4534d3b2354dd1801faee10a95ed..311fd25ddc4b92ac8bc7542cf7317e6ab28ea77c 100644
--- a/typo3/sysext/filelist/Classes/FileList.php
+++ b/typo3/sysext/filelist/Classes/FileList.php
@@ -19,7 +19,6 @@ use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
 use TYPO3\CMS\Backend\RecordList\AbstractRecordList;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
@@ -785,11 +784,13 @@ class FileList extends AbstractRecordList
     protected function getTranslationsForMetaData($metaDataRecord)
     {
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_metadata');
-        $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+        $queryBuilder->getRestrictions()->removeAll();
         $translationRecords = $queryBuilder->select('*')
             ->from('sys_file_metadata')
-            ->where($queryBuilder->expr()->eq($GLOBALS['TCA']['sys_file_metadata']['ctrl']['transOrigPointerField'], (int)$metaDataRecord['uid']))
-            ->andWhere($queryBuilder->expr()->gt($GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField'], 0))
+            ->where(
+                $queryBuilder->expr()->eq($GLOBALS['TCA']['sys_file_metadata']['ctrl']['transOrigPointerField'], (int)$metaDataRecord['uid']),
+                $queryBuilder->expr()->gt($GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField'], 0)
+            )
             ->execute()
             ->fetchAll();
 
@@ -1045,9 +1046,12 @@ class FileList extends AbstractRecordList
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
         $referenceCount = $queryBuilder->count('*')
             ->from('sys_refindex')
-            ->where($queryBuilder->expr()->eq('ref_table', $queryBuilder->quote('sys_file')))
-            ->andWhere($queryBuilder->expr()->eq('ref_uid', (int)$fileOrFolderObject->getUid()))
-            ->andWhere($queryBuilder->expr()->neq('tablename', $queryBuilder->quote('sys_file_metadata')))
+            ->where(
+                $queryBuilder->expr()->eq('deleted', 0),
+                $queryBuilder->expr()->eq('ref_table', $queryBuilder->quote('sys_file')),
+                $queryBuilder->expr()->eq('ref_uid', (int)$fileOrFolderObject->getUid()),
+                $queryBuilder->expr()->neq('tablename', $queryBuilder->quote('sys_file_metadata'))
+            )
             ->execute()
             ->fetchColumn();
 
diff --git a/typo3/sysext/lowlevel/Classes/MissingRelationsCommand.php b/typo3/sysext/lowlevel/Classes/MissingRelationsCommand.php
index 3bb4d46d4f1cab7e0cd20a1910200847020b50a3..c59cf1d26fafd07d85e6d8baa0a74d8f693f2395 100644
--- a/typo3/sysext/lowlevel/Classes/MissingRelationsCommand.php
+++ b/typo3/sysext/lowlevel/Classes/MissingRelationsCommand.php
@@ -16,7 +16,6 @@ namespace TYPO3\CMS\Lowlevel;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\ReferenceIndex;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -92,15 +91,12 @@ Reports missing relations';
         );
 
         // Select DB relations from reference table
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
         $rowIterator = $queryBuilder
             ->select('ref_uid', 'ref_table', 'softref_key', 'hash', 'tablename', 'recuid', 'field', 'flexpointer', 'deleted')
             ->from('sys_refindex')
             ->where(
-                $queryBuilder->expr()->neq('ref_table', $queryBuilder->quote('_FILE'))
-            )
-            ->andWhere(
+                $queryBuilder->expr()->neq('ref_table', $queryBuilder->createNamedParameter('_FILE')),
                 $queryBuilder->expr()->gt('ref_uid', 0)
             )
             ->orderBy('sorting', 'DESC')
diff --git a/typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php b/typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php
index b699a8adb7644141730cdd36e9c08a05a1e76714..55379925e59f3c3447655547573a21cb3d5bb02f 100644
--- a/typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php
+++ b/typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php
@@ -16,7 +16,6 @@ namespace TYPO3\CMS\Recycler\Domain\Model;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -147,17 +146,17 @@ class DeletedRecords
             return;
         }
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
-        $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+        $queryBuilder->getRestrictions()->removeAll();
 
         // find the 'deleted' field for this table
         $deletedField = RecyclerUtility::getDeletedField($table);
 
         // create the filter WHERE-clause
         $filterConstraint = null;
-        if (trim($filter) != '') {
+        if (trim($filter) !== '') {
             $labelConstraint = $queryBuilder->expr()->like(
                 $tcaCtrl['label'],
-                $queryBuilder->quote('%' . addcslashes($filter, '_%') . '%')
+                $queryBuilder->quote('%' . $queryBuilder->escapeLikeWildcards($filter) . '%')
             );
             if (MathUtility::canBeInterpretedAsInteger($filter)) {
                 $filterConstraint = $queryBuilder->expr()->orX(
@@ -265,7 +264,7 @@ class DeletedRecords
         if ($allowDepth && $depth >= 1) {
             // check recursively for elements beneath this page
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
-            $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+            $queryBuilder->getRestrictions()->removeAll();
             $resPages = $queryBuilder
                 ->select('uid')
                 ->from('pages')
@@ -415,12 +414,14 @@ class DeletedRecords
     protected function getDeletedParentPages($uid, &$pages = array())
     {
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
-        $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+        $queryBuilder->getRestrictions()->removeAll();
         $record = $queryBuilder
             ->select('uid', 'pid')
             ->from('pages')
-            ->where($queryBuilder->expr()->eq('uid', (int)$uid))
-            ->andWhere($queryBuilder->expr()->eq($GLOBALS['TCA']['pages']['ctrl']['delete'], 1))
+            ->where(
+                $queryBuilder->expr()->eq('uid', (int)$uid),
+                $queryBuilder->expr()->eq($GLOBALS['TCA']['pages']['ctrl']['delete'], 1)
+            )
             ->execute()
             ->fetch();
         if ($record) {
diff --git a/typo3/sysext/recycler/Classes/Domain/Model/Tables.php b/typo3/sysext/recycler/Classes/Domain/Model/Tables.php
index a17270ec37f67ecfbe82ec58207a08fb53ccb773..1d2cd0be8ef927b67d2a484b010c8072de247f4a 100644
--- a/typo3/sysext/recycler/Classes/Domain/Model/Tables.php
+++ b/typo3/sysext/recycler/Classes/Domain/Model/Tables.php
@@ -15,7 +15,6 @@ namespace TYPO3\CMS\Recycler\Domain\Model;
  */
 
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Recycler\Utility\RecyclerUtility;
 
@@ -42,7 +41,7 @@ class Tables
             if ($deletedField) {
                 // Determine whether the table has deleted records:
                 $queryBuilder = $connection->getQueryBuilderForTable($tableName);
-                $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+                $queryBuilder->getRestrictions()->removeAll();
 
                 $deletedCount = $queryBuilder->count('uid')
                     ->from($tableName)
diff --git a/typo3/sysext/recycler/Classes/Task/CleanerTask.php b/typo3/sysext/recycler/Classes/Task/CleanerTask.php
index a16d9ee8f0d0612ba941082aa75dcb0560dc0acc..a503850f2d962becfd18daeeb6999fd96cce9dde 100644
--- a/typo3/sysext/recycler/Classes/Task/CleanerTask.php
+++ b/typo3/sysext/recycler/Classes/Task/CleanerTask.php
@@ -62,6 +62,7 @@ class CleanerTask extends AbstractTask
     {
         if (isset($GLOBALS['TCA'][$tableName]['ctrl']['delete'])) {
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
+            $queryBuilder->getRestrictions()->removeAll();
 
             $constraints = [
                 $queryBuilder->expr()->eq($GLOBALS['TCA'][$tableName]['ctrl']['delete'], 1),
@@ -69,25 +70,17 @@ class CleanerTask extends AbstractTask
 
             if ($GLOBALS['TCA'][$tableName]['ctrl']['tstamp']) {
                 $dateBefore = $this->getPeriodAsTimestamp();
-                $constraints[] = $queryBuilder->expr()->lt($GLOBALS['TCA'][$tableName]['ctrl']['tstamp'], $dateBefore);
+                $constraints[] = $queryBuilder->expr()->lt($GLOBALS['TCA'][$tableName]['ctrl']['tstamp'], (int)$dateBefore);
             }
-
             $this->checkFileResourceFieldsBeforeDeletion($tableName, $constraints);
-
-            $queryBuilder
-                ->getQueryContext()
-                ->setIgnoreEnableFields(true)
-                ->setIncludeDeleted(true);
-
             try {
                 $queryBuilder->delete($tableName)
-                    ->where($queryBuilder->expr()->andX(...$constraints))
+                    ->where(...$constraints)
                     ->execute();
             } catch (\Doctrine\DBAL\DBALException $e) {
                 return false;
             }
         }
-
         return true;
     }
 
@@ -189,10 +182,7 @@ class CleanerTask extends AbstractTask
     protected function deleteFilesForTable($table, array $constraints, array $fieldList)
     {
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
-        $queryBuilder
-            ->getQueryContext()
-            ->setIgnoreEnableFields(true)
-            ->setIncludeDeleted(true);
+        $queryBuilder->getRestrictions()->removeAll();
 
         $result = $queryBuilder
             ->select(...$fieldList)
diff --git a/typo3/sysext/recycler/Classes/Utility/RecyclerUtility.php b/typo3/sysext/recycler/Classes/Utility/RecyclerUtility.php
index f605b9ae12b62d0669c26ece3ef104272568efc8..72d1e8ee9c32f40d13ecbdccedf73f8b66e2c869 100644
--- a/typo3/sysext/recycler/Classes/Utility/RecyclerUtility.php
+++ b/typo3/sysext/recycler/Classes/Utility/RecyclerUtility.php
@@ -16,7 +16,6 @@ namespace TYPO3\CMS\Recycler\Utility;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -86,7 +85,7 @@ class RecyclerUtility
             return $output;
         }
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
-        $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+        $queryBuilder->getRestrictions()->removeAll();
 
         $clause = trim($clause);
         $loopCheck = 100;
@@ -154,7 +153,7 @@ class RecyclerUtility
             return false;
         }
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
-        $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+        $queryBuilder->getRestrictions()->removeAll();
 
         $deleted = $queryBuilder
             ->select('deleted')
@@ -176,7 +175,7 @@ class RecyclerUtility
     public static function getPidOfUid($uid, $table)
     {
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
-        $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+        $queryBuilder->getRestrictions()->removeAll();
 
         $pid = $queryBuilder
             ->select('pid')
diff --git a/typo3/sysext/recycler/Tests/Unit/Task/CleanerTaskTest.php b/typo3/sysext/recycler/Tests/Unit/Task/CleanerTaskTest.php
index 4cb11dcf022b2871ced8a9d9b63844bd99c7d6b8..3dfb434c56479aee81851f0d2f43e8699cc2d8f7 100644
--- a/typo3/sysext/recycler/Tests/Unit/Task/CleanerTaskTest.php
+++ b/typo3/sysext/recycler/Tests/Unit/Task/CleanerTaskTest.php
@@ -83,8 +83,9 @@ class CleanerTaskTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         $connection->getExpressionBuilder()->willReturn(new ExpressionBuilder($connection->reveal()));
         $connection->quoteIdentifier(Argument::cetera())->willReturnArgument(0);
 
-        $queryBuilder = GeneralUtility::makeInstance(
-            QueryBuilder::class,
+        // TODO: This should rather be a functional test if we need a query builder
+        // or we should clean up the code itself to not need to mock internal behavior here
+        $queryBuilder = new QueryBuilder(
             $connection->reveal(),
             null,
             new \Doctrine\DBAL\Query\QueryBuilder($connection->reveal())
@@ -116,8 +117,9 @@ class CleanerTaskTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         $connection->getExpressionBuilder()->willReturn(new ExpressionBuilder($connection->reveal()));
         $connection->quoteIdentifier(Argument::cetera())->willReturnArgument(0);
 
-        $queryBuilder = GeneralUtility::makeInstance(
-            QueryBuilder::class,
+        // TODO: This should rather be a functional test if we need a query builder
+        // or we should clean up the code itself to not need to mock internal behavior here
+        $queryBuilder = new QueryBuilder(
             $connection->reveal(),
             null,
             new \Doctrine\DBAL\Query\QueryBuilder($connection->reveal())
diff --git a/typo3/sysext/setup/Classes/Controller/SetupModuleController.php b/typo3/sysext/setup/Classes/Controller/SetupModuleController.php
index 26b0efe664895f2cf6e91aeab3ddcacd0e94ccd2..0e13d88bd390a633237b05edf699321d71d1f74d 100644
--- a/typo3/sysext/setup/Classes/Controller/SetupModuleController.php
+++ b/typo3/sysext/setup/Classes/Controller/SetupModuleController.php
@@ -22,7 +22,6 @@ use TYPO3\CMS\Backend\Module\ModuleLoader;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
 use TYPO3\CMS\Core\Imaging\Icon;
@@ -747,13 +746,14 @@ class SetupModuleController extends AbstractModule
         unset($this->OLD_BE_USER);
         if ($this->getBackendUser()->isAdmin()) {
             $this->simUser = (int)GeneralUtility::_GP('simUser');
-            /** @var QueryBuilder $queryBuilder */
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
             $users = $queryBuilder
                 ->select('*')
                 ->from('be_users')
-                ->where($queryBuilder->expr()->neq('uid', (int)$this->getBackendUser()->user['uid']))
-                ->andWhere($queryBuilder->expr()->notLike('username', $queryBuilder->createNamedParameter('_cli_%')))
+                ->where(
+                    $queryBuilder->expr()->neq('uid', (int)$this->getBackendUser()->user['uid']),
+                    $queryBuilder->expr()->notLike('username', $queryBuilder->createNamedParameter($queryBuilder->escapeLikeWildcards('_cli_') . '%'))
+                )
                 ->orderBy('username')
                 ->execute()
                 ->fetchAll();
@@ -891,15 +891,16 @@ class SetupModuleController extends AbstractModule
      */
     protected function getAvatarFileUid($beUserId)
     {
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference');
         $file = $queryBuilder
             ->select('uid_local')
             ->from('sys_file_reference')
-            ->where($queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('be_users')))
-            ->andWhere($queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('avatar')))
-            ->andWhere($queryBuilder->expr()->eq('table_local', $queryBuilder->createNamedParameter('sys_file')))
-            ->andWhere($queryBuilder->expr()->eq('uid_foreign', (int)$beUserId))
+            ->where(
+                $queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('be_users')),
+                $queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('avatar')),
+                $queryBuilder->expr()->eq('table_local', $queryBuilder->createNamedParameter('sys_file')),
+                $queryBuilder->expr()->eq('uid_foreign', (int)$beUserId)
+            )
             ->execute()
             ->fetchColumn();
         return (int)$file;
@@ -920,14 +921,16 @@ class SetupModuleController extends AbstractModule
             return;
         }
 
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference');
+        $queryBuilder->getRestrictions()->removeAll();
         $queryBuilder
             ->delete('sys_file_reference')
-            ->where($queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('be_users')))
-            ->andWhere($queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('avatar')))
-            ->andWhere($queryBuilder->expr()->eq('table_local', $queryBuilder->createNamedParameter('sys_file')))
-            ->andWhere($queryBuilder->expr()->eq('uid_foreign', (int)$beUserId))
+            ->where(
+                $queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('be_users')),
+                $queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('avatar')),
+                $queryBuilder->expr()->eq('table_local', $queryBuilder->createNamedParameter('sys_file')),
+                $queryBuilder->expr()->eq('uid_foreign', (int)$beUserId)
+            )
             ->execute();
 
         // Create new reference
diff --git a/typo3/sysext/sys_note/Classes/Core/Bootstrap.php b/typo3/sysext/sys_note/Classes/Core/Bootstrap.php
index cb76d08992c7b3f59d9f416a2dd04631bbdf939a..80384abde49465b75a87f9cde1110e68d38927fe 100644
--- a/typo3/sysext/sys_note/Classes/Core/Bootstrap.php
+++ b/typo3/sysext/sys_note/Classes/Core/Bootstrap.php
@@ -14,9 +14,7 @@ namespace TYPO3\CMS\SysNote\Core;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -76,16 +74,15 @@ class Bootstrap
         if (!isset($arguments['pids']) || empty($arguments['pids']) || empty($GLOBALS['BE_USER']->user['uid'])) {
             return false;
         }
-        $pidList = GeneralUtility::intExplode(',', $arguments['pids'], true);
-        if (empty($pidList)) {
+        $cleanedPageIds = GeneralUtility::intExplode(',', $arguments['pids'], true);
+        if (empty($cleanedPageIds)) {
             return false;
         }
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_note');
         $count = $queryBuilder
             ->count('uid')
             ->from('sys_note')
-            ->where($queryBuilder->expr()->in('pid', $pidList))
+            ->where($queryBuilder->expr()->in('pid', $cleanedPageIds))
             ->execute()
             ->fetchColumn();
         return (bool)$count;