From 765adc6972c99e6ce5c600266014abb68484f184 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Sat, 11 Aug 2018 20:45:27 +0200 Subject: [PATCH] [BUGFIX] Modify SQL Server IDENTITY_INSERT when creating records In SQL Server (MSSQL), hard setting uid auto-increment primary keys is only allowed if the table is prepared for such an operation beforehand. When executing functional test cases a new database connection wrapper is invoked, which automatically modifies IDENTITY_INSERT in case the use database platform is SQL Server. --- Classes/Core/DatabaseConnectionWrapper.php | 92 +++++++++++++++++++ .../Core/Functional/FunctionalTestCase.php | 2 + 2 files changed, 94 insertions(+) create mode 100644 Classes/Core/DatabaseConnectionWrapper.php diff --git a/Classes/Core/DatabaseConnectionWrapper.php b/Classes/Core/DatabaseConnectionWrapper.php new file mode 100644 index 00000000..d7738931 --- /dev/null +++ b/Classes/Core/DatabaseConnectionWrapper.php @@ -0,0 +1,92 @@ +modifyServerIdentity($tableName, true); + + $result = parent::insert($tableName, $data, $types); + + if ($modified) { + $this->modifyServerIdentity($tableName, false); + } + + return $result; + } + + + /** + * In SQL Server (MSSQL), hard setting uid auto-increment primary keys is + * only allowed if the table is prepared for such an operation beforehand. + * This method has to be invoked explicitly before INSERT statements in + * order to enable the behavior and has to be disabled afterwards again. + * + * Quotation of SQL Server SET IDENTITY_INSERT (Transact-SQL) documentation: + * > At any time, only one table in a session can have the IDENTITY_INSERT + * > property set to ON. If a table already has this property set to ON, + * > and a SET IDENTITY_INSERT ON statement is issued for another table, + * > SQL Server returns an error message that states SET IDENTITY_INSERT + * > is already ON and reports the table it is set ON for. + * + * @param string $tableName Table name to be modified + * @param bool $enable Whether to enable ('ON') or disable ('OFF') + * @return bool Whether executed statement has be successful + */ + private function modifyServerIdentity(string $tableName, bool $enable): bool + { + try { + $platform = $this->getDatabasePlatform(); + } catch (DBALException $exception) { + return false; + } + + if (!$platform instanceof SQLServerPlatform) { + return false; + } + + try { + $statement = sprintf( + 'SET IDENTITY_INSERT %s %s', + $tableName, + $enable ? 'ON' : 'OFF' + ); + $this->exec($statement); + return true; + } catch (DBALException $e) { + // Some tables like sys_refindex don't have an auto-increment uid field and thus no + // IDENTITY column. Instead of testing existance, we just try to set IDENTITY ON + // and catch the possible error that occurs. + } + + return false; + } +} diff --git a/Classes/Core/Functional/FunctionalTestCase.php b/Classes/Core/Functional/FunctionalTestCase.php index 396c4d6a..451ec005 100644 --- a/Classes/Core/Functional/FunctionalTestCase.php +++ b/Classes/Core/Functional/FunctionalTestCase.php @@ -24,6 +24,7 @@ use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\TestingFramework\Core\BaseTestCase; +use TYPO3\TestingFramework\Core\DatabaseConnectionWrapper; use TYPO3\TestingFramework\Core\Exception; use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\DataSet; use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest; @@ -288,6 +289,7 @@ protected function setUp() // Append the unique identifier to the base database name to end up with a single database per test case $dbName = $originalDatabaseName . '_ft' . $this->identifier; $localConfiguration['DB']['Connections']['Default']['dbname'] = $dbName; + $localConfiguration['DB']['Connections']['Default']['wrapperClass'] = DatabaseConnectionWrapper::class; $testbase->testDatabaseNameIsNotTooLong($originalDatabaseName, $localConfiguration); } else { $dbPath = $this->instancePath . '/test.sqlite';