# Rust та SQLx у 2026: Запити з Перевіркою під час Компіляції та Питання на Співбесідах > Повний посібник з використання SQLx у Rust - перевірка запитів під час компіляції, порівняння з ORM та практичні питання для технічних співбесід. - Published: 2026-09-12 - Updated: 2026-09-12 - Author: Anthony Fillion-Maillet - Tags: rust, sqlx, database, postgresql, tutorial - Reading time: 12 min --- Бібліотека SQLx революціонізувала спосіб, яким розробники Rust взаємодіють з базами даних. На відміну від традиційних ORM, SQLx пропонує унікальний підхід: сирі SQL-запити з перевіркою під час компіляції. Ця комбінація забезпечує типобезпеку без жертвування продуктивністю або контролем над згенерованим SQL. > SQLx 0.8+ підтримує PostgreSQL, MySQL, MariaDB та SQLite з повною асинхронною підтримкою через tokio або async-std. Перевірка запитів під час компіляції виявляє синтаксичні помилки SQL та невідповідності типів до запуску застосунку. ## Чому SQLx Замість Традиційних ORM Екосистема Rust пропонує кілька варіантів доступу до баз даних: Diesel як повнофункціональний ORM, SeaORM для підходу ActiveRecord та SQLx для сирого SQL з перевіркою типів. Кожен інструмент служить різним цілям. Diesel генерує SQL з типобезпечних конструкторів запитів. Цей підхід добре працює для стандартних CRUD-операцій, але стає громіздким при складних запитах, що вимагають специфічних функцій бази даних. SQLx приймає іншу філософію - розробники пишуть справжні SQL-запити, які компілятор перевіряє на відповідність схемі бази даних. ```rust use sqlx::postgres::PgPoolOptions; use sqlx::FromRow; #[derive(Debug, FromRow)] struct User { id: i32, email: String, created_at: chrono::DateTime, } async fn fetch_users(pool: &sqlx::PgPool) -> Result, sqlx::Error> { sqlx::query_as::<_, User>( "SELECT id, email, created_at FROM users WHERE active = $1" ) .bind(true) .fetch_all(pool) .await } ``` Макрос `query_as` перевіряє, що стовпці `id`, `email` та `created_at` існують у таблиці users і що їхні типи відповідають полям структури. Якщо схема бази даних зміниться, компіляція завершиться невдачею з чіткими повідомленнями про помилки. ## Налаштування Перевірки під час Компіляції Перевірка запитів під час компіляції вимагає з'єднання з базою даних під час процесу збірки. SQLx керує цим через змінну середовища `DATABASE_URL` або файли `.env`. ```bash # .env DATABASE_URL=postgres://user:password@localhost/myapp_dev ``` Для CI-середовищ, де з'єднання з базою даних недоступне, SQLx надає офлайн-режим. Команда `cargo sqlx prepare` генерує метадані запитів, які комітяться до репозиторію. ```bash cargo install sqlx-cli cargo sqlx prepare ``` Ця команда створює директорію `.sqlx`, що містить скомпільовану інформацію про запити. Коли `DATABASE_URL` не встановлено, SQLx використовує ці закешовані метадані. ```rust // Примусова офлайн-перевірка навіть коли база даних доступна #[sqlx::test] async fn test_user_query(pool: PgPool) { let users = sqlx::query_as!( User, r#"SELECT id, email, created_at FROM users LIMIT 10"# ) .fetch_all(&pool) .await .unwrap(); assert!(users.len() <= 10); } ``` ## Розширені Патерни Запитів Продакшн-застосунки вимагають більше, ніж прості CRUD-операції. SQLx підтримує складні сценарії, включаючи динамічні запити, транзакції та пакетні операції. ### Динамічні Запити з Опціональними Фільтрами ```rust use sqlx::QueryBuilder; async fn search_users( pool: &PgPool, email_filter: Option<&str>, min_age: Option, limit: i64, ) -> Result, sqlx::Error> { let mut builder = QueryBuilder::new( "SELECT id, email, created_at FROM users WHERE 1=1" ); if let Some(email) = email_filter { builder.push(" AND email ILIKE "); builder.push_bind(format!("%{}%", email)); } if let Some(age) = min_age { builder.push(" AND age >= "); builder.push_bind(age); } builder.push(" LIMIT "); builder.push_bind(limit); builder .build_query_as::() .fetch_all(pool) .await } ``` ### Транзакції з Обробкою Відкату ```rust async fn transfer_funds( pool: &PgPool, from_account: i32, to_account: i32, amount: rust_decimal::Decimal, ) -> Result<(), sqlx::Error> { let mut tx = pool.begin().await?; // Списати з вихідного рахунку let result = sqlx::query!( r#"UPDATE accounts SET balance = balance - $1 WHERE id = $2 AND balance >= $1"#, amount, from_account ) .execute(&mut *tx) .await?; if result.rows_affected() == 0 { return Err(sqlx::Error::RowNotFound); } // Зарахувати на цільовий рахунок sqlx::query!( "UPDATE accounts SET balance = balance + $1 WHERE id = $2", amount, to_account ) .execute(&mut *tx) .await?; tx.commit().await?; Ok(()) } ``` ### Пакетне Вставлення ```rust async fn bulk_insert_users( pool: &PgPool, users: &[(String, String)], ) -> Result<(), sqlx::Error> { let mut builder = QueryBuilder::new( "INSERT INTO users (email, name) " ); builder.push_values(users, |mut b, (email, name)| { b.push_bind(email).push_bind(name); }); builder.build().execute(pool).await?; Ok(()) } ``` ## Міграції та Керування Схемою CLI SQLx надає вбудоване керування міграціями, яке безшовно інтегрується з перевіркою під час компіляції. ```bash # Створити нову міграцію sqlx migrate add create_users_table # Запустити очікуючі міграції sqlx migrate run # Відкатити останню міграцію sqlx migrate revert ``` Файли міграцій використовують нумерацію часових міток для впорядкування: ```sql -- migrations/20260915120000_create_users_table.sql CREATE TABLE users ( id SERIAL PRIMARY KEY, email VARCHAR(255) NOT NULL UNIQUE, name VARCHAR(255) NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_users_email ON users(email); ``` Для складних застосунків міграції можуть містити як up, так і down файли: ```sql -- migrations/20260915120000_create_users_table.up.sql CREATE TABLE users (...); -- migrations/20260915120000_create_users_table.down.sql DROP TABLE users; ``` ## Пул З'єднань та Продакшн-Конфігурація Продакшн-розгортання вимагають належної конфігурації пулу з'єднань, обробки таймаутів та моніторингу. ```rust use sqlx::postgres::{PgPoolOptions, PgConnectOptions}; use std::time::Duration; async fn create_production_pool() -> Result { let options = PgConnectOptions::new() .host("db.example.com") .database("production") .username("app_user") .password("secure_password") .ssl_mode(sqlx::postgres::PgSslMode::Require); PgPoolOptions::new() .max_connections(20) .min_connections(5) .acquire_timeout(Duration::from_secs(5)) .idle_timeout(Duration::from_secs(600)) .max_lifetime(Duration::from_secs(1800)) .connect_with(options) .await } ``` Розмір пулу з'єднань залежить від кількох факторів: кількості ядер CPU, характеристик навантаження на базу даних та лімітів ресурсів. Типова відправна точка - 2-3 з'єднання на ядро CPU з коригуванням на основі метрик. ## Обробка Помилок та Стійкість Продакшн-застосунки баз даних мають елегантно обробляти різні сценарії збоїв. ```rust use sqlx::Error as SqlxError; #[derive(Debug, thiserror::Error)] enum DatabaseError { #[error("Record not found")] NotFound, #[error("Duplicate entry: {0}")] Duplicate(String), #[error("Connection failed: {0}")] Connection(String), #[error("Query error: {0}")] Query(String), } impl From for DatabaseError { fn from(err: SqlxError) -> Self { match err { SqlxError::RowNotFound => DatabaseError::NotFound, SqlxError::Database(db_err) => { if let Some(code) = db_err.code() { if code == "23505" { return DatabaseError::Duplicate( db_err.message().to_string() ); } } DatabaseError::Query(db_err.message().to_string()) } SqlxError::PoolTimedOut => DatabaseError::Connection( "Connection pool timeout".to_string() ), _ => DatabaseError::Query(err.to_string()), } } } ``` ## Питання на Співбесідах з SQLx Технічні співбесіди часто досліджують розуміння взаємодії з базами даних, типобезпеки та специфічних особливостей SQLx. **Питання: Як SQLx досягає верифікації запитів під час компіляції без процедурних макросів, що модифікують рантайм?** SQLx використовує процедурні макроси, які з'єднуються з базою даних під час компіляції. Макрос `query!` парсить SQL-рядок, надсилає його до бази даних для валідації та генерує Rust-код, що мапить типи результатів. Ця генерація коду відбувається повністю під час компіляції - рантайм виконує лише підготовлені оператори. **Питання: Які компроміси між SQLx та Diesel для продакшн-застосунків?** Diesel забезпечує ергономічніше побудування запитів та кращу підтримку композиції запитів через систему типів. SQLx пропонує прямий доступ до SQL та кращу підтримку специфічних функцій бази даних. Diesel вимагає менше шаблонного коду для стандартних CRUD-операцій, тоді як SQLx надає більший контроль для складних запитів та оптимізації продуктивності. **Питання: Як обробляти nullable значення в SQLx з належною типобезпекою?** ```rust #[derive(FromRow)] struct UserProfile { id: i32, email: String, bio: Option, // Nullable в базі даних } // Запит автоматично обробляє null let profile = sqlx::query_as!( UserProfile, "SELECT id, email, bio FROM user_profiles WHERE id = $1", user_id ) .fetch_one(pool) .await?; ``` **Питання: Поясніть різницю між `fetch_one`, `fetch_optional` та `fetch_all` у SQLx.** `fetch_one` очікує рівно один рядок і повертає помилку, якщо запит повертає нуль або багато рядків. `fetch_optional` повертає `Option`, обробляючи випадки, коли рядок може не існувати. `fetch_all` повертає `Vec` для запитів з множинними результатами. Вибір правильного методу fetch запобігає помилкам рантайму та виражає намір у системі типів. **Питання: Як реалізувати пагінацію з SQLx?** ```rust async fn paginated_users( pool: &PgPool, page: i64, per_page: i64, ) -> Result<(Vec, i64), sqlx::Error> { let offset = (page - 1) * per_page; let users = sqlx::query_as!( User, r#"SELECT id, email, created_at FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2"#, per_page, offset ) .fetch_all(pool) .await?; let total: (i64,) = sqlx::query_as( "SELECT COUNT(*) FROM users" ) .fetch_one(pool) .await?; Ok((users, total.0)) } ``` ## Порівняння Продуктивності та Бенчмарки Продуктивність SQLx конкурує з сирими драйверами баз даних, оскільки не додає накладних витрат абстракції в рантаймі. Система типів та генерація коду під час компіляції усувають витрати на рефлексію, присутні в багатьох ORM. Для застосунків з інтенсивним читанням пули з'єднань та правильне використання підготовлених операторів мають більший вплив, ніж вибір бібліотеки. Офлайн-режим SQLx дозволяє повністю пропустити парсинг запитів у рантаймі. ```rust // Підготовлений оператор - парситься один раз, виконується багато разів let stmt = sqlx::query!("SELECT id, email FROM users WHERE active = $1"); for _ in 0..1000 { let users = stmt.clone().bind(true).fetch_all(pool).await?; } ``` ## Підсумок SQLx представляє прагматичний підхід до доступу до баз даних у Rust. Перевірка запитів під час компіляції виявляє помилки рано без втрати контролю над SQL. Асинхронна підтримка природно інтегрується з асинхронною екосистемою Rust, а CLI-інструментарій спрощує керування міграціями. Для команд, які комфортно працюють з SQL і хочуть типобезпеки Rust, SQLx надає найкраще з обох світів. Система типів гарантує коректність під час компіляції, тоді як сирий SQL зберігає повний доступ до можливостей бази даних. Розуміння патернів, представлених у цьому посібнику, готує розробників як до практичного використання, так і до технічних дискусій на співбесідах щодо архітектури баз даних та специфічних виборів для Rust. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/rust/rust-sqlx-compile-time-queries-tutorial