From 325d95b9a80473f0c92b2394a595609196a75ec9 Mon Sep 17 00:00:00 2001 From: Christian Kuhn <lolli@schwarzbu.ch> Date: Mon, 22 May 2017 15:39:09 +0200 Subject: [PATCH] [BUGFIX] mssql: Proper types inserting / updating rows MS SQL server is more picky about types than postgres and mysql. This is especially true for LOB columns - even empty strings need a proper cast and specific handling. Various parts of the core deal with arbitrary tables and don't know if a column is int, text or lob, or whatever. Those are blindly updated / inserted, resulting in mssql saying "no". Solution is to fetch column schema and to set proper types based on that schema. This is expensive. We will have to refactor that again, and we will probably end up with a (cache?) entry that knows the entire table schema of an instance. Solving that in a good way would also fix various mysql strict issues we still have in the core. However, this needs more work. Goal of the current patch is to bring mssql to a working state. The solution must be seen as hacky, but is restricted to that platform only and can be relaxed and improved as soon as we take the next steps with schema handling in the TYPO3 core. Change-Id: I9b582a9bde7461cfbcc2414192518fb7b7b1341d Resolves: #81498 Releases: master, 8.7 Reviewed-on: https://review.typo3.org/53150 Tested-by: TYPO3com <no-reply@typo3.com> Reviewed-by: Jan Helke <typo3@helke.de> Tested-by: Jan Helke <typo3@helke.de> Reviewed-by: Frank Naegler <frank.naegler@typo3.org> Tested-by: Frank Naegler <frank.naegler@typo3.org> --- .../core/Classes/DataHandling/DataHandler.php | 21 +++++++++---- .../Resource/Index/MetaDataRepository.php | 17 ++++++++--- .../ForeignField/AbstractActionTestCase.php | 1 - .../Generic/Storage/Typo3DbBackend.php | 29 ++++++++++++++++-- .../version/Classes/Hook/DataHandlerHook.php | 30 +++++++++++++++++-- 5 files changed, 83 insertions(+), 15 deletions(-) diff --git a/typo3/sysext/core/Classes/DataHandling/DataHandler.php b/typo3/sysext/core/Classes/DataHandling/DataHandler.php index eb8571603620..c2be5a44a8d7 100644 --- a/typo3/sysext/core/Classes/DataHandling/DataHandler.php +++ b/typo3/sysext/core/Classes/DataHandling/DataHandler.php @@ -16,6 +16,7 @@ namespace TYPO3\CMS\Core\DataHandling; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver\Statement; +use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Types\IntegerType; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; @@ -2535,9 +2536,9 @@ class DataHandler $statement = $this->getUniqueCountStatement($newValue, $table, $field, (int)$id, (int)$newPid); // For as long as records with the test-value existing, try again (with incremented numbers appended) if ($statement->fetchColumn()) { - $statement->bindParam(1, $newValue); for ($counter = 0; $counter <= 100; $counter++) { $newValue = $value . $counter; + $statement->bindValue(1, $newValue); $statement->execute(); if (!$statement->fetchColumn()) { break; @@ -6937,12 +6938,23 @@ class DataHandler unset($fieldArray['uid']); if (!empty($fieldArray)) { $fieldArray = $this->insertUpdateDB_preprocessBasedOnFieldType($table, $fieldArray); + + $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table); + + $types = []; + $platform = $connection->getDatabasePlatform(); + if ($platform instanceof SQLServerPlatform) { + // mssql needs to set proper PARAM_LOB and others to update fields + $tableDetails = $connection->getSchemaManager()->listTableDetails($table); + foreach ($fieldArray as $columnName => $columnValue) { + $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType(); + } + } + // Execute the UPDATE query: $updateErrorMessage = ''; try { - GeneralUtility::makeInstance(ConnectionPool::class) - ->getConnectionForTable($table) - ->update($table, $fieldArray, ['uid' => (int)$id]); + $connection->update($table, $fieldArray, ['uid' => (int)$id], $types); } catch (DBALException $e) { $updateErrorMessage = $e->getPrevious()->getMessage(); } @@ -6951,7 +6963,6 @@ class DataHandler // Update reference index: $this->updateRefIndex($table, $id); if ($this->enableLogging) { - $newRow = []; if ($this->checkStoredRecords) { $newRow = $this->checkStoredRecord($table, $id, $fieldArray, 2); } else { diff --git a/typo3/sysext/core/Classes/Resource/Index/MetaDataRepository.php b/typo3/sysext/core/Classes/Resource/Index/MetaDataRepository.php index f085b24838f2..3435ca22d99a 100644 --- a/typo3/sysext/core/Classes/Resource/Index/MetaDataRepository.php +++ b/typo3/sysext/core/Classes/Resource/Index/MetaDataRepository.php @@ -15,6 +15,7 @@ namespace TYPO3\CMS\Core\Resource\Index; * The TYPO3 project - inspiring people to share! */ +use Doctrine\DBAL\Platforms\SQLServerPlatform; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction; @@ -174,14 +175,22 @@ class MetaDataRepository implements SingletonInterface $row = $this->findByFileUid($fileUid); if (!empty($updateRow)) { $updateRow['tstamp'] = time(); - GeneralUtility::makeInstance(ConnectionPool::class) - ->getConnectionForTable($this->tableName) - ->update( + $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->tableName); + $types = []; + if ($connection->getDatabasePlatform() instanceof SQLServerPlatform) { + // mssql needs to set proper PARAM_LOB and others to update fields + $tableDetails = $connection->getSchemaManager()->listTableDetails($this->tableName); + foreach ($updateRow as $columnName => $columnValue) { + $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType(); + } + } + $connection->update( $this->tableName, $updateRow, [ 'uid' => (int)$row['uid'] - ] + ], + $types ); $this->emitRecordUpdatedSignal(array_merge($row, $updateRow)); diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/AbstractActionTestCase.php b/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/AbstractActionTestCase.php index 6cc6dbea2b79..4a07afeef703 100644 --- a/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/AbstractActionTestCase.php +++ b/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/AbstractActionTestCase.php @@ -512,7 +512,6 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D { unset($GLOBALS['TCA'][self::TABLE_Hotel]['ctrl']['languageField']); unset($GLOBALS['TCA'][self::TABLE_Hotel]['ctrl']['transOrigPointerField']); - unset($GLOBALS['TCA'][self::TABLE_Hotel]['ctrl']['transOrigDiffSourceField']); $GLOBALS['TCA'][self::TABLE_PageOverlay]['columns'][self::FIELD_PageHotel]['config']['behaviour']['allowLanguageSynchronization'] = true; $localizedTableIds = $this->actionService->localizeRecord(self::TABLE_Page, self::VALUE_PageId, self::VALUE_LanguageId); $this->recordIds['localizedPageId'] = $localizedTableIds[self::TABLE_Page][self::VALUE_PageId]; diff --git a/typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbBackend.php b/typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbBackend.php index 37df075d5f06..b15de78be0ad 100644 --- a/typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbBackend.php +++ b/typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbBackend.php @@ -15,6 +15,7 @@ namespace TYPO3\CMS\Extbase\Persistence\Generic\Storage; */ use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Platforms\SQLServerPlatform; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; @@ -141,7 +142,18 @@ class Typo3DbBackend implements BackendInterface, SingletonInterface } try { $connection = $this->connectionPool->getConnectionForTable($tableName); - $connection->insert($tableName, $fieldValues); + + $types = []; + $platform = $connection->getDatabasePlatform(); + if ($platform instanceof SQLServerPlatform) { + // mssql needs to set proper PARAM_LOB and others to update fields + $tableDetails = $connection->getSchemaManager()->listTableDetails($tableName); + foreach ($fieldValues as $columnName => $columnValue) { + $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType(); + } + } + + $connection->insert($tableName, $fieldValues, $types); } catch (DBALException $e) { throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230766); } @@ -175,8 +187,19 @@ class Typo3DbBackend implements BackendInterface, SingletonInterface unset($fieldValues['uid']); try { - $this->connectionPool->getConnectionForTable($tableName) - ->update($tableName, $fieldValues, ['uid' => $uid]); + $connection = $this->connectionPool->getConnectionForTable($tableName); + + $types = []; + $platform = $connection->getDatabasePlatform(); + if ($platform instanceof SQLServerPlatform) { + // mssql needs to set proper PARAM_LOB and others to update fields + $tableDetails = $connection->getSchemaManager()->listTableDetails($tableName); + foreach ($fieldValues as $columnName => $columnValue) { + $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType(); + } + } + + $connection->update($tableName, $fieldValues, ['uid' => $uid], $types); } catch (DBALException $e) { throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230767); } diff --git a/typo3/sysext/version/Classes/Hook/DataHandlerHook.php b/typo3/sysext/version/Classes/Hook/DataHandlerHook.php index 1bd371260f89..168167dda9e4 100644 --- a/typo3/sysext/version/Classes/Hook/DataHandlerHook.php +++ b/typo3/sysext/version/Classes/Hook/DataHandlerHook.php @@ -15,6 +15,7 @@ namespace TYPO3\CMS\Version\Hook; */ use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Platforms\SQLServerPlatform; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; @@ -907,11 +908,28 @@ class DataHandlerHook // Execute swapping: $sqlErrors = []; $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table); + + $platform = $connection->getDatabasePlatform(); + $tableDetails = null; + if ($platform instanceof SQLServerPlatform) { + // mssql needs to set proper PARAM_LOB and others to update fields + $tableDetails = $connection->getSchemaManager()->listTableDetails($table); + } + try { + $types = []; + + if ($platform instanceof SQLServerPlatform) { + foreach ($curVersion as $columnName => $columnValue) { + $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType(); + } + } + $connection->update( $table, $swapVersion, - ['uid' => (int)$id] + ['uid' => (int)$id], + $types ); } catch (DBALException $e) { $sqlErrors[] = $e->getPrevious()->getMessage(); @@ -919,10 +937,18 @@ class DataHandlerHook if (empty($sqlErrors)) { try { + $types = []; + if ($platform instanceof SQLServerPlatform) { + foreach ($curVersion as $columnName => $columnValue) { + $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType(); + } + } + $connection->update( $table, $curVersion, - ['uid' => (int)$swapWith] + ['uid' => (int)$swapWith], + $types ); unlink($lockFileName); } catch (DBALException $e) { -- GitLab