Как SQLx меняет работу с базами данных в Rust
Представьте ситуацию: вы написали сложный SQL-запрос, скомпилировали проект на Rust, запустили его и... получили Runtime Error, потому что ошиблись в названии колонки или забыли передать один аргумент. Обидно, правда? Особенно в языке, который славится своей безопасностью. Именно эту проблему решает SQLx.
Что это вообще такое
SQLx — это не типичная ORM вроде Hibernate или SQLAlchemy. Разработчики называют его «SQL toolkit». Главная фишка проекта заключается в том, что он проверяет ваши SQL-запросы прямо во время компиляции. Если вы опечатались в SELECT или пытаетесь запихнуть строку туда, где база ждет число, компилятор Rust просто не даст собрать бинарник.
Проект полностью асинхронный и написан на чистом Rust (за исключением драйвера для SQLite, который тянет за собой C-библиотеку). Он поддерживает PostgreSQL, MySQL и SQLite. Раньше была поддержка MSSQL, но в версии 0.7 её убрали, пообещав переписать драйвер с нуля.
Проверка запросов на этапе компиляции
Это, пожалуй, самая сильная сторона библиотеки. Вместо того чтобы строить запросы через цепочки методов (DSL), как это делает Diesel или SeaORM, вы пишете обычный SQL внутри макроса.
let countries = sqlx::query!(
"SELECT country, COUNT(*) as count FROM users WHERE organization = $1 GROUP BY country",
org_id
)
.fetch_all(&pool)
.await?;
Как это работает? Во время сборки SQLx подключается к вашей базе данных (путь к которой указан в переменной DATABASE_URL), просит её провалидировать запрос и возвращает информацию о типах. Если база говорит, что запрос корректен, макрос генерирует анонимную структуру с результатами.
Кстати, если вы не хотите, чтобы CI-сервер каждый раз стучался в реальную базу при сборке, у SQLx есть «offline mode». Вы один раз подготавливаете кэш с метаданными запросов, и дальше проект собирается без подключения к сети.
Асинхронность и производительность
SQLx изначально проектировался под async/await. Он не пытается имитировать синхронный API, завернутый в потоки. Библиотека умеет стримить данные: если вам нужно выгрузить миллион строк, вы можете обрабатывать их по одной по мере поступления из сокета, не забивая всю память сразу.
Интересно, что проект не привязан к конкретному рантайму. Вы можете использовать tokio, async-std или actix — достаточно просто включить нужный feature-флаг в Cargo.toml.
# Пример для тех, кто использует tokio и rustls
sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-ring-native-roots", "postgres" ] }
Почему это не ORM
В SQLx нет привычных понятий вроде «моделей» или «связей». Вы не описываете User has_many Posts в коде Rust. Вы пишете JOIN в SQL-запросе.
Для тех, кто привык к полному контролю над базой, это огромный плюс. Вы используете всю мощь конкретной СУБД: специфические типы данных (вроде JSONB в Postgres), оконные функции или сложные триггеры. SQLx просто берет на себя скучную работу по маппингу результатов в структуры Rust через дерайв-макрос FromRow.
#[derive(sqlx::FromRow)]
struct User {
id: i64,
name: String,
}
let user = sqlx::query_as::<_, User>("SELECT id, name FROM users WHERE id = $1")
.bind(1)
.fetch_one(&pool)
.await?;
Маленькие, но полезные детали
В библиотеке много приятных мелочей, которые обычно приходится искать в сторонних плагинах:
- Встроенный пул соединений. Не нужно подключать
r2d2или аналоги. - Поддержка миграций. Есть CLI-утилита и макрос
migrate!, который вшивает миграции прямо в ваш бинарник. - Поддержка
LISTEN/NOTIFYдля PostgreSQL из коробки. - Работа с транзакциями, включая вложенные транзакции через точки сохранения (savepoints).
Стоит ли его использовать
SQLx идеально подойдет, если вы любите SQL и хотите использовать возможности своей базы на 100%, не теряя при этом типобезопасности Rust.
Однако за проверку запросов при компиляции приходится платить временем сборки. Если у вас сотни макросов query!, cargo check может стать заметно медленнее. В таких случаях разработчики советуют подкрутить уровень оптимизации для макросов в профиле разработки:
[profile.dev.package.sqlx-macros]
opt-level = 3
Если же вам нужна классическая ORM с автоматической генерацией запросов, лучше посмотреть в сторону SeaORM, которая, к слову, построена поверх того же SQLx.
В целом, проект выглядит очень зрелым. У него более 17 тысяч звезд на GitHub и активное сообщество. Если вы начинаете новый бэкенд на Rust и не боитесь писать SQL руками — это, пожалуй, лучший выбор на сегодняшний день.
