Squirrel - Собираем SQL-запросы в Go как из кубиков Lego

24 Apr, 2024

Репозиторий давно не обновлялся

Последнее обновление было 2 года назад.

7,951
🔱 509
👥 56

Знакомая ситуация? Вы пишете очередной сервис на Go, и вам нужно сделать запрос в базу. Сначала все просто: SELECT * FROM users. Но потом появляются фильтры. И вот уже ваш код пестрит конкатенацией строк, условными блоками, добавляющими AND, и постоянной головной болью: "А не забыл ли я где-то пробел? А что насчет SQL-инъекций?".

Конечно, можно взять тяжеловесную ORM, которая спрячет от вас SQL под слоем абстракций. Но что, если вы любите SQL, хотите контролировать каждый JOIN и просто ищете инструмент, который избавит от рутины, а не от самого языка запросов?

Сегодня мы посмотрим на проект, который стал для многих Go-разработчиков той самой золотой серединой. Встречайте — Squirrel, элегантный конструктор SQL-запросов.

Stability: Maintenance

Что такое 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-проекте.

🍪 Мы используем файлы cookie и сервис аналитики Яндекс.Метрика, чтобы сайт работал лучше. Продолжая пользоваться devtrends.ru, вы соглашаетесь с обработкой данных согласно Политике конфиденциальности.