Что такое Diesel и зачем он нужен?
Diesel — это мощная, безопасная и расширяемая ORM (Object-Relational Mapping) библиотека для языка программирования Rust. Она предоставляет типобезопасный способ взаимодействия с реляционными базами данных, такими как PostgreSQL, MySQL и SQLite. В отличие от многих других ORM, Diesel не использует магию или рефлексию во время выполнения. Вместо этого он генерирует весь необходимый код на этапе компиляции, что гарантирует, что ваши запросы корректны и не приведут к ошибкам во время выполнения.
Основные причины использовать Diesel:
- Типобезопасность: Ошибки в SQL-запросах (например, неверное имя столбца или несоответствие типов) обнаруживаются на этапе компиляции, а не в production.
- Производительность: Diesel генерирует высокооптимизированный код, который работает на уровне рукописных SQL-запросов.
- Расширяемость: Вы можете легко добавлять поддержку новых типов данных, операторов и функций.
- Миграции: Встроенная система миграций позволяет управлять схемой базы данных как кодом.
- Безопасность: Diesel автоматически экранирует параметры запросов, предотвращая SQL-инъекции.
Diesel идеально подходит для проектов, где важна надежность, производительность и безопасность данных. Он широко используется в веб-приложениях, API-сервисах, инструментах для обработки данных и любых других Rust-проектах, требующих взаимодействия с базами данных.
Установка Diesel
Установка Diesel состоит из двух частей: добавление библиотеки в проект и установка CLI-инструмента для работы с миграциями.
1. Добавление в Cargo.toml
Откройте файл Cargo.toml вашего проекта и добавьте зависимости. Выберите соответствующий бэкенд базы данных (или несколько):
[dependencies]
diesel = { version = "2.1", features = ["postgres"] } # Для PostgreSQL
# diesel = { version = "2.1", features = ["mysql"] } # Для MySQL
# diesel = { version = "2.1", features = ["sqlite"] } # Для SQLite
2. Установка CLI (Command Line Interface)
CLI-инструмент diesel_cli используется для создания и выполнения миграций. Установите его через cargo:
# Для PostgreSQL
cargo install diesel_cli --no-default-features --features postgres
# Для MySQL
cargo install diesel_cli --no-default-features --features mysql
# Для SQLite
cargo install diesel_cli --no-default-features --features sqlite
# Для всех бэкендов
cargo install diesel_cli
3. Настройка подключения
Создайте файл .env в корне проекта для хранения строки подключения:
DATABASE_URL=postgres://username:password@localhost/diesel_demo
Затем инициализируйте Diesel в вашем проекте:
diesel setup
Эта команда создаст файл diesel.toml, папку migrations и попытается создать базу данных, если она не существует.
Быстрый старт с примером кода
Давайте создадим простое приложение для управления пользователями.
1. Создание миграции
Создадим таблицу users:
diesel migration generate create_users
Отредактируйте файлы миграции:
up.sql:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
email VARCHAR NOT NULL UNIQUE,
age INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
down.sql:
DROP TABLE users;
Примените миграцию:
diesel migration run
2. Определение модели
Создайте файл src/models.rs:
use diesel::prelude::*;
use chrono::NaiveDateTime;
#[derive(Queryable, Selectable)]
#[diesel(table_name = crate::schema::users)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct User {
pub id: i32,
pub name: String,
pub email: String,
pub age: i32,
pub created_at: NaiveDateTime,
}
#[derive(Insertable)]
#[diesel(table_name = crate::schema::users)]
pub struct NewUser {
pub name: String,
pub email: String,
pub age: i32,
}
3. Определение схемы
Diesel автоматически генерирует файл схемы. Обычно он находится в src/schema.rs:
// @generated automatically by Diesel CLI.
diesel::table! {
users (id) {
id -> Int4,
name -> Varchar,
email -> Varchar,
age -> Int4,
created_at -> Timestamp,
}
}
4. Основной код
В src/main.rs:
use diesel::prelude::*;
use dotenvy::dotenv;
use std::env;
mod models;
mod schema;
use models::{NewUser, User};
use schema::users;
fn main() {
dotenv().ok();
let database_url = env::var("DATABASE_URL")
.expect("DATABASE_URL must be set");
let mut connection = PgConnection::establish(&database_url)
.expect(&format!("Error connecting to {}", database_url));
// Создание нового пользователя
let new_user = NewUser {
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
age: 30,
};
let inserted_user = diesel::insert_into(users::table)
.values(&new_user)
.get_result::<User>(&mut connection)
.expect("Error inserting user");
println!("Inserted user: {:?}", inserted_user);
// Получение всех пользователей
let all_users = users::table
.load::<User>(&mut connection)
.expect("Error loading users");
for user in all_users {
println!("User: {} ({})", user.name, user.email);
}
}
Основные методы и функции Diesel
1. diesel::insert_into
Что делает: Начинает построение INSERT запроса. Позволяет вставлять одну или несколько записей в таблицу.
Параметры:
table— ссылка на таблицу (например,users::table)
Рабочий пример:
use diesel::insert_into;
let new_user = NewUser { name: "Bob".into(), email: "bob@test.com".into(), age: 25 };
let result = insert_into(users::table)
.values(&new_user)
.execute(&mut connection)?;
2. .get_result / .get_results
Что делает: Выполняет запрос и возвращает одну (get_result) или несколько (get_results) записей. Используется после insert_into, update, delete или select.
Параметры:
connection— изменяемая ссылка на соединение с БД
Рабочий пример:
let inserted_user: User = diesel::insert_into(users::table)
.values(&new_user)
.get_result(&mut connection)?;
let all_users: Vec<User> = users::table
.get_results(&mut connection)?;
3. diesel::update
Что делает: Начинает построение UPDATE запроса. Позволяет обновлять существующие записи.
Параметры:
target— таблица или подзапрос, определяющий, какие записи обновлять
Рабочий пример:
use diesel::update;
let updated_user = update(users::table.find(1))
.set(users::name.eq("Updated Name"))
.get_result::<User>(&mut connection)?;
4. diesel::delete
Что делает: Начинает построение DELETE запроса. Удаляет записи из таблицы.
Параметры:
target— таблица или подзапрос, определяющий, какие записи удалять
Рабочий пример:
use diesel::delete;
let deleted_count = delete(users::table.find(1))
.execute(&mut connection)?;
// Удаление по условию
let deleted = delete(users::table.filter(users::age.lt(18)))
.execute(&mut connection)?;
5. .filter
Что делает: Добавляет условие WHERE к запросу. Позволяет фильтровать записи по заданным критериям.
Параметры:
predicate— выражение, возвращающее булево значение (например,users::age.gt(21))
Рабочий пример:
let adults = users::table
.filter(users::age.ge(18))
.load::<User>(&mut connection)?;
let named_users = users::table
.filter(users::name.eq("Alice"))
.load::<User>(&mut connection)?;
6. .order / .order_by
Что делает: Добавляет сортировку ORDER BY к запросу. Позволяет упорядочить результаты по одному или нескольким столбцам.
Параметры:
column— столбец для сортировки (можно использовать.asc()или.desc())
Рабочий пример:
let sorted_users = users::table
.order(users::age.desc())
.load::<User>(&mut connection)?;
let sorted_by_name = users::table
.order((users::name.asc(), users::age.desc()))
.load::<User>(&mut connection)?;
7. .limit / .offset
Что делает: .limit ограничивает количество возвращаемых записей, .offset пропускает указанное количество записей. Используется для пагинации.
Параметры:
n— целое число (i64)
Рабочий пример:
let first_ten = users::table
.limit(10)
.load::<User>(&mut connection)?;
let next_ten = users::table
.limit(10)
.offset(10)
.load::<User>(&mut connection)?;
8. .select
Что делает: Позволяет выбрать только определенные столбцы из таблицы. По умолчанию Diesel выбирает все столбцы, соответствующие структуре.
Параметры:
columns— кортеж или список столбцов для выборки
Рабочий пример:
let names = users::table
.select(users::name)
.load::&