feat: Add support for NOT ILIKE in query builder expressions

Signed-off-by: Kostiantyn Miakshyn <molodchick@gmail.com>
This commit is contained in:
Kostiantyn Miakshyn
2026-06-23 00:08:37 +02:00
parent 88e5f26c93
commit 727772cd9e
8 changed files with 133 additions and 0 deletions
@@ -315,6 +315,26 @@ class ExpressionBuilder implements IExpressionBuilder {
return $this->expressionBuilder->notLike($x, $y);
}
/**
* Creates a NOT ILIKE() comparison expression with the given arguments.
*
* @param ILiteral|IParameter|IQueryFunction|string $x Field in string format to be inspected by NOT ILIKE() comparison.
* @param ILiteral|IParameter|IQueryFunction|string $y Argument to be used in NOT ILIKE() comparison.
* @param int|string|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
* @return string
* @since 35.0.0
*/
#[\Override]
public function notILike(
string|IParameter|ILiteral|IQueryFunction $x,
string|IParameter|ILiteral|IQueryFunction $y,
int|string|null $type = null,
): string {
return $this->expressionBuilder->notLike((string)$this->functionBuilder->lower($x), (string)$this->functionBuilder->lower($y));
}
/**
* Creates a IN () comparison expression with the given arguments.
*
@@ -10,6 +10,8 @@ namespace OC\DB\QueryBuilder\ExpressionBuilder;
use OC\DB\ConnectionAdapter;
use OC\DB\QueryBuilder\QueryFunction;
use OCP\DB\QueryBuilder\ILiteral;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\QueryBuilder\IQueryFunction;
use Psr\Log\LoggerInterface;
@@ -34,6 +36,17 @@ class MySqlExpressionBuilder extends ExpressionBuilder {
return $this->expressionBuilder->comparison($x, ' COLLATE ' . $this->collation . ' LIKE', $y);
}
#[\Override]
public function notILike(
string|IParameter|ILiteral|IQueryFunction $x,
string|IParameter|ILiteral|IQueryFunction $y,
int|string|null $type = null,
): string {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnName($y);
return $this->expressionBuilder->comparison($x, ' COLLATE ' . $this->collation . ' NOT LIKE', $y);
}
/**
* Returns a IQueryFunction that casts the column to the given type
*
@@ -140,4 +140,13 @@ class OCIExpressionBuilder extends ExpressionBuilder {
public function iLike($x, $y, $type = null): string {
return $this->like($this->functionBuilder->lower($x), $this->functionBuilder->lower($y));
}
#[\Override]
public function notILike(
string|IParameter|ILiteral|IQueryFunction $x,
string|IParameter|ILiteral|IQueryFunction $y,
int|string|null $type = null,
): string {
return $this->notLike($this->functionBuilder->lower($x), $this->functionBuilder->lower($y));
}
}
@@ -57,4 +57,15 @@ class PgSqlExpressionBuilder extends ExpressionBuilder {
$y = $this->helper->quoteColumnName($y);
return $this->expressionBuilder->comparison($x, 'ILIKE', $y);
}
#[\Override]
public function notILike(
string|IParameter|ILiteral|IQueryFunction $x,
string|IParameter|ILiteral|IQueryFunction $y,
int|string|null $type = null,
): string {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnName($y);
return $this->expressionBuilder->comparison($x, 'NOT ILIKE', $y);
}
}
@@ -27,6 +27,15 @@ class SqliteExpressionBuilder extends ExpressionBuilder {
return $this->like($this->functionBuilder->lower($x), $this->functionBuilder->lower($y), $type);
}
#[\Override]
public function notILike(
string|IParameter|ILiteral|IQueryFunction $x,
string|IParameter|ILiteral|IQueryFunction $y,
int|string|null $type = null,
): string {
return $this->notLike($this->functionBuilder->lower($x), $this->functionBuilder->lower($y), $type);
}
/**
* @param mixed $column
* @param mixed|null $type
@@ -315,6 +315,24 @@ interface IExpressionBuilder {
*/
public function iLike($x, $y, $type = null): string;
/**
* Creates a NOT ILIKE() comparison expression with the given arguments.
*
* @param ILiteral|IParameter|IQueryFunction|string $x Field in string format to be inspected by NOT LIKE() comparison.
* @param ILiteral|IParameter|IQueryFunction|string $y Argument to be used in NOT ILIKE() comparison.
* @param IQueryBuilder::PARAM_*|null $type one of the IQueryBuilder::PARAM_* constants
* required when comparing text fields for oci compatibility
*
*
* @return string
* @since 35.0.0
*
* @psalm-taint-sink sql $x
* @psalm-taint-sink sql $y
* @psalm-taint-sink sql $type
*/
public function notILike(string|IParameter|ILiteral|IQueryFunction $x, string|IParameter|ILiteral|IQueryFunction $y, int|string|null $type = null): string;
/**
* Creates a IN () comparison expression with the given arguments.
*
@@ -103,6 +103,43 @@ class ExpressionBuilderDBTest extends TestCase {
$this->assertEquals($match, $column);
}
public static function notILikeProvider(): array {
$connection = Server::get(IDBConnection::class);
return [
['foo', 'bar', true],
['foo', 'foo', false],
['foo', 'Foo', false],
['foo', 'f%', false],
['foo', '%o', false],
['foo', '%', false],
['foo', 'fo_', false],
['foo', 'foo_', true],
['foo', $connection->escapeLikeParameter('fo_'), true],
['foo', $connection->escapeLikeParameter('f%'), true],
];
}
/**
*
* @param string $param1
* @param string $param2
* @param boolean $match
*/
#[\PHPUnit\Framework\Attributes\DataProvider('notILikeProvider')]
public function testNotILike($param1, $param2, $match): void {
$query = $this->connection->getQueryBuilder();
$query->select(new Literal('1'))
->from('users')
->where($query->expr()->notILike($query->createNamedParameter($param1), $query->createNamedParameter($param2)));
$result = $query->executeQuery();
$column = $result->fetchOne();
$result->closeCursor();
$this->assertEquals($match, $column);
}
public function testCastColumn(): void {
$appId = $this->getUniqueID('testing');
$this->createConfig($appId, '1', '4');
@@ -194,6 +194,18 @@ class ExpressionBuilderTest extends TestCase {
);
}
public function testILike(): void {
// iLike is implemented using lower() on both sides, so we just verify it returns a string
$result = $this->expressionBuilder->iLike('test', 'value');
$this->assertIsString($result);
}
public function testNotILike(): void {
// notILike is implemented using notLike with lower() on both sides, so we just verify it returns a string
$result = $this->expressionBuilder->notILike('test', 'value');
$this->assertIsString($result);
}
public static function dataIn(): array {
return [
['value', false],
@@ -294,6 +306,10 @@ class ExpressionBuilderTest extends TestCase {
['like', 'under\_%', IQueryBuilder::PARAM_STR, false, 1],
['notLike', '%5%', IQueryBuilder::PARAM_STR, false, 8],
['notLike', '%5%', IQueryBuilder::PARAM_STR, true, 6],
['iLike', '%5%', IQueryBuilder::PARAM_STR, false, 3],
['iLike', '%5%', IQueryBuilder::PARAM_STR, true, 1],
['notILike', '%5%', IQueryBuilder::PARAM_STR, false, 8],
['notILike', '%5%', IQueryBuilder::PARAM_STR, true, 6],
['in', ['5'], IQueryBuilder::PARAM_STR_ARRAY, false, 3],
['in', ['5'], IQueryBuilder::PARAM_STR_ARRAY, true, 1],
['notIn', ['5'], IQueryBuilder::PARAM_STR_ARRAY, false, 8],