En este tutorial, exploraremos dos bibliotecas utilizadas al interactuar con bases de datos relacionales en Rust: Diesel y SQLx.
Este artículo utilizará una base de datos de clase simple con estudiantes para demostrar cada enfoque. Realizaremos operaciones CRUD utilizando Diesel ORM y SQLx.
Lo que cubriremos:
Para completar este tutorial, necesitará un conocimiento práctico de Rust, así como la capacidad de acceder y usar Rust, el sistema de compilación y el administrador de paquetes de Rust’s Cargo, y una instancia de un servidor MySQL. .
¿Qué es Diésel?
Diesel es un ORM que soporta PostgreSQL, MySQL, SQLite. ORM significa mapeo relacional de objetos. Los ORM ayudan a los programadores orientados a objetos a abstraer los detalles de las bases de datos relacionales.
Los ORM vienen con generadores de consultas, por lo que no tiene que preocuparse por escribir consultas SQL sin formato. Mediante el uso de ORM, puede comunicarse con bases de datos relacionales como si estuvieran orientadas a objetos.
Para los desarrolladores menos experimentados, el uso de ORM puede ser preferible porque los ORM crean consultas SQL optimizadas. Los ORM también lo hacen menos propenso a los ataques de inyección SQL.
¿Qué es SQLx?
A diferencia de Diesel, SQLx no es un ORM. SQLx es una caja asíncrona de Rust SQL que ofrece verificaciones de consultas SQL en tiempo de compilación. Es independiente tanto de la base de datos como del tiempo de ejecución.
SQLx admite la agrupación de conexiones, el desarrollo multiplataforma, la agrupación anidada, las notificaciones asincrónicas, la seguridad de la capa de transporte y otras características excelentes. Al usar SQLx, debe crear las consultas SQL y las migraciones usted mismo.
Habiendo arañado la superficie, exploremos cómo interactuar con bases de datos relacionales con Diesel y SQLx.
Primeros pasos con Diesel ORM
Los siguientes pasos muestran cómo configurar un proyecto de Rust con Cargo que usa Diesel ORM.
Inicialización de un nuevo proyecto con Diesel ORM
El primer paso es inicializar el proyecto ejecutando el siguiente comando:
cargo new -- lib classroom_diesel cd classroom_diesel
En el código anterior, configuramos el proyecto y lo llamamos class_diesel. El nuevo directorio del proyecto debería verse así:
./ │ ├── src/ │ └── lib.rs │ ├── .gitignore └── Cargo.toml
También necesitamos actualizar el archivo Cargo.toml con las dependencias que necesitamos en el proyecto, así:
[dependencies] diesel = { version = "1.4.4", features = ["mysql"] } dotenv = "0.15.0"
La dependencia dotenv nos ayuda a administrar las variables de entorno en el proyecto.
Instalación de la CLI diésel
Diesel usa una herramienta CLI separada. Es un binario independiente; no necesitamos agregarlo como una dependencia en el archivo cargo.toml. Simplemente instálelo con el siguiente comando:
cargo install diesel_cli
Configurando nuestro entorno Diesel
Necesitamos definir una variable DATABASE_URL en nuestro entorno. Así es como Diesel sabe a qué base de datos MySQL conectarse:
Más artículos interesantes de LogRocket:
echo DATABASE_URL=mysql://<username>:<password>@localhost/<database> > .env
Cambie la cadena de conexión para que coincida con las credenciales de su base de datos local.
El directorio de su proyecto ahora se verá así:
./ │ ├── src/ │ └── lib.rs │ ├── .env ├── .gitignore └── Cargo.toml
Ahora ejecute el siguiente comando:
diesel setup
Este comando nos ayudará a configurar la base de datos y crear un directorio de migraciones vacío para administrar el esquema de la base de datos.
Implementación de migraciones Diesel
Las migraciones ayudan al ORM a realizar un seguimiento de las operaciones de la base de datos, como agregar un campo o eliminar una tabla. Puede pensar en ellos como un sistema de control de versiones para su base de datos.
Comencemos creando migraciones para la aplicación de clase usando Diesel CLI. Idealmente, deberíamos tener una tabla que contenga datos sobre los estudiantes de la clase.
Necesitamos crear archivos de migración vacíos y luego llenarlos con SQL para crear una tabla.
diesel migration generate create_students
Su árbol de archivos se verá así:
./ │ ├── migrations/ │ │ │ ├── 2022-07-04-062521_create_students/ │ │ ├── down.sql │ │ └── up.sql │ │ │ └── .gitkeep │ ├── src/ │ └── lib.rs │ ├── .env ├── .gitignore ├── Cargo.toml └── diesel.toml
El archivo up.sql se usa para crear una migración, mientras que el archivo down.sql se usa para revertirla.
Actualice el archivo up.sql con el SQL para la migración:
sql CREATE TABLE students ( id INTEGER AUTO_INCREMENT PRIMARY KEY, firstname VARCHAR(255) NOT NULL, lastname TEXT NOT NULL, age INTEGER NOT NULL );
Edite el archivo down.sql con SQL que puede revertir la migración:
sql DROP TABLE students;
Después de crear las migraciones ascendentes y descendentes, debemos ejecutar el SQL en la base de datos:
diesel migration run
Podemos comenzar a escribir Rust para realizar consultas en la tabla.
Creando filas con Diesel ORM
Escribamos algo de código para establecer una conexión con el servidor MySQL utilizando la cadena de conexión definida en el archivo .env.
#[macro_use] extern crate diesel; extern crate dotenv; pub mod models; pub mod schema; use diesel::prelude::*; use dotenv::dotenv; use std::env; pub fn create_connection() -> MysqlConnection { dotenv().ok(); let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); MysqlConnection::establish(&database_url) .unwrap_or_else(|_| panic!("Error connecting to {}", database_url)) }
A continuación, necesitamos escribir un modelo para la tabla de estudiantes. Los modelos son donde tiene lugar el mapeo relacional de objetos. La plantilla generará el código necesario para convertir una o más filas de la tabla de Estudiantes a la estructura de Estudiantes en Rust.
cd ./src touch model.rs
En el nuevo archivo model.rs que acabamos de crear, agregue lo siguiente:
use super::schema::students; #[derive(Queryable)] pub struct Student { pub id: i32, pub firstname: String, pub lastname: String, pub age: i32, } #[derive(Insertable)] #[table_name = "students"] pub struct NewStudent<'a> { pub firstname: &'a str, pub lastname: &'a str, pub age: &'a i32, }
Con este patrón, la información de la tabla Students se asignará a la estructura Student correspondiente en Rust. La carpeta src ahora debería verse así:
src/ ├── lib.rs ├── models.rs └── schema.rs
Ahora podemos escribir un script para agregar un estudiante:
cd src mkdir bin cd bin touch create_students.rs
En el archivo create_students.rs, podemos invocar los patrones y funciones escritos anteriormente para crear un nuevo estudiante:
extern crate classroom_diesel; extern crate diesel; use self::classroom_diesel::*; fn main() { let connection = create_connection(); let firstname = "John"; let lastname = "Doe"; let age: i32 = 64; let student = create_post(&connection, firstname, lastname, &age); println!( "Saved student {} with id {}", student.firstname, student.id ); }
La estructura del proyecto ahora se verá así:
./ │ ├── migrations/ │ │ │ ├── 2022-07-04-062521_create_students/ │ │ ├── down.sql │ │ └── up.sql │ │ │ └── .gitkeep │ ├── src/ │ │ │ ├── bin/ │ │ └── create_students.rs │ │ │ ├── lib.rs │ ├── models.rs │ └── schema.rs │ ├── .env ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── diesel.toml
Ejecute el nuevo script con el siguiente comando:
cargo run --bin create_students
Como puede ver en la imagen a continuación, el nuevo archivo de estudiante de John se guardó con una ID de 1. Podemos usar esta ID para consultar las bases de datos de Rust, que veremos en la siguiente sección.
Consultando la base de datos de Rust con Diesel ORM
En la sección anterior, vimos cómo escribir en la base de datos de Rust usando Diesel ORM. También es esencial comprender cómo funciona el sondeo o la lectura.
Escribamos un script para consultar a un estudiante cuya identificación es 1. Comience creando un archivo query_students.rs:
cd bin touch query_students.rs
A continuación, en el archivo query_students.rs que acabamos de crear, agregue lo siguiente:
extern crate classroom_diesel; extern crate diesel; use self::models::*; use classroom_diesel::*; use diesel::prelude::*; fn main() { use self::schema::students::dsl::*; let connection = create_connection(); let result = students .filter(id.eq(1)) .load::<Student>(&connection) .expect("Error loading students"); println!( "Student: {} {} {} years", result[0].firstname, result[0].lastname, result[0].age ); }
Ejecute el script:
cargo run --bin query_students
Como puede ver en la imagen a continuación, el resultado es una línea impresa que contiene el nombre, el apellido y la edad del registro del estudiante que consultamos en la base de datos:
Primeros pasos con SQLx
Ahora que sabemos cómo crear un proyecto que use Diesel ORM para interactuar con bases de datos en Rust, veamos cómo crear un proyecto que use SQLx en su lugar.
Inicializar un nuevo proyecto con SQLx
Comience ejecutando el siguiente comando:
cargo new classroom_sqlx --bin
A continuación, agregue las dependencias requeridas al archivo cargo.toml:
[dependencies] sqlx = { version = "0.5", features = [ "runtime-async-std-native-tls", "mysql" ] } async-std = { version = "1", features = [ "attributes" ] }
Eso es todo lo que necesita para la configuración. Sencillo, ¿verdad?
Para usar SQLx para interactuar con bases de datos en Rust, todo lo que tenemos que hacer es escribir consultas SQL y código Rust. En la sección Diesel ORM, creamos y leímos un archivo de estudiante; en esta sección escribiremos consultas para actualizar y eliminar un registro.
Use SQLx y Rust para actualizar o eliminar registros de la base de datos
Primero, necesitamos escribir código Rust para conectar SQLx al servidor MySQL:
//main.rs use sqlx::mysql::MySqlPoolOptions; #[async_std::main] async fn main() -> Result<(), sqlx::Error> { let pool = MySqlPoolOptions::new() .max_connections(7) .connect("mysql://root:@localhost/classroom_diesel") .await?; Ok(()) }
SQLx admite consultas SQL preparadas y no preparadas. Las declaraciones SQL preparadas se oponen a la inyección SQL.
Veamos cómo actualizar el nombre y apellido de un registro con una clave principal de 1:
use sqlx::mysql::MySqlPoolOptions; #[async_std::main] async fn main() -> Result<(), sqlx::Error> { let pool = MySqlPoolOptions::new() .max_connections(5) .connect("mysql://root:@localhost/classroom_diesel") .await?; sqlx::query("UPDATE students SET firstname=?, lastname=? WHERE id=?") .bind("Richard") .bind("Roe") .bind(1) .execute(&pool) .await?; Ok(()) }
Ejecute el script con el siguiente comando:
cargo run
Eliminar el registro también sigue un patrón similar; la única diferencia es la consulta SQL:
use sqlx::mysql::MySqlPoolOptions; #[async_std::main] async fn main() -> Result<(), sqlx::Error> { let pool = MySqlPoolOptions::new() .max_connections(5) .connect("mysql://root:@localhost/classroom_diesel") .await?; sqlx::query("DELETE FROM students WHERE id=?") .bind(1) .execute(&pool) .await?; Ok(()) }
Ejecute el script con el siguiente comando:
cargo run
Ahora puede interactuar con bases de datos en Rust usando Diesel o SQLx.
Conclusión
Los ORM como Diesel son adecuados; lo ayudan a generar parte del SQL que necesita. La mayoría de las veces, solo necesita lo adecuado en sus aplicaciones.
Sin embargo, puede requerir más «magia», en otras palabras, su tiempo y esfuerzo, en aplicaciones más grandes para que los ORM funcionen correctamente y generen consultas SQL.
Si es necesario crear consultas más complejas con requisitos de alto volumen y baja latencia, puede ser mejor usar bibliotecas como SQLx para ejecutar consultas SQL sin formato.
LogRocket: visibilidad completa de las aplicaciones Rust de producción
La depuración de aplicaciones de Rust puede ser un desafío, especialmente cuando los usuarios encuentran problemas que son difíciles de reproducir. Si está interesado en monitorear y rastrear el rendimiento de sus aplicaciones Rust, detectar errores automáticamente y rastrear solicitudes de red lentas y tiempos de carga, pruebe LogRocket.
LogRocket es como un DVR para aplicaciones web y móviles, literalmente graba todo lo que sucede en su aplicación Rust. En lugar de adivinar por qué ocurren los problemas, puede agregar e informar el estado en el que se encontraba su aplicación cuando ocurrió un problema. LogRocket también supervisa el rendimiento de su aplicación, informando métricas como la carga de la CPU del cliente, el uso de la memoria del cliente y más.
Modernice la forma en que depura sus aplicaciones de Rust: comience a monitorear de forma gratuita.