diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php index 8dea79417a4e7..1dbab95376f3f 100644 --- a/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php +++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/ExpressionBuilder.php @@ -315,6 +315,26 @@ public function notLike($x, $y, $type = null): string { 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. * diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php index 345921249bce7..e0963bb54bbb5 100644 --- a/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php +++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php @@ -10,6 +10,8 @@ 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 @@ public function iLike($x, $y, $type = null): string { 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 * diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php index 16b8cde858298..49bd0f5b8bc14 100644 --- a/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php +++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php @@ -140,4 +140,13 @@ public function like($x, $y, $type = null): string { 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)); + } } diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/PgSqlExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/PgSqlExpressionBuilder.php index 6b3a855afebbe..5ff4ef5a3df3e 100644 --- a/lib/private/DB/QueryBuilder/ExpressionBuilder/PgSqlExpressionBuilder.php +++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/PgSqlExpressionBuilder.php @@ -57,4 +57,15 @@ public function iLike($x, $y, $type = null): string { $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); + } } diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php index e10b299c7ddc2..fd2821aa0aa2f 100644 --- a/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php +++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/SqliteExpressionBuilder.php @@ -27,6 +27,15 @@ public function iLike($x, $y, $type = null): string { 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 diff --git a/lib/public/DB/QueryBuilder/IExpressionBuilder.php b/lib/public/DB/QueryBuilder/IExpressionBuilder.php index 5d4dac3966f87..fb73657b3de3e 100644 --- a/lib/public/DB/QueryBuilder/IExpressionBuilder.php +++ b/lib/public/DB/QueryBuilder/IExpressionBuilder.php @@ -315,6 +315,24 @@ public function notLike($x, $y, $type = null): string; */ 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. * diff --git a/tests/lib/DB/QueryBuilder/ExpressionBuilderDBTest.php b/tests/lib/DB/QueryBuilder/ExpressionBuilderDBTest.php index fc1d7849976ec..66ca16bd22c77 100644 --- a/tests/lib/DB/QueryBuilder/ExpressionBuilderDBTest.php +++ b/tests/lib/DB/QueryBuilder/ExpressionBuilderDBTest.php @@ -103,6 +103,43 @@ public function testILike($param1, $param2, $match): void { $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'); diff --git a/tests/lib/DB/QueryBuilder/ExpressionBuilderTest.php b/tests/lib/DB/QueryBuilder/ExpressionBuilderTest.php index 3281166d68e37..54071dd68f0c6 100644 --- a/tests/lib/DB/QueryBuilder/ExpressionBuilderTest.php +++ b/tests/lib/DB/QueryBuilder/ExpressionBuilderTest.php @@ -194,6 +194,18 @@ public function testNotLike(string $input, bool $isLiteral): void { ); } + 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 @@ public static function dataClobComparisons(): array { ['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],