Documentation
¶
Overview ¶
Package filter — простой парсер filter-выражений API Kachō.
Текущая поддержка:
<field> = "<value>" — точное равенство
<field> CONTAINS "<value>" — подстрока (LIKE %…%, подстановочные знаки
значения экранируются)
Где <field> — whitelisted set (например "name"), <value> — double-quoted строка. Возвращает (FilterAST, error). FilterAST использует SQL-binding (без string concat) при превращении в WHERE clause.
CONTAINS заведён под строку поиска в списке: точное равенство ей бесполезно (набирающий имя по частям не совпадёт никогда), поэтому без него поиск оставался бы клиентским — то есть отвечал бы про загруженную страницу, а не про список.
Значение проверяется по двум осям — длина и запрещённый знак; правила и их довод — у MaxValueLen ниже. Алфавит значения НЕ сужается, и это решение, а не упущение: фильтруемые поля дерева несут не только имена (идентификаторы, вид размещения в верхнем регистре, точечный вид области), поэтому алфавит формы имени отверг бы законный фильтр.
Формат сообщений об ошибках:
"Bad expression at column N. Unknown field: \"<field>\"" "Bad expression at column N. Expected an operator" "Bad expression at column N. Expected a string, integer, date-time or boolean value" "Bad expression at column N. Value of \"<field>\" is longer than 256 characters" "Bad expression at column N. Value of \"<field>\" contains a NUL character"
Поддержка AND/OR/STARTS_WITH/IN — не заведена: у неё нет вызывающего.
Index ¶
Constants ¶
const ( OpEquals = "=" OpContains = "CONTAINS" )
OpEquals / OpContains — операторы, которые эмитит Parse. Значение Op вне этого набора ToSQL трактует как равенство: расширять набор надо в обоих местах разом.
const MaxValueLen = 256
MaxValueLen — предел длины значения фильтра, В ЗНАКАХ.
ПОЧЕМУ ПРЕДЕЛ ВООБЩЕ ЕСТЬ (задача продукта #1654). Значение приходит от клиента и уезжает в запрос параметром; у оператора `CONTAINS` оно становится образцом `LIKE '%…%'`, и стоимость каждой просмотренной строки растёт вместе с длиной образца. Такой запрос дёшев в отправке и не дёшев в обслуживании, а предела у него не было ни одного: `page_size` конвенция ограничивает, значение фильтра оставалось единственной неограниченной строкой на пути чтения.
ПОЧЕМУ ИМЕННО 256. Всё, что фильтр в этом дереве способен СОПОСТАВИТЬ, короче: имя ресурса — DNS-label, не длиннее 63 знаков; идентификаторы — около 21; точечный вид области и вид размещения — меньше двадцати. 256 — вчетверо больше самого длинного из них, поэтому законного вызывающего предел не задевает и запас на будущее поле оставляет. Предел объявлен ЧИСЛОМ и вывезен наружу: строящий клиента узнаёт его из контракта, а не отказом.
ПОЧЕМУ В ЗНАКАХ, А НЕ В БАЙТАХ. Имя и метка бывают не только латинскими; байтовый предел отверг бы кириллическое значение вдвое короче объявленного — то есть правило зависело бы от письменности, а не от длины.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type FilterAST ¶
FilterAST — узел AST. Для текущего узла-минимума: одно сравнение.
func Parse ¶
Parse разбирает filter-выражение. allowedFields — whitelist полей.
Возвращает (nil, nil) для пустого input — означает "no filter". Возвращает *FilterAST или *ParseError.
func (*FilterAST) ToSQL ¶
ToSQL превращает AST в безопасный SQL fragment. Возвращает (whereFragment, args). whereFragment использует placeholder $N, где N стартует с argStartIdx.
Например: ast{Field:"name",Op:"=",Value:"foo"}, argStartIdx=3
→ ("name = $3", []any{"foo"}, nil)
func (*FilterAST) ToSQLOn ¶
ToSQLOn — то же, что ToSQL, но предикат строится на КОЛОНКЕ, которую называет вызывающий, а не на имени разобранного поля.
Зачем это отдельная точка входа. `ToSQL` полагает, что поле контракта и колонка таблицы называются одинаково. У доброй половины владельцев это неверно: колонка уточнена псевдонимом (`v.name`, `i.name`, `s.name`), либо предикат собирается не в том слое, где разбирали выражение. Такому владельцу применить узел было НЕЧЕМ — и он забирал из узла одно значение, теряя вместе с ним ОПЕРАТОР: запрос подстроки молча отвечал точным равенством. Это и есть содержание #460; отсутствие этой функции — его причина, а не невнимательность авторов.
`column` обязана быть идентификатором (при необходимости уточнённым точкой) — не выражением. Всё, что не проходит `safeFieldRe`, защитно закавычивается, как и `Field` в `ToSQL`: колонка приходит от вызывающего, поэтому защита имени действует и на этом входе. Владельцу, которому нужна не колонка, а выражение (`lower(email)`), эта функция не подходит — он обязан разобрать оператор сам и назвать неподдерживаемый отказом, а не свести его к равенству.
type ParseError ¶
ParseError — ошибка парсинга с message в фиксированном формате.
func (*ParseError) Error ¶
func (e *ParseError) Error() string