Введение в работу с базами данных на языке Rust
Язык Rust стремительно набирает популярность в системном программировании, но его возможности выходят далеко за рамки низкоуровневых задач. Благодаря строгой системе типов, безопасности памяти и высокой производительности, Rust становится отличным выбором для создания надёжных серверных приложений, работающих с базами данных. В этой статье мы подробно разберём, как подключаться и выполнять операции с двумя популярными СУБД: реляционной PostgreSQL и документо-ориентированной MongoDB.
Мы рассмотрим основные библиотеки (крейты), такие как diesel для PostgreSQL и официальный драйвер mongodb, а также покажем примеры CRUD-операций на чистом Rust. Статья будет полезна как новичкам, так и опытным разработчикам, желающим расширить свой стек технологий.
1. Работа с PostgreSQL: крейт Diesel
Diesel — это безопасный, расширяемый ORM и конструктор запросов для Rust. Он обеспечивает проверку типов на этапе компиляции, что исключает множество ошибок времени выполнения, связанных с SQL-запросами. Для начала работы необходимо добавить зависимости в файл Cargo.toml.
Установка и настройка
Добавьте следующие крейты:
[dependencies]
diesel = { version = "2.1", features = ["postgres"] }
dotenvy = "0.15"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }
Также установите утилиту командной строки Diesel CLI:
cargo install diesel_cli --no-default-features --features postgres
Создайте файл .env в корне проекта с URL вашей базы данных:
DATABASE_URL=postgres://username:password@localhost/my_database
Выполните команду для настройки миграций:
diesel setup
diesel migration generate create_users
Определение схемы и модели
В файле миграции up.sql напишите SQL для создания таблицы:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
email VARCHAR NOT NULL UNIQUE,
age INTEGER NOT NULL
);
Затем выполните diesel migration run. После этого сгенерируйте схему Rust (файл src/schema.rs) с помощью:
diesel print-schema > src/schema.rs
Теперь определим модель и структуру для вставки данных:
// src/models.rs
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
use crate::schema::users;
#[derive(Queryable, Selectable, Serialize, Deserialize)]
#[diesel(table_name = users)]
pub struct User {
pub id: i32,
pub name: String,
pub email: String,
pub age: i32,
}
#[derive(Insertable, Deserialize)]
#[diesel(table_name = users)]
pub struct NewUser {
pub name: String,
pub email: String,
pub age: i32,
}
CRUD-операции с PostgreSQL
Рассмотрим основные операции. Подключение к БД и создание нового пользователя:
use diesel::prelude::*;
use dotenvy::dotenv;
use std::env;
use crate::models::{NewUser, User};
use crate::schema::users::dsl::*;
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))
}
pub fn create_user(conn: &mut PgConnection, name: &str, email: &str, age: i32) -> User {
let new_user = NewUser {
name: name.to_string(),
email: email.to_string(),
age,
};
diesel::insert_into(users)
.values(&new_user)
.returning(User::as_returning())
.get_result(conn)
.expect("Error saving new user")
}
Чтение всех пользователей:
pub fn get_all_users(conn: &mut PgConnection) -> Vec<User> {
users
.load::<User>(conn)
.expect("Error loading users")
}
Обновление возраста пользователя по ID:
use diesel::associations::HasTable;
pub fn update_user_age(conn: &mut PgConnection, user_id: i32, new_age: i32) -> usize {
diesel::update(users.find(user_id))
.set(age.eq(new_age))
.execute(conn)
.expect("Error updating user")
}
Удаление пользователя:
pub fn delete_user(conn: &mut PgConnection, user_id: i32) -> usize {
diesel::delete(users.find(user_id))
.execute(conn)
.expect("Error deleting user")
}
2. Работа с MongoDB: официальный драйвер
MongoDB — это NoSQL база данных, хранящая данные в формате JSON-подобных документов. Для работы с ней в Rust используется асинхронный драйвер mongodb на базе tokio. Он предоставляет гибкий API для работы с коллекциями.
Установка и подключение
Добавьте зависимости в Cargo.toml:
[dependencies]
mongodb = "2.8"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Пример подключения к локальной MongoDB:
use mongodb::{Client, Collection};
use mongodb::bson::{doc, Document};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct User {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
id: Option<bson::oid::ObjectId>,
name: String,
email: String,
age: i32,
}
#[tokio::main]
async fn main() -> mongodb::error::Result<()> {
let client = Client::with_uri_str("mongodb://localhost:27017").await?;
let database = client.database("my_database");
let collection: Collection<User> = database.collection("users");
Ok(())
}
CRUD-операции с MongoDB
Создание (Insert):
async fn create_user(collection: &Collection<User>, user: User) -> mongodb::error::Result<()> {