Repository navigation
msProducts: сортировка по menuindex с parents даёт ошибку SQL — пустая выдача #736
Description
Кратко
С #627 (c3c84ddf, «Индивидуальный menuindex для товаров в дополнительных категориях») сниппет msProducts меняет sortby при двух условиях: sortby содержит menuindex и заданы parents. Туда подставляется SQL-выражение CASE … COALESCE(…, …) END.
pdoTools режет строку sortby по запятым и экранирует куски по отдельности. Выражение ломается, MariaDB возвращает синтаксическую ошибку, сниппет не выводит ни одного товара.
Любой каталог, где msProducts вызван с parents и сортировкой по menuindex, остаётся пустым. Через mFilter пустеет вся выдача каталога: mFilter получает базовые id товаров вызовом msProducts с returnIds и sortby из вызова сниппета.
Окружение
- MiniShop3 — ветка
beta,f0b1c9c3; пакет на тестовом сайтеminishop3-1.13.0-beta1 - pdoTools 3.0.2-pl
- MariaDB 10.6.23
Воспроизведение
{'!msProducts' | snippet : ['parents' => 5, 'sortby' => 'menuindex', 'sortdir' => 'ASC', 'limit' => 3]}
{'!msProducts' | snippet : ['parents' => 5, 'sortby' => 'msProduct.menuindex', 'sortdir' => 'ASC', 'limit' => 3]}
{'!msProducts' | snippet : ['parents' => 5, 'sortby' => 'pagetitle', 'sortdir' => 'ASC', 'limit' => 3]}
Проверено на тестовом сайте, на разделах с 2 023 и 30 232 товарами:
| sortby | Результат |
|---|---|
menuindex |
Пусто, ошибка в журнале |
pagetitle |
3 товара |
Запрос целиком выводит тот же вызов с 'return' => 'sql'.
Журнал ошибок MODX на каждый вызов:
(ERROR @ core/components/pdotools/src/Fetch.php : 185) [pdoTools] Error 42000: You have an error in your SQL syntax;
check the manual that corresponds to your MariaDB server version for the right syntax to use near
'ELSE COALESCE(MIN(CategoryMember.menuindex),` msProduct`.`menuindex)`END ASC ...' at line 20
Причина
1. Сниппет подставляет выражение в sortby — ms3_products.php:163-186. CategoryProductMenuindexService::effectiveMenuindexCase() возвращает для нескольких разделов:
CASE WHEN msProduct.parent IN (5,6,7) THEN msProduct.menuindex
ELSE COALESCE(MIN(CategoryMember.menuindex), msProduct.menuindex) ENDДля одного раздела выражение то же, но с COALESCE(CategoryMember.menuindex, msProduct.menuindex).
2. pdoTools режет sortby по запятым — Fetch.php, addSort():
$tmp = explode(',', $sortby);
array_walk($tmp, function (&$value) {
if (strpos($value, '`') === false) {
$value = preg_replace('#(.*?)\.(.*?)\s#', '`$1`.`$2`', $value);
}
});
$sortby = implode(',', $tmp);Кусок после explode |
После замены |
|---|---|
CASE WHEN msProduct.parent IN (5 |
`CASE WHEN msProduct`.`parent`IN (5 |
6, … |
без изменений |
7) THEN msProduct.menuindex ELSE COALESCE(MIN(CategoryMember.menuindex) |
`7) THEN msProduct`.`menuindex`ELSE COALESCE(MIN(CategoryMember.menuindex) |
msProduct.menuindex) END |
` msProduct`.`menuindex)`END |
Последние два куска после implode дают в точности фрагмент из текста ошибки. Для одного раздела выражение ломается тем же preg_replace, даже без запятой: замена срабатывает на любом куске с точкой и пробелом.
3. Почему тест не поймал. tests/CategoryProductMenuindexServiceTest.php:39-45 проверяет только, что строка выражения содержит нужные фрагменты. Через pdoTools выражение не прогоняется.
Другие места с тем же выражением работают:
- грид категории (
CategoryProductsListService) сортирует по псевдониму из SELECT; - Web API (
ProductCatalogService) сортирует через xPDO без pdoTools.
Как исправить
Экранирование пропускает куски с обратной кавычкой. Если все идентификаторы выражения записать в обратных кавычках, каждый кусок после explode будет либо с кавычкой, либо из одних цифр, и implode вернёт выражение целым:
CASE WHEN `msProduct`.`parent` IN (5,6,7) THEN `msProduct`.`menuindex`
ELSE COALESCE(MIN(`CategoryMember`.`menuindex`), `msProduct`.`menuindex`) ENDxPDOQuery::isValidClause() такое выражение пропускает, GROUP BY msProduct.id для MIN() уже есть.
Вариант «выражение в select с псевдонимом», как в гриде, для сниппета не подходит:
- режим
returnIds: pdoTools выбирает только первичный ключ, и псевдонима вORDER BYне будет; - вложенные скобки:
addSelects()защищает запятые только внутри одной пары скобок.
Нужен тест, который прогоняет выражение через экранирование sortby pdoTools, в обычном режиме и с returnIds.
Что не установлено
Код попал на тестовый сайт 7–8 сентября, а выдача каталога через mFilter ещё днём работала. Гипотеза: базовые id брались из кэша mFilter, и msProducts не вызывался, пока кэш не сбросился. Не проверено.
Activity
fix(category): keep effective menuindex intact in grid select and msP…
fix(category): keep grid total consistent with rows and log failed qu…