Unverified Commit ea7657f0 authored by Marco Pivetta's avatar Marco Pivetta Committed by GitHub

Merge pull request #3013 from greg0ire/escape_like_literals

Escape LIKE metacharacters
parents 69742aee f397fe04
......@@ -42,6 +42,11 @@ use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\TransactionIsolationLevel;
use Doctrine\DBAL\Types;
use Doctrine\DBAL\Types\Type;
use function addcslashes;
use function preg_quote;
use function preg_replace;
use function sprintf;
use function strlen;
/**
* Base class for all DatabasePlatforms. The DatabasePlatforms are the central
......@@ -3578,4 +3583,26 @@ abstract class AbstractPlatform
{
return "'";
}
/**
* Escapes metacharacters in a string intended to be used with a LIKE
* operator.
*
* @param string $inputString a literal, unquoted string
* @param string $escapeChar should be reused by the caller in the LIKE
* expression.
*/
final public function escapeStringForLike(string $inputString, string $escapeChar) : string
{
return preg_replace(
'~([' . preg_quote($this->getLikeWildcardCharacters() . $escapeChar, '~') . '])~u',
addcslashes($escapeChar, '\\') . '$1',
$inputString
);
}
protected function getLikeWildcardCharacters() : string
{
return '%_';
}
}
......@@ -116,4 +116,9 @@ class SQLServer2008Platform extends SQLServer2005Platform
{
return Keywords\SQLServer2008Keywords::class;
}
protected function getLikeWildcardCharacters() : string
{
return parent::getLikeWildcardCharacters() . '[]^';
}
}
......@@ -20,6 +20,9 @@
namespace Doctrine\DBAL\Query\Expression;
use Doctrine\DBAL\Connection;
use function func_get_arg;
use function func_num_args;
use function sprintf;
/**
* ExpressionBuilder class is responsible to dynamically create SQL query parts.
......@@ -254,9 +257,10 @@ class ExpressionBuilder
*
* @return string
*/
public function like($x, $y)
public function like($x, $y/*, ?string $escapeChar = null */)
{
return $this->comparison($x, 'LIKE', $y);
return $this->comparison($x, 'LIKE', $y) .
(func_num_args() >= 3 ? sprintf(' ESCAPE %s', func_get_arg(2)) : '');
}
/**
......@@ -267,9 +271,10 @@ class ExpressionBuilder
*
* @return string
*/
public function notLike($x, $y)
public function notLike($x, $y/*, ?string $escapeChar = null */)
{
return $this->comparison($x, 'NOT LIKE', $y);
return $this->comparison($x, 'NOT LIKE', $y) .
(func_num_args() >= 3 ? sprintf(' ESCAPE %s', func_get_arg(2)) : '');
}
/**
......
<?php
namespace Doctrine\Tests\DBAL\Functional;
use Doctrine\Tests\DbalFunctionalTestCase;
use function sprintf;
use function str_replace;
final class LikeWildcardsEscapingTest extends DbalFunctionalTestCase
{
public function testFetchLikeExpressionResult() : void
{
$string = '_25% off_ your next purchase \o/ [$̲̅(̲̅5̲̅)̲̅$̲̅] (^̮^)';
$escapeChar = '!';
$databasePlatform = $this->_conn->getDatabasePlatform();
$stmt = $this->_conn->prepare(str_replace(
'1',
sprintf(
"(CASE WHEN '%s' LIKE '%s' ESCAPE '%s' THEN 1 ELSE 0 END)",
$string,
$databasePlatform->escapeStringForLike($string, $escapeChar),
$escapeChar
),
$databasePlatform->getDummySelectSQL()
));
$stmt->execute();
$this->assertTrue((bool) $stmt->fetchColumn());
}
}
......@@ -1468,4 +1468,12 @@ abstract class AbstractPlatformTestCase extends \Doctrine\Tests\DbalTestCase
array(array('precision' => 8, 'scale' => 2), 'DOUBLE PRECISION'),
);
}
public function testItEscapesStringsForLike() : void
{
self::assertSame(
'\_25\% off\_ your next purchase \\\\o/',
$this->_platform->escapeStringForLike('_25% off_ your next purchase \o/', '\\')
);
}
}
......@@ -219,4 +219,33 @@ class ExpressionBuilderTest extends \Doctrine\Tests\DbalTestCase
{
self::assertEquals('u.groups NOT IN (:values)', $this->expr->notIn('u.groups', ':values'));
}
public function testLikeWithoutEscape()
{
self::assertEquals("a.song LIKE 'a virgin'", $this->expr->like('a.song', "'a virgin'"));
}
public function testLikeWithEscape()
{
self::assertEquals(
"a.song LIKE 'a virgin' ESCAPE '💩'",
$this->expr->like('a.song', "'a virgin'", "'💩'")
);
}
public function testNotLikeWithoutEscape()
{
self::assertEquals(
"s.last_words NOT LIKE 'this'",
$this->expr->notLike('s.last_words', "'this'")
);
}
public function testNotLikeWithEscape()
{
self::assertEquals(
"p.description NOT LIKE '20💩%' ESCAPE '💩'",
$this->expr->notLike('p.description', "'20💩%'", "'💩'")
);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment