Commit 5107f249 authored by Stefan Bürk's avatar Stefan Bürk Committed by Andreas Fernandez
Browse files

[TASK] Remove MSSQL supportive code

Core requirements have been changed and publicly announced with:
https://typo3.org/article/new-system-requirements-for-upcoming-typo3-v12

This patch removes supportive code for MSSQL due dropped support
for this dbmns. MSSQL specific report and status checks are removed
with #97411.

Resolves: #97462
Related: #96553
Related: #97411
Releases: main
Change-Id: I131805157b7780a4de6d0f3ab8e9d0d6883b7872
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/74382

Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Oliver Klee's avatarOliver Klee <typo3-coding@oliverklee.de>
Reviewed-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
parent 0efa3a2b
......@@ -17,7 +17,6 @@ namespace TYPO3\CMS\Core\DataHandling;
use Doctrine\DBAL\Exception as DBALException;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SQLServer2012Platform as SQLServerPlatform;
use Doctrine\DBAL\Types\IntegerType;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
......@@ -1156,27 +1155,6 @@ class DataHandler implements LoggerAwareInterface
}
}
/**
* @param string $table
* @param string $value
* @param string $dbType
* @return string
*/
protected function normalizeTimeFormat(string $table, string $value, string $dbType): string
{
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
$platform = $connection->getDatabasePlatform();
if ($platform instanceof SQLServerPlatform) {
$defaultLength = QueryHelper::getDateTimeFormats()[$dbType]['empty'];
$value = substr(
$value,
0,
strlen($defaultLength)
);
}
return $value;
}
/**
* Sets the "sorting" DB field and the "pid" field of an incoming record that should be added (NEW1234)
* depending on the record that should be added or where it should be added.
......@@ -7480,8 +7458,8 @@ class DataHandler implements LoggerAwareInterface
$types = [];
$platform = $connection->getDatabasePlatform();
if ($platform instanceof SQLServerPlatform || $platform instanceof PostgreSQLPlatform) {
// mssql and postgres needs to set proper PARAM_LOB and others to update fields.
if ($platform instanceof PostgreSQLPlatform) {
// postgres needs to set proper PARAM_LOB and others to update fields.
$tableDetails = $connection->createSchemaManager()->listTableDetails($table);
foreach ($fieldArray as $columnName => $columnValue) {
$types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType();
......@@ -7666,9 +7644,6 @@ class DataHandler implements LoggerAwareInterface
}
} else {
$dbType = $GLOBALS['TCA'][$table]['columns'][$key]['config']['dbType'] ?? false;
if ($dbType === 'datetime' || $dbType === 'time') {
$row[$key] = $this->normalizeTimeFormat($table, $row[$key], $dbType);
}
if ((string)$value !== (string)$row[$key]) {
// The is_numeric check catches cases where we want to store a float/double value
// and database returns the field as a string with the least required amount of
......@@ -9728,40 +9703,10 @@ class DataHandler implements LoggerAwareInterface
// Return the actual ID we forced on insert as a surrogate.
return $suggestedUid;
}
if ($connection->getDatabasePlatform() instanceof SQLServerPlatform) {
return $this->postProcessSqlServerInsert($connection, $tableName);
}
$id = $connection->lastInsertId($tableName);
return (int)$id;
}
/**
* Get the last insert ID from sql server
*
* - first checks whether doctrine might be able to fetch the ID from the
* sequence table
* - if that does not succeed it manually selects the current IDENTITY value
* from a table
* - returns 0 if both fail
*
* @param \TYPO3\CMS\Core\Database\Connection $connection
* @param string $tableName
* @return int
* @throws \Doctrine\DBAL\Exception
*/
protected function postProcessSqlServerInsert(Connection $connection, string $tableName): int
{
$id = $connection->lastInsertId($tableName);
if (!((int)$id > 0)) {
$table = $connection->quoteIdentifier($tableName);
$result = $connection->executeQuery('SELECT IDENT_CURRENT(\'' . $table . '\') AS id')->fetchAssociative();
if (isset($result['id']) && $result['id'] > 0) {
$id = $result['id'];
}
}
return (int)$id;
}
/**
* PostgreSQL works with sequences for auto increment columns. A sequence is not updated when a value is
* written to such a column. To avoid clashes when the sequence returns an existing ID this helper will
......
......@@ -21,7 +21,6 @@ use Doctrine\Common\EventManager;
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSqlPlatform;
use Doctrine\DBAL\Platforms\SQLServer2012Platform;
use Doctrine\DBAL\Result;
use Doctrine\DBAL\Types\Type;
use Psr\Log\LoggerAwareInterface;
......@@ -392,10 +391,6 @@ class Connection extends \Doctrine\DBAL\Connection implements LoggerAwareInterfa
case 'pdo_oracle':
$version = 'Oracle';
break;
case 'sqlsrv':
case 'pdo_sqlsrv':
$version = 'SQLServer';
break;
}
// if clause can be removed with Doctrine DBAL 4.
......@@ -444,13 +439,6 @@ class Connection extends \Doctrine\DBAL\Connection implements LoggerAwareInterfa
if ($databasePlatform instanceof PostgreSqlPlatform) {
return parent::lastInsertId(trim(implode('_', [$tableName, $fieldName, 'seq']), '_'));
}
if ($databasePlatform instanceof SQLServer2012Platform) {
// lastInsertId() in mssql >2012 takes a sequence name and not the table name as
// argument. If no argument is given, last insert id of latest table is returned.
// https://docs.microsoft.com/de-de/sql/connect/php/pdo-lastinsertid?view=sql-server-2017
return (string)parent::lastInsertId();
}
return (string)parent::lastInsertId($tableName);
}
......
......@@ -23,7 +23,6 @@ use Doctrine\DBAL\Types\Type;
use TYPO3\CMS\Core\Database\Driver\PDOMySql\Driver as PDOMySqlDriver;
use TYPO3\CMS\Core\Database\Driver\PDOPgSql\Driver as PDOPgSqlDriver;
use TYPO3\CMS\Core\Database\Driver\PDOSqlite\Driver as PDOSqliteDriver;
use TYPO3\CMS\Core\Database\Driver\PDOSqlsrv\Driver as PDOSqlsrvDriver;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Database\Schema\EventListener\SchemaAlterTableListener;
use TYPO3\CMS\Core\Database\Schema\EventListener\SchemaColumnDefinitionListener;
......@@ -71,7 +70,6 @@ class ConnectionPool
'pdo_mysql' => PDOMySqlDriver::class,
'pdo_sqlite' => PDOSqliteDriver::class,
'pdo_pgsql' => PDOPgSqlDriver::class,
'pdo_sqlsrv' => PDOSqlsrvDriver::class,
// TODO: not supported yet, need to be checked later
// 'pdo_oci' => PDOOCIDriver::class,
// 'drizzle_pdo_mysql' => DrizzlePDOMySQLDriver::class,
......
<?php
declare(strict_types=1);
/*
* 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!
*/
namespace TYPO3\CMS\Core\Database\Driver\PDOSqlsrv;
use Doctrine\DBAL\Driver\PDO\Exception;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use TYPO3\CMS\Core\Database\Driver\DriverConnection;
/**
* This is a full "clone" of the class of package doctrine/dbal. Scope is to use the PDOConnection of TYPO3.
*
* @internal this implementation is not part of TYPO3's Public API.
*/
class Connection extends DriverConnection
{
public function prepare(string $sql): StatementInterface
{
try {
$stmt = $this->connection->prepare($sql);
assert($stmt instanceof \PDOStatement);
// use TYPO3's Sqlsrv Statement object in favor of Doctrine's Statement wrapper
return new Statement($stmt);
} catch (\PDOException $exception) {
throw Exception::new($exception);
}
}
/**
* {@inheritDoc}
*/
public function lastInsertId($name = null)
{
if ($name === null) {
return parent::lastInsertId($name);
}
$stmt = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?');
$result = $stmt->execute([$name]);
return $result->fetchOne();
}
}
<?php
declare(strict_types=1);
/*
* 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!
*/
namespace TYPO3\CMS\Core\Database\Driver\PDOSqlsrv;
use Doctrine\DBAL\Driver\AbstractSQLServerDriver;
use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost;
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\PDO\Exception as PDOException;
/**
* The main change in favor of Doctrine's implementation is to use our custom DriverConnection (which in turn creates
* a custom Result object).
*
* All private methods have to be checked on every release of doctrine/dbal.
*
* @internal this implementation is not part of TYPO3's Public API.
*/
class Driver extends AbstractSQLServerDriver
{
/**
* {@inheritdoc}
*
* @return DriverConnection
*/
public function connect(array $params)
{
$driverOptions = $dsnOptions = [];
if (isset($params['driverOptions'])) {
foreach ($params['driverOptions'] as $option => $value) {
if (is_int($option)) {
$driverOptions[$option] = $value;
} else {
$dsnOptions[$option] = $value;
}
}
}
if (!empty($params['persistent'])) {
$driverOptions[\PDO::ATTR_PERSISTENT] = true;
}
try {
$pdo = new \PDO(
$this->_constructPdoDsn($params, $dsnOptions),
$params['user'] ?? '',
$params['password'] ?? '',
$driverOptions
);
} catch (\PDOException $exception) {
throw PDOException::new($exception);
}
return new Connection($pdo);
}
/**
* Constructs the Sqlsrv PDO DSN.
*
* @param mixed[] $params
* @param string[] $connectionOptions
*
* @return string The DSN.
*/
private function _constructPdoDsn(array $params, array $connectionOptions)
{
$dsn = 'sqlsrv:server=';
if (isset($params['host'])) {
$dsn .= $params['host'];
if (isset($params['port'])) {
$dsn .= ',' . $params['port'];
}
} elseif (isset($params['port'])) {
throw PortWithoutHost::new();
}
if (isset($params['dbname'])) {
$connectionOptions['Database'] = $params['dbname'];
}
if (isset($params['MultipleActiveResultSets'])) {
$connectionOptions['MultipleActiveResultSets'] = $params['MultipleActiveResultSets'] ? 'true' : 'false';
}
return $dsn . $this->getConnectionOptionsDsn($connectionOptions);
}
/**
* Converts a connection options array to the DSN
*
* @param string[] $connectionOptions
*/
private function getConnectionOptionsDsn(array $connectionOptions): string
{
$connectionOptionsDsn = '';
foreach ($connectionOptions as $paramName => $paramValue) {
$connectionOptionsDsn .= sprintf(';%s=%s', $paramName, $paramValue);
}
return $connectionOptionsDsn;
}
/**
* {@inheritdoc}
*
* @deprecated
*/
public function getName()
{
return 'pdo_sqlsrv';
}
}
<?php
declare(strict_types=1);
/*
* 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!
*/
namespace TYPO3\CMS\Core\Database\Driver\PDOSqlsrv;
use Doctrine\DBAL\ParameterType;
use TYPO3\CMS\Core\Database\Driver\DriverStatement;
/**
* This is a full "clone" of the class of package doctrine/dbal. Scope is to use the PDOConnection of TYPO3.
* All private methods have to be checked on every release of doctrine/dbal.
*
* @internal this implementation is not part of TYPO3's Public API.
*/
class Statement extends DriverStatement
{
/**
* {@inheritDoc}
*
* @param mixed $param
* @param mixed $variable
* @param int $type
* @param int|null $length
* @param mixed $driverOptions The usage of the argument is deprecated.
*/
public function bindParam(
$param,
&$variable,
$type = ParameterType::STRING,
$length = null,
$driverOptions = null
): bool {
if (($type === ParameterType::LARGE_OBJECT || $type === ParameterType::BINARY)
&& $driverOptions === null
) {
$driverOptions = \PDO::SQLSRV_ENCODING_BINARY;
}
return parent::bindParam($param, $variable, $type, $length, $driverOptions);
}
/**
* {@inheritdoc}
*/
public function bindValue($param, $value, $type = ParameterType::STRING)
{
return $this->bindParam($param, $value, $type);
}
}
......@@ -22,7 +22,6 @@ use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Platforms\SQLServer2012Platform as SQLServerPlatform;
/**
* Helper to handle platform specific details
......@@ -37,7 +36,6 @@ class PlatformInformation
protected static $identifierLimits = [
'mysql' => 63,
'postgresql' => 63,
'sqlserver' => 128,
'sqlite' => 1024, // arbitrary limit, SQLite is only limited by the total statement length
];
......@@ -47,7 +45,6 @@ class PlatformInformation
protected static $bindParameterLimits = [
'mysql' => 65535,
'postgresql' => 34464,
'sqlserver' => 2100,
'sqlite' => 999,
];
......@@ -57,7 +54,6 @@ class PlatformInformation
protected static $charSetMap = [
'mysql' => 'utf8mb4',
'postgresql' => 'UTF8',
'sqlserver' => 'UTF-8',
'sqlite' => 'utf8',
];
......@@ -67,7 +63,6 @@ class PlatformInformation
protected static $databaseCreateWithCharsetMap = [
'mysql' => 'CHARACTER SET %s',
'postgresql' => "ENCODING '%s'",
'sqlserver' => '',
];
/**
......@@ -149,9 +144,6 @@ class PlatformInformation
if ($platform instanceof PostgreSqlPlatform) {
return 'postgresql';
}
if ($platform instanceof SQLServerPlatform) {
return 'sqlserver';
}
if ($platform instanceof SqlitePlatform) {
return 'sqlite';
}
......
......@@ -186,38 +186,9 @@ class BulkInsertQuery
*/
public function execute(): int
{
$platform = $this->connection->getDatabasePlatform();
$insertMaxRows = $this->getInsertMaxRows();
if ($insertMaxRows > 0 && count($this->values) > $insertMaxRows) {
throw new \LogicException(
sprintf(
'You can only insert %d rows in a single INSERT statement with platform "%s".',
$insertMaxRows,
$platform->getName()
),
1476049654
);
}
return $this->connection->executeStatement($this->getSQL(), $this->parameters, $this->types);
}
/**
* Return the maximum number of rows that can be inserted at the same time.
*
* @return int
*/
protected function getInsertMaxRows(): int
{
$platform = $this->connection->getDatabasePlatform();
if ($platform->getName() === 'mssql' && $platform->getReservedKeywordsList()->isKeyword('MERGE')) {
return 1000;
}
return 0;
}
/**
* Returns the parameters for this INSERT query being constructed indexed by parameter index.
*
......
......@@ -339,31 +339,6 @@ class ExpressionBuilder
'FIND_IN_SET support for database platform "Oracle" not yet implemented.',
1459696680
);
case 'sqlsrv':
case 'pdo_sqlsrv':
case 'mssql':
// See unit and functional tests for details
if ($isColumn) {
$expression = $this->or(
$this->eq($fieldName, $value),
$this->like($fieldName, $value . ' + \',%\''),
$this->like($fieldName, '\'%,\' + ' . $value),
$this->like($fieldName, '\'%,\' + ' . $value . ' + \',%\'')
);
} else {
$likeEscapedValue = str_replace(
['[', '%'],
['[[]', '[%]'],
$this->unquoteLiteral($value)
);
$expression = $this->or(
$this->eq($fieldName, $this->literal($this->unquoteLiteral((string)$value))),
$this->like($fieldName, $this->literal($likeEscapedValue . ',%')),
$this->like($fieldName, $this->literal('%,' . $likeEscapedValue)),
$this->like($fieldName, $this->literal('%,' . $likeEscapedValue . ',%'))
);
}
return (string)$expression;
case 'sqlite':
case 'sqlite3':
case 'pdo_sqlite':
......@@ -452,31 +427,6 @@ class ExpressionBuilder
'negative FIND_IN_SET support for database platform "Oracle" not yet implemented.',
1627573101
);
case 'sqlsrv':
case 'pdo_sqlsrv':
case 'mssql':
// See unit and functional tests for details
if ($isColumn) {
$expression = $this->and(
$this->neq($fieldName, $value),
$this->notLike($fieldName, $value . ' + \',%\''),
$this->notLike($fieldName, '\'%,\' + ' . $value),
$this->notLike($fieldName, '\'%,\' + ' . $value . ' + \',%\'')
);
} else {
$likeEscapedValue = str_replace(
['[', '%'],
['[[]', '[%]'],
$this->unquoteLiteral($value)
);
$expression = $this->and(
$this->neq($fieldName, $this->literal($this->unquoteLiteral((string)$value))),
$this->notLike($fieldName, $this->literal($likeEscapedValue . ',%')),
$this->notLike($fieldName, $this->literal('%,' . $likeEscapedValue)),
$this->notLike($fieldName, $this->literal('%,' . $likeEscapedValue . ',%'))
);
}
return (string)$expression;
case 'sqlite':
case 'sqlite3':
case 'pdo_sqlite':
......
......@@ -18,13 +18,11 @@ declare(strict_types=1);
namespace TYPO3\CMS\Core\Database\Query;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Platforms\SQLServer2012Platform as SQLServerPlatform;
use Doctrine\DBAL\Query\Expression\CompositeExpression;
use Doctrine\DBAL\Result;
use Doctrine\DBAL\Statement;
......@@ -1300,10 +1298,6 @@ class QueryBuilder
if ($databasePlatform instanceof SqlitePlatform) {
return sprintf('CAST(%s as TEXT)', $this->connection->quoteIdentifier($fieldName));
}
// https://docs.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver15#implicit-conversions
if ($databasePlatform instanceof SQLServerPlatform) {
return sprintf('CAST(%s as VARCHAR)', $this->connection->quoteIdentifier($fieldName));
}
// https://docs.oracle.com/javadb/10.8.3.0/ref/rrefsqlj33562.html
if ($databasePlatform instanceof OraclePlatform) {
return sprintf('CAST(%s as VARCHAR)', $this->connection->quoteIdentifier($fieldName));
......@@ -1330,15 +1324,9 @@ class QueryBuilder
{
$identifier = trim($identifier);
$platform = $this->getConnection()->getDatabasePlatform();
if ($platform instanceof SQLServerPlatform) {
// mssql quotes identifiers with [ and ], not a single character
$identifier = ltrim($identifier, '[');
$identifier = rtrim($identifier, ']');
} else {
$quoteChar = $platform->getIdentifierQuoteCharacter();
$identifier = trim($identifier, $quoteChar);
$identifier = str_replace($quoteChar . $quoteChar, $quoteChar, $identifier);
}
$quoteChar = $platform->getIdentifierQuoteCharacter();
$identifier = trim($identifier, $quoteChar);