Режимы совпадения
Четыре строковых оператора MUI (contains, notContains, startsWith, endsWith)
регистронезависимы по определению, а переносимого способа записать это в SQL не существует.
TextMatch — перечисление, которое выбирает написание; по умолчанию оно определяется по
драйверу вашей базы.
Зачем это нужно
Одна и та же операция пишется в трёх СУБД тремя способами, и разница не косметическая:
| СУБД | Регистронезависимое совпадение | Что будет с ILIKE |
|---|---|---|
| PostgreSQL | col ILIKE :v |
работает |
| MySQL / MariaDB | col LIKE :v (при коллации *_ci) |
синтаксическая ошибка |
| SQLite | col LIKE :v (регистр ASCII) |
синтаксическая ошибка |
То есть на MySQL и SQLite ILIKE — не «медленнее» и не «менее точно», а полностью
нерабочий запрос. Поэтому написание нельзя зашить в библиотеку: оно принадлежит соединению,
а не гриду.
Значения
| Case | SQL для contains |
Для чего |
|---|---|---|
TextMatch::Auto |
определяется по драйверу репозитория | значение по умолчанию |
TextMatch::ILike |
col ILIKE :v |
PostgreSQL |
TextMatch::Like |
col LIKE :v |
MySQL / SQLite — регистр решает коллация |
TextMatch::Lower |
lower(col) LIKE lower(:v) |
любая СУБД, включая свёртку не-ASCII |
Остальные девятнадцать операторов — обычный стандартный SQL, режим их не касается.
Автоопределение
В режиме Auto метод MuiGrid::wrap() разрешает написание один раз за вызов, по драйверу
базы, с которой работает переданный репозиторий:
| Драйвер PDO | Режим |
|---|---|
pgsql |
ILike |
mysql |
Like |
sqlite |
Like |
| любой другой | Lower |
Драйвер берётся из конфигурации базы, привязанной к репозиторию. Конфигурация к этому
моменту уже зарегистрирована пулом соединений и лежит в его кэше, поэтому определение стоит
одного обращения к статическому массиву, а не нового соединения. Если конфигурацию
разрешить не удалось — нет пула, класс не зарегистрирован, в тестах подставлен двойник —
применяется портируемый Lower, а не исключение.
$schema = GridSchema::make(
GridColumn::for('title', 'a.title')->filterable(FilterType::String)->sortable(),
);
// на PostgreSQL → a.title ILIKE :iqb0
// на MySQL и SQLite → a.title LIKE :iqb0
// код при этом один и тот жеЗадать вручную
Автоопределение знает драйвер, но не знает вашей коллации. Явный режим нужен в трёх
случаях: бинарная коллация MySQL, где LIKE регистрозависим; экзотический драйвер;
построение условия без живого пула соединений.
На всю схему
GridSchema::make(/* … */)->textMatch(TextMatch::Lower);На одну колонку
GridColumn::for('name', 'au.name')
->filterable(FilterType::String)
->textMatch(TextMatch::Lower);Режим колонки перекрывает режим схемы. Значение TextMatch::Auto на колонке означает
«наследовать от схемы» — то же самое, что не вызывать метод.
Методы
TextMatch::forDriver()
Возвращает конкретный режим для имени драйвера PDO.
Синтаксис
public static function forDriver(string $driver): selfПараметры
$driver — имя драйвера, например pgsql, mysql, sqlite. Регистр не важен.
Возвращает
TextMatch — конкретный режим; для нераспознанного драйвера — Lower.
TextMatch::forRepository()
Возвращает конкретный режим для базы, с которой работает репозиторий.
Полезен за пределами грида: если вы строите собственное условие поиска, тот же вызов сделает его портируемым — см. Фильтры вне грида.
Синтаксис
public static function forRepository(RepositoryInterface $repository): selfПараметры
$repository — репозиторий, чьё соединение определяет диалект.
Возвращает
TextMatch — конкретный режим; Lower, если конфигурацию разрешить не удалось.
Пример
$m = TextMatch::forRepository($repo);
$repo->andWhere(Qb::clip(Qb::or(
$m->match('a.title', "%$s%"),
$m->match('a.body', "%$s%"),
)));TextMatch::match()
Строит условие совпадения по шаблону в этом режиме.
Синтаксис
public function match(string $column, string $pattern, bool $negated = false): QbПараметры
$column — доверенное SQL-выражение колонки. Как и везде, оно приходит из вашего кода.
$pattern — шаблон LIKE вместе с подстановочными знаками: их расставляете вы, метод
ничего не дописывает.
$negated — построить отрицание (NOT LIKE / NOT ILIKE).
Возвращает
Qb — условие со связанным значением.
Ошибки
LogicException — при вызове на TextMatch::Auto. Это режим-указание, а не написание: его
нужно разрешить до построения SQL.
Пример
echo TextMatch::Lower->match('a.title', '%Winter%')->getQuery();Результат
lower(a.title) LIKE :iqb0Особенности режима Lower
Lower сворачивает регистр с обеих сторон: колонку — функцией SQL lower(), шаблон — через
mb_strtolower() в PHP. Отсюда два следствия.
Он единственный чинит не-ASCII. В SQLite LIKE без ICU сравнивает без учёта регистра
только латиницу: Ünïcode и ünïcode для него разные строки. Свёртка через lower()
решает это.
// значение, ушедшее в байнд
TextMatch::Like->match('a.title', '%WiNTeR Ünïcode%'); // '%WiNTeR Ünïcode%'
TextMatch::Lower->match('a.title', '%WiNTeR Ünïcode%'); // '%winter ünïcode%'Две реализации lower() могут разойтись. Свёртка в PHP и свёртка в базе используют
разные таблицы регистров и в редких локалях дают разный результат. Если это важно, задайте
ILike или Like явно.
Индексы
ILIKE 'x%' не использует btree-индекс никогда, а lower(col) LIKE 'x%' — использует, если
для lower(col) создан функциональный индекс. Для contains разница не имеет значения:
шаблон с ведущим % неиндексируем в любой СУБД. Подробнее — в
Диалектах и индексах.
Что дальше
- Операторы — какие операторы вообще существуют.
- Диалекты и индексы — почему сделано именно так и что это значит для производительности.