Squirrel - Собираем SQL-запросы в Go как из кубиков Lego
Репозиторий давно не обновлялся
Последнее обновление было 2 года назад.
Знакомая ситуация? Вы пишете очередной сервис на Go, и вам нужно сделать запрос в базу. Сначала все просто: SELECT * FROM users. Но потом появляются фильтры. И вот уже ваш код пестрит конкатенацией строк, условными блоками, добавляющими AND, и постоянной головной болью: "А не забыл ли я где-то пробел? А что насчет SQL-инъекций?".
Конечно, можно взять тяжеловесную ORM, которая спрячет от вас SQL под слоем абстракций. Но что, если вы любите SQL, хотите контролировать каждый JOIN и просто ищете инструмент, который избавит от рутины, а не от самого языка запросов?
Сегодня мы посмотрим на проект, который стал для многих Go-разработчиков той самой золотой серединой. Встречайте — Squirrel, элегантный конструктор SQL-запросов.
Что такое Squirrel и зачем он нужен?
Сразу оговоримся: Squirrel — это не ORM. Он не пытается отображать структуры на таблицы, не управляет связями и не прячет от вас SQL. Его задача — помочь вам писать SQL чисто, безопасно и, что немаловажно, читабельно.
Представьте, что вы собираете запрос не из строк, а из готовых, логически понятных блоков. Это как конструктор Lego: у вас есть кубики Select, From, Where, Join, и вы соединяете их в нужном порядке. Результат — предсказуемый, надежный и легко модифицируемый SQL-запрос.
Давайте посмотрим, как это избавляет от головной боли на практике.
Ключевые возможности: от простоты к магии
Squirrel подкупает своим "текучим" (fluent) интерфейсом, который позволяет выстраивать запросы в виде цепочки вызовов.
1. Читабельность и композиция
Вместо того чтобы собирать строку по частям, вы описываете запрос так, как вы бы его произнесли.
import sq "github.com/Masterminds/squirrel"
// Создаем основу запроса
usersQuery := sq.Select("*").From("users").Join("emails USING (email_id)")
// Добавляем условие
activeUsersQuery := usersQuery.Where(sq.Eq{"deleted_at": nil})
// Получаем готовый SQL и аргументы для плейсхолдеров
sql, args, err := activeUsersQuery.ToSql()
// На выходе получаем:
// sql: "SELECT * FROM users JOIN emails USING (email_id) WHERE deleted_at IS NULL"
// args: []
Обратите внимание: запрос можно строить по частям. usersQuery — это самостоятельный объект, который можно переиспользовать, добавляя к нему новые условия. Это невероятно удобно для создания базовых запросов, которые потом конкретизируются в разных частях приложения.
2. Динамические запросы без боли
А вот и та самая магия, которая решает проблему условных фильтров. Допустим, у нас есть поисковый запрос от пользователя, который может быть пустым.
Без Squirrel код выглядел бы примерно так:
// Классический "ручной" подход
var args []interface{}
sql := "SELECT * FROM users WHERE active = true"
if q != "" {
sql += " AND name LIKE ?"
args = append(args, "%"+q+"%")
}
А теперь с Squirrel:
query := sq.Select("*").From("users").Where(sq.Eq{"active": true})
if len(q) > 0 {
// Просто добавляем еще одно условие, если нужно
query = query.Where("name LIKE ?", fmt.Sprintf("%%%s%%", q))
}
// Генерируем и выполняем
sql, args, err := query.ToSql()
Чисто, правда? Никаких ручных склеек AND, никаких забот о порядке условий. Squirrel сам разберется, как правильно составить итоговый WHERE.
3. Особая любовь к PostgreSQL
Разработчики Squirrel явно питают теплые чувства к Postgres. Библиотека из коробки умеет работать с его специфичным синтаксисом плейсхолдеров ($1, $2 вместо ?).
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
sql, _, _ := psql.Select("*").
From("elephants").
Where("name IN (?,?)", "Dumbo", "Verna").
ToSql()
// squirrel сам заменит '?' на '$N'
// sql: "SELECT * FROM elephants WHERE name IN ($1,$2)"
Еще одна киллер-фича — поддержка RETURNING. Получить ID только что вставленной записи? Легко!
var newId uint64
query := psql.Insert("nodes").
Columns("uuid", "type").
Values(node.Uuid, node.Type).
Suffix("RETURNING \"id\""). // Просто добавляем нужный суффикс
RunWith(db)
err := query.QueryRow().Scan(&newId)
Это элегантное решение, которое избавляет от необходимости делать дополнительный SELECT после INSERT.
4. Не только конструктор, но и исполнитель
Squirrel умеет не только генерировать SQL-строки. Он может сам выполнять запросы, если передать ему активное соединение с базой.
// Выбираем трех пользователей из списка
stooges := sq.Select("*").
From("users").
Where(sq.Eq{"username": []string{"moe", "larry", "curly", "shemp"}}).
Limit(3)
// Выполняем запрос напрямую
rows, err := stooges.RunWith(db).Query()
defer rows.Close()
// ... дальше работаем с rows как обычно
Для оптимизации производительности есть встроенный кэш подготовленных выражений (StmtCache). Это позволяет избежать повторной компиляции одного и того же запроса базой данных при частых вызовах.
Стабильность как преимущество
Зайдя на GitHub, вы увидите плашку "Maintenance". Не спешите пугаться! В данном контексте это не значит "проект заброшен". Это означает, "проект завершен и стабилен". API устоялось, основные функции реализованы, и библиотека просто работает. Для многих продакшн-систем это огромный плюс — вам не придется переписывать код после каждого минорного обновления.
Выводы: кому и когда стоит использовать Squirrel?
Squirrel — это идеальный инструмент для разработчиков, которые:
- Ценят SQL и хотят полного контроля над запросами.
- Устали от ручного склеивания строк и связанных с этим ошибок.
- Ищут что-то легче и гибче, чем полнофункциональная ORM.
- Работают с динамическими фильтрами и сложными отчетами.
- Используют PostgreSQL и хотят удобно работать с его фичами.
Эта библиотека не будет писать за вас бизнес-логику, но она станет вашим верным помощником в работе с базой данных, сделав код чище, надежнее и приятнее для чтения. Если вы еще не пробовали — настоятельно рекомендую взглянуть на Squirrel в своем следующем Go-проекте.
