Skip to content

Commit 4c01bd0

Browse files
authored
Merge pull request #2816 from nextcloud/feature/case-insensetive-search
Fix: Make case-insensitive search for the view filters
2 parents 1d45bc5 + d10acda commit 4c01bd0

2 files changed

Lines changed: 76 additions & 16 deletions

File tree

lib/Db/Row2Mapper.php

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -434,11 +434,11 @@ private function getFilterExpression(IQueryBuilder $qb, Column $column, string $
434434
switch ($operator) {
435435
case 'begins-with':
436436
$includeDefault = str_starts_with((string)($defaultValue ?? ''), (string)$value);
437-
$filterExpression = $qb->expr()->like('value', $qb->createNamedParameter($this->db->escapeLikeParameter($value) . '%', $paramType));
437+
$filterExpression = $qb->expr()->iLike('value', $qb->createNamedParameter($this->db->escapeLikeParameter($value) . '%', $paramType));
438438
break;
439439
case 'ends-with':
440440
$includeDefault = str_ends_with((string)($defaultValue ?? ''), (string)$value);
441-
$filterExpression = $qb->expr()->like('value', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($value), $paramType));
441+
$filterExpression = $qb->expr()->iLike('value', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($value), $paramType));
442442
break;
443443
case 'contains':
444444
$filterExpressions = [];
@@ -469,14 +469,14 @@ private function getFilterExpression(IQueryBuilder $qb, Column $column, string $
469469
if ($column->getType() === 'selection' && $column->getSubtype() === 'multi') {
470470
$value = str_replace(['"', '\''], '', $value);
471471
$filterExpression = $qb2->expr()->orX(
472-
$qb->expr()->like('value', $qb->createNamedParameter('[' . $this->db->escapeLikeParameter($value) . ']')),
473-
$qb->expr()->like('value', $qb->createNamedParameter('[' . $this->db->escapeLikeParameter($value) . ',%')),
474-
$qb->expr()->like('value', $qb->createNamedParameter('%,' . $this->db->escapeLikeParameter($value) . ']%')),
475-
$qb->expr()->like('value', $qb->createNamedParameter('%,' . $this->db->escapeLikeParameter($value) . ',%'))
472+
$qb->expr()->iLike('value', $qb->createNamedParameter('[' . $this->db->escapeLikeParameter($value) . ']')),
473+
$qb->expr()->iLike('value', $qb->createNamedParameter('[' . $this->db->escapeLikeParameter($value) . ',%')),
474+
$qb->expr()->iLike('value', $qb->createNamedParameter('%,' . $this->db->escapeLikeParameter($value) . ']%')),
475+
$qb->expr()->iLike('value', $qb->createNamedParameter('%,' . $this->db->escapeLikeParameter($value) . ',%'))
476476
);
477477
break;
478478
}
479-
$filterExpression = $qb->expr()->like('value', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%', $paramType));
479+
$filterExpression = $qb->expr()->iLike('value', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%', $paramType));
480480
break;
481481
case 'does-not-contain':
482482
if (is_array($value) && $column->getType() === Column::TYPE_USERGROUP) {
@@ -514,14 +514,14 @@ private function getFilterExpression(IQueryBuilder $qb, Column $column, string $
514514
if ($column->getType() === 'selection' && $column->getSubtype() === 'multi') {
515515
$value = str_replace(['"', '\''], '', $value);
516516
$filterExpression = $qb2->expr()->andX(
517-
$qb->expr()->notLike('value', $qb->createNamedParameter('[' . $this->db->escapeLikeParameter($value) . ']')),
518-
$qb->expr()->notLike('value', $qb->createNamedParameter('[' . $this->db->escapeLikeParameter($value) . ',%')),
519-
$qb->expr()->notLike('value', $qb->createNamedParameter('%,' . $this->db->escapeLikeParameter($value) . ']%')),
520-
$qb->expr()->notLike('value', $qb->createNamedParameter('%,' . $this->db->escapeLikeParameter($value) . ',%'))
517+
$this->notILike($qb, 'value', $qb->createNamedParameter('[' . $this->db->escapeLikeParameter($value) . ']')),
518+
$this->notILike($qb, 'value', $qb->createNamedParameter('[' . $this->db->escapeLikeParameter($value) . ',%')),
519+
$this->notILike($qb, 'value', $qb->createNamedParameter('%,' . $this->db->escapeLikeParameter($value) . ']%')),
520+
$this->notILike($qb, 'value', $qb->createNamedParameter('%,' . $this->db->escapeLikeParameter($value) . ',%'))
521521
);
522522
break;
523523
}
524-
$filterExpression = $qb->expr()->notLike('value', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%', $paramType));
524+
$filterExpression = $this->notILike($qb, 'value', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%', $paramType));
525525
break;
526526
case 'is-equal':
527527
$includeDefault = $defaultValue === $value;
@@ -601,6 +601,19 @@ private function getMetaFilterExpression(IQueryBuilder $qb, int $columnId, strin
601601
return $qb2;
602602
}
603603

604+
/**
605+
* Helper method to use notILike if available, otherwise fall back to not(iLike(...))
606+
*/
607+
private function notILike(IQueryBuilder $qb, string $column, $parameter): string {
608+
$expr = $qb->expr();
609+
// Nextcloud v35 introduced shortcut method
610+
if (method_exists($expr, 'notILike')) {
611+
return $expr->notILike($column, $parameter);
612+
}
613+
614+
return 'NOT (' . $expr->iLike($column, $parameter) . ')';
615+
}
616+
604617
/**
605618
* @param string $operator
606619
* @param IQueryBuilder $qb
@@ -612,10 +625,10 @@ private function getMetaFilterExpression(IQueryBuilder $qb, int $columnId, strin
612625
*/
613626
private function getSqlOperator(string $operator, IQueryBuilder $qb, string $columnName, $value, $paramType): string {
614627
return match ($operator) {
615-
'begins-with' => $qb->expr()->like($columnName, $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($value), $paramType)),
616-
'ends-with' => $qb->expr()->like($columnName, $qb->createNamedParameter($this->db->escapeLikeParameter($value) . '%', $paramType)),
617-
'contains' => $qb->expr()->like($columnName, $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%', $paramType)),
618-
'does-not-contain' => $qb->expr()->notLike($columnName, $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%', $paramType)),
628+
'begins-with' => $qb->expr()->iLike($columnName, $qb->createNamedParameter($this->db->escapeLikeParameter($value) . '%', $paramType)),
629+
'ends-with' => $qb->expr()->iLike($columnName, $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($value), $paramType)),
630+
'contains' => $qb->expr()->iLike($columnName, $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%', $paramType)),
631+
'does-not-contain' => $this->notILike($qb, $columnName, $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%', $paramType)),
619632
'is-equal' => $qb->expr()->eq($columnName, $qb->createNamedParameter($value, $paramType)),
620633
'is-not-equal' => $qb->expr()->neq($columnName, $qb->createNamedParameter($value, $paramType)),
621634
'is-greater-than' => $qb->expr()->gt($columnName, $qb->createNamedParameter($value, $paramType)),

tests/integration/features/APIv1.feature

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,53 @@ Feature: APIv1
553553
| task |
554554
| New general task |
555555

556+
@api1 @views @view-filters
557+
Scenario: Case-insensitive filter with contains operator
558+
Given table "Product List" with emoji "📦" exists for user "participant1" as "product-list"
559+
Then column "name" exists with following properties
560+
| type | text |
561+
| subtype | line |
562+
| mandatory | 1 |
563+
| description | Product name |
564+
Then row exists with following values
565+
| name | Apple |
566+
Then row exists with following values
567+
| name | Banana |
568+
Then row exists with following values
569+
| name | Orange |
570+
And user "participant1" create view "Fruit Products" with emoji "🍎" for "product-list" as "fruit-products"
571+
When user "participant1" sets columnSettings "name" to view "fruit-products"
572+
And user "participant1" sets filter to view "fruit-products"
573+
| column | operator | value |
574+
| name | contains | APP |
575+
Then view "fruit-products" has exactly the following rows
576+
| name |
577+
| Apple |
578+
579+
@api1 @views @view-filters
580+
Scenario: Case-insensitive filter with does-not-contain operator
581+
Given table "Produce List" with emoji "🥕" exists for user "participant1" as "produce-list"
582+
Then column "name" exists with following properties
583+
| type | text |
584+
| subtype | line |
585+
| mandatory | 1 |
586+
| description | Produce name |
587+
Then row exists with following values
588+
| name | Apple |
589+
Then row exists with following values
590+
| name | Banana |
591+
Then row exists with following values
592+
| name | Orange |
593+
And user "participant1" create view "Produce Filtered" with emoji "🚫" for "produce-list" as "produce-filtered"
594+
When user "participant1" sets columnSettings "name" to view "produce-filtered"
595+
And user "participant1" sets filter to view "produce-filtered"
596+
| column | operator | value |
597+
| name | does-not-contain | APP |
598+
Then view "produce-filtered" has exactly the following rows
599+
| name |
600+
| Banana |
601+
| Orange |
602+
556603
@api1 @views @technical-name
557604
Scenario: Create and update view with technical name
558605
Given table "View technical name test" with emoji "🔧" exists for user "participant1" as "tech-name-table"

0 commit comments

Comments
 (0)