Crate sea_orm[][src]

Expand description

SeaORM

๐Ÿš An async & dynamic ORM for Rust

crate docs build status

Built with ๐Ÿ”ฅ by ๐ŸŒŠ๐Ÿฆ€๐Ÿš

SeaORM

SeaORM is a relational ORM to help you build light weight and concurrent web services in Rust.

Getting Started Usage Example Rocket Example Discord

Features

  1. Async

    Relying on SQLx, SeaORM is a new library with async support from day 1.

// execute multiple queries in parallel
let cakes_and_fruits: (Vec<cake::Model>, Vec<fruit::Model>) =
    futures::try_join!(Cake::find().all(&db), Fruit::find().all(&db))?;
  1. Dynamic

    Built upon SeaQuery, SeaORM allows you to build complex queries without โ€˜fighting the ORMโ€™.

// build subquery with ease
let cakes_with_filling: Vec<cake::Model> = cake::Entity::find()
    .filter(
        Condition::any().add(
            cake::Column::Id.in_subquery(
                Query::select()
                    .column(cake_filling::Column::CakeId)
                    .from(cake_filling::Entity)
                    .to_owned(),
            ),
        ),
    )
    .all(&db)
    .await?;
  1. Testable

    Use mock connections to write unit tests for your logic.

// Setup mock connection
let db = MockDatabase::new(DbBackend::Postgres)
    .append_query_results(vec![
        vec![
            cake::Model {
                id: 1,
                name: "New York Cheese".to_owned(),
            },
        ],
    ])
    .into_connection();

// Perform your application logic
assert_eq!(
    cake::Entity::find().one(&db).await?,
    Some(cake::Model {
        id: 1,
        name: "New York Cheese".to_owned(),
    })
);

// Compare it against the expected transaction log
assert_eq!(
    db.into_transaction_log(),
    vec![
        Transaction::from_sql_and_values(
            DbBackend::Postgres,
            r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#,
            vec![1u64.into()]
        ),
    ]
);
  1. Service Oriented

    Quickly build services that join, filter, sort and paginate data in APIs.

โ“˜
#[get("/?<page>&<posts_per_page>")]
async fn list(
    conn: Connection<Db>,
    page: Option<usize>,
    per_page: Option<usize>,
) -> Template {
    // Set page number and items per page
    let page = page.unwrap_or(1);
    let per_page = per_page.unwrap_or(10);

    // Setup paginator
    let paginator = Post::find()
        .order_by_asc(post::Column::Id)
        .paginate(&conn, per_page);
    let num_pages = paginator.num_pages().await.unwrap();

    // Fetch paginated posts
    let posts = paginator
        .fetch_page(page - 1)
        .await
        .expect("could not retrieve posts");

    Template::render(
        "index",
        context! {
            page: page,
            per_page: per_page,
            posts: posts,
            num_pages: num_pages,
        },
    )
}

A quick taste of SeaORM

Select

// find all models
let cakes: Vec<cake::Model> = Cake::find().all(db).await?;

// find and filter
let chocolate: Vec<cake::Model> = Cake::find()
    .filter(cake::Column::Name.contains("chocolate"))
    .all(db)
    .await?;

// find one model
let cheese: Option<cake::Model> = Cake::find_by_id(1).one(db).await?;
let cheese: cake::Model = cheese.unwrap();

// find related models (lazy)
let fruits: Vec<fruit::Model> = cheese.find_related(Fruit).all(db).await?;

// find related models (eager)
let cake_with_fruits: Vec<(cake::Model, Vec<fruit::Model>)> =
    Cake::find().find_with_related(Fruit).all(db).await?;

Insert

let apple = fruit::ActiveModel {
    name: Set("Apple".to_owned()),
    ..Default::default() // no need to set primary key
};

let pear = fruit::ActiveModel {
    name: Set("Pear".to_owned()),
    ..Default::default()
};

// insert one
let pear = pear.insert(db).await?;

// insert many
Fruit::insert_many(vec![apple, pear]).exec(db).await?;

Update

use sea_orm::sea_query::{Expr, Value};

let pear: Option<fruit::Model> = Fruit::find_by_id(1).one(db).await?;
let mut pear: fruit::ActiveModel = pear.unwrap().into();

pear.name = Set("Sweet pear".to_owned());

// update one
let pear: fruit::ActiveModel = pear.update(db).await?;

// update many: UPDATE "fruit" SET "cake_id" = NULL WHERE "fruit"."name" LIKE '%Apple%'
Fruit::update_many()
    .col_expr(fruit::Column::CakeId, Expr::value(Value::Int(None)))
    .filter(fruit::Column::Name.contains("Apple"))
    .exec(db)
    .await?;

Save

let banana = fruit::ActiveModel {
    id: Unset(None),
    name: Set("Banana".to_owned()),
    ..Default::default()
};

// create, because primary key `id` is `Unset`
let mut banana = banana.save(db).await?;

banana.name = Set("Banana Mongo".to_owned());

// update, because primary key `id` is `Set`
let banana = banana.save(db).await?;

Delete

let orange: Option<fruit::Model> = Fruit::find_by_id(1).one(db).await?;
let orange: fruit::ActiveModel = orange.unwrap().into();

// delete one
fruit::Entity::delete(orange).exec(db).await?;
// or simply
orange.delete(db).await?;

// delete many: DELETE FROM "fruit" WHERE "fruit"."name" LIKE 'Orange'
fruit::Entity::delete_many()
    .filter(fruit::Column::Name.contains("Orange"))
    .exec(db)
    .await?;

Learn More

  1. Design
  2. Architecture
  3. Compare with Diesel

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Re-exports

pub use entity::*;
pub use error::*;
pub use query::*;
pub use schema::*;
pub use sea_query;
pub use sea_strum;

Modules

Macros

Structs

Enums

Traits

Functions

Type Definitions

Derive Macros