Skip to main content

61 posts tagged with "news"

View All Tags

SeaORM FAQ.01

· 2 min read
Chris Tsang
SeaQL Team

FAQ.01 Why SeaORM does not nest objects for parent-child relation?

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

Consider the above API, Cake and Fruit are two separate models.

If you come from a dynamic language, you'd probably used to:

struct Cake {
id: u64,
fruit: Fruit,
..
}

It's so convenient that you can simply:

let cake = Cake::find().one(db).await?;
println!("Fruit = {}", cake.fruit.name);

Sweet right? Okay so, the problem with this pattern is that it does not fit well with Rust.

Let's look at this playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6fb0a981189ace081fbb2aa04f50146b

struct Parent {
a: u64,
child: Option<Child>,
}

struct ParentWithBox {
a: u64,
child: Option<Box<Child>>,
}

struct Child {
a: u64,
b: u64,
c: u64,
d: u64,
}

fn main() {
dbg!(std::mem::size_of::<Parent>());
dbg!(std::mem::size_of::<ParentWithBox>());
dbg!(std::mem::size_of::<Child>());
}

What's the output you guess?

[src/main.rs:21] std::mem::size_of::<Parent>() = 48
[src/main.rs:22] std::mem::size_of::<ParentWithBox>() = 16
[src/main.rs:23] std::mem::size_of::<Child>() = 32

In dynamic languages, objects are always held by pointers, and that maps to a Box in Rust. In Rust, we don't put objects in Boxes by default, because it forces the object to be allocated on the heap. And that is an extra cost! Because objects are always first constructed on the stack and then being copied over to the heap.

Ref:

  1. https://users.rust-lang.org/t/how-to-create-large-objects-directly-in-heap/26405
  2. https://github.com/PoignardAzur/placement-by-return/blob/placement-by-return/text/0000-placement-by-return.md

We face the dilemma where we either put the object on the stack and waste some space (it takes up 48 bytes no matter child is None or not) or put the object in a box and waste some cycles.

If you are new to Rust, all these might be unfamiliar, but a Rust programmer has to consciously make decisions over memory management, and we don't want to make decisions on behalf of our users.

That said, there were proposals to add API with this style to SeaORM, and we might implement that in the future. Hopefully this would shed some light on the matter meanwhile.

Introducing StarfishQL

· 12 min read
SeaQL Team
Chris Tsang

We are pleased to introduce StarfishQL to the Rust community today. StarfishQL is a graph database and query engine to enable graph analysis and visualization on the web. It is an experimental project, with its primary purpose to explore the dependency network of Rust crates published on crates.io.

Motivation

StarfishQL is a framework for providing a graph database and a graph query engine that interacts with it.

A concrete example (Freeport) involving the graph of crate dependency on crates.io is used for illustration. With this example, you can see StarfishQL in action.

At the end of the day, we're interested in performing graph analysis, that is to extract meaningful information out of plain graph data. To achieve that, we believe that visualization is a crucial aid.

StarfishQL's query engine is designed to be able to incorporate different forms of visualization by using a flexible query language. However, the development of the project has been centred around the following, as showcased in our demo apps.

Traverse the dependency graph in the normal direction starting from the N most connected nodes.

Traverse the dependency tree in both forward and reverse directions starting from a particular node.

Design

In general, a query engine takes input queries written in a specific query language (e.g. SQL statements), performs the necessary operations in the database, and then outputs the data of interest to the user application. You may also view a query engine as an abstraction layer such that the user can design queries simply in the supported query language and let the query engine do the rest.

In the case of a graph query engine, the output data is a graph (wiki).

Graph query engine overview

In the case of StarfishQL, the query language is a custom language we defined in the JSON format, which enables the engine to be highly accessible and portable.

Implementation

In the example of Freeport, StarfishQL consists of the following three components.

Graph Query Engine

As a core component of StarfishQL, the graph query engine is a Rust backend application powered by the Rocket web framework and the SeaQL ecosystem.

The engine listens at the following endpoints for the corresponding operation:

You could also invoke the endpoints above programmatically.

Graph data are stored in a relational database:

  • Metadata - Definition of each entity and relation, e.g. attributes of crates and dependency
  • Node Data - An instance of an entity, e.g. crate name and version number
  • Edge Data - An instance of a relation, e.g. one crate depends on another

crates.io Crawler

To obtain the crate data to insert into the database, we used a fast, non-disruptive crawler on a local clone of the public index repo of crates.io.

Graph Visualization

We used d3.js to create force-directed graphs to display the results. The two colourful graphs above are such products.

Findings

Here are some interesting findings we made during the process.

Top-10 Dependencies

List of top 10 crates order by different decay modes.

Decay Mode: Immediate / Simple Connectivity
crateconnectivity
serde17,441
serde_json10,528
log9,220
clap6,323
thiserror5,547
rand5,340
futures5,263
lazy_static5,211
tokio5,168
chrono4,794
Decay Mode: Medium (.5) / Complex Connectivity
crateconnectivity
quote4,126
syn4,069
pure-rust-locales4,067
reqwest3,950
proc-macro23,743
num_threads3,555
value-bag3,506
futures-macro3,455
time-macros3,450
thiserror-impl3,416
Decay Mode: None / Compound Connectivity
crateconnectivity
unicode-xid54,982
proc-macro254,949
quote54,910
syn54,744
rustc-std-workspace-core51,650
libc51,645
serde_derive51,056
serde51,054
jobserver50,567
cc50,566

If we look at Decay Mode: Immediate, where the connectivity is simply the number of immediate dependants, we can see that serde and serde_json are at the top. I guess that supports our decision of defining the query language in JSON.

Decay Mode: None tells another interesting story: when the connectivity is the entire tree of dependants, we are looking at the really core crates that are nested somewhere deeply inside the most crates. In other words, these are the ones that are built along with the most crates. Under this setting, the utility crates that interacts with the low-level, more fundamental aspects of Rust are ranked higher,like quote with syntax trees, proc-macro2 with procedural macros, and unicode-xid with Unicode checking.

Number of crates without Dependencies

19,369 out of 79,972 crates, or 24% of the crates, do not depend on any crates.

e.g.  aa-a0,  ...,  zyx_testzz-bufferz_table

In other words, about 76% of the crates are standing on the shoulders of giants! 💪

Number of crates without Dependants

53,910 out of 79,972 crates, or 67% of the crates, have no dependants, i.e. no other crates depend on them.

e.g.  aa-a-bot,  ...,  zzp-toolszzzz_table

We imagine many of those crates are binaries/executables, if only we could figure out a way to check that... 🤔

As of March 30, 2022

Conclusion

StarfishQL allows flexible and portable definition, manipulation, retrieval, and visualization of graph data.

The graph query engine built in Rust provides a nice interface for any web applications to access data in the relational graph database with stable performance and memory safety.

Admittedly, StarfishQL is still in its infancy, so every detail in the design and implementation is subject to change. Fortunately, the good thing about this is, like all other open-source projects developed by brilliant Rust developers, you can contribute to it if you also find the concept interesting. With its addition to the SeaQL ecosystem, together we are one step closer to the vision of Rust for data engineering.

People

StarfishQL is created by the following SeaQL team members:

Chris Tsang

Billy Chan

Sanford Pun

Contributing

We are super excited to be selected as a Google Summer of Code 2022 mentor organization!

StarfishQL is one of the GSoC project ideas that opens for development proposals. Join us on GSoC 2022 by following the instructions on GSoC Contributing Guide.

What's new in SeaORM 0.7.0

· 8 min read
SeaQL Team
Chris Tsang

🎉 We are pleased to release SeaORM 0.7.0 today! Here are some feature highlights 🌟:

Update ActiveModel by JSON

[#492] If you want to save user input into the database you can easily convert JSON value into ActiveModel.

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "fruit")]
pub struct Model {
#[sea_orm(primary_key)]
#[serde(skip_deserializing)] // Skip deserializing
pub id: i32,
pub name: String,
pub cake_id: Option<i32>,
}

Set the attributes in ActiveModel with set_from_json method.

// A ActiveModel with primary key set
let mut fruit = fruit::ActiveModel {
id: ActiveValue::Set(1),
name: ActiveValue::NotSet,
cake_id: ActiveValue::NotSet,
};

// Note that this method will not alter the primary key values in ActiveModel
fruit.set_from_json(json!({
"id": 8,
"name": "Apple",
"cake_id": 1,
}))?;

assert_eq!(
fruit,
fruit::ActiveModel {
id: ActiveValue::Set(1),
name: ActiveValue::Set("Apple".to_owned()),
cake_id: ActiveValue::Set(Some(1)),
}
);

Create a new ActiveModel from JSON value with the from_json method.

let fruit = fruit::ActiveModel::from_json(json!({
"name": "Apple",
}))?;

assert_eq!(
fruit,
fruit::ActiveModel {
id: ActiveValue::NotSet,
name: ActiveValue::Set("Apple".to_owned()),
cake_id: ActiveValue::NotSet,
}
);

Proposed by:



qltk

Contributed by:



Billy Chan

Support time crate in Model

[#602] You can define datetime column in Model with time crate. You can migrate your Model originally defined in chrono to time crate.

Model defined in chrono crate.

use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "transaction_log")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub date: Date, // chrono::NaiveDate
pub time: Time, // chrono::NaiveTime
pub date_time: DateTime, // chrono::NaiveDateTime
pub date_time_tz: DateTimeWithTimeZone, // chrono::DateTime<chrono::FixedOffset>
}

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

impl ActiveModelBehavior for ActiveModel {}

Model defined in time crate.

use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "transaction_log")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub date: TimeDate, // time::Date
pub time: TimeTime, // time::Time
pub date_time: TimeDateTime, // time::PrimitiveDateTime
pub date_time_tz: TimeDateTimeWithTimeZone, // time::OffsetDateTime
}

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

impl ActiveModelBehavior for ActiveModel {}

Proposed by:



Tom Hacohen

Contributed by:



Billy Chan

Delete by Primary Key

[#590] Instead of selecting Model from the database then deleting it. You could also delete a row from database directly by its primary key.

let res: DeleteResult = Fruit::delete_by_id(38).exec(db).await?;
assert_eq!(res.rows_affected, 1);

Proposed by:



Shouvik Ghosh

Contributed by:



Zhenwei Guo

Paginate Results from Raw Query

[#617] You can paginate SelectorRaw and fetch Model in batch.

let mut cake_pages = cake::Entity::find()
.from_raw_sql(Statement::from_sql_and_values(
DbBackend::Postgres,
r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" = $1"#,
vec![1.into()],
))
.paginate(db, 50);

while let Some(cakes) = cake_pages.fetch_and_next().await? {
// Do something on cakes: Vec<cake::Model>
}

Proposed by:



Bastian

Contributed by:



shinbunbun

Create Database Index

[#593] To create indexes in database instead of writing IndexCreateStatement manually, you can derive it from Entity using Schema::create_index_from_entity.

use sea_orm::{sea_query, tests_cfg::*, Schema};

let builder = db.get_database_backend();
let schema = Schema::new(builder);

let stmts = schema.create_index_from_entity(indexes::Entity);
assert_eq!(stmts.len(), 2);

let idx = sea_query::Index::create()
.name("idx-indexes-index1_attr")
.table(indexes::Entity)
.col(indexes::Column::Index1Attr)
.to_owned();
assert_eq!(builder.build(&stmts[0]), builder.build(&idx));

let idx = sea_query::Index::create()
.name("idx-indexes-index2_attr")
.table(indexes::Entity)
.col(indexes::Column::Index2Attr)
.to_owned();
assert_eq!(builder.build(&stmts[1]), builder.build(&idx));

Proposed by:



Jochen Görtler

Contributed by:



Nick Burrett

Our GitHub Sponsor profile is up! If you feel generous, a small donation will be greatly appreciated.

A big shout out to our sponsors 😇:

Émile Fugulin

Zachary Vander Velden

Dean Sheather

Shane Sveller

Sakti Dwi Cahyono

Unnamed Sponsor

Community

SeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust's future.

Here is the roadmap for SeaORM 0.8.x.

GSoC 2022

We are super excited to be selected as a Google Summer of Code 2022 mentor organization. Prospective contributors, please visit our GSoC 2022 Organization Profile!

Google Summer of Code 2022

· 2 min read
SeaQL Team
Chris Tsang

GSoC 2022 Organization Profile

We are super excited to be selected as a Google Summer of Code 2022 mentor organization. Thank you everyone in the SeaQL community for your support and adoption!

In 2020, when we were developing systems in Rust, we noticed a missing piece in the ecosystem: an ORM that integrates well with the Rust async ecosystem. With that in mind, we designed SeaORM to have a familiar API that welcomes developers from node.js, Go, Python, PHP, Ruby and your favourite language.

The first piece of tool we released is SeaQuery, a query builder with a fluent API. It has a simplified AST that reflects SQL syntax. It frees you from stitching strings together in case you needed to construct SQL dynamically and safely, with the advantages of Rust typings.

The second piece of tool is SeaSchema, a schema manager that allows you to discover and manipulate database schema. The type definition of the schema is database-specific and thus reflecting the features of MySQL, Postgres and SQLite tightly.

The third piece of tool is SeaORM, an Object Relational Mapper for building web services in Rust, whether it's REST, gRPC or GraphQL. We have "async & dynamic" in mind, so developers from dynamic languages can feel right at home.

But why stops at three?

This is just the foundation to setup Rust to be the best language for data engineering, and we have many more ideas on our idea list!

Your participation is what makes us unique; your adoption is what drives us forward.

Thank you everyone for all your karma, it's the Rust community here that makes it possible. We will gladly take the mission to nurture open source developers during GSoC.

Prospective contributors, stay in touch with us. We also welcome any discussion on the future of the Rust ecosystem and the SeaQL organization.

GSoC 2022 Idea List

What's new in SeaORM 0.6.0

· 7 min read
SeaQL Team
Chris Tsang

🎉 We are pleased to release SeaORM 0.6.0 today! Here are some feature highlights 🌟:

Migration

[#335] Version control you database schema with migrations written in SeaQuery or in raw SQL. View migration docs to learn more.

  1. Setup the migration directory by executing sea-orm-cli migrate init.
    migration
    ├── Cargo.toml
    ├── README.md
    └── src
    ├── lib.rs
    ├── m20220101_000001_create_table.rs
    └── main.rs
  2. Defines the migration in SeaQuery.
    use sea_schema::migration::prelude::*;

    pub struct Migration;

    impl MigrationName for Migration {
    fn name(&self) -> &str {
    "m20220101_000001_create_table"
    }
    }

    #[async_trait::async_trait]
    impl MigrationTrait for Migration {
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
    manager
    .create_table( ... )
    .await
    }

    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
    manager
    .drop_table( ... )
    .await
    }
    }
  3. Apply the migration by executing sea-orm-cli migrate.
    $ sea-orm-cli migrate
    Applying all pending migrations
    Applying migration 'm20220101_000001_create_table'
    Migration 'm20220101_000001_create_table' has been applied

Designed by:



Chris Tsang

Contributed by:



Billy Chan

Support DateTimeUtc & DateTimeLocal in Model

[#489] Represents database's timestamp column in Model with attribute of type DateTimeLocal (chrono::DateTime<Local>) or DateTimeUtc (chrono::DateTime<Utc>).

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "satellite")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub satellite_name: String,
pub launch_date: DateTimeUtc,
pub deployment_date: DateTimeLocal,
}

Proposed by:



lz1998


Chris Tsang

Contributed by:



Charles·Chege


Billy Chan

Mock Join Results

[#455] Constructs mock results of related model with tuple of model.

let db = MockDatabase::new(DbBackend::Postgres)
// Mocking result of cake with its related fruit
.append_query_results(vec![vec![(
cake::Model {
id: 1,
name: "Apple Cake".to_owned(),
},
fruit::Model {
id: 2,
name: "Apple".to_owned(),
cake_id: Some(1),
},
)]])
.into_connection();

assert_eq!(
cake::Entity::find()
.find_also_related(fruit::Entity)
.all(&db)
.await?,
vec![(
cake::Model {
id: 1,
name: "Apple Cake".to_owned(),
},
Some(fruit::Model {
id: 2,
name: "Apple".to_owned(),
cake_id: Some(1),
})
)]
);

Proposed by:



Bastian

Contributed by:



Bastian


Billy Chan

Support Max Connection Lifetime Option

[#493] You can set the maximum lifetime of individual connection with the max_lifetime method.

let mut opt = ConnectOptions::new("protocol://username:password@host/database".to_owned());
opt.max_lifetime(Duration::from_secs(8))
.max_connections(100)
.min_connections(5)
.connect_timeout(Duration::from_secs(8))
.idle_timeout(Duration::from_secs(8))
.sqlx_logging(true);

let db = Database::connect(opt).await?;

Proposed by:



Émile Fugulin

Contributed by:



Billy Chan

SeaORM CLI & Codegen Updates

  • [#433] Generates the column_name macro attribute for column which is not named in snake case
  • [#335] Introduces migration subcommands sea-orm-cli migrate

Proposed by:



Gabriel Paulucci

Contributed by:



Billy Chan

Our GitHub Sponsor profile is up! If you feel generous, a small donation will be greatly appreciated.

A big shout out to our sponsors 😇:

Émile Fugulin

Zachary Vander Velden

Shane Sveller

Sakti Dwi Cahyono

Unnamed Sponsor

Community

SeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust's future.

Here is the roadmap for SeaORM 0.7.x.

What's new in SeaORM 0.5.0

· 6 min read
SeaQL Team
Chris Tsang

🎉 We are pleased to release SeaORM 0.5.0 today! Here are some feature highlights 🌟:

Insert and Update Return Model

[#339] As asked and requested by many of our community members. You can now get the refreshed Model after insert or update operations. If you want to mutate the model and save it back to the database you can convert it into ActiveModel with the method into_active_model.

Breaking Changes:

  • ActiveModel::insert and ActiveModel::update return Model instead of ActiveModel
  • Method ActiveModelBehavior::after_save takes Model as input instead of ActiveModel
// Construct a `ActiveModel`
let active_model = ActiveModel {
name: Set("Classic Vanilla Cake".to_owned()),
..Default::default()
};
// Do insert
let cake: Model = active_model.insert(db).await?;
assert_eq!(
cake,
Model {
id: 1,
name: "Classic Vanilla Cake".to_owned(),
}
);

// Covert `Model` into `ActiveModel`
let mut active_model = cake.into_active_model();
// Change the name of cake
active_model.name = Set("Chocolate Cake".to_owned());
// Do update
let cake: Model = active_model.update(db).await?;
assert_eq!(
cake,
Model {
id: 1,
name: "Chocolate Cake".to_owned(),
}
);

// Do delete
cake.delete(db).await?;

Proposed by:



Julien Nicoulaud


Edgar

Contributed by:



Billy Chan

ActiveValue Revamped

[#340] The ActiveValue is now defined as an enum instead of a struct. The public API of it remains unchanged, except Unset was deprecated and ActiveValue::NotSet should be used instead.

Breaking Changes:

  • Rename method sea_orm::unchanged_active_value_not_intended_for_public_use to sea_orm::Unchanged
  • Rename method ActiveValue::unset to ActiveValue::not_set
  • Rename method ActiveValue::is_unset to ActiveValue::is_not_set
  • PartialEq of ActiveValue will also check the equality of state instead of just checking the equality of value
/// Defines a stateful value used in ActiveModel.
pub enum ActiveValue<V>
where
V: Into<Value>,
{
/// A defined [Value] actively being set
Set(V),
/// A defined [Value] remain unchanged
Unchanged(V),
/// An undefined [Value]
NotSet,
}

Designed by:



Chris Tsang

Contributed by:



Billy Chan

SeaORM CLI & Codegen Updates

Install latest version of sea-orm-cli:

cargo install sea-orm-cli

Updates related to entity files generation (cargo generate entity):

  • [#348] Discovers and defines PostgreSQL enums
  • [#386] Supports SQLite database, you can generate entity files from all supported databases including MySQL, PostgreSQL and SQLite

Proposed by:



Zachary Vander Velden

Contributed by:



Charles·Chege


Billy Chan

Tracing

[#373] You can trace the query executed by SeaORM with debug-print feature enabled and tracing-subscriber up and running.

pub async fn main() {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.with_test_writer()
.init();

// ...
}

Contributed by:

Marco Napetti

Our GitHub Sponsor profile is up! If you feel generous, a small donation will be greatly appreciated.

A big shout out to our sponsors 😇:

Sakti Dwi Cahyono

Shane Sveller

Zachary Vander Velden

Praveen Perera

Unnamed Sponsor

Community

SeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust's future.

Here is the roadmap for SeaORM 0.6.x.

What's new in SeaORM 0.4.0

· 5 min read
SeaQL Team
Chris Tsang

🎉 We are pleased to release SeaORM 0.4.0 today! Here are some feature highlights 🌟:

Rust Edition 2021

[#273] Upgrading SeaORM to Rust Edition 2021 🦀❤🐚!

Contributed by:

Carter Snook

Enumeration

[#252] You can now use Rust enums in model where the values are mapped to a database string, integer or native enum. Learn more here.

#[derive(Debug, Clone, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "active_enum")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
// Use our custom enum in a model
pub category: Option<Category>,
pub color: Option<Color>,
pub tea: Option<Tea>,
}

#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "String", db_type = "String(Some(1))")]
// An enum serialized into database as a string value
pub enum Category {
#[sea_orm(string_value = "B")]
Big,
#[sea_orm(string_value = "S")]
Small,
}

#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "i32", db_type = "Integer")]
// An enum serialized into database as an integer value
pub enum Color {
#[sea_orm(num_value = 0)]
Black,
#[sea_orm(num_value = 1)]
White,
}

#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")]
// An enum serialized into database as a database native enum
pub enum Tea {
#[sea_orm(string_value = "EverydayTea")]
EverydayTea,
#[sea_orm(string_value = "BreakfastTea")]
BreakfastTea,
}

Designed by:



Chris Tsang

Contributed by:



Billy Chan

Supports RETURNING Clause on PostgreSQL

[#183] When performing insert or update operation on ActiveModel against PostgreSQL, RETURNING clause will be used to perform select in a single SQL statement.

// For PostgreSQL
cake::ActiveModel {
name: Set("Apple Pie".to_owned()),
..Default::default()
}
.insert(&postgres_db)
.await?;

assert_eq!(
postgres_db.into_transaction_log(),
vec![Transaction::from_sql_and_values(
DbBackend::Postgres,
r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id", "name""#,
vec!["Apple Pie".into()]
)]);
// For MySQL & SQLite
cake::ActiveModel {
name: Set("Apple Pie".to_owned()),
..Default::default()
}
.insert(&other_db)
.await?;

assert_eq!(
other_db.into_transaction_log(),
vec![
Transaction::from_sql_and_values(
DbBackend::MySql,
r#"INSERT INTO `cake` (`name`) VALUES (?)"#,
vec!["Apple Pie".into()]
),
Transaction::from_sql_and_values(
DbBackend::MySql,
r#"SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = ? LIMIT ?"#,
vec![15.into(), 1u64.into()]
)]);

Proposed by:



Marlon Brandão de Sousa

Contributed by:



Billy Chan

Axum Integration Example

[#297] Added Axum integration example. More examples wanted!

Contributed by:

Yoshiera

Our GitHub Sponsor profile is up! If you feel generous, a small donation will be greatly appreciated.

A big shout out to our first sponsors 😇:

Shane Sveller

Zachary Vander Velden

Unnamed Sponsor

Community

SeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust's future.

Here is the roadmap for SeaORM 0.5.x.

What's new in SeaORM 0.3.0

· 5 min read
SeaQL Team
Chris Tsang

🎉 We are pleased to release SeaORM 0.3.0 today! Here are some feature highlights 🌟:

Transaction

[#222] Use database transaction to perform atomic operations

Two transaction APIs are provided:

  • closure style. Will be committed on Ok and rollback on Err.

    // <Fn, A, B> -> Result<A, B>
    db.transaction::<_, _, DbErr>(|txn| {
    Box::pin(async move {
    bakery::ActiveModel {
    name: Set("SeaSide Bakery".to_owned()),
    ..Default::default()
    }
    .save(txn)
    .await?;

    bakery::ActiveModel {
    name: Set("Top Bakery".to_owned()),
    ..Default::default()
    }
    .save(txn)
    .await?;

    Ok(())
    })
    })
    .await;
  • RAII style. begin the transaction followed by commit or rollback. If txn goes out of scope, it'd automatically rollback.

    let txn = db.begin().await?;

    // do something with txn

    txn.commit().await?;

Contributed by:

Marco Napetti

Chris Tsang

Streaming

[#222] Use async stream on any Select for memory efficiency.

let mut stream = Fruit::find().stream(&db).await?;

while let Some(item) = stream.try_next().await? {
let item: fruit::ActiveModel = item.into();
// do something with item
}

Contributed by:

Marco Napetti

API for custom logic on save & delete

[#210] We redefined the trait methods of ActiveModelBehavior. You can now perform custom validation before and after insert, update, save, delete actions. You can abort an action even after it is done, if you are inside a transaction.

impl ActiveModelBehavior for ActiveModel {
// Override default values
fn new() -> Self {
Self {
serial: Set(Uuid::new_v4()),
..ActiveModelTrait::default()
}
}

// Triggered before insert / update
fn before_save(self, insert: bool) -> Result<Self, DbErr> {
if self.price.as_ref() <= &0.0 {
Err(DbErr::Custom(format!(
"[before_save] Invalid Price, insert: {}",
insert
)))
} else {
Ok(self)
}
}

// Triggered after insert / update
fn after_save(self, insert: bool) -> Result<Self, DbErr> {
Ok(self)
}

// Triggered before delete
fn before_delete(self) -> Result<Self, DbErr> {
Ok(self)
}

// Triggered after delete
fn after_delete(self) -> Result<Self, DbErr> {
Ok(self)
}
}

Contributed by:

Muhannad

Billy Chan

Generate Entity Models That Derive Serialize / Deserialize

[#237] You can use sea-orm-cli to generate entity models that also derive serde Serialize / Deserialize traits.

//! SeaORM Entity. Generated by sea-orm-codegen 0.3.0

use sea_orm::entity::prelude:: * ;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "cake")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(column_type = "Text", nullable)]
pub name: Option<String> ,
}

// ...

Contributed by:

Tim Eggert

Introduce DeriveIntoActiveModel macro & IntoActiveValue Trait

[#240] introduced a new derive macro DeriveIntoActiveModel for implementing IntoActiveModel on structs. This is useful when creating your own struct with only partial fields of a model, for example as a form submission in a REST API.

IntoActiveValue trait allows converting Option<T> into ActiveValue<T> with the method into_active_value.

// Define regular model as usual
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
#[sea_orm(table_name = "users")]
pub struct Model {
pub id: Uuid,
pub created_at: DateTimeWithTimeZone,
pub updated_at: DateTimeWithTimeZone,
pub email: String,
pub password: String,
pub full_name: Option<String>,
pub phone: Option<String>,
}

// Create a new struct with some fields omitted
#[derive(DeriveIntoActiveModel)]
pub struct NewUser {
// id, created_at and updated_at are omitted from this struct,
// and will always be `ActiveValue::unset`
pub email: String,
pub password: String,
// Full name is usually optional, but it can be required here
pub full_name: String,
// Option implements `IntoActiveValue`, and when `None` will be `unset`
pub phone: Option<String>,
}

#[derive(DeriveIntoActiveModel)]
pub struct UpdateUser {
// Option<Option<T>> allows for Some(None) to update the column to be NULL
pub phone: Option<Option<String>>,
}

Contributed by:

Ari Seyhun

Community

SeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust's future.

Here is the roadmap for SeaORM 0.4.x.

What's new in SeaORM 0.2.4

· 3 min read
SeaQL Team
Chris Tsang

🎉 We are pleased to release SeaORM 0.2.4 today! Some feature highlights:

Better ergonomic when working with custom select list

[#208] Use Select::into_values to quickly select a custom column list and destruct as tuple.

use sea_orm::{entity::*, query::*, tests_cfg::cake, DeriveColumn, EnumIter};

#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryAs {
CakeName,
NumOfCakes,
}

let res: Vec<(String, i64)> = cake::Entity::find()
.select_only()
.column_as(cake::Column::Name, QueryAs::CakeName)
.column_as(cake::Column::Id.count(), QueryAs::NumOfCakes)
.group_by(cake::Column::Name)
.into_values::<_, QueryAs>()
.all(&db)
.await?;

assert_eq!(
res,
vec![("Chocolate Forest".to_owned(), 2i64)]
);

Contributed by:

Muhannad

Rename column name & column enum variant

[#209] Rename the column name and enum variant of a model attribute, especially helpful when the column name is a Rust keyword.

mod my_entity {
use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "my_entity")]
pub struct Model {
#[sea_orm(primary_key, enum_name = "IdentityColumn", column_name = "id")]
pub id: i32,
#[sea_orm(column_name = "type")]
pub type_: String,
}

//...
}

assert_eq!(my_entity::Column::IdentityColumn.to_string().as_str(), "id");
assert_eq!(my_entity::Column::Type.to_string().as_str(), "type");

Contributed by:

Billy Chan

not on a condition tree

[#145] Build a complex condition tree with Condition.

use sea_orm::{entity::*, query::*, tests_cfg::cake, sea_query::Expr, DbBackend};

assert_eq!(
cake::Entity::find()
.filter(
Condition::all()
.add(
Condition::all()
.not()
.add(Expr::val(1).eq(1))
.add(Expr::val(2).eq(2))
)
.add(
Condition::any()
.add(Expr::val(3).eq(3))
.add(Expr::val(4).eq(4))
)
)
.build(DbBackend::Postgres)
.to_string(),
r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE (NOT (1 = 1 AND 2 = 2)) AND (3 = 3 OR 4 = 4)"#
);

Contributed by:

nitnelave

6xzo

Community

SeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust's future.

Here is the roadmap for SeaORM 0.3.x.

Introducing SeaORM 🐚

· 6 min read
Chris Tsang
SeaQL Team

We are pleased to introduce SeaORM 0.2.2 to the Rust community today. It's our pleasure to have received feedback and contributions from awesome people to SeaQuery and SeaORM since 0.1.0.

Rust is a wonderful language that can be used to build anything. One of the FAQs is "Are We Web Yet?", and if Rocket (or your favourite web framework) is Rust's Rail, then SeaORM is precisely Rust's ActiveRecord.

SeaORM is an async ORM built from the ground up, designed to play well with the async ecosystem, whether it's actix, async-std, tokio or any web framework built on top.

Let's have a quick tour of SeaORM.

Async

Here is how you'd execute multiple queries in parallel:

// 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))?;

Dynamic

You can use SeaQuery 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?;

More on SeaQuery

Testable

To write unit tests, you can use our mock interface:

// 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()]
),
]
);

More on testing

Service Oriented

Here is an example Rocket handler with pagination:

#[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,
},
)
}

Full Rocket example

We are building more examples for other web frameworks too.

People

SeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust's future.

Core Members

Chris Tsang

Billy Chan

Contributors

As a courtesy, here is the list of SeaQL's early contributors (in alphabetic order):

Ari Seyhun

Ayomide Bamidele

Ben Armstead

Bobby Ng

Daniel Lyne

Hirtol

Sylvie Rinner

Marco Napetti

Markus Merklinger

Muhannad

nitnelave

Raphaël Duchaîne

Rémi Kalbe

Sam Samai

Release Model

· One min read
Chris Tsang
SeaQL Team

Today we will outline our release plan in the near future.

One of Rust's slogan is Stability Without Stagnation, and SeaQL's take on it, is 'progression without stagnation'.

Before reaching 1.0, we will be releasing every week, incorporating the latest changes and merged pull requests. There will be at most one incompatible release per month, so you will be expecting 0.2 in Sep 2021 and 0.9 in Apr 2022. We will decide by then whether the next release is an incremental 0.10 or a stable 1.0.

After that, a major release will be rolled out every year. So you will probably be expecting a 2.0 in 2023.

All of these is only made possible with a solid infrastructure. While we have a test suite, its coverage will likely never be enough. We urge you to submit test cases to SeaORM if a particular feature is of importance to you.

We hope that a rolling release model will provide momentum to the community and propell us forward in the near future.

Hello World

· One min read
Chris Tsang
SeaQL Team

After 8 months of secrecy, SeaORM is now public!

The Rust async ecosystem is definitely thriving, with Tokio announcing Axum a week before.

We are now busy doing the brush ups to head towards our announcement in Sep.

If you stumbled upon us just now, well, hello! We sincerely invite you to be our alpha tester.