Skip to content

Commit

Permalink
[BUGFIX] Modify SQL Server IDENTITY_INSERT when creating records
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ohader committed Aug 11, 2018
1 parent 975e3fe commit 765adc6
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 0 deletions.
92 changes: 92 additions & 0 deletions Classes/Core/DatabaseConnectionWrapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace TYPO3\TestingFramework\Core;

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

use TYPO3\CMS\Core\Database\Connection;

/**
* Wrapper for database connections in order to intercept statement executions.
*/
class DatabaseConnectionWrapper extends Connection
{
/**
* Wraps insert execution in order to consider SQL Server IDENTITY_INSERT.
*
* @param string $tableName
* @param array $data
* @param array $types
* @return int
*/
public function insert($tableName, array $data, array $types = []): int
{
$modified = $this->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;
}
}
2 changes: 2 additions & 0 deletions Classes/Core/Functional/FunctionalTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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';
Expand Down

0 comments on commit 765adc6

Please sign in to comment.