Как исправить SQL запрос, который собирает строку без контекста?
Что проверяют
Обычно это задача на ревью: запрос склеивается через fmt.Sprintf или конкатенацию и выполняется через db.Query без context.Context. Проверяют, увидите ли вы обе проблемы: SQL-инъекцию и отсутствие контроля над временем выполнения. Следом спрашивают, что делать с динамической сортировкой, где плейсхолдер не работает.
Как отвечать
Разберите две правки по отдельности.
- параметры вместо склейки: значения передаются отдельно от текста запроса, и драйвер отдаёт их базе как данные:
db.QueryContext(ctx, "SELECT id, name FROM users WHERE email = $1", email). Синтаксис плейсхолдеров зависит от драйвера:$1у PostgreSQL (pgx,lib/pq),?у MySQL; - белый список для идентификаторов: плейсхолдер подставляет только значения. Имя колонки или направление сортировки параметром не передать, их выбирают из заранее известного набора, например
map[string]string{"date": "created_at"}; - контекст:
QueryContext,ExecContextиQueryRowContextпринимаютctx, который приходит из HTTP-запроса (r.Context()), часто сcontext.WithTimeout. Клиент отключился или истёк таймаут: драйвер прерывает запрос, соединение возвращается в пул. Без контекста несколько тяжёлых запросов под нагрузкой занимают весь пулdatabase/sql; - обвязка:
defer rows.Close()сразу после проверки ошибки иrows.Err()после циклаrows.Next().
Для сложных динамических фильтров подойдёт билдер вроде squirrel: он собирает текст запроса и сам расставляет плейсхолдеры.
Где спотыкаются
Экранируют кавычки вручную через strings.ReplaceAll и считают, что инъекции больше нет. Надёжная защита одна: значение вообще не попадает в текст запроса.
Создают context.Background() прямо в репозитории. Формально контекст есть, но отмену от клиента он не несёт. ctx нужно пробрасывать первым аргументом через все слои.
Забывают rows.Close(). Соединение не возвращается в пул, и под нагрузкой сервис упирается в SetMaxOpenConns или в лимит соединений самой базы, после чего запросы начинают ждать.