mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 01:25:55 +02:00
Added a custom function to make PostgresSQL searches case insensitive
This is required only for postgres as every other database is case invariant by default. But to achieve a portable way, we implement it via a custom DQL function. This fixes issue #784
This commit is contained in:
parent
b1ba26e0b9
commit
e223078af9
11 changed files with 94 additions and 22 deletions
|
@ -57,6 +57,7 @@ doctrine:
|
||||||
field2: App\Doctrine\Functions\Field2
|
field2: App\Doctrine\Functions\Field2
|
||||||
natsort: App\Doctrine\Functions\Natsort
|
natsort: App\Doctrine\Functions\Natsort
|
||||||
array_position: App\Doctrine\Functions\ArrayPosition
|
array_position: App\Doctrine\Functions\ArrayPosition
|
||||||
|
ilike: App\Doctrine\Functions\ILike
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
doctrine:
|
doctrine:
|
||||||
|
|
|
@ -50,7 +50,7 @@ final class LikeFilter extends AbstractFilter
|
||||||
}
|
}
|
||||||
$parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters
|
$parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters
|
||||||
$queryBuilder
|
$queryBuilder
|
||||||
->andWhere(sprintf('o.%s LIKE :%s', $property, $parameterName))
|
->andWhere(sprintf('ILIKE(o.%s, :%s) = TRUE', $property, $parameterName))
|
||||||
->setParameter($parameterName, $value);
|
->setParameter($parameterName, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,10 +61,10 @@ final class TagFilter extends AbstractFilter
|
||||||
$expr = $queryBuilder->expr();
|
$expr = $queryBuilder->expr();
|
||||||
|
|
||||||
$tmp = $expr->orX(
|
$tmp = $expr->orX(
|
||||||
$expr->like('o.'.$property, ':' . $tag_identifier_prefix . '_1'),
|
'ILIKE(o.'.$property.', :' . $tag_identifier_prefix . '_1) = TRUE',
|
||||||
$expr->like('o.'.$property, ':' . $tag_identifier_prefix . '_2'),
|
'ILIKE(o.'.$property.', :' . $tag_identifier_prefix . '_2) = TRUE',
|
||||||
$expr->like('o.'.$property, ':' . $tag_identifier_prefix . '_3'),
|
'ILIKE(o.'.$property.', :' . $tag_identifier_prefix . '_3) = TRUE',
|
||||||
$expr->eq('o.'.$property, ':' . $tag_identifier_prefix . '_4'),
|
'ILIKE(o.'.$property.', :' . $tag_identifier_prefix . '_4) = TRUE',
|
||||||
);
|
);
|
||||||
|
|
||||||
$queryBuilder->andWhere($tmp);
|
$queryBuilder->andWhere($tmp);
|
||||||
|
|
|
@ -93,10 +93,10 @@ class TagsConstraint extends AbstractConstraint
|
||||||
$expr = $queryBuilder->expr();
|
$expr = $queryBuilder->expr();
|
||||||
|
|
||||||
$tmp = $expr->orX(
|
$tmp = $expr->orX(
|
||||||
$expr->like($this->property, ':' . $tag_identifier_prefix . '_1'),
|
'ILIKE(' . $this->property . ', :' . $tag_identifier_prefix . '_1) = TRUE',
|
||||||
$expr->like($this->property, ':' . $tag_identifier_prefix . '_2'),
|
'ILIKE(' . $this->property . ', :' . $tag_identifier_prefix . '_2) = TRUE',
|
||||||
$expr->like($this->property, ':' . $tag_identifier_prefix . '_3'),
|
'ILIKE(' . $this->property . ', :' . $tag_identifier_prefix . '_3) = TRUE',
|
||||||
$expr->eq($this->property, ':' . $tag_identifier_prefix . '_4'),
|
'ILIKE(' . $this->property . ', :' . $tag_identifier_prefix . '_4) = TRUE',
|
||||||
);
|
);
|
||||||
|
|
||||||
//Set the parameters for the LIKE expression, in each variation of the tag (so with a comma, at the end, at the beginning, and on both ends, and equaling the tag)
|
//Set the parameters for the LIKE expression, in each variation of the tag (so with a comma, at the end, at the beginning, and on both ends, and equaling the tag)
|
||||||
|
|
|
@ -107,7 +107,8 @@ class TextConstraint extends AbstractConstraint
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($like_value !== null) {
|
if ($like_value !== null) {
|
||||||
$this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, 'LIKE', $like_value);
|
$queryBuilder->andWhere(sprintf('ILIKE(%s, :%s) = TRUE', $this->property, $this->identifier));
|
||||||
|
$queryBuilder->setParameter($this->identifier, $like_value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ declare(strict_types=1);
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
namespace App\DataTables\Filters;
|
namespace App\DataTables\Filters;
|
||||||
|
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
|
||||||
class PartSearchFilter implements FilterInterface
|
class PartSearchFilter implements FilterInterface
|
||||||
|
@ -132,15 +131,15 @@ class PartSearchFilter implements FilterInterface
|
||||||
return sprintf("REGEXP(%s, :search_query) = TRUE", $field);
|
return sprintf("REGEXP(%s, :search_query) = TRUE", $field);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sprintf("%s LIKE :search_query", $field);
|
return sprintf("ILIKE(%s, :search_query) = TRUE", $field);
|
||||||
}, $fields_to_search);
|
}, $fields_to_search);
|
||||||
|
|
||||||
//Add Or concatation of the expressions to our query
|
//Add Or concatenation of the expressions to our query
|
||||||
$queryBuilder->andWhere(
|
$queryBuilder->andWhere(
|
||||||
$queryBuilder->expr()->orX(...$expressions)
|
$queryBuilder->expr()->orX(...$expressions)
|
||||||
);
|
);
|
||||||
|
|
||||||
//For regex we pass the query as is, for like we add % to the start and end as wildcards
|
//For regex, we pass the query as is, for like we add % to the start and end as wildcards
|
||||||
if ($this->regex) {
|
if ($this->regex) {
|
||||||
$queryBuilder->setParameter('search_query', $this->keyword);
|
$queryBuilder->setParameter('search_query', $this->keyword);
|
||||||
} else {
|
} else {
|
||||||
|
|
71
src/Doctrine/Functions/ILike.php
Normal file
71
src/Doctrine/Functions/ILike.php
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
|
namespace App\Doctrine\Functions;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
|
||||||
|
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
|
||||||
|
use Doctrine\DBAL\Platforms\SQLitePlatform;
|
||||||
|
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||||
|
use Doctrine\ORM\Query\Parser;
|
||||||
|
use Doctrine\ORM\Query\SqlWalker;
|
||||||
|
use Doctrine\ORM\Query\TokenType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A platform invariant version of the case-insensitive LIKE operation.
|
||||||
|
* On MySQL and SQLite this is the normal LIKE, but on PostgreSQL it is the ILIKE operator.
|
||||||
|
*/
|
||||||
|
class ILike extends FunctionNode
|
||||||
|
{
|
||||||
|
|
||||||
|
public $value = null;
|
||||||
|
|
||||||
|
public $expr = null;
|
||||||
|
|
||||||
|
public function parse(Parser $parser): void
|
||||||
|
{
|
||||||
|
$parser->match(TokenType::T_IDENTIFIER);
|
||||||
|
$parser->match(TokenType::T_OPEN_PARENTHESIS);
|
||||||
|
$this->value = $parser->StringPrimary();
|
||||||
|
$parser->match(TokenType::T_COMMA);
|
||||||
|
$this->expr = $parser->StringExpression();
|
||||||
|
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSql(SqlWalker $sqlWalker): string
|
||||||
|
{
|
||||||
|
$platform = $sqlWalker->getConnection()->getDatabasePlatform();
|
||||||
|
|
||||||
|
//
|
||||||
|
if ($platform instanceof AbstractMySQLPlatform || $platform instanceof SQLitePlatform) {
|
||||||
|
$operator = 'LIKE';
|
||||||
|
} elseif ($platform instanceof PostgreSQLPlatform) {
|
||||||
|
//Use the case-insensitive operator, to have the same behavior as MySQL
|
||||||
|
$operator = 'ILIKE';
|
||||||
|
} else {
|
||||||
|
throw new \RuntimeException('Platform ' . gettype($platform) . ' does not support case insensitive like expressions.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return '(' . $this->value->dispatch($sqlWalker) . ' ' . $operator . ' ' . $this->expr->dispatch($sqlWalker) . ')';
|
||||||
|
}
|
||||||
|
}
|
|
@ -75,8 +75,8 @@ class AttachmentRepository extends DBElementRepository
|
||||||
{
|
{
|
||||||
$qb = $this->createQueryBuilder('attachment');
|
$qb = $this->createQueryBuilder('attachment');
|
||||||
$qb->select('COUNT(attachment)')
|
$qb->select('COUNT(attachment)')
|
||||||
->where('attachment.path LIKE :http')
|
->where('ILIKE(attachment.path, :http) = TRUE')
|
||||||
->orWhere('attachment.path LIKE :https');
|
->orWhere('ILIKE(attachment.path, :https) = TRUE');
|
||||||
$qb->setParameter('http', 'http://%');
|
$qb->setParameter('http', 'http://%');
|
||||||
$qb->setParameter('https', 'https://%');
|
$qb->setParameter('https', 'https://%');
|
||||||
$query = $qb->getQuery();
|
$query = $qb->getQuery();
|
||||||
|
|
|
@ -44,7 +44,7 @@ class ParameterRepository extends DBElementRepository
|
||||||
->select('parameter.name')
|
->select('parameter.name')
|
||||||
->addSelect('parameter.symbol')
|
->addSelect('parameter.symbol')
|
||||||
->addSelect('parameter.unit')
|
->addSelect('parameter.unit')
|
||||||
->where('parameter.name LIKE :name');
|
->where('ILIKE(parameter.name, :name) = TRUE');
|
||||||
if ($exact) {
|
if ($exact) {
|
||||||
$qb->setParameter('name', $name);
|
$qb->setParameter('name', $name);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -81,10 +81,10 @@ class PartRepository extends NamedDBElementRepository
|
||||||
->leftJoin('part.category', 'category')
|
->leftJoin('part.category', 'category')
|
||||||
->leftJoin('part.footprint', 'footprint')
|
->leftJoin('part.footprint', 'footprint')
|
||||||
|
|
||||||
->where('part.name LIKE :query')
|
->where('ILIKE(part.name, :query) = TRUE')
|
||||||
->orWhere('part.description LIKE :query')
|
->orWhere('ILIKE(part.description, :query) = TRUE')
|
||||||
->orWhere('category.name LIKE :query')
|
->orWhere('ILIKE(category.name, :query) = TRUE')
|
||||||
->orWhere('footprint.name LIKE :query')
|
->orWhere('ILIKE(footprint.name, :query) = TRUE')
|
||||||
;
|
;
|
||||||
|
|
||||||
$qb->setParameter('query', '%'.$query.'%');
|
$qb->setParameter('query', '%'.$query.'%');
|
||||||
|
|
|
@ -66,7 +66,7 @@ class TagFinder
|
||||||
|
|
||||||
$qb->select('p.tags')
|
$qb->select('p.tags')
|
||||||
->from(Part::class, 'p')
|
->from(Part::class, 'p')
|
||||||
->where('p.tags LIKE ?1')
|
->where('ILIKE(p.tags, ?1) = TRUE')
|
||||||
->setMaxResults($options['query_limit'])
|
->setMaxResults($options['query_limit'])
|
||||||
//->orderBy('RAND()')
|
//->orderBy('RAND()')
|
||||||
->setParameter(1, '%'.$keyword.'%');
|
->setParameter(1, '%'.$keyword.'%');
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue