[TASK] Restructure nightly plans in stages a 50 jobs
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Database / ReferenceIndex.php
index 1ab054d..692e864 100644 (file)
@@ -14,24 +14,26 @@ namespace TYPO3\CMS\Core\Database;
  * The TYPO3 project - inspiring people to share!
  */
 
  * The TYPO3 project - inspiring people to share!
  */
 
+use Doctrine\DBAL\DBALException;
+use Psr\EventDispatcher\EventDispatcherInterface;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
+use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
+use TYPO3\CMS\Core\DataHandling\Event\IsTableExcludedFromReferenceIndexEvent;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
-use TYPO3\CMS\Core\Messaging\FlashMessageService;
+use TYPO3\CMS\Core\Messaging\FlashMessageRendererResolver;
 use TYPO3\CMS\Core\Registry;
 use TYPO3\CMS\Core\Registry;
-use TYPO3\CMS\Core\Resource\File;
-use TYPO3\CMS\Core\Resource\Folder;
-use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\PathUtility;
 
 /**
  * Reference index processing and relation extraction
  *
  * NOTICE: When the reference index is updated for an offline version the results may not be correct.
 
 /**
  * Reference index processing and relation extraction
  *
  * NOTICE: When the reference index is updated for an offline version the results may not be correct.
- * First, lets assumed that the reference update happens in LIVE workspace (ALWAYS update from Live workspace if you analyse whole database!)
+ * First, lets assumed that the reference update happens in LIVE workspace (ALWAYS update from Live workspace if you analyze whole database!)
  * Secondly, lets assume that in a Draft workspace you have changed the data structure of a parent page record - this is (in TemplaVoila) inherited by subpages.
  * When in the LIVE workspace the data structure for the records/pages in the offline workspace will not be evaluated to the right one simply because the data
  * structure is taken from a rootline traversal and in the Live workspace that will NOT include the changed DataStructure! Thus the evaluation will be based
  * Secondly, lets assume that in a Draft workspace you have changed the data structure of a parent page record - this is (in TemplaVoila) inherited by subpages.
  * When in the LIVE workspace the data structure for the records/pages in the offline workspace will not be evaluated to the right one simply because the data
  * structure is taken from a rootline traversal and in the Live workspace that will NOT include the changed DataStructure! Thus the evaluation will be based
@@ -40,40 +42,39 @@ use TYPO3\CMS\Core\Utility\PathUtility;
  * maintaining the index for workspace records. Or we can say that the index is precise for all Live elements while glitches might happen in an offline workspace?
  * Anyway, I just wanted to document this finding - I don't think we can find a solution for it. And its very TemplaVoila specific.
  */
  * maintaining the index for workspace records. Or we can say that the index is precise for all Live elements while glitches might happen in an offline workspace?
  * Anyway, I just wanted to document this finding - I don't think we can find a solution for it. And its very TemplaVoila specific.
  */
-class ReferenceIndex
+class ReferenceIndex implements LoggerAwareInterface
 {
 {
+    use LoggerAwareTrait;
+
     /**
     /**
-     * Definition of tables to exclude from searching for relations
+     * Definition of tables to exclude from the ReferenceIndex
      *
      * Only tables which do not contain any relations and never did so far since references also won't be deleted for
      * these. Since only tables with an entry in $GLOBALS['TCA] are handled by ReferenceIndex there is no need to add
      * *_mm-tables.
      *
      *
      * Only tables which do not contain any relations and never did so far since references also won't be deleted for
      * these. Since only tables with an entry in $GLOBALS['TCA] are handled by ReferenceIndex there is no need to add
      * *_mm-tables.
      *
-     * This is implemented as an array with fields as keys and booleans as values to be able to fast isset() instead of
-     * slow in_array() lookup.
+     * Implemented as array with fields as keys and booleans as values for fast isset() lookup instead of slow in_array()
      *
      * @var array
      * @see updateRefIndexTable()
      *
      * @var array
      * @see updateRefIndexTable()
-     * @todo #65461 Create configuration for tables to exclude from ReferenceIndex
+     * @see shouldExcludeTableFromReferenceIndex()
      */
      */
-    protected static $nonRelationTables = array(
+    protected static $excludedTables = [
         'sys_log' => true,
         'sys_log' => true,
-        'sys_history' => true,
         'tx_extensionmanager_domain_model_extension' => true
         'tx_extensionmanager_domain_model_extension' => true
-    );
+    ];
 
     /**
 
     /**
-     * Definition of fields to exclude from searching for relations
+     * Definition of fields to exclude from ReferenceIndex in *every* table
      *
      *
-     * This is implemented as an array with fields as keys and booleans as values to be able to fast isset() instead of
-     * slow in_array() lookup.
+     * Implemented as array with fields as keys and booleans as values for fast isset() lookup instead of slow in_array()
      *
      * @var array
      * @see getRelations()
      * @see fetchTableRelationFields()
      *
      * @var array
      * @see getRelations()
      * @see fetchTableRelationFields()
-     * @todo #65460 Create configuration for fields to exclude from ReferenceIndex
+     * @see shouldExcludeTableColumnFromReferenceIndex()
      */
      */
-    protected static $nonRelationFields = array(
+    protected static $excludedColumns = [
         'uid' => true,
         'perms_userid' => true,
         'perms_groupid' => true,
         'uid' => true,
         'perms_userid' => true,
         'perms_groupid' => true,
@@ -81,7 +82,7 @@ class ReferenceIndex
         'perms_group' => true,
         'perms_everybody' => true,
         'pid' => true
         'perms_group' => true,
         'perms_everybody' => true,
         'pid' => true
-    );
+    ];
 
     /**
      * Fields of tables that could contain relations are cached per table. This is the prefix for the cache entries since
 
     /**
      * Fields of tables that could contain relations are cached per table. This is the prefix for the cache entries since
@@ -95,9 +96,11 @@ class ReferenceIndex
      * This array holds the FlexForm references of a record
      *
      * @var array
      * This array holds the FlexForm references of a record
      *
      * @var array
-     * @see getRelations(),FlexFormTools::traverseFlexFormXMLData(),getRelations_flexFormCallBack()
+     * @see getRelations()
+     * @see FlexFormTools::traverseFlexFormXMLData()
+     * @see getRelations_flexFormCallBack()
      */
      */
-    public $temp_flexRelations = array();
+    public $temp_flexRelations = [];
 
     /**
      * This variable used to indicate whether referencing should take workspace overlays into account
 
     /**
      * This variable used to indicate whether referencing should take workspace overlays into account
@@ -112,9 +115,18 @@ class ReferenceIndex
      * An index of all found references of a single record created in createEntryData() and accumulated in generateRefIndexData()
      *
      * @var array
      * An index of all found references of a single record created in createEntryData() and accumulated in generateRefIndexData()
      *
      * @var array
-     * @see createEntryData(),generateRefIndexData()
+     * @see createEntryData()
+     * @see generateRefIndexData()
+     */
+    public $relations = [];
+
+    /**
+     * A cache to avoid that identical rows are refetched from the database
+     *
+     * @var array
+     * @see getRecordRawCached()
      */
      */
-    public $relations = array();
+    protected $recordCache = [];
 
     /**
      * Number which we can increase if a change in the code means we will have to force a re-generation of the index.
 
     /**
      * Number which we can increase if a change in the code means we will have to force a re-generation of the index.
@@ -134,16 +146,28 @@ class ReferenceIndex
     /**
      * Runtime Cache to store and retrieve data computed for a single request
      *
     /**
      * Runtime Cache to store and retrieve data computed for a single request
      *
-     * @var \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
+     * @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
      */
      */
-    protected $runtimeCache = null;
+    protected $runtimeCache;
 
     /**
 
     /**
-     * Constructor
+     * Enables $runtimeCache and $recordCache
+     * @var bool
      */
      */
-    public function __construct()
+    protected $useRuntimeCache = false;
+
+    /**
+     * @var EventDispatcherInterface
+     */
+    protected $eventDispatcher;
+
+    /**
+     * @param EventDispatcherInterface $eventDispatcher
+     */
+    public function __construct(EventDispatcherInterface $eventDispatcher = null)
     {
     {
-        $this->runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
+        $this->eventDispatcher = $eventDispatcher ?? GeneralUtility::getContainer()->get(EventDispatcherInterface::class);
+        $this->runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
     }
 
     /**
     }
 
     /**
@@ -161,7 +185,8 @@ class ReferenceIndex
      * Gets the current workspace id
      *
      * @return int
      * Gets the current workspace id
      *
      * @return int
-     * @see updateRefIndexTable(),createEntryData()
+     * @see updateRefIndexTable()
+     * @see createEntryData()
      */
     public function getWorkspaceId()
     {
      */
     public function getWorkspaceId()
     {
@@ -182,47 +207,65 @@ class ReferenceIndex
      */
     public function updateRefIndexTable($tableName, $uid, $testOnly = false)
     {
      */
     public function updateRefIndexTable($tableName, $uid, $testOnly = false)
     {
-
         // First, secure that the index table is not updated with workspace tainted relations:
         $this->WSOL = false;
 
         // Init:
         // First, secure that the index table is not updated with workspace tainted relations:
         $this->WSOL = false;
 
         // Init:
-        $result = array(
+        $result = [
             'keptNodes' => 0,
             'deletedNodes' => 0,
             'addedNodes' => 0
             'keptNodes' => 0,
             'deletedNodes' => 0,
             'addedNodes' => 0
-        );
+        ];
+
+        $uid = $uid ? (int)$uid : 0;
+        if (!$uid) {
+            return $result;
+        }
 
         // If this table cannot contain relations, skip it
 
         // If this table cannot contain relations, skip it
-        if (isset(static::$nonRelationTables[$tableName])) {
+        if ($this->shouldExcludeTableFromReferenceIndex($tableName)) {
             return $result;
         }
 
         // Fetch tableRelationFields and save them in cache if not there yet
         $cacheId = static::$cachePrefixTableRelationFields . $tableName;
             return $result;
         }
 
         // Fetch tableRelationFields and save them in cache if not there yet
         $cacheId = static::$cachePrefixTableRelationFields . $tableName;
-        if (!$this->runtimeCache->has($cacheId)) {
+        if (!$this->useRuntimeCache || !$this->runtimeCache->has($cacheId)) {
             $tableRelationFields = $this->fetchTableRelationFields($tableName);
             $this->runtimeCache->set($cacheId, $tableRelationFields);
         } else {
             $tableRelationFields = $this->runtimeCache->get($cacheId);
         }
 
             $tableRelationFields = $this->fetchTableRelationFields($tableName);
             $this->runtimeCache->set($cacheId, $tableRelationFields);
         } else {
             $tableRelationFields = $this->runtimeCache->get($cacheId);
         }
 
-        $databaseConnection = $this->getDatabaseConnection();
+        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
+        $connection = $connectionPool->getConnectionForTable('sys_refindex');
 
         // Get current index from Database with hash as index using $uidIndexField
 
         // Get current index from Database with hash as index using $uidIndexField
-        $currentRelations = $databaseConnection->exec_SELECTgetRows(
-            '*',
-            'sys_refindex',
-            'tablename=' . $databaseConnection->fullQuoteStr($tableName, 'sys_refindex')
-            . ' AND recuid=' . (int)$uid . ' AND workspace=' . $this->getWorkspaceId(),
-            '', '', '', 'hash'
-        );
+        // no restrictions are needed, since sys_refindex is not a TCA table
+        $queryBuilder = $connection->createQueryBuilder();
+        $queryBuilder->getRestrictions()->removeAll();
+        $queryResult = $queryBuilder->select('hash')->from('sys_refindex')->where(
+            $queryBuilder->expr()->eq('tablename', $queryBuilder->createNamedParameter($tableName, \PDO::PARAM_STR)),
+            $queryBuilder->expr()->eq('recuid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)),
+            $queryBuilder->expr()->eq(
+                'workspace',
+                $queryBuilder->createNamedParameter($this->getWorkspaceId(), \PDO::PARAM_INT)
+            )
+        )->execute();
+        $currentRelationHashes = [];
+        while ($relation = $queryResult->fetch()) {
+            $currentRelationHashes[$relation['hash']] = true;
+        }
 
         // If the table has fields which could contain relations and the record does exist (including deleted-flagged)
 
         // If the table has fields which could contain relations and the record does exist (including deleted-flagged)
-        if ($tableRelationFields !== '' && BackendUtility::getRecordRaw($tableName, 'uid=' . (int)$uid, 'uid')) {
-            // Then, get relations:
-            $relations = $this->generateRefIndexData($tableName, $uid);
-            if (is_array($relations)) {
+        if ($tableRelationFields !== '') {
+            $existingRecord = $this->getRecordRawCached($tableName, $uid);
+            if ($existingRecord) {
+                // Table has relation fields and record exists - get relations
+                $this->relations = [];
+                $relations = $this->generateDataUsingRecord($tableName, $existingRecord);
+                if (!is_array($relations)) {
+                    return $result;
+                }
                 // Traverse the generated index:
                 foreach ($relations as &$relation) {
                     if (!is_array($relation)) {
                 // Traverse the generated index:
                 foreach ($relations as &$relation) {
                     if (!is_array($relation)) {
@@ -230,35 +273,46 @@ class ReferenceIndex
                     }
                     $relation['hash'] = md5(implode('///', $relation) . '///' . $this->hashVersion);
                     // First, check if already indexed and if so, unset that row (so in the end we know which rows to remove!)
                     }
                     $relation['hash'] = md5(implode('///', $relation) . '///' . $this->hashVersion);
                     // First, check if already indexed and if so, unset that row (so in the end we know which rows to remove!)
-                    if (isset($currentRelations[$relation['hash']])) {
-                        unset($currentRelations[$relation['hash']]);
+                    if (isset($currentRelationHashes[$relation['hash']])) {
+                        unset($currentRelationHashes[$relation['hash']]);
                         $result['keptNodes']++;
                         $relation['_ACTION'] = 'KEPT';
                     } else {
                         // If new, add it:
                         if (!$testOnly) {
                         $result['keptNodes']++;
                         $relation['_ACTION'] = 'KEPT';
                     } else {
                         // If new, add it:
                         if (!$testOnly) {
-                            $databaseConnection->exec_INSERTquery('sys_refindex', $relation);
+                            $connection->insert('sys_refindex', $relation);
                         }
                         $result['addedNodes']++;
                         $relation['_ACTION'] = 'ADDED';
                     }
                 }
                 $result['relations'] = $relations;
                         }
                         $result['addedNodes']++;
                         $relation['_ACTION'] = 'ADDED';
                     }
                 }
                 $result['relations'] = $relations;
-            } else {
-                return $result;
             }
         }
 
         // If any old are left, remove them:
             }
         }
 
         // If any old are left, remove them:
-        if (!empty($currentRelations)) {
-            $hashList = array_keys($currentRelations);
+        if (!empty($currentRelationHashes)) {
+            $hashList = array_keys($currentRelationHashes);
             if (!empty($hashList)) {
                 $result['deletedNodes'] = count($hashList);
                 $result['deletedNodes_hashList'] = implode(',', $hashList);
                 if (!$testOnly) {
             if (!empty($hashList)) {
                 $result['deletedNodes'] = count($hashList);
                 $result['deletedNodes_hashList'] = implode(',', $hashList);
                 if (!$testOnly) {
-                    $databaseConnection->exec_DELETEquery(
-                        'sys_refindex', 'hash IN (' . implode(',', $databaseConnection->fullQuoteArray($hashList, 'sys_refindex')) . ')'
-                    );
+                    $maxBindParameters = PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
+                    foreach (array_chunk($hashList, $maxBindParameters - 10, true) as $chunk) {
+                        if (empty($chunk)) {
+                            continue;
+                        }
+                        $queryBuilder = $connection->createQueryBuilder();
+                        $queryBuilder
+                            ->delete('sys_refindex')
+                            ->where(
+                                $queryBuilder->expr()->in(
+                                    'hash',
+                                    $queryBuilder->createNamedParameter($chunk, Connection::PARAM_STR_ARRAY)
+                                )
+                            )
+                            ->execute();
+                    }
                 }
             }
         }
                 }
             }
         }
@@ -272,7 +326,7 @@ class ReferenceIndex
      *
      * @param string $tableName Table name from $GLOBALS['TCA']
      * @param int $uid Record UID
      *
      * @param string $tableName Table name from $GLOBALS['TCA']
      * @param int $uid Record UID
-     * @return array|NULL Index Rows
+     * @return array|null Index Rows
      */
     public function generateRefIndexData($tableName, $uid)
     {
      */
     public function generateRefIndexData($tableName, $uid)
     {
@@ -280,39 +334,63 @@ class ReferenceIndex
             return null;
         }
 
             return null;
         }
 
-        $this->relations = array();
+        $this->relations = [];
 
 
-        // Fetch tableRelationFields and save them in cache if not there yet
-        $cacheId = static::$cachePrefixTableRelationFields . $tableName;
-        if (!$this->runtimeCache->has($cacheId)) {
-            $tableRelationFields = $this->fetchTableRelationFields($tableName);
-            $this->runtimeCache->set($cacheId, $tableRelationFields);
-        } else {
-            $tableRelationFields = $this->runtimeCache->get($cacheId);
+        $record = null;
+        $uid = $uid ? (int)$uid : 0;
+        if ($uid) {
+            // Get raw record from DB
+            $record = $this->getRecordRawCached($tableName, $uid);
         }
 
         }
 
-        // Return if there are no fields which could contain relations
-        if ($tableRelationFields === '') {
-            return $this->relations;
+        if (!is_array($record)) {
+            return null;
         }
 
         }
 
-        $deleteField = $GLOBALS['TCA'][$tableName]['ctrl']['delete'];
+        return $this->generateDataUsingRecord($tableName, $record);
+    }
 
 
-        if ($tableRelationFields === '*') {
-            // If one field of a record is of type flex, all fields have to be fetched to be passed to BackendUtility::getFlexFormDS
-            $selectFields = '*';
-        } else {
-            // otherwise only fields that might contain relations are fetched
-            $selectFields = 'uid,' . $tableRelationFields . ($deleteField ? ',' . $deleteField : '');
-        }
+    /**
+     * Returns the amount of references for the given record
+     *
+     * @param string $tableName
+     * @param int $uid
+     * @return int
+     */
+    public function getNumberOfReferencedRecords(string $tableName, int $uid): int
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
+        return (int)$queryBuilder
+            ->count('*')->from('sys_refindex')
+            ->where(
+                $queryBuilder->expr()->eq(
+                    'ref_table',
+                    $queryBuilder->createNamedParameter($tableName, \PDO::PARAM_STR)
+                ),
+                $queryBuilder->expr()->eq(
+                    'ref_uid',
+                    $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
+                ),
+                $queryBuilder->expr()->eq(
+                    'deleted',
+                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                )
+            )->execute()->fetchColumn(0);
+    }
 
 
-        // Get raw record from DB:
-        $record = $this->getDatabaseConnection()->exec_SELECTgetSingleRow($selectFields, $tableName, 'uid=' . (int)$uid);
-        if (!is_array($record)) {
-            return null;
-        }
+    /**
+     * Calculate the relations for a record of a given table
+     *
+     * @param string $tableName Table being processed
+     * @param array $record Record from $tableName
+     * @return array
+     */
+    protected function generateDataUsingRecord(string $tableName, array $record): array
+    {
+        $this->relations = [];
+        $deleteField = $GLOBALS['TCA'][$tableName]['ctrl']['delete'];
 
 
-        // Deleted:
+        // Is the record deleted?
         $deleted = $deleteField && $record[$deleteField] ? 1 : 0;
 
         // Get all relations from record:
         $deleted = $deleteField && $record[$deleteField] ? 1 : 0;
 
         // Get all relations from record:
@@ -322,43 +400,31 @@ class ReferenceIndex
             // Based on type
             switch ((string)$fieldRelations['type']) {
                 case 'db':
             // Based on type
             switch ((string)$fieldRelations['type']) {
                 case 'db':
-                    $this->createEntryData_dbRels($tableName, $uid, $fieldName, '', $deleted, $fieldRelations['itemArray']);
-                    break;
-                case 'file_reference':
-                    // not used (see getRelations()), but fallback to file
-                case 'file':
-                    $this->createEntryData_fileRels($tableName, $uid, $fieldName, '', $deleted, $fieldRelations['newValueFiles']);
+                    $this->createEntryDataForDatabaseRelationsUsingRecord($tableName, $record, $fieldName, '', $deleted, $fieldRelations['itemArray']);
                     break;
                 case 'flex':
                     // DB references in FlexForms
                     if (is_array($fieldRelations['flexFormRels']['db'])) {
                         foreach ($fieldRelations['flexFormRels']['db'] as $flexPointer => $subList) {
                     break;
                 case 'flex':
                     // DB references in FlexForms
                     if (is_array($fieldRelations['flexFormRels']['db'])) {
                         foreach ($fieldRelations['flexFormRels']['db'] as $flexPointer => $subList) {
-                            $this->createEntryData_dbRels($tableName, $uid, $fieldName, $flexPointer, $deleted, $subList);
-                        }
-                    }
-                    // File references in FlexForms
-                    // @todo #65463 Test correct handling of file references in FlexForms
-                    if (is_array($fieldRelations['flexFormRels']['file'])) {
-                        foreach ($fieldRelations['flexFormRels']['file'] as $flexPointer => $subList) {
-                            $this->createEntryData_fileRels($tableName, $uid, $fieldName, $flexPointer, $deleted, $subList);
+                            $this->createEntryDataForDatabaseRelationsUsingRecord($tableName, $record, $fieldName, $flexPointer, $deleted, $subList);
                         }
                     }
                     // Soft references in FlexForms
                     // @todo #65464 Test correct handling of soft references in FlexForms
                     if (is_array($fieldRelations['flexFormRels']['softrefs'])) {
                         foreach ($fieldRelations['flexFormRels']['softrefs'] as $flexPointer => $subList) {
                         }
                     }
                     // Soft references in FlexForms
                     // @todo #65464 Test correct handling of soft references in FlexForms
                     if (is_array($fieldRelations['flexFormRels']['softrefs'])) {
                         foreach ($fieldRelations['flexFormRels']['softrefs'] as $flexPointer => $subList) {
-                            $this->createEntryData_softreferences($tableName, $uid, $fieldName, $flexPointer, $deleted, $subList['keys']);
+                            $this->createEntryDataForSoftReferencesUsingRecord($tableName, $record, $fieldName, $flexPointer, $deleted, $subList['keys']);
                         }
                     }
                     break;
             }
             // Soft references in the field
             if (is_array($fieldRelations['softrefs'])) {
                         }
                     }
                     break;
             }
             // Soft references in the field
             if (is_array($fieldRelations['softrefs'])) {
-                $this->createEntryData_softreferences($tableName, $uid, $fieldName, '', $deleted, $fieldRelations['softrefs']['keys']);
+                $this->createEntryDataForSoftReferencesUsingRecord($tableName, $record, $fieldName, '', $deleted, $fieldRelations['softrefs']['keys']);
             }
         }
 
             }
         }
 
-        return $this->relations;
+        return array_filter($this->relations);
     }
 
     /**
     }
 
     /**
@@ -370,38 +436,75 @@ class ReferenceIndex
      * @param string $field Fieldname of source record (where reference is located)
      * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in [field]
      * @param int $deleted Whether record is deleted-flagged or not
      * @param string $field Fieldname of source record (where reference is located)
      * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in [field]
      * @param int $deleted Whether record is deleted-flagged or not
-     * @param string $ref_table For database references; the tablename the reference points to. Special keyword "_FILE" indicates that "ref_string" is a file reference either absolute or relative to PATH_site. Special keyword "_STRING" indicates some special usage (typ. softreference) where "ref_string" is used for the value.
-     * @param int $ref_uid For database references; The UID of the record (zero "ref_table" is "_FILE" or "_STRING")
-     * @param string $ref_string For "_FILE" or "_STRING" references: The filepath (relative to PATH_site or absolute) or other string.
+     * @param string $ref_table For database references; the tablename the reference points to. Special keyword "_STRING" indicates some special usage (typ. softreference) where "ref_string" is used for the value.
+     * @param int $ref_uid For database references; The UID of the record (zero "ref_table" is "_STRING")
+     * @param string $ref_string For "_STRING" references: The string.
      * @param int $sort The sorting order of references if many (the "group" or "select" TCA types). -1 if no sorting order is specified.
      * @param string $softref_key If the reference is a soft reference, this is the soft reference parser key. Otherwise empty.
      * @param string $softref_id Soft reference ID for key. Might be useful for replace operations.
      * @param int $sort The sorting order of references if many (the "group" or "select" TCA types). -1 if no sorting order is specified.
      * @param string $softref_key If the reference is a soft reference, this is the soft reference parser key. Otherwise empty.
      * @param string $softref_id Soft reference ID for key. Might be useful for replace operations.
-     * @return array Array record to insert into table.
+     * @return array|null Array record to insert into table.
      */
     public function createEntryData($table, $uid, $field, $flexPointer, $deleted, $ref_table, $ref_uid, $ref_string = '', $sort = -1, $softref_key = '', $softref_id = '')
     {
      */
     public function createEntryData($table, $uid, $field, $flexPointer, $deleted, $ref_table, $ref_uid, $ref_string = '', $sort = -1, $softref_key = '', $softref_id = '')
     {
-        if (BackendUtility::isTableWorkspaceEnabled($table)) {
-            $element = BackendUtility::getRecord($table, $uid, 't3ver_wsid');
-            if ($element !== null && isset($element['t3ver_wsid']) && (int)$element['t3ver_wsid'] !== $this->getWorkspaceId()) {
-                //The given Element is ws-enabled but doesn't live in the selected workspace
-                // => don't add index as it's not actually there
+        $uid = $uid ? (int)$uid : 0;
+        if (!$uid) {
+            return null;
+        }
+        return $this->createEntryDataUsingRecord(
+            (string)$table,
+            $this->getRecordRawCached($table, $uid),
+            (string)$field,
+            (string)$flexPointer,
+            $deleted ? (int)$deleted : 0,
+            (string)$ref_table,
+            $ref_uid ? (int)$ref_uid : 0,
+            (string)$ref_string,
+            $sort ? (int)$sort : 0,
+            (string)$softref_key,
+            (string)$softref_id
+        );
+    }
+
+    /**
+     * Create array with field/value pairs ready to insert in database
+     *
+     * @param string $tableName Tablename of source record (where reference is located)
+     * @param array $record Record from $table
+     * @param string $fieldName Fieldname of source record (where reference is located)
+     * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in [$field]
+     * @param int $deleted Whether record is deleted-flagged or not
+     * @param string $referencedTable In database references the tablename the reference points to. Keyword "_STRING" indicates special usage (typ. SoftReference) in $referenceString
+     * @param int $referencedUid In database references the UID of the record (zero $referencedTable is "_STRING")
+     * @param string $referenceString For "_STRING" references: The string.
+     * @param int $sort The sorting order of references if many (the "group" or "select" TCA types). -1 if no sorting order is specified.
+     * @param string $softReferenceKey If the reference is a soft reference, this is the soft reference parser key. Otherwise empty.
+     * @param string $softReferenceId Soft reference ID for key. Might be useful for replace operations.
+     * @return array|bool Array to insert in DB or false if record should not be processed
+     */
+    protected function createEntryDataUsingRecord(string $tableName, array $record, string $fieldName, string $flexPointer, int $deleted, string $referencedTable, int $referencedUid, string $referenceString = '', int $sort = -1, string $softReferenceKey = '', string $softReferenceId = '')
+    {
+        $workspaceId = 0;
+        if (BackendUtility::isTableWorkspaceEnabled($tableName)) {
+            $workspaceId = $this->getWorkspaceId();
+            if (isset($record['t3ver_wsid']) && (int)$record['t3ver_wsid'] !== $workspaceId) {
+                // The given record is workspace-enabled but doesn't live in the selected workspace => don't add index as it's not actually there
                 return false;
             }
         }
                 return false;
             }
         }
-        return array(
-            'tablename' => $table,
-            'recuid' => $uid,
-            'field' => $field,
+        return [
+            'tablename' => $tableName,
+            'recuid' => $record['uid'],
+            'field' => $fieldName,
             'flexpointer' => $flexPointer,
             'flexpointer' => $flexPointer,
-            'softref_key' => $softref_key,
-            'softref_id' => $softref_id,
+            'softref_key' => $softReferenceKey,
+            'softref_id' => $softReferenceId,
             'sorting' => $sort,
             'sorting' => $sort,
-            'deleted' => $deleted,
-            'workspace' => $this->getWorkspaceId(),
-            'ref_table' => $ref_table,
-            'ref_uid' => $ref_uid,
-            'ref_string' => $ref_string
-        );
+            'deleted' => (int)$deleted,
+            'workspace' => $workspaceId,
+            'ref_table' => $referencedTable,
+            'ref_uid' => $referencedUid,
+            'ref_string' => mb_substr($referenceString, 0, 1024)
+        ];
     }
 
     /**
     }
 
     /**
@@ -413,34 +516,37 @@ class ReferenceIndex
      * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in [field]
      * @param int $deleted Whether record is deleted-flagged or not
      * @param array $items Data array with database relations (table/id)
      * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in [field]
      * @param int $deleted Whether record is deleted-flagged or not
      * @param array $items Data array with database relations (table/id)
-     * @return void
      */
     public function createEntryData_dbRels($table, $uid, $fieldName, $flexPointer, $deleted, $items)
     {
      */
     public function createEntryData_dbRels($table, $uid, $fieldName, $flexPointer, $deleted, $items)
     {
-        foreach ($items as $sort => $i) {
-            $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, $i['table'], $i['id'], '', $sort);
+        $uid = $uid ? (int)$uid : 0;
+        if (!$uid) {
+            return;
         }
         }
+        $this->createEntryDataForDatabaseRelationsUsingRecord(
+            (string)$table,
+            $this->getRecordRawCached($table, $uid),
+            (string)$fieldName,
+            (string)$flexPointer,
+            $deleted ? (int)$deleted : 0,
+            (array)$items
+        );
     }
 
     /**
     }
 
     /**
-     * Enter file references to ->relations array
+     * Add database references to ->relations array based on fetched record
      *
      *
-     * @param string $table Tablename of source record (where reference is located)
-     * @param int $uid UID of source record (where reference is located)
+     * @param string $tableName Tablename of source record (where reference is located)
+     * @param array $record Record from $tableName
      * @param string $fieldName Fieldname of source record (where reference is located)
      * @param string $fieldName Fieldname of source record (where reference is located)
-     * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in [field]
+     * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in $fieldName
      * @param int $deleted Whether record is deleted-flagged or not
      * @param int $deleted Whether record is deleted-flagged or not
-     * @param array $items Data array with file relations
-     * @return void
+     * @param array $items Data array with database relations (table/id)
      */
      */
-    public function createEntryData_fileRels($table, $uid, $fieldName, $flexPointer, $deleted, $items)
+    protected function createEntryDataForDatabaseRelationsUsingRecord(string $tableName, array $record, string $fieldName, string $flexPointer, int $deleted, array $items)
     {
         foreach ($items as $sort => $i) {
     {
         foreach ($items as $sort => $i) {
-            $filePath = $i['ID_absFile'];
-            if (GeneralUtility::isFirstPartOfStr($filePath, PATH_site)) {
-                $filePath = PathUtility::stripPathSitePrefix($filePath);
-            }
-            $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, '_FILE', 0, $filePath, $sort);
+            $this->relations[] = $this->createEntryDataUsingRecord($tableName, $record, $fieldName, $flexPointer, $deleted, $i['table'], (int)$i['id'], '', $sort);
         }
     }
 
         }
     }
 
@@ -453,29 +559,47 @@ class ReferenceIndex
      * @param string $flexPointer Pointer to location inside FlexForm structure
      * @param int $deleted
      * @param array $keys Data array with soft reference keys
      * @param string $flexPointer Pointer to location inside FlexForm structure
      * @param int $deleted
      * @param array $keys Data array with soft reference keys
-     * @return void
      */
     public function createEntryData_softreferences($table, $uid, $fieldName, $flexPointer, $deleted, $keys)
     {
      */
     public function createEntryData_softreferences($table, $uid, $fieldName, $flexPointer, $deleted, $keys)
     {
-        if (is_array($keys)) {
-            foreach ($keys as $spKey => $elements) {
-                if (is_array($elements)) {
-                    foreach ($elements as $subKey => $el) {
-                        if (is_array($el['subst'])) {
-                            switch ((string)$el['subst']['type']) {
-                                case 'db':
-                                    list($tableName, $recordId) = explode(':', $el['subst']['recordRef']);
-                                    $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, $tableName, $recordId, '', -1, $spKey, $subKey);
-                                    break;
-                                case 'file_reference':
-                                    // not used (see getRelations()), but fallback to file
-                                case 'file':
-                                    $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, '_FILE', 0, $el['subst']['relFileName'], -1, $spKey, $subKey);
-                                    break;
-                                case 'string':
-                                    $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, '_STRING', 0, $el['subst']['tokenValue'], -1, $spKey, $subKey);
-                                    break;
-                            }
+        $uid = $uid ? (int)$uid : 0;
+        if (!$uid || !is_array($keys)) {
+            return;
+        }
+        $this->createEntryDataForSoftReferencesUsingRecord(
+            (string)$table,
+            $this->getRecordRawCached($table, $uid),
+            (string)$fieldName,
+            (string)$flexPointer,
+            $deleted ? (int)$deleted : 0,
+            (array)$keys
+        );
+    }
+
+    /**
+     * Add SoftReference references to ->relations array based on fetched record
+     *
+     * @param string $tableName Tablename of source record (where reference is located)
+     * @param array $record Record from $tableName
+     * @param string $fieldName Fieldname of source record (where reference is located)
+     * @param string $flexPointer Pointer to location inside FlexForm structure where reference is located in $fieldName
+     * @param int $deleted Whether record is deleted-flagged or not
+     * @param array $keys Data array with soft reference keys
+     */
+    protected function createEntryDataForSoftReferencesUsingRecord(string $tableName, array $record, string $fieldName, string $flexPointer, int $deleted, array $keys)
+    {
+        foreach ($keys as $spKey => $elements) {
+            if (is_array($elements)) {
+                foreach ($elements as $subKey => $el) {
+                    if (is_array($el['subst'])) {
+                        switch ((string)$el['subst']['type']) {
+                            case 'db':
+                                list($referencedTable, $referencedUid) = explode(':', $el['subst']['recordRef']);
+                                $this->relations[] = $this->createEntryDataUsingRecord($tableName, $record, $fieldName, $flexPointer, $deleted, $referencedTable, (int)$referencedUid, '', -1, $spKey, $subKey);
+                                break;
+                            case 'string':
+                                $this->relations[] = $this->createEntryDataUsingRecord($tableName, $record, $fieldName, $flexPointer, $deleted, '_STRING', 0, $el['subst']['tokenValue'], -1, $spKey, $subKey);
+                                break;
                         }
                     }
                 }
                         }
                     }
                 }
@@ -492,7 +616,7 @@ class ReferenceIndex
     /**
      * Returns relation information for a $table/$row-array
      * Traverses all fields in input row which are configured in TCA/columns
     /**
      * Returns relation information for a $table/$row-array
      * Traverses all fields in input row which are configured in TCA/columns
-     * It looks for hard relations to files and records in the TCA types "select" and "group"
+     * It looks for hard relations to records in the TCA types "select" and "group"
      *
      * @param string $table Table name
      * @param array $row Row from table
      *
      * @param string $table Table name
      * @param array $row Row from table
@@ -504,80 +628,50 @@ class ReferenceIndex
     {
         // Initialize:
         $uid = $row['uid'];
     {
         // Initialize:
         $uid = $row['uid'];
-        $outRow = array();
+        $outRow = [];
         foreach ($row as $field => $value) {
         foreach ($row as $field => $value) {
-            if (!isset(static::$nonRelationFields[$field]) && is_array($GLOBALS['TCA'][$table]['columns'][$field]) && (!$onlyField || $onlyField === $field)) {
+            if ($this->shouldExcludeTableColumnFromReferenceIndex($table, $field, $onlyField) === false) {
                 $conf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
                 $conf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
-                // Add files
-                $resultsFromFiles = $this->getRelations_procFiles($value, $conf, $uid);
-                if (!empty($resultsFromFiles)) {
-                    // We have to fill different arrays here depending on the result.
-                    // internal_type file is still a relation of type file and
-                    // since http://forge.typo3.org/issues/49538 internal_type file_reference
-                    // is a database relation to a sys_file record
-                    $fileResultsFromFiles = array();
-                    $dbResultsFromFiles = array();
-                    foreach ($resultsFromFiles as $resultFromFiles) {
-                        if (isset($resultFromFiles['table']) && $resultFromFiles['table'] === 'sys_file') {
-                            $dbResultsFromFiles[] = $resultFromFiles;
-                        } else {
-                            // Creates an entry for the field with all the files:
-                            $fileResultsFromFiles[] = $resultFromFiles;
-                        }
-                    }
-                    if (!empty($fileResultsFromFiles)) {
-                        $outRow[$field] = array(
-                            'type' => 'file',
-                            'newValueFiles' => $fileResultsFromFiles
-                        );
-                    }
-                    if (!empty($dbResultsFromFiles)) {
-                        $outRow[$field] = array(
-                            'type' => 'db',
-                            'itemArray' => $dbResultsFromFiles
-                        );
-                    }
-                }
                 // Add a softref definition for link fields if the TCA does not specify one already
                 // Add a softref definition for link fields if the TCA does not specify one already
-                if ($conf['type'] === 'input' && isset($conf['wizards']['link']) && empty($conf['softref'])) {
+                if ($conf['type'] === 'input' && $conf['renderType'] === 'inputLink' && empty($conf['softref'])) {
                     $conf['softref'] = 'typolink';
                 }
                 // Add DB:
                     $conf['softref'] = 'typolink';
                 }
                 // Add DB:
-                $resultsFromDatabase = $this->getRelations_procDB($value, $conf, $uid, $table, $field);
+                $resultsFromDatabase = $this->getRelations_procDB($value, $conf, $uid, $table);
                 if (!empty($resultsFromDatabase)) {
                     // Create an entry for the field with all DB relations:
                 if (!empty($resultsFromDatabase)) {
                     // Create an entry for the field with all DB relations:
-                    $outRow[$field] = array(
+                    $outRow[$field] = [
                         'type' => 'db',
                         'itemArray' => $resultsFromDatabase
                         'type' => 'db',
                         'itemArray' => $resultsFromDatabase
-                    );
+                    ];
                 }
                 }
-                // For "flex" fieldtypes we need to traverse the structure looking for file and db references of course!
+                // For "flex" fieldtypes we need to traverse the structure looking for db references of course!
                 if ($conf['type'] === 'flex') {
                     // Get current value array:
                 if ($conf['type'] === 'flex') {
                     // Get current value array:
-                    // NOTICE: failure to resolve Data Structures can lead to integrity problems with the reference index. Please look up the note in the JavaDoc documentation for the function \TYPO3\CMS\Backend\Utility\BackendUtility::getFlexFormDS()
+                    // NOTICE: failure to resolve Data Structures can lead to integrity problems with the reference index. Please look up
+                    // the note in the JavaDoc documentation for the function FlexFormTools->getDataStructureIdentifier()
                     $currentValueArray = GeneralUtility::xml2array($value);
                     $currentValueArray = GeneralUtility::xml2array($value);
-                    // Traversing the XML structure, processing files:
+                    // Traversing the XML structure, processing:
                     if (is_array($currentValueArray)) {
                     if (is_array($currentValueArray)) {
-                        $this->temp_flexRelations = array(
-                            'db' => array(),
-                            'file' => array(),
-                            'softrefs' => array()
-                        );
+                        $this->temp_flexRelations = [
+                            'db' => [],
+                            'softrefs' => []
+                        ];
                         // Create and call iterator object:
                         $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
                         $flexFormTools->traverseFlexFormXMLData($table, $field, $row, $this, 'getRelations_flexFormCallBack');
                         // Create an entry for the field:
                         // Create and call iterator object:
                         $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
                         $flexFormTools->traverseFlexFormXMLData($table, $field, $row, $this, 'getRelations_flexFormCallBack');
                         // Create an entry for the field:
-                        $outRow[$field] = array(
+                        $outRow[$field] = [
                             'type' => 'flex',
                             'flexFormRels' => $this->temp_flexRelations
                             'type' => 'flex',
                             'flexFormRels' => $this->temp_flexRelations
-                        );
+                        ];
                     }
                 }
                 // Soft References:
                 if ((string)$value !== '') {
                     $softRefValue = $value;
                     }
                 }
                 // Soft References:
                 if ((string)$value !== '') {
                     $softRefValue = $value;
-                    $softRefs = BackendUtility::explodeSoftRefParserList($conf['softref']);
-                    if ($softRefs !== false) {
+                    if (!empty($conf['softref'])) {
+                        $softRefs = BackendUtility::explodeSoftRefParserList($conf['softref']);
                         foreach ($softRefs as $spKey => $spParams) {
                             $softRefObj = BackendUtility::softRefParserObj($spKey);
                             if (is_object($softRefObj)) {
                         foreach ($softRefs as $spKey => $spParams) {
                             $softRefObj = BackendUtility::softRefParserObj($spKey);
                             if (is_object($softRefObj)) {
@@ -601,56 +695,32 @@ class ReferenceIndex
     }
 
     /**
     }
 
     /**
-     * Callback function for traversing the FlexForm structure in relation to finding file and DB references!
+     * Callback function for traversing the FlexForm structure in relation to finding DB references!
      *
      * @param array $dsArr Data structure for the current value
      * @param mixed $dataValue Current value
      * @param array $PA Additional configuration used in calling function
      * @param string $structurePath Path of value in DS structure
      *
      * @param array $dsArr Data structure for the current value
      * @param mixed $dataValue Current value
      * @param array $PA Additional configuration used in calling function
      * @param string $structurePath Path of value in DS structure
-     * @param object $parentObject Object reference to caller (unused)
-     * @return void
-     * @see DataHandler::checkValue_flex_procInData_travDS(),FlexFormTools::traverseFlexFormXMLData()
+     * @see DataHandler::checkValue_flex_procInData_travDS()
+     * @see FlexFormTools::traverseFlexFormXMLData()
      */
      */
-    public function getRelations_flexFormCallBack($dsArr, $dataValue, $PA, $structurePath, $parentObject)
+    public function getRelations_flexFormCallBack($dsArr, $dataValue, $PA, $structurePath)
     {
         // Removing "data/" in the beginning of path (which points to location in data array)
         $structurePath = substr($structurePath, 5) . '/';
         $dsConf = $dsArr['TCEforms']['config'];
         // Implode parameter values:
     {
         // Removing "data/" in the beginning of path (which points to location in data array)
         $structurePath = substr($structurePath, 5) . '/';
         $dsConf = $dsArr['TCEforms']['config'];
         // Implode parameter values:
-        list($table, $uid, $field) = array(
+        list($table, $uid, $field) = [
             $PA['table'],
             $PA['uid'],
             $PA['field']
             $PA['table'],
             $PA['uid'],
             $PA['field']
-        );
-        // Add files
-        $resultsFromFiles = $this->getRelations_procFiles($dataValue, $dsConf, $uid);
-        if (!empty($resultsFromFiles)) {
-            // We have to fill different arrays here depending on the result.
-            // internal_type file is still a relation of type file and
-            // since http://forge.typo3.org/issues/49538 internal_type file_reference
-            // is a database relation to a sys_file record
-            $fileResultsFromFiles = array();
-            $dbResultsFromFiles = array();
-            foreach ($resultsFromFiles as $resultFromFiles) {
-                if (isset($resultFromFiles['table']) && $resultFromFiles['table'] === 'sys_file') {
-                    $dbResultsFromFiles[] = $resultFromFiles;
-                } else {
-                    $fileResultsFromFiles[] = $resultFromFiles;
-                }
-            }
-            if (!empty($fileResultsFromFiles)) {
-                $this->temp_flexRelations['file'][$structurePath] = $fileResultsFromFiles;
-            }
-            if (!empty($dbResultsFromFiles)) {
-                $this->temp_flexRelations['db'][$structurePath] = $dbResultsFromFiles;
-            }
-        }
+        ];
         // Add a softref definition for link fields if the TCA does not specify one already
         // Add a softref definition for link fields if the TCA does not specify one already
-        if ($dsConf['type'] === 'input' && isset($dsConf['wizards']['link']) && empty($dsConf['softref'])) {
+        if ($dsConf['type'] === 'input' && $dsConf['renderType'] === 'inputLink' && empty($dsConf['softref'])) {
             $dsConf['softref'] = 'typolink';
         }
         // Add DB:
             $dsConf['softref'] = 'typolink';
         }
         // Add DB:
-        $resultsFromDatabase = $this->getRelations_procDB($dataValue, $dsConf, $uid, $table, $field);
+        $resultsFromDatabase = $this->getRelations_procDB($dataValue, $dsConf, $uid, $table);
         if (!empty($resultsFromDatabase)) {
             // Create an entry for the field with all DB relations:
             $this->temp_flexRelations['db'][$structurePath] = $resultsFromDatabase;
         if (!empty($resultsFromDatabase)) {
             // Create an entry for the field with all DB relations:
             $this->temp_flexRelations['db'][$structurePath] = $resultsFromDatabase;
@@ -680,111 +750,35 @@ class ReferenceIndex
     }
 
     /**
     }
 
     /**
-     * Check field configuration if it is a file relation field and extract file relations if any
-     *
-     * @param string $value Field value
-     * @param array $conf Field configuration array of type "TCA/columns
-     * @param int $uid Field uid
-     * @return bool|array If field type is OK it will return an array with the files inside. Else FALSE
-     */
-    public function getRelations_procFiles($value, $conf, $uid)
-    {
-        if ($conf['type'] !== 'group' || ($conf['internal_type'] !== 'file' && $conf['internal_type'] !== 'file_reference')) {
-            return false;
-        }
-
-        // Collect file values in array:
-        if ($conf['MM']) {
-            $theFileValues = array();
-            $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
-            $dbAnalysis->start('', 'files', $conf['MM'], $uid);
-            foreach ($dbAnalysis->itemArray as $someval) {
-                if ($someval['id']) {
-                    $theFileValues[] = $someval['id'];
-                }
-            }
-        } else {
-            $theFileValues = explode(',', $value);
-        }
-        // Traverse the files and add them:
-        $uploadFolder = $conf['internal_type'] === 'file' ? $conf['uploadfolder'] : '';
-        $destinationFolder = $this->destPathFromUploadFolder($uploadFolder);
-        $newValueFiles = array();
-        foreach ($theFileValues as $file) {
-            if (trim($file)) {
-                $realFile = $destinationFolder . '/' . trim($file);
-                $newValueFile = array(
-                    'filename' => basename($file),
-                    'ID' => md5($realFile),
-                    'ID_absFile' => $realFile
-                );
-                // Set sys_file and id for referenced files
-                if ($conf['internal_type'] === 'file_reference') {
-                    try {
-                        $file = ResourceFactory::getInstance()->retrieveFileOrFolderObject($file);
-                        if ($file instanceof File || $file instanceof Folder) {
-                            // For setting this as sys_file relation later, the keys filename, ID and ID_absFile
-                            // have not to be included, because the are not evaluated for db relations.
-                            $newValueFile = array(
-                                'table' => 'sys_file',
-                                'id' => $file->getUid()
-                            );
-                        }
-                    } catch (\Exception $e) {
-                    }
-                }
-                $newValueFiles[] = $newValueFile;
-            }
-        }
-        return $newValueFiles;
-    }
-
-    /**
      * Check field configuration if it is a DB relation field and extract DB relations if any
      *
      * @param string $value Field value
      * @param array $conf Field configuration array of type "TCA/columns
      * @param int $uid Field uid
      * @param string $table Table name
      * Check field configuration if it is a DB relation field and extract DB relations if any
      *
      * @param string $value Field value
      * @param array $conf Field configuration array of type "TCA/columns
      * @param int $uid Field uid
      * @param string $table Table name
-     * @param string $field Field name
-     * @return array If field type is OK it will return an array with the database relations. Else FALSE
+     * @return array|bool If field type is OK it will return an array with the database relations. Else FALSE
      */
      */
-    public function getRelations_procDB($value, $conf, $uid, $table = '', $field = '')
+    public function getRelations_procDB($value, $conf, $uid, $table = '')
     {
         // Get IRRE relations
         if (empty($conf)) {
             return false;
     {
         // Get IRRE relations
         if (empty($conf)) {
             return false;
-        } elseif ($conf['type'] === 'inline' && !empty($conf['foreign_table']) && empty($conf['MM'])) {
+        }
+        if ($conf['type'] === 'inline' && !empty($conf['foreign_table']) && empty($conf['MM'])) {
             $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
             $dbAnalysis->setUseLiveReferenceIds(false);
             $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
             return $dbAnalysis->itemArray;
             // DB record lists:
             $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
             $dbAnalysis->setUseLiveReferenceIds(false);
             $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
             return $dbAnalysis->itemArray;
             // DB record lists:
-        } elseif ($this->isDbReferenceField($conf)) {
+        }
+        if ($this->isDbReferenceField($conf)) {
             $allowedTables = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
             if ($conf['MM_opposite_field']) {
             $allowedTables = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
             if ($conf['MM_opposite_field']) {
-                return array();
+                return [];
             }
             $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
             $dbAnalysis->start($value, $allowedTables, $conf['MM'], $uid, $table, $conf);
             return $dbAnalysis->itemArray;
             }
             $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
             $dbAnalysis->start($value, $allowedTables, $conf['MM'], $uid, $table, $conf);
             return $dbAnalysis->itemArray;
-        } elseif ($conf['type'] === 'inline' && $conf['foreign_table'] === 'sys_file_reference') {
-            // @todo It looks like this was never called before since isDbReferenceField also checks for type 'inline' and any 'foreign_table'
-            $files = $this->getDatabaseConnection()->exec_SELECTgetRows(
-                'uid_local',
-                'sys_file_reference',
-                'tablenames=\'' . $table . '\' AND fieldname=\'' . $field . '\' AND uid_foreign=' . $uid . ' AND deleted=0'
-            );
-            $fileArray = array();
-            if (!empty($files)) {
-                foreach ($files as $fileUid) {
-                    $fileArray[] = array(
-                        'table' => 'sys_file',
-                        'id' => $fileUid['uid_local']
-                    );
-                }
-            }
-            return $fileArray;
         }
         return false;
     }
         }
         return false;
     }
@@ -818,10 +812,20 @@ class ReferenceIndex
     {
         $backendUser = $this->getBackendUser();
         if ($backendUser->workspace === 0 && $backendUser->isAdmin() || $bypassWorkspaceAdminCheck) {
     {
         $backendUser = $this->getBackendUser();
         if ($backendUser->workspace === 0 && $backendUser->isAdmin() || $bypassWorkspaceAdminCheck) {
-            $databaseConnection = $this->getDatabaseConnection();
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
+            $queryBuilder->getRestrictions()->removeAll();
+
+            // Get current index from Database
+            $referenceRecord = $queryBuilder
+                ->select('*')
+                ->from('sys_refindex')
+                ->where(
+                    $queryBuilder->expr()->eq('hash', $queryBuilder->createNamedParameter($hash, \PDO::PARAM_STR))
+                )
+                ->setMaxResults(1)
+                ->execute()
+                ->fetch();
 
 
-            // Get current index from Database:
-            $referenceRecord = $databaseConnection->exec_SELECTgetSingleRow('*', 'sys_refindex', 'hash=' . $databaseConnection->fullQuoteStr($hash, 'sys_refindex'));
             // Check if reference existed.
             if (!is_array($referenceRecord)) {
                 return 'ERROR: No reference record with hash="' . $hash . '" was found!';
             // Check if reference existed.
             if (!is_array($referenceRecord)) {
                 return 'ERROR: No reference record with hash="' . $hash . '" was found!';
@@ -831,14 +835,29 @@ class ReferenceIndex
                 return 'ERROR: Table "' . $referenceRecord['tablename'] . '" was not in TCA!';
             }
 
                 return 'ERROR: Table "' . $referenceRecord['tablename'] . '" was not in TCA!';
             }
 
-            // Get that record from database:
-            $record = $databaseConnection->exec_SELECTgetSingleRow('*', $referenceRecord['tablename'], 'uid=' . (int)$referenceRecord['recuid']);
+            // Get that record from database
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                ->getQueryBuilderForTable($referenceRecord['tablename']);
+            $queryBuilder->getRestrictions()->removeAll();
+            $record = $queryBuilder
+                ->select('*')
+                ->from($referenceRecord['tablename'])
+                ->where(
+                    $queryBuilder->expr()->eq(
+                        'uid',
+                        $queryBuilder->createNamedParameter($referenceRecord['recuid'], \PDO::PARAM_INT)
+                    )
+                )
+                ->setMaxResults(1)
+                ->execute()
+                ->fetch();
+
             if (is_array($record)) {
                 // Get relation for single field from record
                 $recordRelations = $this->getRelations($referenceRecord['tablename'], $record, $referenceRecord['field']);
                 if ($fieldRelation = $recordRelations[$referenceRecord['field']]) {
                     // Initialize data array that is to be sent to DataHandler afterwards:
             if (is_array($record)) {
                 // Get relation for single field from record
                 $recordRelations = $this->getRelations($referenceRecord['tablename'], $record, $referenceRecord['field']);
                 if ($fieldRelation = $recordRelations[$referenceRecord['field']]) {
                     // Initialize data array that is to be sent to DataHandler afterwards:
-                    $dataArray = array();
+                    $dataArray = [];
                     // Based on type
                     switch ((string)$fieldRelation['type']) {
                         case 'db':
                     // Based on type
                     switch ((string)$fieldRelation['type']) {
                         case 'db':
@@ -847,14 +866,6 @@ class ReferenceIndex
                                 return $error;
                             }
                             break;
                                 return $error;
                             }
                             break;
-                        case 'file_reference':
-                            // not used (see getRelations()), but fallback to file
-                        case 'file':
-                            $error = $this->setReferenceValue_fileRels($referenceRecord, $fieldRelation['newValueFiles'], $newValue, $dataArray);
-                            if ($error) {
-                                return $error;
-                            }
-                            break;
                         case 'flex':
                             // DB references in FlexForms
                             if (is_array($fieldRelation['flexFormRels']['db'][$referenceRecord['flexpointer']])) {
                         case 'flex':
                             // DB references in FlexForms
                             if (is_array($fieldRelation['flexFormRels']['db'][$referenceRecord['flexpointer']])) {
@@ -863,13 +874,6 @@ class ReferenceIndex
                                     return $error;
                                 }
                             }
                                     return $error;
                                 }
                             }
-                            // File references in FlexForms
-                            if (is_array($fieldRelation['flexFormRels']['file'][$referenceRecord['flexpointer']])) {
-                                $error = $this->setReferenceValue_fileRels($referenceRecord, $fieldRelation['flexFormRels']['file'][$referenceRecord['flexpointer']], $newValue, $dataArray, $referenceRecord['flexpointer']);
-                                if ($error) {
-                                    return $error;
-                                }
-                            }
                             // Soft references in FlexForms
                             if ($referenceRecord['softref_key'] && is_array($fieldRelation['flexFormRels']['softrefs'][$referenceRecord['flexpointer']]['keys'][$referenceRecord['softref_key']])) {
                                 $error = $this->setReferenceValue_softreferences($referenceRecord, $fieldRelation['flexFormRels']['softrefs'][$referenceRecord['flexpointer']], $newValue, $dataArray, $referenceRecord['flexpointer']);
                             // Soft references in FlexForms
                             if ($referenceRecord['softref_key'] && is_array($fieldRelation['flexFormRels']['softrefs'][$referenceRecord['flexpointer']]['keys'][$referenceRecord['softref_key']])) {
                                 $error = $this->setReferenceValue_softreferences($referenceRecord, $fieldRelation['flexFormRels']['softrefs'][$referenceRecord['flexpointer']], $newValue, $dataArray, $referenceRecord['flexpointer']);
@@ -889,21 +893,19 @@ class ReferenceIndex
                     // Data Array, now ready to be sent to DataHandler
                     if ($returnDataArray) {
                         return $dataArray;
                     // Data Array, now ready to be sent to DataHandler
                     if ($returnDataArray) {
                         return $dataArray;
-                    } else {
-                        // Execute CMD array:
-                        $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
-                        $dataHandler->dontProcessTransformations = true;
-                        $dataHandler->bypassWorkspaceRestrictions = true;
-                        $dataHandler->bypassFileHandling = true;
-                        // Otherwise this cannot update things in deleted records...
-                        $dataHandler->bypassAccessCheckForRecords = true;
-                        // Check has been done previously that there is a backend user which is Admin and also in live workspace
-                        $dataHandler->start($dataArray, array());
-                        $dataHandler->process_datamap();
-                        // Return errors if any:
-                        if (!empty($dataHandler->errorLog)) {
-                            return LF . 'DataHandler:' . implode((LF . 'DataHandler:'), $dataHandler->errorLog);
-                        }
+                    }
+                    // Execute CMD array:
+                    $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
+                    $dataHandler->dontProcessTransformations = true;
+                    $dataHandler->bypassWorkspaceRestrictions = true;
+                    // Otherwise this cannot update things in deleted records...
+                    $dataHandler->bypassAccessCheckForRecords = true;
+                    // Check has been done previously that there is a backend user which is Admin and also in live workspace
+                    $dataHandler->start($dataArray, []);
+                    $dataHandler->process_datamap();
+                    // Return errors if any:
+                    if (!empty($dataHandler->errorLog)) {
+                        return LF . 'DataHandler:' . implode(LF . 'DataHandler:', $dataHandler->errorLog);
                     }
                 }
             }
                     }
                 }
             }
@@ -935,14 +937,14 @@ class ReferenceIndex
                 list($itemArray[$refRec['sorting']]['table'], $itemArray[$refRec['sorting']]['id']) = explode(':', $newValue);
             }
             // Traverse and compile new list of records:
                 list($itemArray[$refRec['sorting']]['table'], $itemArray[$refRec['sorting']]['id']) = explode(':', $newValue);
             }
             // Traverse and compile new list of records:
-            $saveValue = array();
+            $saveValue = [];
             foreach ($itemArray as $pair) {
                 $saveValue[] = $pair['table'] . '_' . $pair['id'];
             }
             // Set in data array:
             if ($flexPointer) {
                 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
             foreach ($itemArray as $pair) {
                 $saveValue[] = $pair['table'] . '_' . $pair['id'];
             }
             // Set in data array:
             if ($flexPointer) {
                 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
-                $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = array();
+                $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = [];
                 $flexFormTools->setArrayValueByPath(substr($flexPointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], implode(',', $saveValue));
             } else {
                 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = implode(',', $saveValue);
                 $flexFormTools->setArrayValueByPath(substr($flexPointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], implode(',', $saveValue));
             } else {
                 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = implode(',', $saveValue);
@@ -955,51 +957,10 @@ class ReferenceIndex
     }
 
     /**
     }
 
     /**
-     * Setting a value for a reference for a FILE field:
-     *
-     * @param array $refRec sys_refindex record
-     * @param array $itemArray Array of references from that field
-     * @param string $newValue Value to substitute current value with (or NULL to unset it)
-     * @param array $dataArray Data array in which the new value is set (passed by reference)
-     * @param string $flexPointer Flexform pointer, if in a flex form field.
-     * @return string Error message if any, otherwise FALSE = OK
-     */
-    public function setReferenceValue_fileRels($refRec, $itemArray, $newValue, &$dataArray, $flexPointer = '')
-    {
-        $ID_absFile = PathUtility::stripPathSitePrefix($itemArray[$refRec['sorting']]['ID_absFile']);
-        if ($ID_absFile === (string)$refRec['ref_string'] && $refRec['ref_table'] === '_FILE') {
-            // Setting or removing value:
-            // Remove value:
-            if ($newValue === null) {
-                unset($itemArray[$refRec['sorting']]);
-            } else {
-                $itemArray[$refRec['sorting']]['filename'] = $newValue;
-            }
-            // Traverse and compile new list of records:
-            $saveValue = array();
-            foreach ($itemArray as $fileInfo) {
-                $saveValue[] = $fileInfo['filename'];
-            }
-            // Set in data array:
-            if ($flexPointer) {
-                $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
-                $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = array();
-                $flexFormTools->setArrayValueByPath(substr($flexPointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], implode(',', $saveValue));
-            } else {
-                $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = implode(',', $saveValue);
-            }
-        } else {
-            return 'ERROR: either "' . $refRec['ref_table'] . '" was not "_FILE" or file PATH_site+"' . $refRec['ref_string'] . '" did not match that of the record ("' . $itemArray[$refRec['sorting']]['ID_absFile'] . '") in sorting index "' . $refRec['sorting'] . '"';
-        }
-
-        return false;
-    }
-
-    /**
      * Setting a value for a soft reference token
      *
      * @param array $refRec sys_refindex record
      * Setting a value for a soft reference token
      *
      * @param array $refRec sys_refindex record
-     * @param array $softref Array of soft reference occurencies
+     * @param array $softref Array of soft reference occurrences
      * @param string $newValue Value to substitute current value with
      * @param array $dataArray Data array in which the new value is set (passed by reference)
      * @param string $flexPointer Flexform pointer, if in a flex form field.
      * @param string $newValue Value to substitute current value with
      * @param array $dataArray Data array in which the new value is set (passed by reference)
      * @param string $flexPointer Flexform pointer, if in a flex form field.
@@ -1020,10 +981,10 @@ class ReferenceIndex
             }
         }
         // Set in data array:
             }
         }
         // Set in data array:
-        if (!strstr($softref['tokenizedContent'], '{softref:')) {
+        if (strpos($softref['tokenizedContent'], '{softref:') === false) {
             if ($flexPointer) {
                 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
             if ($flexPointer) {
                 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
-                $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = array();
+                $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = [];
                 $flexFormTools->setArrayValueByPath(substr($flexPointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], $softref['tokenizedContent']);
             } else {
                 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = $softref['tokenizedContent'];
                 $flexFormTools->setArrayValueByPath(substr($flexPointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], $softref['tokenizedContent']);
             } else {
                 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = $softref['tokenizedContent'];
@@ -1049,13 +1010,13 @@ class ReferenceIndex
      */
     protected function isDbReferenceField(array $configuration)
     {
      */
     protected function isDbReferenceField(array $configuration)
     {
-        return (
+        return
             ($configuration['type'] === 'group' && $configuration['internal_type'] === 'db')
             || (
                 ($configuration['type'] === 'select' || $configuration['type'] === 'inline')
                 && !empty($configuration['foreign_table'])
             )
             ($configuration['type'] === 'group' && $configuration['internal_type'] === 'db')
             || (
                 ($configuration['type'] === 'select' || $configuration['type'] === 'inline')
                 && !empty($configuration['foreign_table'])
             )
-        );
+            ;
     }
 
     /**
     }
 
     /**
@@ -1066,17 +1027,15 @@ class ReferenceIndex
      */
     public function isReferenceField(array $configuration)
     {
      */
     public function isReferenceField(array $configuration)
     {
-        return (
+        return
             $this->isDbReferenceField($configuration)
             ||
             $this->isDbReferenceField($configuration)
             ||
-            ($configuration['type'] === 'group' && ($configuration['internal_type'] === 'file' || $configuration['internal_type'] === 'file_reference')) // getRelations_procFiles
-            ||
-            ($configuration['type'] === 'input' && isset($configuration['wizards']['link'])) // getRelations_procDB
+            ($configuration['type'] === 'input' && $configuration['renderType'] === 'inputLink') // getRelations_procDB
             ||
             $configuration['type'] === 'flex'
             ||
             isset($configuration['softref'])
             ||
             $configuration['type'] === 'flex'
             ||
             isset($configuration['softref'])
-        );
+            ;
     }
 
     /**
     }
 
     /**
@@ -1091,14 +1050,14 @@ class ReferenceIndex
             return '';
         }
 
             return '';
         }
 
-        $fields = array();
+        $fields = [];
 
         foreach ($GLOBALS['TCA'][$tableName]['columns'] as $field => $fieldDefinition) {
             if (is_array($fieldDefinition['config'])) {
                 // Check for flex field
                 if (isset($fieldDefinition['config']['type']) && $fieldDefinition['config']['type'] === 'flex') {
                     // Fetch all fields if the is a field of type flex in the table definition because the complete row is passed to
 
         foreach ($GLOBALS['TCA'][$tableName]['columns'] as $field => $fieldDefinition) {
             if (is_array($fieldDefinition['config'])) {
                 // Check for flex field
                 if (isset($fieldDefinition['config']['type']) && $fieldDefinition['config']['type'] === 'flex') {
                     // Fetch all fields if the is a field of type flex in the table definition because the complete row is passed to
-                    // BackendUtility::getFlexFormDS in the end and might be needed in ds_pointerField or $hookObj->getFlexFormDS_postProcessDS
+                    // FlexFormTools->getDataStructureIdentifier() in the end and might be needed in ds_pointerField or a hook
                     return '*';
                 }
                 // Only fetch this field if it can contain a reference
                     return '*';
                 }
                 // Only fetch this field if it can contain a reference
@@ -1112,20 +1071,6 @@ class ReferenceIndex
     }
 
     /**
     }
 
     /**
-     * Returns destination path to an upload folder given by $folder
-     *
-     * @param string $folder Folder relative to PATH_site
-     * @return string Input folder prefixed with PATH_site. No checking for existence is done. Output must be a folder without trailing slash.
-     */
-    public function destPathFromUploadFolder($folder)
-    {
-        if (!$folder) {
-            return substr(PATH_site, 0, -1);
-        }
-        return PATH_site . $folder;
-    }
-
-    /**
      * Updating Index (External API)
      *
      * @param bool $testOnly If set, only a test
      * Updating Index (External API)
      *
      * @param bool $testOnly If set, only a test
@@ -1134,9 +1079,8 @@ class ReferenceIndex
      */
     public function updateIndex($testOnly, $cli_echo = false)
     {
      */
     public function updateIndex($testOnly, $cli_echo = false)
     {
-        $databaseConnection = $this->getDatabaseConnection();
-        $errors = array();
-        $tableNames = array();
+        $errors = [];
+        $tableNames = [];
         $recCount = 0;
         $tableCount = 0;
         $headerContent = $testOnly ? 'Reference Index being TESTED (nothing written, remove the "--check" argument)' : 'Reference Index being Updated';
         $recCount = 0;
         $tableCount = 0;
         $headerContent = $testOnly ? 'Reference Index being TESTED (nothing written, remove the "--check" argument)' : 'Reference Index being Updated';
@@ -1144,29 +1088,48 @@ class ReferenceIndex
             echo '*******************************************' . LF . $headerContent . LF . '*******************************************' . LF;
         }
         // Traverse all tables:
             echo '*******************************************' . LF . $headerContent . LF . '*******************************************' . LF;
         }
         // Traverse all tables:
+        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
+        $refIndexConnectionName = empty($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping']['sys_refindex'])
+                ? ConnectionPool::DEFAULT_CONNECTION_NAME
+                : $GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping']['sys_refindex'];
+
         foreach ($GLOBALS['TCA'] as $tableName => $cfg) {
         foreach ($GLOBALS['TCA'] as $tableName => $cfg) {
-            if (isset(static::$nonRelationTables[$tableName])) {
+            if ($this->shouldExcludeTableFromReferenceIndex($tableName)) {
                 continue;
             }
                 continue;
             }
-            // Traverse all records in tables, including deleted records:
-            $fieldNames = (BackendUtility::isTableWorkspaceEnabled($tableName) ? 'uid,t3ver_wsid' : 'uid');
-            $res = $databaseConnection->exec_SELECTquery($fieldNames, $tableName, '1=1');
-            if ($databaseConnection->sql_error()) {
-                // Table exists in $TCA but does not exist in the database
-                GeneralUtility::sysLog(sprintf('Table "%s" exists in $TCA but does not exist in the database. You should run the Database Analyzer in the Install Tool to fix this.', $tableName), 'core', GeneralUtility::SYSLOG_SEVERITY_ERROR);
+            $tableConnectionName = empty($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName])
+                ? ConnectionPool::DEFAULT_CONNECTION_NAME
+                : $GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName];
+
+            $fields = ['uid'];
+            if (BackendUtility::isTableWorkspaceEnabled($tableName)) {
+                $fields[] = 't3ver_wsid';
+            }
+            // Traverse all records in tables, including deleted records
+            $queryBuilder = $connectionPool->getQueryBuilderForTable($tableName);
+            $queryBuilder->getRestrictions()->removeAll();
+            try {
+                $queryResult = $queryBuilder
+                    ->select(...$fields)
+                    ->from($tableName)
+                    ->execute();
+            } catch (DBALException $e) {
+                // Table exists in TCA but does not exist in the database
+                $msg = 'Table "' .
+                        $tableName .
+                        '" exists in TCA but does not exist in the database. You should run the Database Analyzer in the Install Tool to fix this.';
+                $this->logger->error($msg, ['exception' => $e]);
                 continue;
             }
                 continue;
             }
+
             $tableNames[] = $tableName;
             $tableCount++;
             $tableNames[] = $tableName;
             $tableCount++;
-            $uidList = array(0);
-            while ($record = $databaseConnection->sql_fetch_assoc($res)) {
-                /** @var $refIndexObj ReferenceIndex */
-                $refIndexObj = GeneralUtility::makeInstance(ReferenceIndex::class);
+            while ($record = $queryResult->fetch()) {
+                $refIndexObj = GeneralUtility::makeInstance(self::class);
                 if (isset($record['t3ver_wsid'])) {
                     $refIndexObj->setWorkspaceId($record['t3ver_wsid']);
                 }
                 $result = $refIndexObj->updateRefIndexTable($tableName, $record['uid'], $testOnly);
                 if (isset($record['t3ver_wsid'])) {
                     $refIndexObj->setWorkspaceId($record['t3ver_wsid']);
                 }
                 $result = $refIndexObj->updateRefIndexTable($tableName, $record['uid'], $testOnly);
-                $uidList[] = $record['uid'];
                 $recCount++;
                 if ($result['addedNodes'] || $result['deletedNodes']) {
                     $error = 'Record ' . $tableName . ':' . $record['uid'] . ' had ' . $result['addedNodes'] . ' added indexes and ' . $result['deletedNodes'] . ' deleted indexes';
                 $recCount++;
                 if ($result['addedNodes'] || $result['deletedNodes']) {
                     $error = 'Record ' . $tableName . ':' . $record['uid'] . ' had ' . $result['addedNodes'] . ' added indexes and ' . $result['deletedNodes'] . ' deleted indexes';
@@ -1176,35 +1139,93 @@ class ReferenceIndex
                     }
                 }
             }
                     }
                 }
             }
-            $databaseConnection->sql_free_result($res);
-
-            // Searching lost indexes for this table:
-            $where = 'tablename=' . $databaseConnection->fullQuoteStr($tableName, 'sys_refindex') . ' AND recuid NOT IN (' . implode(',', $uidList) . ')';
-            $lostIndexes = $databaseConnection->exec_SELECTgetRows('hash', 'sys_refindex', $where);
-            $lostIndexesCount = count($lostIndexes);
-            if ($lostIndexesCount) {
-                $error = 'Table ' . $tableName . ' has ' . $lostIndexesCount . ' lost indexes which are now deleted';
+
+            // Subselect based queries only work on the same connection
+            if ($refIndexConnectionName !== $tableConnectionName) {
+                $this->logger->error('Not checking table "' . $tableName . '" for lost indexes, "sys_refindex" table uses a different connection');
+                continue;
+            }
+
+            // Searching for lost indexes for this table
+            // Build sub-query to find lost records
+            $subQueryBuilder = $connectionPool->getQueryBuilderForTable($tableName);
+            $subQueryBuilder->getRestrictions()->removeAll();
+            $subQueryBuilder
+                ->select('uid')
+                ->from($tableName, 'sub_' . $tableName)
+                ->where(
+                    $subQueryBuilder->expr()->eq(
+                        'sub_' . $tableName . '.uid',
+                        $queryBuilder->quoteIdentifier('sys_refindex.recuid')
+                    )
+                );
+
+            // Main query to find lost records
+            $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_refindex');
+            $queryBuilder->getRestrictions()->removeAll();
+            $lostIndexes = $queryBuilder
+                ->count('hash')
+                ->from('sys_refindex')
+                ->where(
+                    $queryBuilder->expr()->eq(
+                        'tablename',
+                        $queryBuilder->createNamedParameter($tableName, \PDO::PARAM_STR)
+                    ),
+                    'NOT EXISTS (' . $subQueryBuilder->getSQL() . ')'
+                )
+                ->execute()
+                ->fetchColumn(0);
+
+            if ($lostIndexes > 0) {
+                $error = 'Table ' . $tableName . ' has ' . $lostIndexes . ' lost indexes which are now deleted';
                 $errors[] = $error;
                 if ($cli_echo) {
                     echo $error . LF;
                 }
                 if (!$testOnly) {
                 $errors[] = $error;
                 if ($cli_echo) {
                     echo $error . LF;
                 }
                 if (!$testOnly) {
-                    $databaseConnection->exec_DELETEquery('sys_refindex', $where);
+                    $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_refindex');
+                    $queryBuilder->delete('sys_refindex')
+                        ->where(
+                            $queryBuilder->expr()->eq(
+                                'tablename',
+                                $queryBuilder->createNamedParameter($tableName, \PDO::PARAM_STR)
+                            ),
+                            'NOT EXISTS (' . $subQueryBuilder->getSQL() . ')'
+                        )
+                        ->execute();
                 }
             }
         }
                 }
             }
         }
-        // Searching lost indexes for non-existing tables:
-        $where = 'tablename NOT IN (' . implode(',', $databaseConnection->fullQuoteArray($tableNames, 'sys_refindex')) . ')';
-        $lostTables = $databaseConnection->exec_SELECTgetRows('hash', 'sys_refindex', $where);
-        $lostTablesCount = count($lostTables);
-        if ($lostTablesCount) {
-            $error = 'Index table hosted ' . $lostTablesCount . ' indexes for non-existing tables, now removed';
+
+        // Searching lost indexes for non-existing tables
+        $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_refindex');
+        $queryBuilder->getRestrictions()->removeAll();
+        $lostTables = $queryBuilder
+            ->count('hash')
+            ->from('sys_refindex')
+            ->where(
+                $queryBuilder->expr()->notIn(
+                    'tablename',
+                    $queryBuilder->createNamedParameter($tableNames, Connection::PARAM_STR_ARRAY)
+                )
+            )->execute()
+            ->fetchColumn(0);
+
+        if ($lostTables > 0) {
+            $error = 'Index table hosted ' . $lostTables . ' indexes for non-existing tables, now removed';
             $errors[] = $error;
             if ($cli_echo) {
                 echo $error . LF;
             }
             if (!$testOnly) {
             $errors[] = $error;
             if ($cli_echo) {
                 echo $error . LF;
             }
             if (!$testOnly) {
-                $databaseConnection->exec_DELETEquery('sys_refindex', $where);
+                $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_refindex');
+                $queryBuilder->delete('sys_refindex')
+                    ->where(
+                        $queryBuilder->expr()->notIn(
+                            'tablename',
+                            $queryBuilder->createNamedParameter($tableNames, Connection::PARAM_STR_ARRAY)
+                        )
+                    )->execute();
             }
         }
         $errorCount = count($errors);
             }
         }
         $errorCount = count($errors);
@@ -1215,12 +1236,9 @@ class ReferenceIndex
             $recordsCheckedString,
             $errorCount ? FlashMessage::ERROR : FlashMessage::OK
         );
             $recordsCheckedString,
             $errorCount ? FlashMessage::ERROR : FlashMessage::OK
         );
-        /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
-        $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
-        /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
-        $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
-        $defaultFlashMessageQueue->enqueue($flashMessage);
-        $bodyContent = $defaultFlashMessageQueue->renderFlashMessages();
+
+        $flashMessageRenderer = GeneralUtility::makeInstance(FlashMessageRendererResolver::class)->resolve();
+        $bodyContent = $flashMessageRenderer->render([$flashMessage]);
         if ($cli_echo) {
             echo $recordsCheckedString . ($errorCount ? 'Updates: ' . $errorCount : 'Index Integrity was perfect!') . LF;
         }
         if ($cli_echo) {
             echo $recordsCheckedString . ($errorCount ? 'Updates: ' . $errorCount : 'Index Integrity was perfect!') . LF;
         }
@@ -1228,17 +1246,143 @@ class ReferenceIndex
             $registry = GeneralUtility::makeInstance(Registry::class);
             $registry->set('core', 'sys_refindex_lastUpdate', $GLOBALS['EXEC_TIME']);
         }
             $registry = GeneralUtility::makeInstance(Registry::class);
             $registry->set('core', 'sys_refindex_lastUpdate', $GLOBALS['EXEC_TIME']);
         }
-        return array($headerContent, $bodyContent, $errorCount);
+        return [$headerContent, $bodyContent, $errorCount];
     }
 
     /**
     }
 
     /**
-     * Return DatabaseConnection
+     * Gets one record from database and stores it in an internal cache (which expires along with object lifecycle) for faster retrieval
+     *
+     * Assumption:
+     *
+     * - This method is only used from within delegate methods and so only caches queries generated based on the record being indexed; the query
+     *   to select origin side record is uncached
+     * - Origin side records do not change in database while updating the reference index
+     * - Origin record does not get removed while updating index
+     * - Relations may change during indexing, which is why only the origin record is cached and all relations are re-process even when repeating
+     *   indexing of the same origin record
+     *
+     * Please note that the cache is disabled by default but can be enabled using $this->enableRuntimeCaches()
+     * due to possible side-effects while handling references that were changed during one single
+     * request.
      *
      *
-     * @return \TYPO3\CMS\Core\Database\DatabaseConnection
+     * @param string $tableName
+     * @param int $uid
+     * @return array|false
+     */
+    protected function getRecordRawCached(string $tableName, int $uid)
+    {
+        $recordCacheId = $tableName . ':' . $uid;
+        if (!$this->useRuntimeCache || !isset($this->recordCache[$recordCacheId])) {
+
+            // Fetch fields of the table which might contain relations
+            $cacheId = static::$cachePrefixTableRelationFields . $tableName;
+            if (!$this->useRuntimeCache || !$this->runtimeCache->has($cacheId)) {
+                $tableRelationFields = $this->fetchTableRelationFields($tableName);
+                if ($this->useRuntimeCache) {
+                    $this->runtimeCache->set($cacheId, $tableRelationFields);
+                }
+            } else {
+                $tableRelationFields = $this->runtimeCache->get($cacheId);
+            }
+
+            // Return if there are no fields which could contain relations
+            if ($tableRelationFields === '') {
+                return $this->relations;
+            }
+
+            if ($tableRelationFields === '*') {
+                // If one field of a record is of type flex, all fields have to be fetched to be passed to FlexFormTools->getDataStructureIdentifier()
+                $selectFields = '*';
+            } else {
+                // otherwise only fields that might contain relations are fetched
+                $selectFields = 'uid,' . $tableRelationFields;
+                $deleteField = $GLOBALS['TCA'][$tableName]['ctrl']['delete'];
+                if ($deleteField) {
+                    $selectFields .= ',' . $deleteField;
+                }
+                if (BackendUtility::isTableWorkspaceEnabled($tableName)) {
+                    $selectFields .= ',t3ver_wsid';
+                }
+            }
+
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                ->getQueryBuilderForTable($tableName);
+            $queryBuilder->getRestrictions()->removeAll();
+            $row = $queryBuilder
+                ->select(...GeneralUtility::trimExplode(',', $selectFields, true))
+                ->from($tableName)
+                ->where(
+                    $queryBuilder->expr()->eq(
+                        'uid',
+                        $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
+                    )
+                )
+                ->execute()
+                ->fetch();
+
+            $this->recordCache[$recordCacheId] = $row;
+        }
+        return $this->recordCache[$recordCacheId];
+    }
+
+    /**
+     * Checks if a given table should be excluded from ReferenceIndex
+     *
+     * @param string $tableName Name of the table
+     * @return bool true if it should be excluded
+     */
+    protected function shouldExcludeTableFromReferenceIndex($tableName)
+    {
+        if (isset(static::$excludedTables[$tableName])) {
+            return static::$excludedTables[$tableName];
+        }
+
+        // Only exclude tables from ReferenceIndex which do not contain any relations and never
+        // did since existing references won't be deleted!
+        $event = new IsTableExcludedFromReferenceIndexEvent($tableName);
+        $event = $this->eventDispatcher->dispatch($event);
+        static::$excludedTables[$tableName] = $event->isTableExcluded();
+
+        return static::$excludedTables[$tableName];
+    }
+
+    /**
+     * Checks if a given column in a given table should be excluded in the ReferenceIndex process
+     *
+     * @param string $tableName Name of the table
+     * @param string $column Name of the column
+     * @param string $onlyColumn Name of a specific column to fetch
+     * @return bool true if it should be excluded
+     */
+    protected function shouldExcludeTableColumnFromReferenceIndex($tableName, $column, $onlyColumn)
+    {
+        if (isset(static::$excludedColumns[$column])) {
+            return true;
+        }
+
+        if (is_array($GLOBALS['TCA'][$tableName]['columns'][$column]) && (!$onlyColumn || $onlyColumn === $column)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Enables the runtime-based caches
+     * Could lead to side effects, depending if the reference index instance is run multiple times
+     * while records would be changed.
+     */
+    public function enableRuntimeCache()
+    {
+        $this->useRuntimeCache = true;
+    }
+
+    /**
+     * Disables the runtime-based cache
      */
      */
-    protected function getDatabaseConnection()
+    public function disableRuntimeCache()
     {
     {
-        return $GLOBALS['TYPO3_DB'];
+        $this->useRuntimeCache = false;
     }
 
     /**
     }
 
     /**