Работа с базами данных в Rust с помощью Diesel

от автора

Привет, Хабр!

Сегодня мы поговорим о Diesel ORM — инструменте, который превращает работу с базами данных в Rust в настоящее удовольствие. Diesel ORM — это расширяемый и безопасный объектно-реляционный маппер и конструктор запросов для Rust. Он имеет высокоуровневый API для взаимодействия с различными СУБД: PostgreSQL, MySQL и SQLite.

Начнем с установки и настройки!

Установка / настройка

Создаем новый проект на Rust:

cargo new my_diesel_project cd my_diesel_project

Открываем файлик Cargo.toml проекта и добавляем зависимости для Diesel и необходимых библиотек:

[dependencies] diesel = { version = "2.0.0", features = ["postgres"] } dotenv = "0.15"

Файл указывает Cargo установить Diesel ORM с поддержкой PostgreSQL (выбрали постгрес для примера), а также библиотеку dotenv. Также допустим то, что Postqre уже установлен.

Для работы с Diesel также нужен Diesel CLI:

sudo apt-get update sudo apt-get install postgresql postgresql-contrib

Это установит Diesel CLI с поддержкой PostgreSQL. Далее, инициализируем Diesel в проекте:

brew install postgresql brew services start postgresql

Команда создаст файл diesel.toml и папку migrations в проекте. Теперь создаем файл .env в корне вашего проекта и добавляем в него строку подключения к БД:

sudo -u postgres createuser myuser -s sudo -u postgres createdb mydatabase

Теперь создаем файлик src/schema.rs для хранения схемы БД:

touch src/schema.rs

В main.rs подключаем библиотеки Diesel и dotenv:

#[macro_use] extern crate diesel; extern crate dotenv;  pub mod schema; pub mod models;  use diesel::prelude::*; use dotenv::dotenv; use std::env;  pub fn establish_connection() -> PgConnection {     dotenv().ok();      let database_url = env::var("DATABASE_URL")         .expect("DATABASE_URL must be set");     PgConnection::establish(&database_url)         .expect(&format!("Error connecting to {}", database_url)) }

Так мы загрузили переменные окружения из файла .env, устанавили соединение с базой данных и возвращаем его.

CRUD операции

Создание

Создаем файл src/models.rs и определяем структуру модели данных:

#[derive(Queryable)] pub struct Post {     pub id: i32,     pub title: String,     pub body: String,     pub published: bool, }  #[derive(Insertable)] #[table_name="posts"] pub struct NewPost<'a> {     pub title: &'a str,     pub body: &'a str, }

В src/schema.rs добавляем схему таблицы:

table! {     posts (id) {         id -> Int4,         title -> Varchar,         body -> Text,         published -> Bool,     } }

Для вставки новых данных, в src/lib.rs создадим функцию для вставки новых записей:

use diesel::prelude::*; use diesel::pg::PgConnection; use crate::schema::posts; use crate::models::{Post, NewPost};  pub fn create_post<'a>(conn: &PgConnection, title: &'a str, body: &'a str) -> Post {     let new_post = NewPost {         title,         body,     };      diesel::insert_into(posts::table)         .values(&new_post)         .get_result(conn)         .expect("Error saving new post") }

Чтение

Для чтения данных создадим функцию в src/lib.rs:

pub fn get_posts(conn: &PgConnection) -> Vec<Post> {     use crate::schema::posts::dsl::*;      posts         .load::<Post>(conn)         .expect("Error loading posts") }

Например, фильтрация опубликованных постов и сортировка по ID выглядит так::

pub fn get_published_posts(conn: &PgConnection) -> Vec<Post> {     use crate::schema::posts::dsl::*;      posts         .filter(published.eq(true))         .order(id.desc())         .load::<Post>(conn)         .expect("Error loading published posts") }

Обновление и удаление

Для обновления записи создадим функцию:

pub fn update_post_title(conn: &PgConnection, post_id: i32, new_title: &str) -> Post {     use crate::schema::posts::dsl::{posts, id, title};      diesel::update(posts.find(post_id))         .set(title.eq(new_title))         .get_result::<Post>(conn)         .expect("Error updating post title") }

Для удаления записи есть такая функция:

pub fn delete_post(conn: &PgConnection, post_id: i32) -> usize {     use crate::schema::posts::dsl::posts;      diesel::delete(posts.find(post_id))         .execute(conn)         .expect("Error deleting post") }

Миграции

Миграции в Diesel очень удобны и легки в использовании.

Чтобы создать новую миграцию есть команда migration:

diesel migration generate create_posts

Команда создаст новую миграцию с именем create_posts и создаст две SQL файла: up.sql и down.sql в папке migrations.

В файле up.sql определим SQL команды для создания таблицы. Например, для создания таблицы posts:

CREATE TABLE posts (     id SERIAL PRIMARY KEY,     title VARCHAR NOT NULL,     body TEXT NOT NULL,     published BOOLEAN NOT NULL DEFAULT 'f' );

В файле down.sql определим SQL команды для отката изменений, например, удаления таблицы posts:

DROP TABLE posts;

Для применения миграций используем команду:

diesel migration run

Команда выполнит все миграции, которые еще не были применены, и обновит структуру базы данных.

Если хочется откатить последнюю миграцию есть такая команда:

diesel migration revert

Эта команда выполнит команды из файла down.sql последней миграции и вернет базу данных в предыдущее состояние.

Когда миграции применены, Diesel автоматом обновляет файл src/schema.rs, который содержит описание схемы базы данных в виде Rust кода. Этот файл генерируется Diesel CLI и не должен изменяться вручную.

Обработка ошибок

В Rust тип Result используется для обработки операций, которые могут завершиться ошибкой. Этот тип либо содержит значение успешной операции Ok, либо описание ошибки Err). В Diesel ORM этот механизм используется довольно часто.

Рассмотрим функцию, которая извлекает пост из базы данных по его ID:

use diesel::prelude::*; use crate::models::Post; use crate::schema::posts::dsl::*;  pub fn find_post_by_id(conn: &PgConnection, post_id: i32) -> Result<Post, diesel::result::Error> {     posts.find(post_id).first(conn) } 

Функция возвращает Result<Post, diesel::result::Error>, т.е она либо успешно возвращает объект Post, либо ошибку типа diesel::result::Error.

При вызове этой функции, можно использовать выражение match для обработки возможных ошибок:

fn main() {     let connection = establish_connection();      match find_post_by_id(&connection, 1) {         Ok(post) => println!("Found post: {}", post.title),         Err(err) => println!("Error finding post: {:?}", err),     } }

Так можно обрабатывать ошибки на месте вызова функции.

Тип Option в Rust используется для обработки случаев, когда значение может быть отсутствующим. В контексте БД, это, например, может быть полезно для операций, которые могут не вернуть ни одной строки:

pub fn find_post_by_title(conn: &PgConnection, post_title: &str) -> Option<Post> {     posts.filter(title.eq(post_title)).first(conn).ok() }

Функция возвращает Option<Post>, что означает, что она либо возвращает объект Post, либо None, если пост с указанным заголовком не найден.

Diesel ORM помимо вышеописанных функций автоматом использует параметризованные запросы. Например, напишем функцию, которая выполняет параметризованный запрос для поиска постов по заголовку:

pub fn search_posts_by_title(conn: &PgConnection, search: &str) -> Vec<Post> {     posts.filter(title.like(format!("%{}%", search)))         .load::<Post>(conn)         .expect("Error loading posts") }

Метод filter автоматически использует параметры вместо прямой вставки значений в SQL-запрос, что предотвращает возможность SQL-инъекций.

Когда работаешь с пользовательским вводом, всегда важно проверять и очищать данные. Например:

pub fn create_post(conn: &PgConnection, new_title: &str, new_body: &str) -> Post {     let new_post = NewPost {         title: new_title.trim(),         body: new_body.trim(),     };      diesel::insert_into(posts::table)         .values(&new_post)         .get_result(conn)         .expect("Error saving new post") }

С методом trim() можно удалять лишние пробелы из пользовательского ввода.


Подробнее с Diesel можно ознакомиться здесь.

А с другими языками программирования инструментами можно ознакомиться в рамках практических онлайн-курсов от моих друзей из OTUS. Подробнее в каталоге.


ссылка на оригинал статьи https://habr.com/ru/articles/817777/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *