diff --git a/typo3/sysext/core/Classes/Database/Query/QueryBuilder.php b/typo3/sysext/core/Classes/Database/Query/QueryBuilder.php index cfbb5b18f03439bb55ca38a4f3998d0f55d43548..e17b9f6e903eb938b446f879fa5f2b116b981b7b 100644 --- a/typo3/sysext/core/Classes/Database/Query/QueryBuilder.php +++ b/typo3/sysext/core/Classes/Database/Query/QueryBuilder.php @@ -15,6 +15,10 @@ namespace TYPO3\CMS\Core\Database\Query; * The TYPO3 project - inspiring people to share! */ +use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\OraclePlatform; +use Doctrine\DBAL\Platforms\PostgreSqlPlatform; +use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Query\Expression\CompositeExpression; use TYPO3\CMS\Core\Database\Connection; @@ -1034,6 +1038,46 @@ class QueryBuilder return $this->getConnection()->quoteColumnValuePairs($input); } + /** + * Creates a cast of the $fieldName to a text datatype depending on the database management system. + * + * @param string $fieldName The fieldname will be quoted and casted according to database platform automatically + * @return string + */ + public function castFieldToTextType(string $fieldName): string + { + $databasePlatform = $this->connection->getDatabasePlatform(); + // https://dev.mysql.com/doc/refman/5.7/en/cast-functions.html#function_convert + if ($databasePlatform instanceof MySqlPlatform) { + return sprintf('CONVERT(%s, CHAR)', $this->connection->quoteIdentifier($fieldName)); + } + // https://www.postgresql.org/docs/current/sql-createcast.html + if ($databasePlatform instanceof PostgreSqlPlatform) { + return sprintf('%s::text', $this->connection->quoteIdentifier($fieldName)); + } + // https://www.sqlite.org/lang_expr.html#castexpr + 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)); + } + + throw new \RuntimeException( + sprintf( + '%s is not implemented for the used database platform "%s", yet!', + __METHOD__, + get_class($this->connection->getDatabasePlatform()) + ), + 1584637096 + ); + } + /** * Unquote a single identifier (no dot expansion). Used to unquote the table names * from the expressionBuilder so that the table can be found in the TCA definition. diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php index 424ef1ff69ef45e12611997a298476ef9520c171..929a52f3bf70f7da6818b5cd7f9b203b04dfbcc4 100644 --- a/typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php +++ b/typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php @@ -17,7 +17,9 @@ namespace TYPO3\CMS\Core\Tests\Unit\Database\Query; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Platforms\PostgreSqlPlatform; +use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Platforms\SQLServerPlatform; use Prophecy\Argument; use TYPO3\CMS\Core\Database\Connection; @@ -1392,4 +1394,54 @@ class QueryBuilderTest extends UnitTestCase ], ]; } + + public function castFieldToTextTypeDataProvider(): array + { + return [ + 'Test cast for MySqlPlatform' => [ + new MySqlPlatform(), + 'CONVERT(aField, CHAR)' + ], + 'Test cast for PostgreSqlPlatform' => [ + new PostgreSqlPlatform(), + 'aField::text' + ], + 'Test cast for SqlitePlatform' => [ + new SqlitePlatform(), + 'CAST(aField as TEXT)' + ], + 'Test cast for SQLServerPlatform' => [ + new SQLServerPlatform(), + 'CAST(aField as VARCHAR)' + ], + 'Test cast for OraclePlatform' => [ + new OraclePlatform(), + 'CAST(aField as VARCHAR)' + ], + ]; + } + + /** + * @test + * @dataProvider castFieldToTextTypeDataProvider + * + * @param AbstractPlatform $platform + * @param string $expectation + */ + public function castFieldToTextType(AbstractPlatform $platform, string $expectation): void + { + $this->connection->quoteIdentifier('aField') + ->shouldBeCalled() + ->willReturnArgument(0); + + $this->connection->getDatabasePlatform()->willReturn($platform); + + $concreteQueryBuilder = new \Doctrine\DBAL\Query\QueryBuilder($this->connection->reveal()); + + $subject = new QueryBuilder($this->connection->reveal(), null, $concreteQueryBuilder); + $result = $subject->castFieldToTextType('aField'); + + $this->connection->quoteIdentifier('aField')->shouldHaveBeenCalled(); + self::assertSame($expectation, $result); + } } diff --git a/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php b/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php index 42b6cb260a3b454aec82e3068f717df335dc8a10..ecba9e9f9c7e8b9031978156937cfad787e60363 100644 --- a/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php +++ b/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php @@ -3343,7 +3343,7 @@ class DatabaseRecordList $evalRules = $fieldConfig['eval'] ?: ''; $searchConstraint = $expressionBuilder->andX( $expressionBuilder->comparison( - 'LOWER(' . $queryBuilder->quoteIdentifier($fieldName) . ')', + 'LOWER(' . $queryBuilder->castFieldToTextType($fieldName) . ')', 'LIKE', 'LOWER(' . $like . ')' ) diff --git a/typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php b/typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php index 4cb22a0c9dba4fe5d0be1d77a4ce908854b3b8a8..8b030569ed97f7616a7e8945e8f4168759e2557e 100644 --- a/typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php +++ b/typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php @@ -270,8 +270,9 @@ class DeletedRecords // create the filter WHERE-clause $filterConstraint = null; if (trim($filter) !== '') { - $filterConstraint = $queryBuilder->expr()->like( - $GLOBALS['TCA'][$table]['ctrl']['label'], + $filterConstraint = $queryBuilder->expr()->comparison( + $queryBuilder->castFieldToTextType($GLOBALS['TCA'][$table]['ctrl']['label']), + 'LIKE', $queryBuilder->createNamedParameter( '%' . $queryBuilder->escapeLikeWildcards($filter) . '%', \PDO::PARAM_STR