Rust Logosea-orm

sea-orm is an asynchronous, dynamic, and type-safe Object-Relational Mapper (ORM) for Rust. It provides a robust framework for interacting with relational databases, allowing developers to define database schemas and queries using Rust code rather than raw SQL strings. This approach significantly reduces boilerplate, enhances type safety, and improves developer productivity.

Key features of sea-orm include:

1. Asynchronous by Design: Built on Rust's `async`/`await` primitives, sea-orm integrates seamlessly with popular asynchronous runtimes like Tokio.
2. Type Safety: Leverages Rust's strong type system to ensure that database interactions are type-checked at compile time, catching many errors that would otherwise only appear at runtime.
3. Query Builder: Offers a powerful and flexible query builder API that allows constructing complex SQL queries programmatically, supporting operations like filtering, ordering, joining, and aggregation.
4. Database Agnostic: Supports multiple popular relational databases including PostgreSQL, MySQL, SQLite, and Microsoft SQL Server, by using `sqlx` as its underlying database driver.
5. Migrations: Integrates with `sea-orm-migration` to manage schema changes over time in a structured and version-controlled manner.
6. Entity-Relationship Mapping: Maps Rust structs (called "entities") directly to database tables, and allows defining relationships between these entities.
7. Active Record Pattern: Provides an `ActiveModel` for each entity, facilitating Create, Read, Update, and Delete (CRUD) operations on individual records.

Core Concepts:

* Entity: A Rust struct that represents a database table. It typically derives `DeriveEntityModel` and contains fields corresponding to table columns.
* Model: The actual data structure representing a row from the database, derived from the Entity. It's immutable.
* ActiveModel: A mutable version of the `Model` used for creating new records or updating existing ones. It wraps fields in a `Set` enum.
* Column: An enum representing the columns of a table.
* Relation: Defines relationships (e.g., one-to-many, many-to-many) between entities.
* DatabaseConnection: The connection pool to the database.

By abstracting away much of the SQL, sea-orm enables developers to focus more on application logic while still retaining fine-grained control when needed, thanks to its expressive query builder.

Example Code

```rust
// Cargo.toml dependencies (add these to your project):
// [dependencies]
// sea-orm = { version = "0.12", features = ["sqlx-sqlite", "runtime-tokio-native-tls", "macros"] }
// tokio = { version = "1.32", features = ["full"] }

use sea_orm::{
    ActiveModelTrait, ColumnTrait, Database, DatabaseConnection, DbErr, EntityTrait, QueryFilter,
    Set,
};
use sea_orm::prelude::*; // Imports common types like `ModelTrait`

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "post")]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    pub title: String,
    pub text: String,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}

impl ActiveModelBehavior for ActiveModel {}

#[tokio::main]
async fn main() -> Result<(), DbErr> {
    // Establish a database connection (in-memory SQLite for simplicity)
    // In a real application, you'd connect to a persistent database (e.g., PostgreSQL, MySQL)
    // and manage schema with `sea-orm-migration`.
    let db: DatabaseConnection = Database::connect("sqlite::memory:").await?;

    // Manually create table schema for this in-memory example.
    // In a real project, this would be handled by `sea-orm-migration`.
    db.execute_unprepared(
        r#"
        CREATE TABLE IF NOT EXISTS post (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            text TEXT NOT NULL
        );
        "#,
    )
    .await?;

    println!("Database connected and table 'post' created.");

    // --- C: Create a new post ---
    let new_post = ActiveModel {
        title: Set("My First Post".to_owned()),
        text: Set("This is the content of my first post.".to_owned()),
        ..Default::default() // For fields like `id` which are auto-generated
    };
    let post_insert_result = new_post.insert(&db).await?;
    println!("\nCreated post: {:?}", post_insert_result);

    // --- R: Read all posts ---
    let all_posts = Entity::find().all(&db).await?;
    println!("\nAll posts:");
    for p in &all_posts {
        println!("- Id: {}, Title: '{}', Text: '{}'", p.id, p.title, p.text);
    }

    // --- U: Update a post ---
    if let Some(mut post_to_update) = all_posts.into_iter().next() { // Take the first post
        let mut active_model: ActiveModel = post_to_update.into_active_model();
        active_model.title = Set("Updated Title for First Post".to_owned());
        active_model.text = Set("The content has been updated!".to_owned());
        let updated_post = active_model.update(&db).await?;
        println!("\nUpdated post: {:?}", updated_post);

        // Verify update
        let found_updated_post = Entity::find_by_id(updated_post.id).one(&db).await?;
        if let Some(p) = found_updated_post {
            println!("Verified updated post: Id: {}, Title: '{}'", p.id, p.title);
        }
    }

    // --- D: Delete a post ---
    // Let's create another post to demonstrate deletion clearly.
    let another_post = ActiveModel {
        title: Set("Post to Delete".to_owned()),
        text: Set("This post will be deleted.".to_owned()),
        ..Default::default()
    };
    let post_to_delete_id = another_post.insert(&db).await?.id;
    println!("\nCreated post for deletion: id {}", post_to_delete_id);

    // Delete by ID
    let delete_result = Entity::delete_by_id(post_to_delete_id).exec(&db).await?;
    println!("Deleted post with id {}. Rows affected: {}", post_to_delete_id, delete_result.rows_affected);

    // Verify deletion
    let remaining_posts = Entity::find().all(&db).await?;
    println!("\nRemaining posts after deletion:");
    for p in &remaining_posts {
        println!("- Id: {}, Title: '{}'", p.id, p.title);
    }
    
    Ok(())
}
```