filter

package
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 10, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

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

View Source
const (
	OpEquals   = "="
	OpContains = "CONTAINS"
)

OpEquals / OpContains — операторы, которые эмитит Parse. Значение Op вне этого набора ToSQL трактует как равенство: расширять набор надо в обоих местах разом.

View Source
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

type FilterAST struct {
	Field string
	Op    string // OpEquals | OpContains
	Value string
}

FilterAST — узел AST. Для текущего узла-минимума: одно сравнение.

func Parse

func Parse(input string, allowedFields []string) (*FilterAST, error)

Parse разбирает filter-выражение. allowedFields — whitelist полей.

Возвращает (nil, nil) для пустого input — означает "no filter". Возвращает *FilterAST или *ParseError.

func (*FilterAST) ToSQL

func (a *FilterAST) ToSQL(argStartIdx int) (string, []any)

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

func (a *FilterAST) ToSQLOn(column string, argStartIdx int) (string, []any)

ToSQLOn — то же, что ToSQL, но предикат строится на КОЛОНКЕ, которую называет вызывающий, а не на имени разобранного поля.

Зачем это отдельная точка входа. `ToSQL` полагает, что поле контракта и колонка таблицы называются одинаково. У доброй половины владельцев это неверно: колонка уточнена псевдонимом (`v.name`, `i.name`, `s.name`), либо предикат собирается не в том слое, где разбирали выражение. Такому владельцу применить узел было НЕЧЕМ — и он забирал из узла одно значение, теряя вместе с ним ОПЕРАТОР: запрос подстроки молча отвечал точным равенством. Это и есть содержание #460; отсутствие этой функции — его причина, а не невнимательность авторов.

`column` обязана быть идентификатором (при необходимости уточнённым точкой) — не выражением. Всё, что не проходит `safeFieldRe`, защитно закавычивается, как и `Field` в `ToSQL`: колонка приходит от вызывающего, поэтому защита имени действует и на этом входе. Владельцу, которому нужна не колонка, а выражение (`lower(email)`), эта функция не подходит — он обязан разобрать оператор сам и назвать неподдерживаемый отказом, а не свести его к равенству.

type ParseError

type ParseError struct {
	Column  int
	Message string
}

ParseError — ошибка парсинга с message в фиксированном формате.

func (*ParseError) Error

func (e *ParseError) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL